07 | 行锁功过:怎么减少行锁对性能的影响?

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同时更新一条数据是互斥的。因为多种锁同时存在时,以粒度最小的锁为准的原因么?

不是,多种锁,必须“全部不互斥”才并行,有互斥就等

你可能感兴趣的:(07 | 行锁功过:怎么减少行锁对性能的影响?)