3.4 锁机制 事务

MySQL锁机制

锁:

  读锁:共享锁,也成为s锁,只读不可写(包括当前事务),多个读互不阻塞

  写锁:独占锁,排它锁,也成为X锁,写锁会阻塞其它事务(不包括当前事务)的读和写

 

举例:

  读锁和读锁是兼容的,写锁和其它锁不兼容

  事务T1获取了一个行r1的读锁,另外事务T2可以立即获得r1的读锁,此时T1和T2共同获得行r1的读锁,此种情况成为锁兼容

  但是另外一个事务T3如果想获得行r1的写锁,则必须等待T1对行r1读锁的释放,此种情况也成为锁冲突。

锁粒度:

  表级锁:MyISAM

     行级锁:InnoDB

实现:

  存储引擎:自行实现其锁策略和锁粒度

  服务器级:实现了锁,表级锁,用户可显示请求

分类:

  隐式锁:由存储引擎自动施加锁

  显式锁:用户手动请求

锁策略:在锁粒度及数据安全性寻求的平衡机制

锁优缺点:

  表级锁:开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低。

  行级锁:开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高。

  页面锁:开销和加锁时间界于表锁和行锁之间;会出现死锁;锁定粒度界于表锁和行锁之间,并发度一般

 

案例一:

需求:将投资的钱拆成几份随机分配给借款人。

起初业务程序思路是这样的:

投资人投资后,将金额随机分为几份,然后随机从借款人表里面选几个,然后通过一条条select for update 去更新借款人表里面的余额等。

 

抽象出来就是一个session通过for循环会有几条如下的语句:

Select * from xxx where id='随机id' for update

 

基本来说,程序开启后不一会就死锁。

这可以是说最经典的死锁情形了。

 

例如两个用户同时投资,A用户金额随机分为2份,分给借款人1,2

B用户金额随机分为2份,分给借款人2,1

由于加锁的顺序不一样,死锁当然很快就出现了。

 

 

测试单个会话读锁:
1,对表date加读锁
lock table date read;
2,执行读操作和写操作
select * from date;
insert into date (name) values ("cccc");
测试多个会话读锁:
会话1
1.对表table读锁
lock table date read;
会话2
1.查看当前能否读取和写入
2.对表table读锁(能否成功)
lock table date read;
lock table date write;



在会话1和2测试读取写入:
当会话2 unlock tables,会话1的读锁还在么
写锁能否成功?
测试单个会话写锁:
测试多个会话写锁:

 

事务:

有事务之前:

  数据库修改一条数据是直接在内存中修改然后写入数据库文件

有事务之后:

  数据库修改的数据先存入事务日志(告诉用户成功),然后再存入数据库文件

  日志是顺序写的,更快(更详细),数据库是随机读写

 

什么是事务:

  事务Transactions:一组原子性的SQL语句,或一个独立工作单元
  事务日志:记录事务信息,实现undo,redo等故障恢复功能

事务特性

  ACID特性:
  A:atomicity原子性;整个事务中的所有操作要么全部成功执行,要么全部失败后回滚
  C:consistency一致性;数据库总是从一个一致性状态转换为另一个一致性状态
  I:Isolation隔离性;一个事务所做出的操作在提交之前,是不能为其它事务所见;隔离有多种隔离级别,实现并发
  D:durability持久性;一旦事务提交,其所做的修改会永久保存于数据库中
事务生命周期
  

 

管理事务

显示启动事务

BEGIN 
BEGIN WORK
START TRANSCATION

结束事务

#提交
COMMIT
#回滚
ROLLBACK

注意:只有事务型存储引擎中的DML语句才能支持此类操作

自动提交

set autocommit={1|0}
默认为1,为0时非自动提交
建议:显示请求和提交事务,而不要使用自动提交功能

 

事务支持保存点

SAVEPOINT identifier
ROLLBACK [WORK] TO [SAVEPOINT] identifier
RELEASE SAVEPOINT identifier

