MySQL在RR隔离级别下的unique失效和死锁模拟

今天在测试MySQL事务隔离级别的时候,发现了一个有趣的问题,也参考了杨一之前总结的一篇。http://blog.itpub.net/22664653/viewspace-1612574/

    问题的背景是在MySQL隔离级别为RR(Repeatable Read)时,唯一性约束没有失效,多并发的场景下能够复现出下面的问题。

  这样一个看起来不可能的事情,能否复现呢。

  我都这么问了,潜台词就是可以,要不今天的笔记就一个问题就结束了。

  为了模拟这个问题,我们打开两个会话窗口,来模拟一下这个问题。

mysql> create table test3(id1 int primary key,id2 int unique,id3 int);
Query OK, 0 rows affected (0.01 sec)

#会话1
set autocommit=0;
mysql> insert into test3 values(1,20170831,1);
Query OK, 1 row affected (0.00 sec)
commit;

#会话2

这个时候充分利用了MVCC的特性,这是一个快照读。

mysql> select *from test3;
+-----+----------+------+
| id1 | id2      | id3  |
+-----+----------+------+
|   1 | 20170831 |    1 |
+-----+----------+------+
1 row in set (0.00 sec)
会话1插入了一条数据,我们在会话2中删除。
mysql> delete from test3 where id1=1;
Query OK, 1 row affected (0.01 sec)
mysql> commit;
Query OK, 0 rows affected (0.00 sec)
提交之后,会话2中就修改完毕了。

#会话1

这个时候根据MVCC的特点,会话2中已经删除了id1=1的记录。所以主键列相关数据是插入不了了,那么唯一性索引呢。根据MVCC的特点,能够保证重复读的特点,读到的数据还是不变。

mysql> select *from test3;
+-----+----------+------+
| id1 | id2      | id3  |
+-----+----------+------+
|   1 | 20170831 |    1 |
+-----+----------+------+
1 row in set (0.00 sec)

现在的关键就来了,我们插入一条数据,主键不冲突,唯一性索引冲突,看看是否能够插入成功。

mysql> insert into test3 values(2,20170831,2);
Query OK, 1 row affected (0.00 sec)

魔性的一幕上演了。

mysql> select *from test3;
+-----+----------+------+
| id1 | id2      | id3  |
+-----+----------+------+
|   1 | 20170831 |    1 |
|   2 | 20170831 |    2 |
+-----+----------+------+
2 rows in set (0.00 sec)

当然到了这里,我们继续玩一玩,常规来说,插入主键列冲突数据可能是行不通的,比如id1=1,id2=20170831,id3=1,客户端很快会反馈失败。但是在这里做唯一性校验时,因为id1=1的数据已经被物理删除了。

mysql>  insert into test3 values(1,20170831,1);
ERROR 1062 (23000): Duplicate entry '20170831' for key 'id2'

我们就来继续模拟个死锁吧。

会话2:

这个步骤是做一次数据清理,where条件中是根据主键来查找删除。

mysql> delete from test3 where id1=1;
Query OK, 0 rows affected (0.00 sec)

会话1:

mysql> insert into test3 values(1,20170831,1);

这个时候会话会被阻塞

会话2:

这个时候在会话2继续插入这个值,就会报出死锁问题。

mysql> insert into test3 values(1,20170831,1);
ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction

--产生死锁

会话1:

这个时候死锁有了,事务也自动回滚了。再次插入违反约束的数据,就不行了。

mysql> insert into test3 values(1,20170831,1);
ERROR 1062 (23000): Duplicate entry '20170831' for key 'id2'

我们来看看在上面的测试过程中,关于死锁的日志:
2017-08-28T07:27:48.329631Z 14140 [Note] InnoDB: Transactions deadlock detected, dumping detailed information.
2017-08-28T07:27:48.329740Z 14140 [Note] InnoDB:
*** (1) TRANSACTION:

