复现不同索引之间的死锁

不同索引之间的锁也可能造成死锁,常见的如主键索引和辅助索引之间的锁循环等待

事务 A 事务 B
BEGIN BEGIN
update order_record set status=10 where order_no=4 update order_record set status=1 where id=4

分析:

事务 A 事务 B
获取 idx_order_status 辅助索引行锁
获取主键索引行锁
获取主键索引行锁
获取 idx_order_status 辅助索引行锁

复现

需要使用 go 协程进行并发模拟,2个协程,不停地执行这两个语句,并且要保持effected rows>0 , 否则似乎会被数据库优化器优化掉,并不加锁直接返回

package main

import (
    "fmt"
    "github.com/jinzhu/gorm"
    _ "github.com/jinzhu/gorm/dialects/mysql"
    "time"
)

func main() {
    db, err := gorm.Open("mysql", "root:root@(127.0.0.1:3306)/test?charset=utf8mb4&parseTime=True&loc=Local")
    if err!= nil{
        panic(err)
    }
    defer db.Close()

        go func() {
            for {
                err:=db.Exec("update order_record set status=1 where id=4").Error
                if err!=nil {
                    fmt.Println(err.Error())
                }
            }
        }()

        go func() {
            for {
                err:=db.Exec("update order_record set status=10 where order_no=4").Error
                if err!=nil {
                    fmt.Println(err.Error())
                }
            }
        }()

    time.Sleep(1*time.Hour)
}

排查

  • show innodb engine status 查看日志
LATEST DETECTED DEADLOCK
------------------------
2021-11-13 12:28:59 0x7000099ff000
*** (1) TRANSACTION:
TRANSACTION 3416559, ACTIVE 0 sec starting index read
mysql tables in use 1, locked 1
LOCK WAIT 3 lock struct(s), heap size 1136, 3 row lock(s)
MySQL thread id 86892, OS thread handle 123145462673408, query id 1820252 localhost 127.0.0.1 root Searching rows for update
update order_record set status=10 where order_no=4
*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 3193 page no 3 n bits 72 index PRIMARY of table `test`.`order_record` trx id 3416559 lock_mode X locks rec but not gap waiting
Record lock, heap no 5 PHYSICAL RECORD: n_fields 6; compact format; info bits 0
 0: len 4; hex 80000004; asc     ;;
 1: len 6; hex 0000003421ee; asc    4! ;;
 2: len 7; hex 58000010440a70; asc X   D p;;
 3: len 4; hex 80000004; asc     ;;
 4: len 4; hex 80000001; asc     ;;
 5: SQL NULL;

*** (2) TRANSACTION:
TRANSACTION 3416558, ACTIVE 0 sec updating or deleting
mysql tables in use 1, locked 1
3 lock struct(s), heap size 1136, 2 row lock(s), undo log entries 1
MySQL thread id 86891, OS thread handle 123145463787520, query id 1820251 localhost 127.0.0.1 root updating
update order_record set status=1 where id=4
*** (2) HOLDS THE LOCK(S):
RECORD LOCKS space id 3193 page no 3 n bits 72 index PRIMARY of table `test`.`order_record` trx id 3416558 lock_mode X locks rec but not gap
Record lock, heap no 5 PHYSICAL RECORD: n_fields 6; compact format; info bits 0
 0: len 4; hex 80000004; asc     ;;
 1: len 6; hex 0000003421ee; asc    4! ;;
 2: len 7; hex 58000010440a70; asc X   D p;;
 3: len 4; hex 80000004; asc     ;;
 4: len 4; hex 80000001; asc     ;;
 5: SQL NULL;

*** (2) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 3193 page no 4 n bits 80 index idx_order_status of table `test`.`order_record` trx id 3416558 lock_mode X locks rec but not gap waiting
Record lock, heap no 7 PHYSICAL RECORD: n_fields 3; compact format; info bits 0
 0: len 4; hex 80000004; asc     ;;
 1: len 4; hex 8000000a; asc     ;;
 2: len 4; hex 80000004; asc     ;;

*** WE ROLL BACK TRANSACTION (1)
  • 分析日志, 事务 1 等待 index PRIMARY ,事务 2 持有 index PRIMARY ,事务 2 等待 index idx_order_status , 事务 2 被回滚

后续

关于数据库优化器的策略,优化过程日志查看 CMU15-445 回顾