MYISAM不支持行锁,INNODB支持行锁。从并法度来说INNODB要比MYISAM要大。这也是INNODB替代MYISAM的重要原因之一。
从两阶段锁说起
下面的情况会发生什么?
事务 B 的 update 语句会被阻塞,直到事务 A 执行 commit 之后,事务 B 才能继续执行。
那么就有一个结论:在 InnoDB 事务中,行锁是在需要的时候才加上的,但并不是不需要了就立刻释放,而是要等到事务结束时才释放。这个就是两阶段锁协议。
从这个可以得出:如果你的事务中需要锁多个行,要把最可能造成锁冲突、最可能影响并发度的锁尽量往后放。
比如以下场景:
1 从顾客 A 账户余额中扣除电影票价;
2 给影院 B 的账户余额增加这张电影票价;
3 记录一条交易日志。
能提升并法度的顺序就是把语句 2 安排在最后,比如按照 3、1、2 这样的顺序,那么影院账户余额这一行的锁时间就最少。
死锁和死锁检测
死锁的情况,比如:
事务 A 在等待事务 B 释放 id=2 的行锁,而事务 B 在等待事务 A 释放 id=1 的行锁。 事务 A 和事务 B 在互相等待对方的资源释放,就是进入了死锁状态。
解决的方法:
1 直接进入等待,直到超时。这个超时时间可以通过参数 innodb_lock_wait_timeout 来设置。
2 发起死锁检测,发现死锁后,主动回滚死锁链条中的某一个事务,让其他事务得以继续执行。将参数 innodb_deadlock_detect 设置为 on,表示开启这个逻辑。
第一种情况是不好把握超时的时间,设置的太长,业务无法接受,设置的太短,又会很多的错判。
第二种情况由于死锁检测的时间复杂度是o(n),如果 1000 个并发线程要同时更新同一行,那么死锁检测操作就是 100 万这个量级的。会出现CPU利用率极高,却没有执行多少个事务的情况。
1 从业务保证不会出现死锁的情况,把死锁检测关掉。但是死锁不会当做一个严重错误,毕竟出现死锁了,就回滚,然后通过业务重试一般就没问题了,这是业务无损的。而关掉死锁检测意味着可能会出现大量的超时,这是业务有损的。
2 控制并发度。客户端实现的,当客户端增加了,并发度还是很大。所以要在服务器那里作控制。可以写在中间件里面,也可以直接修改MYSQL的源码,思路就是对于相同行的更新,在进入引擎之前排队。这样在 InnoDB 内部就不会有大量的死锁检测工作了。
如果没有数据库专家,可以考虑把一行改成逻辑上的多行来减少锁冲突。比如影院的一行改称10行,就把冲突概率降低了1/10。但是需要根据业务逻辑做详细设计。如果账户余额可能会减少,比如退票逻辑,那么这时候就需要考虑当一部分行记录变成 0 的时候,代码要有特殊处理。
如果你要删除一个表里面的前 10000 行数据,有以下三种方法可以做到:
第一种,直接执行 delete from T limit 10000;
第二种,在一个连接中循环执行 20 次 delete from T limit 500;
第三种,在 20 个连接中同时执行 delete from T limit 500。
第一种方式(即:直接执行 delete from T limit 10000)里面,单个语句占用时间长,锁的时间也比较长;而且大事务还会导致主从延迟。尽量不要有长事务。。。。
第三种方式(即:在 20 个连接中同时执行 delete from T limit 500),会人为造成锁冲突。