MySQL-InnoDB锁(一)

本文主要记录InnoDB存储引擎中锁的关键点,下篇文章通过实例确认加锁的范围。

InnoDB中的锁

1. 锁提供数据完整性和一致性

2. InnoDB行级锁:共享锁(S)和排他锁(X)。

  为了支持多粒度锁定,InnoDB支持意向锁,该锁允许事务在行锁和表锁同时存在。包括意向共享锁(IS,为某些记录加意向共享锁)和意向排他锁(IX,为某些记录加意向排他锁)

  意向锁将锁定的对象分为多个层次,意味着事务希望在更细粒度上进行加锁,如需要对页上的记录r加X锁,分别需要对数据库、表、页加意向锁IX,最后对记录r加X锁,其中任何一部分导致等待,该操作需要等待粗粒度锁的完成。

3. 锁的查看方式

  通过show engine innodb status来查看,其中的transactions片段可以看到事务,其中包括锁等待。

  在information_schema架构下,有3个表记录了事务和锁相关的信息。分别是INNODB_TRX,INNODB_LOCKS,INNODB_LOCK_WAITS。(具体看书或博客)

4. 一致性非锁读

  非锁定读机制,是InnoDB存储引擎的默认设置,默认读取不会占用和等待表上的锁

  InnoDB存储引擎利用行多版本控制实现一致性非锁读,

  当读取的行正在加X锁DELETE或UPDATE时,读操作不会等待锁释放,会读取行的一个快照数据

  快照数据是指该行之前版本的数据,其通过undo段完成的,因为undo用于事务中回滚数据,因此快照数据没有额外的开销,并且读快照也不需要加锁

  每行记录可能有多个版本的快照,由此带来的并发控制,称为多版本并发控制(Multi Version Concurrency Control,MVCC)

  在READ COMMIT事务隔离级别下,非锁一致性读总是读取被锁定行的最新一份快照数据,故可能会读到其他事务的提交,违反了ACID的隔离性

  在REPEATALE READ事务隔离级别下,非锁一致性读总是读取事务开始时的行数据版本

5. 一致性锁定读

  除了默认非锁定一致性读,还可以显式加锁读,有2种一致性锁定读:

    SELECT .... FOR UPDATE    加IX锁

    SELECT .... LOCK INSHARE MODE     加IS锁

6. 自增长与锁

  对含有自增长值的表都有一个自增长计数器,插入时根据该自增长计数器加1赋予该增长列,通过AUTO-INC Locking实现,其为特殊的表锁机制

  并发插入时,该方式性能较差,一个事务需要等待另一个事务而阻塞

  5.1.22版本后,InnoDB提供了一种轻量级互斥量的自增长实现机制,提高插入性能

  InnoDB通过innodb_autoinc_lock_mode参数控制自增长模式,值有0\1\2

  自增长值的列必须是索引,同时必须是索引的第一个列

7. 外键和锁

  对一个外键列,如果没有显式对该列加索引,InnoDB会自动为其加一个索引,可以避免表锁(锁是对索引进行锁定的)

  对于外键值的插入或更新是首先需要查询父表中的记录,即SELECT 操作,会为父表的记录使用SELECT....LOCK IN SHARE MODE加一个S锁。而不是使用一致性非锁定读的方式,因为该方式在读取时可能读到的是行记录的快照,如果父表同时更改该记录,可能会发生数据不一致的问题

  如事务A中父表删除记录r(加X锁,未提交),事务B插入引用父表记录r的行

    若在父表加S锁,碰到父表已经加了X锁,则事务B阻塞等待;

    若使用一致性非锁定读,并在REPEATABLE READ事务隔离级别下,可以读到父表中的r记录,事务B插入成功。

    等到事务A提交后,就会出现父、子表数据不一致的情况。

锁的算法

