InnoDB存储引擎锁的实现提供了一致性的非锁定读、行级锁支持。行级锁没有相关的额外开销,并可以同时得到并发性和一致性。

LOCK与LATCH

Latch

轻量级的锁,因为其要求锁定的时间必须非常短,如果持续的时间长,则应用的性能会非常差。InnoDB存储引擎中,latch又可以分为mutex(互斥量)和rwlock(读写锁),目的就是用来保证并发线程操作临界资源的正确性,通常没有死锁检测机制。

LOCK

Lock对象是事务,用来锁定数据库中的对象,如表、页、行。并且一般lock的对象仅在事务commit或者rollback后进行释放(不同的隔离级别释放时间不同)。lock有死锁机制。

lock & latch

Latch 信息查看

1
show engine innodb mutex;
名称 说明
count mutext被请求的次数
spin_waits spin lock 的次数,InnoDB存储引擎latch在不能获得锁时首先进行自旋,若自旋后还不能获得,则进入等待
spin_rounds 自旋内部循环的总次数,每次自旋的内部循环是一个随机数。spin_rounds/spin_waits 表示平均每次自旋所需要的内部循环次数
os_waits 操作系统等待的次数,当spinlock通过自旋还不能获得latch时,则会进入操作系统等待状态,等待被唤醒
os_yields 进行os_thread_yield唤醒操作的次数
os_wait_times 操作系统等待的时间,单位ms

Lock信息查看

1
2
show engine innodb status;
information_schema 下 innodb_trx、innodb_locks、innodb_lock_waits

InnoDB存储引擎中的锁

锁类型

  • 共享锁 S Lock, 允许事务读一行数据
  • 排他锁 X Lock, 允许事务删除或者更新一行数据。

InnoDB存储引擎支持多力度锁定,允许事务在行级上的锁和表级上的锁同时存在。为了支持在不同粒度上进行加锁操作,Mysql支持意向锁。

一致性非锁定读

一致性非锁定读是InnoDB存储引擎通过行多版本控制的方式来读取当前执行时间数据库中行的数据。
如果读取的行正在执行DELETE或者UPDATE操作,这时读取操作不会u因此去等待行上锁的释放,InnoDB存储引擎会读取行的一个快照数据。快照数据是改行之前的版本数据,通过undo段来完成,undo用来在事务中回滚数据,一次快照数据本身没有额外开销。读取快照数据本身没有额外开销,也不需要上锁。

一个行记录可能有多个快照数据,由此来来的并发控制称为多版本并发控制。
在事务隔离级别为READCOMMITED和REPEATABLEREAD下, InnoDB存储引擎使用非锁定一致性读。但是对于快照的定义不同。

在READ COMMITTED事务隔离级别下,对于快照数据,非一致性读总是读取被锁定行的最新一份快照数据。
在REPEATABLE READ事务隔离级别下,对于快照数据,非一致性读总是读取事务开始时的行数据版本。

一致性锁定读

1
2
select ... for update
select ... lock in share mode

自增长与锁

InnoDB自增长ID的锁机制称为Auto-Inc Locking, 为了提高插入的性能,在完成对自增长值插入的SQL语句后立即释放,而不需要等到事务提交。必须等待前一个插入完成,并且对Inser。。Select的大户巨量的插入会影响插入性能,因为另一个事务中的插入会被阻塞。

轻量级互斥量的自增长实现机制,大大提高了自增长值插入的性能。通过innodb_autoinc_lock_mode来控制自增长的模式。默认值为1.

  • insert-like : 所有的插入语句,insert/replace/insert-select, replace-select, load data.
  • simple-inserts: 插入之前就确定插入行数的语句,如insert/replace。
  • bulk inserts: 插入前不能确定得到插入行数的语句。
  • mixed-mode inserts: 插入中有一部分的数据是自增长的, 一部分是确定的。

innodb_autoinc_lock_mode: 0, auto-inc locking 的方式,
innodb_autoinc_lock_mode: 1, 默认值,
对于imple inserts, 该值会用互斥量去对内存中的计数器进行累加操作,
对于bulk inserts,使用auto-inc locking 方式,不考虑回滚操作时,对于自增值列的增长是连续的,statement-based方式的replication可以很好的工作。
innodb_autoinc_lock_mode: 2, 所有的insert-like自增长的值产生的都是通过互斥量。性能最高。出现的问题是增长的值不连续, 基于statement-based replication 会出现问题。所以在使用当前模式时,任何时候都应该使用 row-based replalication, 这样才能保证最大的并发性能以及replication主从数据的一致。

