开发需要懂的MYSQL - 锁机制整理

217 阅读7分钟

表锁

Myisam存储引擎只支持表锁
InnoDB存储引擎支持表锁和行级锁

# 表共享锁
lock table XXX read
# 表独占锁
lock table XXX write

行级锁

InnoDB存储引擎支持表锁和行级锁

select .... lock in share mode;
select .... for update;
insert update delete

排他锁(行锁)

行级锁之一,一个事务获取了一行数据的排他锁,其他事务不能获取该行数据的排他锁与共享锁,即阻塞其他用户对同一数据的当前读和写操作

共享锁(行锁)

行级锁之一,一个事务获取了一行数据的共享锁,其他事务不能获取该行数据的排他锁,但可以获取共享锁,即只阻塞其他用户对同一数据的写操作

意向排他锁(表锁)

意向锁是表锁,意向锁之间不冲突,但是意向锁和表锁及行锁冲突,所以意向排他锁和排他锁冲突,意向排他锁和共享锁也冲突

意向共享锁(表锁)

意向锁是表锁,意向锁之间不冲突,但是意向锁和表锁及行锁冲突,所以意向共享锁和排他锁冲突

间隙锁

InnoDB范围查询会产生间隙锁,左开右闭,保证数据范围内锁定

next-key锁

InnoDB RR模式下,非唯一索引的查询,或唯一索引查询未命中,或唯一索引查询到多条数据,都会产生next-key锁,即行级锁 + gap间隙锁

  1. where唯一索引,命中,行锁
  2. where唯一索引,未命中,间隙锁
  3. where多个唯一索引,命中,临键锁
  4. where普通索引范围,临键锁
  5. where普通索引,命中,临键锁
  6. where普通索引,未命中,间隙锁
  7. where非索引,会在主键上使用间隙锁,等于锁住全表,开销极大

插入意向锁(特殊的间隙锁)

插入意向锁是一种间隙锁形式的意向锁,意向锁之间不冲突,但是跟真正的间隙锁冲突,所以RR级别下,next-key锁配合插入意向锁可以实现杜绝幻读。 INSERT 语句在执行插入之前,会先在 gap 中加入插入意向锁,如果是唯一索引,还会进行 Duplicate Key 判断,如果存在相同 Key 且该 Key 被加了互斥锁,则还会加共享锁,然后等待(因为这个相同的 Key 之后有可能会回滚删除,这里非常容易死锁)。等到成功插入后,会在这条记录上加排他记录锁

auto-inc锁(表锁)

自增锁是一种特殊的表级锁。专门针对事务插入AUTO_INCREMENT类型的列 如果表中存在自增键,insert语句未指定值,则多个事物的插入操作将产生排队阻塞.

乐观锁

乐观锁是一种思想,而不是真正的锁,总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不会上锁,只在更新的时候会判断一下在此期间别人有没有去更新这个数据。 一般来讲是通过版本号实现。

乐观锁和MVCC区别

乐观锁主要解决写写冲突,MVCC主要解决读写冲突

悲观锁

总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞,直到它拿到锁(共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程)。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。Java中synchronizedReentrantLock等独占锁就是悲观锁思想的实现。

死锁

死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。 死锁的产生:

  1. 行级锁互相阻塞,比如A事物获取a,b两条数据的排他锁,B事物获取数据b,a两条数据的排他锁,A和B可能就会产生相互阻塞等待
  2. 产生间隙锁后,在间隙中执行insert操作,此时产生插入意向锁防止幻读,这个操作本身没问题,但是如果有两个事物同时执行该逻辑,A和B各自都获取了间隙锁(间隙锁可以共存),A想执行insert但是插入意向锁和B的间隙锁冲突,B想执行insert但是和A的间隙锁冲突,互相阻塞,产生死锁 这里的参考文章: blog.csdn.net/fofcn/artic…

INNODB的锁机制

InnoDB支持事物,有四种事物隔离级别.其中RC,RR级别又分为当前读和快照读.

  1. 当前读按照上文所说的封锁协议 + next-key锁实现
  2. 快照度通过MVCC机制实现

当前读-悲观锁实现

一般数据库的四种隔离级别

  1. 未提交读 RU
  2. 提交读(不可重复读) RC
  3. 可重复读 RR
  4. 序列化 Serializable

为了支持这4种隔离级别,一般可以通过三级封锁协议规范实现。

封锁协议:

  1. 一级封锁协议: 读不加锁,写加排他锁,事务结束释放 (RU)
  2. 二级封锁协议: 读加共享锁,读完就释放,写加排他锁,事务结束释放 (RC,解决脏读)
  3. 三级封锁协议:读加共享锁,事务结束释放,写加排他锁,事务结束释放 (RR,解决脏读,不可重复读,但是存在幻读)

MYSQL的RR当前读不仅使用三级封锁协议,还加入了next-key锁,配合插入意向锁,防止幻读产生,但也使得开销更大,更容易发生死锁,推荐使用RC隔离级别(其他数据库默认都是RC,只有MYSQL默认RR,这是MYSQL的binLog日志格式报错的历史问题导致的),至于RC产生的不可重复读问题,很多情况反而契合真实业务,需要具体分析.

快照读-MVCC

各个数据库的多版本并发控制实现并不相同.MYSQL的MVCC通过隐藏字段,undo-log,readview实现

  1. 数据行隐藏字段:每个数据行都有4个隐藏字段

    1. DB_TRX_ID最近操作的事务ID
    2. DB_ROLL_PTR指向上个版本的回滚指针,rollback时使用
    3. DB_ROW_ID隐式自增主键,如果数据没有主键,会生成一个这样的隐藏主键
    4. DELETED_BIT,删除标记位,删除时设置
  2. undo-log 回滚日志,rollback时使用,实现事务的原子性

    1. undolog是逻辑日志,记录相反的操作SQL,比如事务中delete,undolog中就记录insert
    2. 在更新数据之前,MySQL会提前生成undo log日志,当事务提交的时候,并不会立即删除undo log,因为后面可能需要进行回滚操作,要执行回滚(rollback)操作时,从缓存中读取数据。undo log日志的删除是通过通过后台purge线程进行回收处理的。
    3. 大概的链路图(网上找的)2022041510105924.png
  3. readView是一个结构体,主要存储了4个字段

    1. m_ids 表示在生成readview时,当前系统中活跃的读写事务id列表
    2. min_trx_id: 表示在生成readview时,当前系统中活跃的读写事务中最小的事务id,也就是m_ids中最小的值
    3. max_trx_id: 表示生成readview时,系统中应该分配给下一个事务的id值
    4. creator_trx_id: 表示生成该readview的事务的事务id

    RC模式下: 每次快照读select语句生成一个readView,select完成生命周期即结束
    RR模式下: 每次事物的第一个select生成readView,事务结束时生命周期才结束