mysql update加锁分析:是加行锁还是表锁?具体加锁加哪儿?

InnoDB默认事务的隔离级别是可重复读。在可重复读的情况下,会出现幻读的情况。幻读就是同一事务下,两次连续查询的结果不一致,会返回之前不存在的行。

InnoDB存储引擎实现了自己的行锁,通过next-key锁(记录锁和间隙锁的组合)来锁住记录本身和记录之间的间隙,防止其他事务在这个记录之间插入新的记录,从而避免了幻读的现象。

当我们执行update语句的时候,实际上会对记录加独占锁(X锁)另外其他事务对持有独占锁的记录进行修改的时候会被阻塞。这个锁并不是执行完update语句才会释放,而是会等事务结束时才会释放。

InnoDB事务中,对记录加锁的基本单位是next-key锁。但是会因为一些条件会降级成间隙锁,或者记录锁。加锁的位置准确的说是加在索引上,而不是行上。

在 update 语句的 where 条件使用了唯一索引,那么 next-key 锁会降级成记录锁,也就是只会给一行记录加锁。

注意1:InnoDB中,在读已提交隔离级别下,行级锁的种类只有记录锁(也叫独占锁,x锁,分类方式不一样而已),也就是仅仅把一条记录锁上。

注意2:在 MySQL 中,默认情况下会使用行级锁(记录锁),而不是间隙锁。因此,如果要使用间隙锁,必须在事务中显式地设置锁级别。

update 语句的 where 条件没有使用索引,就会全表扫描,于是就会对所有记录加上 next-key 锁(记录锁 + 间隙锁),相当于把整个表锁住了。如果存在索引的话,则对索引进行加锁。

因此,当在数据量非常大的数据库表执行 update 语句时,如果没有使用索引,就会给全表的加上 next-key 锁, 那么锁就会持续很长一段时间,直到事务结束。而这期间除了 select ... from 语句,其他语句都会被锁住不能执行。

 update 语句的 where 带上索引就能避免全表记录加锁了吗?并不是。

关键还得看这条语句在执行过程中,优化器最终选择的是索引扫描,还是全表扫描,如果走了全表扫描,就会对全表的记录加锁了。我们可以将 MySQL 里的 sql_safe_updates 参数设置为 1,开启安全更新模式。

大致的意思是,当 sql_safe_updates 设置为 1 时,update 语句必须满足如下条件之一才能执行成功:

  • 使用 where,并且 where 条件中必须有索引列。
  • 使用 limit。
  • 同时使用 where 和 limit,此时 where 条件中可以没有索引列。

delete 语句必须满足如下条件之一才能执行成功:

  • 使用 where,并且 where 条件中必须有索引列。
  • 同时使用 where 和 limit,此时 where 条件中可以没有索引列。

如果 where 条件带上了索引列,但是优化器最终扫描选择的是全表,而不是索引的话,我们可以使用 force index([index_name]) 可以告诉优化器使用哪个索引,以此避免有几率锁全表带来的隐患。

总结:当我们要执行 update 语句的时候,确保 where 条件中带上了索引列,并且在测试机确认该语句是否走的是索引扫描,防止因为扫描全表,而对表中的所有记录加上锁。我们可以打开 MySQL 里的 sql_safe_updates 参数,这样可以预防 update 操作时 where 条件没有带上索引列。如果发现即使在 where 条件中带上了列索引列,优化器走的还是全标扫描,这时我们就要使用 force index([index_name]) 可以告诉优化器使用哪个索引。

1.mysql存储引擎:

Innodb: 支持事务,更新时采用行级锁,并发性高
MyISAM: 不支持事务,更新时表锁,并发性差
因此使用Innodb才会发生死锁,从mysql5.6开始默认引擎Innodb

2.update更新过程

行级锁并不是直接锁记录,而是锁索引,如果一条SQL语句用到了主键索引,mysql会锁住主键索引;如果一条语句操作了非主键索引,mysql会先锁住非主键索引,再锁定主键索引。反之,
如果操作用到了主键索引会先在主键索引上加锁,然后在其他索引上加锁。
如果没有用到索引,则进行全表扫描,锁表。
当where条件为非主键索引,执行update时,会经过一下步骤:
1)先获取非主键索引的行级锁;
2)由数据库基本原理可知,where条件为非主键索引时,会发生回表查询,进而再获得主键索引的行级锁;
3)更新完毕,进行事务提交。

根据上述步骤可知,对于非主键索引的update操作,其加锁过程并非原子操作,而且是分别需要获取不同索引的行级锁,可能会产生死锁:
例如:

1、A事务,查询时主键索引取的X锁,使用非主键索引更新。B事务,使用非主键索引更新:

 2、两个非主键索引混用发生死锁:

UPDATE test SET is_deleted = 1 WHERE group_id = 1332577 and test_id = 421285

第一个事务先根据group_id索引,已经锁住primary id,然后再根据test_id索引,锁定primary id;

第二个事务先根据test_id索引,已经锁住primary id,然后再根据group_id索引,去锁primary id;

所以这样并发更新就可能出现死索引。

update时,如果where条件里面涉及多个字段,区分度都比较高且字段都分别建了索引的话,mysql会多个索引各走一遍,然后结果取个交集;

单条记录更新不会引发问题;多条记录并发更新时,如果索引行数有重叠,因加锁顺序可能不同,互相等待可能会导致死锁,为什么加锁顺序会不同呢?我们的sql中where条件的顺序是一定的,那么加锁顺序也应该一定,为什么会有加锁顺序不同情况。情况是这样的,因为我们使用的是两个单值索引,where条件中是复合条件,那么mysql会使用index merge进行优化,优化过程是mysql会先用索引1进行扫表,在用索引2进行扫表,然后求交集形成一个合并索引。这个使用索引扫表的过程和我们本身的sql使用索引的顺序可能存在互斥,所以造成了死锁。

解决方案:尽量避免使用非主键索引更新

1、where条件加主键索引
2、先查询出来(可以上锁可不上锁,看业务具体情况),在根据主键更新
 

注意:由于MySQL的行锁是针对索引加的锁,不是针对记录加的锁,所以虽然是访问不同行的记录,但是如果是使用相同的索引键,是会出现锁冲突的


 

  • 6
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值