1. InnoDB支持行级锁,还支持范围锁。有3种行锁实现:

  Record Lock:单行锁,锁住索引记录,如果表没有建立索引,会使用隐式的主键进行锁定

  Gap Lock:间隙锁,锁定一个范围,不包括记录本身。可由不同的事务共同持有,只是阻止插入(MySQL文档描述:Gap locks in InnoDB are purely inhibitive”, which means that their only purpose is to prevent other transactions from inserting to the gap. Gap locks can co-exist. A gap lock taken by one transaction does not prevent another transaction from taking a gap lock on the same gap. There is no difference between shared and exclusive gap locks. They do not conflict with each other, and they perform the same function.

  Next-Key Lock: Record Lock + Gap Lock,锁定一个范围,包括记录本身。可以解决Phantom Problem,在默认的事务隔离级别下,REPEATABLE READ 采用该锁技术。

2. Next-Key Lock 的锁机制

(1)有索引情况

  当索引具有唯一属性时

    ① 查具体行时,Next-Key Lock会降级为Record Lock,即仅锁住索引本身,而不是范围;

    ② 查小于某值时,Next-Key Lock会锁定小于该值的所有行,并锁定到下一个键值的范围;

    ③ 查大于某值时,Next-Key Lock会锁定大于该值的所有行,并锁定到前一个键值的范围;

  当索引列为辅助索引,Next-Key Lock会为聚集索引和辅助索引分别加锁,聚集索引相等时在该索引上只加Record Lock,辅助索引正常加,聚集索引也是范围查找时,应该会加GAP锁。

 

  两种显式关闭Next-Key Lock的方式:事务隔离级别改为READ COMMITTED;设置innodb_locks_unsafe_for_binlog为1

(2)没有索引情况

  因为没有索引键值的时候,自动隐式创建索引会锁定整个区间。但依然是行锁而不是表锁,只是等价于表锁。

锁的问题

1. 脏读

  脏读,是事务读取到脏数据。脏数据是事务对缓冲池中行记录的修改,还没有被提交。如READ UNCOMMIT事务隔离级别,违反了事务隔离性

  脏页,是指事务提交后在缓冲池中已被修改的页,但还没有刷新到磁盘中,即数据库实例内存中的页与磁盘中的页数据不一致,当然当数据刷新到磁盘之前,日志都已经被写到重做日志文件中。

  对脏页的读取是正常的,因为脏页的刷新是异步的,内存中和磁盘最终会达到一致性。这种方式不影响数据的可用性,还可以带来性能的提高。

2. 不可重复读(幻读)

  在一个事务内两次查询得到的数据不一致的情况,为不可重复读。如READ COMMITTED事务隔离级别,违反事务一致性

  在默认的事务隔离级别下,REPEATABLE READ 采用Next-Key Lock锁技术,锁定索引范围,可避免该现象。

3. 丢失更新

  在READ UNCOMMIT事务隔离级别下,并发事务可以相互覆盖其他事务的更改,导致丢失更新。

死锁

  死锁是只两个或两个以上的事务在执行过程中,因争夺资源而造成的一种互相等待的现象

  解决死锁方式:

    ① 最简单的方式是超时,当事务等待时间超过时间阈值,进行回滚。根据FIFO的顺序选择回滚对象时,若超时的事务所占权重比较大,即更新操作很多行,占用较多的undo log,回滚该事务比回滚其他事务占用时间长,该方式就不合适。

    ② 等待图(wait-for graph),一种主动的死锁检测方式,要求数据库存储两种信息:锁的信息链表和事务的等待链表,前者记录当前事务加锁信息,后者记录事务的等待情况。

    通过这两张表可以构建一张事务等待图,当出现回路,表示存在死锁。此时,InnoDB选择回滚undo量最小的事务。 

锁升级

  InnoDB存储引擎不存在锁升级的问题。

  其不是根据每个记录来产生行锁的,而是根据每个事务访问的每个页对锁进行管理的(通过意向锁为行记录加锁),故事务锁住页中一个记录或多个记录,锁开销是一样的。

参考

《MySQL技术内幕-InnoDB存储引擎》

详细介绍MySQL/MariaDB的锁

 

posted @ 2019-08-24 17:48  水木竹水  阅读(360)  评论(0编辑  收藏  举报