1.产生问题

pos机同步数据的时候,涉及的表比较多,写了事务和锁,同步的时候导致死锁

2.分析问题

部分代码如下– InnoDb/REPEATABLE-READ

$serverCurrentTime = time();
Db::startTrans();
try {
    // 处理订单、会员
    $orderId = Db::name('order')->lock(true)->max('id') ?: 0;
    $orderDetailId = Db::name('order_detail')->lock(true)->max('id') ?: 0;
    $orderBandId = Db::name('order_band')->lock(true)->max('id') ?: 0

2.1 举例间隙锁

$orderId = Db::name('order')->lock(true)->max('id') ?: 0;

假设表内有五条数据,id为1-5,锁住的区间为 5这条数据-(5,+∞)本质锁的是两部分,5是record_lock,(5,+∞)是gap_lock

2.1 A线程锁住了order,B线程锁住了order_band,两个线程互相等待造成死锁

2.2 B线程读取完成order最大数据,A开始读取并对其加锁,B线程想插入数据就得等A释放,造成死锁

2.3 针对2.2做出解释–B先查完最大id然后计算出来最大id,查最大id的时候已经形成了间隙锁,如2.1。此时B还是有锁。A开始查最大id,查不到因为B已经对那条数据加锁了。但是在等待的期间A在锁队列里面隐式预留了间隙锁。这个时候B想插入最大id+1的数据进入数据库,但是A已经把最大id,+∞锁住了,所以死锁。简单地说就是A在等最大id那条数据释放,B在等A的间隙锁释放,就造成了死锁

2.4 锁是在事务级别获取的,只有commit或者rollback了才能释放锁

2.5隐式预留间隙锁的解释–这个跟锁等待队列机制有关系,A虽然没有获取到锁,但是已经在队列预留了位置,预留了对最大id到+无穷的间隙

2.6 具体原理

2.6.1. B 查询最大 id,持有 Record Lock (id=5) + Gap Lock (5, +∞),B 还在事务中,锁未释放

2.6.2. A 查询最大 id,被 B 阻塞,在锁队列中等待,A 虽未获得锁,但在队列中预留了对间隙的访问

2.6.3. B 尝试插入 id=6,需要 Insert Intention Lock,被 A 的锁请求阻塞(队列中的 A 挡住了 B)

2.6.4. 死锁形成:A 等 B 释放 id=5 的锁,B 等 A 完成锁请求(或放弃)

2.6.5 B插入数据的请求放在了A的请求后面,等待也是上锁,只不过是上锁没有完成,所以造成死锁,Insert Intention Lock 必须等待所有在它之前排队的锁请求

2.6.7即使 A 还没拿到锁,但 A 已在队列中/B 不能”插队”获取 Insert Intention Lock

2.6.8 Gap Lock 在 REPEATABLE-READ 下防止幻读(相同查询不同记录),但增加死锁概率

2.7 获取死锁日志–如果innodb_print_all_deadlocks = OFF 那么日志在内存中,重启mysql服务之后就会消失,获取死锁信息–输入mysql的root密码即可查看

mysql -u root -p -e "SHOW ENGINE INNODB STATUS\G" | grep -A 50 "LATEST DETECTED DEADLOCK

3.todo

3.1 通过其他方法获取mysql的root密码