本文探讨Mysql 5.6数据库事务隔离级别read committd/repeatable read对InnoDB引擎下select in update的影响。
首先,这两种事务隔离级别都不会阻塞普通的查询语句。
但是在update语句中用到select时,如果该会话的事务隔离级别为repeatable read,且select的目标行上有排它锁,就会让select产生等待。
在此种情况下,如果在本地开启事务先执行了update A,又调用远程服务执行select A in update B,即会产生死锁。
举例说明:
准备测试数据:
CREATETABLET_A (
A_IDINTPRIMARYKEY, A_VALUEVARCHAR(10)
) ENGINE=InnoDBDEFAULTCHARSET=utf8;
CREATETABLET_B (
B_IDINTPRIMARYKEY, B_VALUEVARCHAR(10)
) ENGINE=InnoDBDEFAULTCHARSET=utf8;
INSERTINTOT_ASELECT1,'VA1';
INSERTINTOT_BSELECT1,'VB1';
Step 1:
打开查询分析器,先检查一下事务隔离级别
SELECT@@global.tx_isolation;
SELECT@@session.tx_isolation;
设置自动提交为false
SETAUTOCOMMIT =0;
更新A表一条数据,不提交
UPDATET_ASETA_VALUE =1WHEREA_ID =1;
Step 2:
在另一个客户端中打开同一个数据库的连接
验证当前的事务隔离级别是否为REPEATABLE READ或READ COMMITTED
SELECT@@session.tx_isolation;
无论何种事务隔离级别,执行对A表的查询,都可以顺利查询出来
SELECT*FROMT_AWHEREA_ID =1;
更改当前事务隔离级别为READ COMMITTED
SETSESSION TRANSACTIONISOLATIONLEVELREADCOMMITTED;
执行一个select in update
UPDATET_BSETB_VALUE = (SELECTA_VALUEFROMT_AWHEREA_ID = 1)WHEREB_ID =1;
此时可以更新目标行
更改当前事务隔离级别为REPEATABLE READ
SETSESSION TRANSACTIONISOLATIONLEVEL REPEATABLE READ;
再次执行上面的select in update
UPDATET_BSETB_VALUE = (SELECTA_VALUEFROMT_AWHEREA_ID = 1)WHEREB_ID =1;
此时更新被阻塞,在连接1中提交事务,此更新语句即可继续执行
同样的,如果连接1中的事务未提交,在连接2中尝试在A表上对A_ID=1的行获取共享锁或排它锁也是不可行的
SELECT*FROMT_AWHEREA_ID =1LOCKINSHARE MODE;
SELECT*FROMT_AWHEREA_ID =1FORUPDATE;
都会等待该行数据的锁被释放
在REPEATABLE READ模式下产生阻塞时分析锁状态:
CREATETABLEinnodb_lock_monitor (aINT) ENGINE=INNODB;
然后让select in update产生等待,分析锁状态
SHOWENGINE INNODB STATUS;
--事务2获取T_A主键上的共享锁产生等待
---TRANSACTION 222643, ACTIVE 11 sec starting index read
mysql tables in use 2, locked 2
LOCK WAIT 4 lock struct(s), heap size 1184, 2 row lock(s)
MySQL thread id 49, OS thread handle 0x7f2b58644700, query id 1061 192.168.50.57 root statistics
UPDATE T_B SET B_VALUE = (SELECT A_VALUE FROM T_A WHERE A_ID = 1) WHERE B_ID = 1
Trx read view will not see trx with id >= 222644, sees < 222642
------- TRX HAS BEEN WAITING 11 SEC FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 1611 page no 3 n bits 72 index `PRIMARY` of table `lovol_shop`.`T_A` trx id 222643 lock mode S locks rec but not gapwaiting
Record lock, heap no 2 PHYSICAL RECORD: n_fields 4; compact format; info bits 0
0: len 4; hex 80000001; asc ;;
1: len 6; hex 0000000365b2; asc e ;;
2: len 7; hex 770000025617ba; asc w V ;;
3: len 1; hex 31; asc 1;;
------------------
TABLE LOCK table `lovol_shop`.`T_B` trx id 222643 lock mode IX
RECORD LOCKS space id 1612 page no 3 n bits 72 index `PRIMARY` of table `lovol_shop`.`T_B` trx id 222643 lock_mode X locks rec but not gap--事务2获得了B表的排它锁
Record lock, heap no 2 PHYSICAL RECORD: n_fields 4; compact format; info bits 0
0: len 4; hex 80000001; asc ;;
1: len 6; hex 0000000365b0; asc e ;;
2: len 7; hex f5000001c0011d; asc ;;
3: len 3; hex 564231; asc VB1;;
TABLE LOCK table `lovol_shop`.`T_A` trx id 222643 lock mode IS
RECORD LOCKS space id 1611 page no 3 n bits 72 index `PRIMARY` of table `lovol_shop`.`T_A` trx id 222643 lock mode S locks rec but not gapwaiting
Record lock, heap no 2 PHYSICAL RECORD: n_fields 4; compact format; info bits 0
0: len 4; hex 80000001; asc ;;
1: len 6; hex 0000000365b2; asc e ;;
2: len 7; hex 770000025617ba; asc w V ;;
3: len 1; hex 31; asc 1;;
--事务1获得了表A主键上的排它锁,但是未提交
---TRANSACTION 222642, ACTIVE 43 sec
2 lock struct(s), heap size 360, 1 row lock(s), undo log entries 1
MySQL thread id 35, OS thread handle 0x7f2b58603700, query id 1062 192.168.50.57 root init
/* ApplicationName=DBeaver Enterprise 3.8.0 - Main */ show engine innodb status
TABLE LOCK table `lovol_shop`.`T_A` trx id 222642 lock mode IX
RECORD LOCKS space id 1611 page no 3 n bits 72 index `PRIMARY` of table `lovol_shop`.`T_A` trx id 222642 lock_mode X locks rec but not gap
Record lock, heap no 2 PHYSICAL RECORD: n_fields 4; compact format; info bits 0
0: len 4; hex 80000001; asc ;;
1: len 6; hex 0000000365b2; asc e ;;
2: len 7; hex 770000025617ba; asc w V ;;
3: len 1; hex 31; asc 1;;
INNODB引擎对于普通的select来说不需要获取锁,所以任何语句都不会阻塞普通select的执行。在READ COMMITTED模式下select in update可以成功执行,说明该模式下的select in update语句与普通的select一样不会申请任何锁。
参考资料:
http://blog.csdn.net/hw_libo/article/details/39080809
http://www.cnblogs.com/zemliu/p/3502395.html
http://blog.csdn.net/xifeijian/article/details/20313977
注:
ANSI 99定义了4种事务隔离级别:
l未提交读READ UNCOMMITTED
在读数据时不会检查或使用任何锁。因此,在这种隔离级别中可能读取到没有提交的数据。
l已提交读READ COMMITTED
只读取提交的数据并等待其他事务释放排他锁。读数据的共享锁在读操作完成后立即释放。
l可重复读REPEATABLE READ
像已提交读级别那样读数据,但会保持共享锁直到事务结束。
l可序列化SERIALIZABLE
工作方式类似于可重复读。但它不仅会锁定受影响的数据,还会锁定这个范围。这就阻止了新数据插入查询所涉及的范围,这种情况可以导致幻像读。
Mysql支持全部4种隔离级别,默认级别为REPEATABLE READ,阿里云RDS默认为READ COMMITTED。
Oracle仅支持SQL标准中的READ COMMITTD及SERIALIZABLE,以及特有的READONLY模式。默认级别为READ COMMITTED。
SqlServer支持4种标准级别,以及两种使用行版本控制来读取数据的事务级别(已提交读快照READ_COMMITTED_SNAPSHOT/快照ALLOW_SNAPSHOT_ISOLATION)。默认级别为READ COMMITTED.