MyISAM自增主键是表锁设计,自增长不用考虑并发插入的问题, 因此在Master傻姑娘使用InnoDB存储引擎,slave上使用MyISAM存储引擎的replication架构下用户必须考虑这种问题。???

外键和锁

InnoDB引擎中,对于一个外键列,如果没有显式地对这个列加索引,InnoDB存储引擎自动对其家一个索引,这样就可以避免表锁。

对于外键值的插入或者更新,首先要查询父表中的记录,如果以一致性非锁定读的方式读取,会发生数据不一致的问题,此时需要对父表加一个S锁。如果这时父表已经有一个X锁,则子表的操作会被阻塞。

锁的算法

行锁的三种算法

  • Record Lock: 单行记录上锁,总是会去锁定索引记录,如果InnoDB存储引擎在建立的时候没有设置任何一个索引,那么InnoDB存储引擎会使用隐式的主键来进行锁定。
  • Gap Lock: 锁定一个范围,不包含记录本身,为了阻止多个事务将记录插入到同一个范围内。但是又导致Phantom Problem(幻读)问题的产生。
  • Next-Key Lock: Gap Lock + Record Lock, 锁定一个范围,并锁定记录本身, 解决了Phantom Problem。

当查询的索引含有唯一属性时,InnoDB存储引擎会对Next-Key Lock进行优化,将其降级为Record Lock锁住索引本身而不是范围。

对于辅助所用,加上的是Next-keyLock, 锁定的是范围,并且还会对下一个键值加上Gap Lock.

显示关闭gap_lock后,除了外键约束和唯一性检查依然需要GapLock,其余情况仅需要使用RecordLock进行锁定。

  • 将事务的隔离级别设置为READ COMMITTED
  • 设置innodb_locks_unsafe_for_binlog=1

解决Phantom Problem

默认的可重复读的事务隔离级别下,InnoDB存储引擎采用Next-Key Locking机制来避免幻读。
在ReadCommited模式下,仅采用RecordLock来加锁。

锁问题

通过所及之可以实现事务的隔离性要求,使得事务可以并发地工作。
脏数据是指事务对缓冲池中行记录的修改,并且还没有被提交的数据。

脏读

在不同的事务下,当前事务可以读到另外事务未提交的数据(脏数据)。

不可重复读

在一个事务内,多次读取同一数据集合,由于当前事务看到了另外的事务提交的数据,两次返回的数据是不一样的。
InnoDB存储引擎中,使用Next-Key Lock算法来避免不可重复读的问题(不可重复读也叫幻读)。

丢失更新

一个事务的更新操作会被另一个事务的更新成操作覆盖。

阻塞

因为不同锁止键的兼容性关系,在有些时刻一个事务中的锁需要等待另一个事务中的锁匙放它所占用的资源,就是阻塞,阻塞时为了确保事务可以并发且正常地运行。

innodb_lock_wait_timeout: 等待超时时间
innodb_rollback_on_timeout: 等待超时时是否对进行中的事务进行回滚。默认为OFF,默认情况下InnoDB存储引擎不会回滚超时引发的错误异常。。

1
SET @@innodb_lock_wait_timeout=60;

死锁

概念

两个或者两情歌以上的事务在执行过程中,因为争夺锁资源而造成相互等待的现象。

超时解决

解决思索最简单的方式是不要有等待,将任何的等待都化为回滚,并且事务重新开始。
如果超时的事务更新了很多行,占用了较多的undo log会导致回滚性能差。

死锁检测

采用 wait-for graph(等待图)的方式进行死锁检测。要求数据库保存两种信息,通过这两种信息构造一张图,如果图中有回路,就代表存在死锁,从而选择回滚undo量最小的事务。

  • 锁的信息链表
  • 事务等待链表

锁升级

锁升级是指将所得粒度降低。InnoDB存储引擎不存在锁升级的问题。因为不是根据每个记录来产生行锁,而是根据每个事务访问的每个页对锁进行管理,采用的是位图的方式。因此不管是一个事务锁住页中一个记录还是多个记录,开销通常是一样。