TRANSACTION 31790, ACTIVE 315 sec inserting
mysql tables in use 1, locked 1
LOCK WAIT 5 lock struct(s), heap size 1136, 5 row lock(s), undo log entries 1
MySQL thread id 14138, OS thread handle 139809903986432, query id 108686 localhost root update
insert into test3 values(1,20170831,1)
2017-08-28T07:27:48.329801Z 14140 [Note] InnoDB: *** (1) WAITING FOR THIS LOCK TO BE GRANTED:

RECORD LOCKS space id 36 page no 3 n bits 72 index PRIMARY of table `test`.`test3` trx id 31790 lock mode S waiting
Record lock, heap no 2 PHYSICAL RECORD: n_fields 5; compact format; info bits 0
 0: len 4; hex 80000001; asc     ;;
 1: len 6; hex 000000007c2f; asc     |/;;
 2: len 7; hex 33000001ac2f63; asc 3    /c;;
 3: len 4; hex 8133c84f; asc  3 O;;
 4: len 4; hex 80000001; asc     ;;

2017-08-28T07:27:48.330040Z 14140 [Note] InnoDB: *** (2) TRANSACTION:

TRANSACTION 31791, ACTIVE 51 sec inserting
mysql tables in use 1, locked 1
5 lock struct(s), heap size 1136, 4 row lock(s), undo log entries 1
MySQL thread id 14140, OS thread handle 139809903720192, query id 108687 localhost root update
insert into test3 values(1,20170831,1)
2017-08-28T07:27:48.330084Z 14140 [Note] InnoDB: *** (2) HOLDS THE LOCK(S):

RECORD
LOCKS space id 36 page no 3 n bits 72 index PRIMARY of table
`test`.`test3` trx id 31791 lock_mode X locks rec but not gap
Record lock, heap no 2 PHYSICAL RECORD: n_fields 5; compact format; info bits 0
 0: len 4; hex 80000001; asc     ;;
 1: len 6; hex 000000007c2f; asc     |/;;
 2: len 7; hex 33000001ac2f63; asc 3    /c;;
 3: len 4; hex 8133c84f; asc  3 O;;
 4: len 4; hex 80000001; asc     ;;

2017-08-28T07:27:48.330342Z 14140 [Note] InnoDB: *** (2) WAITING FOR THIS LOCK TO BE GRANTED:

RECORD LOCKS space id 36 page no 4 n bits 72 index id2 of table `test`.`test3` trx id 31791 lock mode S waiting
Record lock, heap no 3 PHYSICAL RECORD: n_fields 2; compact format; info bits 0
 0: len 4; hex 8133c84f; asc  3 O;;
 1: len 4; hex 80000002; asc     ;;
2017-08-28T07:27:48.330470Z 14140 [Note] InnoDB: *** WE ROLL BACK TRANSACTION (2)

这里会充分把x,s锁,细粒度锁的知识联系起来,搞明白又进步了一大截。

会话1:

最后,我们提交一下事务,再次查看数据,一切又恢复了平静。

mysql> commit;
Query OK, 0 rows affected (0.00 sec)

mysql> select *from test3;
+-----+----------+------+
| id1 | id2      | id3  |
+-----+----------+------+
|   2 | 20170831 |    2 |
+-----+----------+------+
1 row in set (0.00 sec)

时间: 2024-09-30 22:47:58

MySQL在RR隔离级别下的unique失效和死锁模拟的相关文章

Innodb:RR隔离级别下insert...select 对select表加锁模型和死锁案列

最近有网友遇到了在RR隔离级别下insert A select B where B.COL=** 由于select表也就是B表引发的死锁的问题.分析死锁日志后,笔者进行模拟重现了这位网友遇到了2种场景并且在本文中进行详细的描述. 本文使用版本percona 5.7.14修改版,能够打印出事务所有的行锁信息结构链(不包含隐含锁) 本文中的测试是在RR隔离级别下完成的,RC不存在这样的问题 笔者对源码的理解有限,如有错误请指正 本文使用了自制工具innblock和bcview,前者用于扫描块结构后者

探讨innodb_locks_unsafe_for_binlog参数在RR隔离级别下取消了 gap lock产生的幻读

具体看例子:

mysql中不同事务隔离级别下数据的显示效果

  事务是一组原子性的SQL查询语句,也可以被看做一个工作单元.如果数据库引擎能够成功地对数据库应用所有的查询语句,它就会执行所有查询,如果任何一条查询语句因为崩溃或其他原因而无法执行,那么所有的语句就都不会执行.也就是说,事务内的语句要么全部执行,要么一句也不执行. 事务的特性:acid,也称为事务的四个测试(原子性,一致性,隔离性,持久性) automicity:原子性,事务所引起的数据库操作,要么都完成,要么都不执行 consisitency:一致性,事务执行前的总和和事务执行后的总和是不

MySQL的事务隔离级别和锁

MySQL的事务隔离级别:Read Uncommitted[读未提交数据]Read Committed[读已提交数据]Repeatable Read[可重读]Serializable[可串行化] 查看MySQL的事务隔离级别:默认.全局和会话事务隔离级别: SELECT @@tx_isolation SELECT @@global.tx_isolation; SELECT @@session.tx_isolation; mysql> select @@tx_isolation; +-------

浅析SQL Server在可序列化隔离级别下,防止幻读的范围锁的锁定问题

原文:浅析SQL Server在可序列化隔离级别下,防止幻读的范围锁的锁定问题   本文出处:http://www.cnblogs.com/wy123/p/7501261.html (保留出处并非什么原创作品权利,本人拙作还远远达不到,仅仅是为了链接到原文,因为后续对可能存在的一些错误进行修正或补充,无他)     数据库在处理并发事物的过程中,在不同的隔离级别下有不同的锁表现,在非可序列化隔离级别下,存在着脏读,不可重复读,丢失更新,幻读等情况.本文不讨论脏读和不可重复读以及丢失更新的情形,仅

mysql+Spring数据库隔离级别与性能分析_Mysql

这里以mysql为例,先明确以下几个问题: 一.一般项目如果不自己配置事务的话,一般默认的是autocommit,即执行完一个操作后自动commit,提交事务. (注:事务是绑定在数据库操作上的,也就是当程序执行(statement.excute等操作)转而到数据库层面上的时候,事务才开始发生)当然spring可以将几个数据库操作动作绑在一个事务中,这样就需要介绍下spring事务配置方法,下面介绍的是常用方法,其他方法网上有很多.spring提供了很多事务配置的策略,很方便,简要介绍一下: 复

MySQL数据库事务隔离级别介绍(Transaction Isolation Level)_Mysql

数据库隔离级别有四种,应用<高性能mysql>一书中的说明: 然后说说修改事务隔离级别的方法: 1.全局修改,修改mysql.ini配置文件,在最后加上 复制代码 代码如下:  #可选参数有:READ-UNCOMMITTED, READ-COMMITTED, REPEATABLE-READ, SERIALIZABLE. [mysqld] transaction-isolation = REPEATABLE-READ 这里全局默认是REPEATABLE-READ,其实MySQL本来默认也是这个级

mysql 详解隔离级别操作过程(cmd)

读未提交示例操作过程-Read uncommitted 1.打开两个MySql的命令提示行,均进入相同数据库,并检查当前表内容为相同数据如下: 2.在A.B两端执行select @@tx_isolation;检查当前默认的隔离级别别,可以发现都是 Repeatable Read –可重复读-(在当前事务内,重复读取第一次读取过的数据就叫可重复读.) 3.修改A端的隔离级别为readuncommitted –读未提交.意思是可以读取别人没有提交的数据. set transactionisolati

使用事件进行同步、事务隔离级别为脏读 还是出现事务死锁问题

问题描述 整个程序就两个业务处理,如下:(1)一个纯查询业务:QueryData(){多个表查询操作······}(2)一个数据更新业务:publicstaticAutoResetEventmEvent=newAutoResetEvent(true);//静态的全局事件对象,用于事务并发同步控制,保证任一时刻只有一个事务得到执行.UpdateData(){//数据更新业务mEvent.WaitOne();//等待信号SqlTransactiontran=BeginTransaction(Syst