查看事务

#查看当前正在进行的事务
SELECT * FROM INFORMATION_SCHEMA.INNODB_TRX; 
#查看当前锁定的事务
SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCKS;
#查看当前等锁的事务
SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCK_WAITS;

范例:找到未完成的导致阻塞的事务

#在第一个会话中开启一个事务
mysql> use db1;
Database changed
mysql> update date set name="centos";^C
mysql> begin;
Query OK, 0 rows affected (0.00 sec)

mysql> update date set name="centos";
Query OK, 3 rows affected (0.00 sec)
Rows matched: 3  Changed: 3  Warnings: 0

mysql> select * from date;
+----+--------+--------+--------+--------+------+-------+---------+-------+-------+-------+
| id | name   | gender | height | weight | bust | hobby | address | years | dates | times |
+----+--------+--------+--------+--------+------+-------+---------+-------+-------+-------+
|  1 | centos | NULL   |   NULL |   NULL | NULL | NULL  | NULL    |  NULL | NULL  | NULL  |
|  2 | centos | NULL   |   NULL |   NULL | NULL | NULL  | NULL    |  NULL | NULL  | NULL  |
|  4 | centos | NULL   |   NULL |   NULL | NULL | NULL  | NULL    |  NULL | NULL  | NULL  |
+----+--------+--------+--------+--------+------+-------+---------+-------+-------+-------+
3 rows in set (0.00 sec)
#在第二个会话中执行(不需要开启一个事务)
mysql> use db1;
Database changed
mysql> select * from date;
+----+----------+--------+--------+--------+------+-------+---------+-------+-------+-------+
| id | name     | gender | height | weight | bust | hobby | address | years | dates | times |
+----+----------+--------+--------+--------+------+-------+---------+-------+-------+-------+
|  1 | huakai1  | NULL   |   NULL |   NULL | NULL | NULL  | NULL    |  NULL | NULL  | NULL  |
|  2 | yase2021 | NULL   |   NULL |   NULL | NULL | NULL  | NULL    |  NULL | NULL  | NULL  |
|  4 | dd       | NULL   |   NULL |   NULL | NULL | NULL  | NULL    |  NULL | NULL  | NULL  |
+----+----------+--------+--------+--------+------+-------+---------+-------+-------+-------+
3 rows in set (0.00 sec)

mysql> update date set name="rhel";
#在运维管理会话
mysql> show engine innodb status\G;
···
------------
TRANSACTIONS
------------
Trx id counter 131099
Purge done for trx's n:o < 131097 undo n:o < 0 state: running but idle
History list length 3
LIST OF TRANSACTIONS FOR EACH SESSION:
---TRANSACTION 421830269083248, not started
0 lock struct(s), heap size 1136, 0 row lock(s)
---TRANSACTION 421830269082336, not started
0 lock struct(s), heap size 1136, 0 row lock(s)
---TRANSACTION 131097, ACTIVE 127 sec
2 lock struct(s), heap size 1136, 4 row lock(s), undo log entries 3
MySQL thread id 11, OS thread handle 140354841700096, query id 163 localhost root
Trx read view will not see trx with id >= 131098, sees < 131098
···

查看当前正在进行的事务
mysql> select * from information_schema.innodb_trx\G;
*************************** 1. row ***************************
                    trx_id: 131097
                 trx_state: RUNNING
               trx_started: 2021-03-12 11:13:14
     trx_requested_lock_id: NULL
          trx_wait_started: NULL
                trx_weight: 5
       trx_mysql_thread_id: 11
                 trx_query: NULL
       trx_operation_state: NULL
         trx_tables_in_use: 0
         trx_tables_locked: 1
          trx_lock_structs: 2
     trx_lock_memory_bytes: 1136
           trx_rows_locked: 4
         trx_rows_modified: 3
   trx_concurrency_tickets: 0
       trx_isolation_level: REPEATABLE READ
         trx_unique_checks: 1
    trx_foreign_key_checks: 1
