这几天,在查看文章时,发现了一个Mysql并发的问题,在一开始仅仅凭借眼睛去查看时,并未发现问题及解决方法,因而咱们对其进行了具体实际操做和测试:
一个事务内:insert记录后根据字段p来update这条记录,然而当出现并发操做的时候,update处会发生dead lock问题,把update改成id,就没事了。sql
同一个表,高并发事务,事务内先插入一条记录,再更新这条记录: (1)若是更新的是惟一索引,有异常; (2)若是更新的是自增主键,就没有异常; 画外音:先不要被“dead lock”描述所迷惑,是死锁问题,阻塞问题,仍是其余异常,还另说。session
create table t (id int(20) primary key AUTO_INCREMENT,cell varchar(20) unique)engine=innodb;
复制代码
新建表: (1)存储引擎是innodb,MySQL版本是5.6; (2)id字段,自增主键; (3)cell字段,惟一索引;并发
start transaction;
insert into t(cell) values(11111111111);
insert into t(cell) values(22222222222);
insert into t(cell) values(33333333333);
commit;
复制代码
插入一些测试数据。高并发
设置事务隔离级别为RR(repeatable read)测试
--设置手动提交
--设置事务隔离级别为RR
set session autocommit=0;
set session transaction isolation level repeatable read;
复制代码
多个终端session模拟并发事务 ui
start TRANSACTION;
INSERT INTO t(cell) VALUES(44444444);
UPDATE t set cell = 123 WHERE cell = 44444444 ;
ROLLBACK;
复制代码
start TRANSACTION;
INSERT INTO t(cell) VALUES(5555555);
UPDATE t set cell= 456 WHERE cell = 5555555 ;
ROLLBACK;
复制代码
在Navicat中开启两个窗口spa
奇怪的出现了!code
按道理,插入不冲突的记录,而后修改这条记录,行锁不该该冲突呀?
惟一索引,主键索引怎么会有差别呢?是否有关?是死锁,仍是其余缘由?orm
百思不得其解,那就先看看innodb status里都有什么吧,复制粘贴下来后查看: cdn
可见Transaction1与Transaction2 同时锁住了同一部分,并且是locak_mode X rec bur not gap Record lock
这就很奇怪了,又不是间隙锁引发的死锁,第一次update为何会等待呢,第二次update为啥会死锁呢?
不懂,就换个地方看看
经过查看information_schema库中inndb_locks表,可看到,确实事务1和事务2,同时锁住了一片数据区域,致使了数据的等待、死锁,可是缘由呢?
因而再次换方法查看:
咦!发现了重大问题
为何这里rows竟然是6!
我update为何会扫了全表??
我是加了索引的啊
找到问题了:update没走索引,而是扫了全表!
既然找到问题了,就看看如何解决,为何update没有走索引呢? 那咱们回头再看看两个update语句
UPDATE t set cell = 123 WHERE cell = 44444444 ;
UPDATE t set cell= 456 WHERE cell = 5555555 ;
复制代码
看着是没啥问题呀?
寻寻腻腻,冷冷清清,凄凄惨惨戚戚,终于,在查看表时,发现了问题:
回头看建表语句/表结构
create table t (id int(20) primary key AUTO_INCREMENT,cell varchar(20) unique)engine=innodb;
复制代码
cell字段数据类型是varchar类型的,而咱们的update写的是cell = 444444;
并未对数据加引号!而致使了update没走索引,扫了全表
因而,咱们再从头看看这个过程:
在事务隔离级别为RR(Repeat Read)下
事务1的insert产生了一个插入意向锁,事务2的insert也产生了一个插入意向锁(不会被互相锁住,由于数据行并不冲突)
此时事务1再进行update语句,因未走索引,致使扫全表,而在扫到事务2插入那条数据时,行锁与插入意向锁冲突了,致使事务1须要等待事务2释放插入意向锁而进行等待。
事务2在进行update时,也一样须要扫全表,可是全表都被事务1的update锁住了,事务2须要等待 等待事务2释放插入意向锁的 事务1 的行锁 释放,所以发生了死锁
那解决方法就很简单了,将语句改成:
UPDATE t set cell = "123" WHERE cell = "44444444" ;
UPDATE t set cell= "456" WHERE cell = "5555555" ;
复制代码
便可解决死锁/等待问题
其实在进行测试时,也曾经怀疑过是否是由于RR的问题,改为RC试试呢?
--将事务隔离级别改成RC
SET TRANSACTION ISOLATION LEVEL REPEATABLE COMMITTED;
复制代码
修改后,对其进行相同的操做:
发现:事务1insert,事务2insert,事务1的update生效,事务2的update发生了等待
根据上文中咱们找到的问题,对其进行分析:
获得结论:
RC下不存在间隙锁
对于RC和RR的比较,咱们对表中数据采起了删数据的方法继续进行测试:
truncate table
复制代码
继续对表进行相同操做,结果:
究其缘由,RR下,事务1插入的数据,事务2能看到,所以在RR下,即便数据清空,事务1仍然锁住了事务2插入的数据。
而在RC下,事务1插入的数据事务2看不到,事务2插入的数据事务1看不到,他们各自仅仅锁住了本身插入的数据,所以能执行成功。
抛开可重复读和读已提交他们在同一个事务中屡次读可读出的东西外,这次发现了他们其余的不一样:
在这次过程当中,咱们发现了: 行锁、间隙锁、插入意向锁。其中因不一样的行为产生了不一样的锁,而其意义、用处也是不一样的:
在咱们处理sql语句去执行时,不一样的语句会选择不一样的锁:
若是更新条件没有走索引,例如执行”update test set name=“hello” where name=“world”;” ,此时会进行全表扫描,扫表的时候,要阻止其余任何的更新操做,因此上升为表锁。
若是更新条件为索引字段,可是并不是惟一索引(包括主键索引),例如执行“update test set name=“hello” where code=9;” 那么此时更新会使用Next-Key Lock。使用Next-Key Lock的缘由:
首先要保证在符合条件的记录上加上排他锁,会锁定当前非惟一索引和对应的主键索引的值;
还要保证锁定的区间不能插入新的数据。
若是更新条件为惟一索引,则使用Record Lock(记录锁)。