InnoDB 行锁,如何减少锁,提升并发度。
概要:1两阶段锁(1锁协议 2多行锁冲突、影响并发度往后放)2死锁检测(设置超时、控制并发度)
一、两阶段锁
1、两阶段锁协议:
InnoDB 事务中,行锁要时加上,结束释放(不是不需要释放)
id 是主键,事务B 被阻塞,直到A 两个行锁,都commit 时释放
2、多行,最可能锁冲突、影响并发度的锁往后放
顾客 A 要在影院 B 购买电影票:
1. 从顾客 A 余额中扣除票价;
2. 给影院 B 的余额增加票价;
3. 记录一条交易日志。
两条update , 一条insert,保证原子性,放一个事务。顺序:
不论怎样排,行锁都提交才释放。如 3、1、2 ,影院余额这行锁时间最少。减少事务之间锁等待,提升并发度。
问题并没完全解决
活动低价预售一年内所有影票,只做一天。活动时间开始时, MySQL 挂了。登服务器看,CPU 消耗接近 100%,整个数据库每秒就执行不到 100 个事务。什么原因呢?
二、死锁和死锁检测
1、死锁:不同线程循环资源依赖,都在等待其他线程释放资源时,导致几个线程无限等待
A 等B 放 id=2 行锁, B 等 A 放 id=1 的行锁。 互相等待死锁。策略:
(1)innodb_lock_wait_timeout 设置超时时间。
(2)死锁检测,发现死锁后,回滚死锁链条中的某个事务,其他执行。innodb_deadlock_detect 设置为 on(默认)。
正常情况用死锁检测(即主动死锁检测),innodb_lock_wait_timeout 默认50s,设置太短,会误伤,但是死锁检测也有额外负担:
事务被锁,就要看它依赖线程有没有被别人锁住,如此循环,造成死锁。
2、所有事务更新同一行场景:
新线程被堵住,判断是否由于自己加入导致死锁,时间复杂度是 O(n) 。1000个并发线程同时更新同一行,死锁检测100 万量级。检测结果没死锁,但消耗大量CPU 资源。
(1)确保一定不会出现死锁,关掉死锁检测。
有风险,出现死锁回滚,重试,业务无损。关掉可能会大量超时,业务有损。
(2)控制并发度。同一行最多只有 10 个线程更新,死锁检测成本低。但客户端多,如600 个,每个5 个并发线程,汇总服务端峰值3000。
思路:相同行更新,进入引擎之前排队。InnoDB 内部没有大量的死锁检测工作。
因此,这个并发控制要在数据库服务端。或在1.中间件实现,2.修改 MySQL 源码,3.设计上优化:
一行改成多行减少锁冲突。10 个记录,账户总额等于10 个记录总和。每次给影院加金额随机选。冲突概率为1/10,减少锁等待和死锁检测CPU 消耗。
看上无损,但要详细设计。余额可能减少,比如退票,考虑变成 0 时特殊处理。
小结
MySQL 行锁,两阶段锁协议、死锁和死锁检测
两阶段协议,事务中语句顺序。锁多行时,最可能造成锁冲突、影响并发度的锁申请往后放。
不能完全避免死锁。所以引入死锁和死锁检测,三个方案。减少死锁主要方向,控制并发量。
问题:删除一个表里前 10000 行数据,选择哪一种?
1、直接delete from T limit 10000;
2、连接中循环20 次delete from T limit 500;
3、20 同时执行 delete from T limit 500。
答:2 ok 1锁时间长;事务主从延迟 3人为造成锁冲突,加剧并发
2串行执行,每次时间相对短,等待时间也短。将资源分片,提高并发性。
ps:innodb行锁,如何加锁(依赖的隔离级别,是否有索引,是否是唯一索引,SQL的执行计划),特别是在RR隔离级别下的GAP锁,对于innodb,RR级别是可以防止幻读的。
如加上特定条件,将10000 行天然分开,可以考虑第三种。实际操作尽量拿到 ID 再删除。
评论1
update列没建索引,update一条记录会锁定整张表吗?update t set t.name='abc' where t.name='cde'; name字段无索引。为何innodb不优化一下,只锁定name='cde'的列?
是的。如果update limit 1, 会怎么锁?Innodb支持行锁,没有“列锁”
评论2
死锁检测innodb_deadlock_detect每条事务执行前都会进行检测吗?即使简单更新单个表,并发量高,也消耗大量资源用于死锁检测?
加锁访问行上有锁,才检测
1. 一致性读不会加锁,不做死锁检测;
2. 不是每次死锁检测都扫所有事务。某个时刻,事务等待状态:
B在等A,
D在等C,
来了E,要等D,E判断D、C是否形成死锁,检测不管B、A
评论3
表锁:同表同时刻只有一个更新。
MDL锁:dml语句产生MDL读锁,而MDL读锁不是互斥,一张表同时多个dml语句操作。有点矛盾?
MDL锁和表锁不同结构。如:
myisam 表更新一行,加MDL读锁和表的写锁;
另线程要更新这表上另一行,也加MDL读锁和表写锁。
第二个线程要等第一个线程执行完成。
评论4
两个update同时更新一条数据是互斥的。因为多种锁同时存在时,以粒度最小的锁为准的原因么?
不是,多种锁,必须“全部不互斥”才并行,有互斥就等