trx_last_foreign_key_error: NULL
 trx_adaptive_hash_latched: 0
 trx_adaptive_hash_timeout: 0
          trx_is_read_only: 0
trx_autocommit_non_locking: 0
1 row in set (0.00 sec)
查看当前锁定的事务
mysql> select * from information_schema.innodb_locks\G;
*************************** 1. row ***************************
    lock_id: 131099:335:3:2
lock_trx_id: 131099
  lock_mode: X
  lock_type: RECORD
 lock_table: `db1`.`date`
 lock_index: PRIMARY
 lock_space: 335
  lock_page: 3
   lock_rec: 2
  lock_data: 1
*************************** 2. row ***************************
    lock_id: 131097:335:3:2
lock_trx_id: 131097
  lock_mode: X
  lock_type: RECORD
 lock_table: `db1`.`date`
 lock_index: PRIMARY
 lock_space: 335
  lock_page: 3
   lock_rec: 2
  lock_data: 1
2 rows in set, 1 warning (0.00 sec)

ERROR:
No query specified
查看当前等锁的事务
mysql> select * from information_schema.innodb_lock_waits\G;
*************************** 1. row ***************************
requesting_trx_id: 131100
requested_lock_id: 131100:335:3:2
  blocking_trx_id: 131097
 blocking_lock_id: 131097:335:3:2
1 row in set, 1 warning (0.00 sec)

ERROR:
No query specified
#在运维管理会话中查看
mysql> show processlist;
+----+------+-----------+------+---------+------+----------+-----------------------------+
| Id | User | Host      | db   | Command | Time | State    | Info                        |
+----+------+-----------+------+---------+------+----------+-----------------------------+
|  9 | root | localhost | NULL | Query   |    0 | starting | show processlist            |
| 10 | root | localhost | db1  | Query   |    6 | updating | update date set name="rhel" |
| 11 | root | localhost | db1  | Sleep   |  574 |          | NULL                        |
+----+------+-----------+------+---------+------+----------+-----------------------------+
3 rows in set (0.00 sec)
杀死lock:
mysql >kill 11;
可以通过show processlist和查看系统事务得到

#查看事务锁的超时时长,默认50s
mysql> show global variables like 'innodb_lock_wait_timeout';
+--------------------------+-------+
| Variable_name            | Value |
+--------------------------+-------+
| innodb_lock_wait_timeout | 50    |
+--------------------------+-------+
1 row in set (0.00 sec)
MySQL 支持四种隔离级别,事务隔离级别:从上至下更加严格
隔离级别 脏读 不可重复读 幻读 加读锁
读未提交 可以出现 可以出现 可以出现
读提交 不允许出现 可以出现 可以出现
可重复读 不允许出现 不允许出现 可以出现
序列化 不允许出现 不允许出现 可以出现
         
READ UNCOMMITTED 
  可读取到未提交数据,产生脏读
READ COMMITTED
  可读取到提交数据,但未提交数据不可读,产生不可重复读,即可读取到多个提交数据,导致每次读取数据不一致
REPEATABLE READ 
  可重复读,多次读取数据都一致,产生幻读,即读取过程中,即使有其它提交的事务修改数据,仍只能读取到未修改前的旧数据。此为MySQL默认设置
SERIALIZABLE 
可串行化,未提交的读事务阻塞修改事务(加读锁,但不阻塞读事务),或者未提交的修改事务阻塞读事务(加写锁,其它事务的读,写都不可以执行)。会导致并发性能差
MVCC和事务的隔离级别: MVCC(多版本并发控制机制)只在REPEATABLE READ和READ COMMITTED两个隔离级别下工作。其他两个隔离级别都和MVCC不兼容,因为READ UNCOMMITTED总是读取最新的数据行,而不是符合当前事务版本的数据行。而SERIALIZABLE则会对所有读取的行都加锁

 

  
posted @ 2021-03-15 11:01  huakai201  阅读(164)  评论(0编辑  收藏  举报