MySQL(InnoDB)的锁

发布于 1月6日  约 6 分钟

锁的定义

不管在任何系统中,锁都是用来实现对共享资源的并发访问的一种手段。

区分两个概念:lock 与 latch

  • lock 是在事务中为了保护数据库内容而实现的锁,比如行锁、表锁、意向锁等。通过等待图(waits-for grapg)、超时时间等实现死锁检测和处理。存在于Lock Manager 的哈希表中。
  • latch 是线程之间为了保护内存数据结构而实现的轻量级锁,比如读写锁、互斥量。一般无死锁检测和处理机制,通过源代码的加锁顺序保证无死锁的情况发生。

show engine innodb mutex; 可以查看latch的状态,在debug版中可以看到更多的信息。
比如:mutex被请求的次数、自旋锁的次数、自旋内部循环总次数等等。
latch锁更多的mysql内部逻辑所需要的锁机制,偏底层一些。而lock指的是用户在对数据进行并发操作时的机制。这两者不是一个概念。对缓冲池的LRU列表的增删移动等需要加锁,这个锁应该就是latch锁。

下面主要讲一下lock~~

锁类型

  • 行锁

    • 共享锁S
    • 排它锁L
  • 表锁

    • 意向共享锁 IS
    • 意向排它锁 IX
    • 意向锁的作用:为了提高锁的检测效率,主要用于锁全表时可以快速检测,而不必逐个检测行锁。

查看锁状态

可以查询information_schema下的三个表:

  • INNODB_TRX 事务
  • INNODB_LOCKS 锁
  • INNODB_LOCK_WAITS 锁等待

一致性非锁定读

一致性非锁定读是innodb提供的一种基于MVCC的不用等待锁的一种访问数据的方式。
当读取的某行被加上了排它锁时,访问此行上的历史版本,而不用等待锁,所以速度更快。
这里的历史版本是通过undo段实现的,undo段是用来实现事务回滚的,此时用来实现非锁定读真是一箭双雕,没有额外的开销,而且历史版本的读取不需要锁,因为不会存在并发修改历史版本。

一致性非锁定读需要隔离级别为:read committed 或 repeatable read。其实也好理解,如果是read uncommit 的话,直接读取脏数据即可,当然是不需要读取历史版本的。如果是serialiable隔离级别,行数据即是一致性版本,直接读取行数据即可。
一致性非锁定读也解释了为什么read committed 是读取已提交数据并可能出现不可重复读的问题。也解释了repeatable read 为什么解决了不可重复读的问题。因为read committed时,非锁定读读取的是历史提交版本的最新版,重复读的时候可能最新版会有变化,所以不可重复读。repeatable read时,非锁定读读取的总是事务开始时的那个版本,所以即使重复读,读到的数据都是一样的。

一致性锁定读

一致性锁定读,即对读操作加锁,保证读到的数据是此刻最新的稳定版本。
主要有两类
select ... for update :对读取的行加排它锁
select ... lock in share mode : 对读取的行加共享锁

自增值与锁

自增值,我们经常用来作为主键,那么InnoDB是如何保证其自增的呢?
InnoDB 使用一种称为 AUTO-INC Locking 的技术实现,其原理是,每个自增列都会在表中分配一个自增计数器,通过表锁机制完成自增。为了提高插入性能,这个锁不是在事务完成后释放,而是在插入sql执行完成后立即释放。

上面的方法虽然进行了优化,但是还是需要等待前一个插入完成。从MySQL5.1.22开始,InnoDB提供一种轻量级互斥量的自增长实现机制,使用互斥量对内存的计数器进行累加操作,这种机制可以大大提高并发性能。并通过innodb_autoinc_lock_mode来控制。
首先把插入语句分为4类:

  • simple inserts : 在插入前能确定插入的行数,比如普通的insert语句
  • bulk inserts : 在插入前不确定插入的行数,比如insert ... from 或者load data
  • mixed-mode inserts :插入数据中一部分是确定值,一部分是自增的。比如 insert into t (id, c) values (1, '1'), (NULL, '2'), (3, '3');
  • inserts-like : 所有插入语句

innodb_autoinc_lock_mode 可以取值 0、1、 2。

  • innodb_autoinc_lock_mode=0:使用原AUTO-INC locking方式
  • innodb_autoinc_lock_mode=1:对于simple inserts 采用互斥量对内存的计数器进行累加操作。对于bulk inserts还是使用AUTO-INC locking方式。这种方式如果不考虑回滚的话,自增值是连续的。
  • innodb_autoinc_lock_mode=2:对于任何插入都采用互斥量的方式。

innodb_autoinc_lock_mode=2 无疑是并发效率最高的,但是会有一些问题。自增值不是连续的,更重要的是在基于Statement-Base Replication 会出现数据不一致的问题。
默认innodb_autoinc_lock_mode=1。

注意 自增长的列必须是索引,并且是索引的第一列。

外键与锁

对于外键的插入或更新,需要查询外键指向的表,我们称为父表。这个查询操作不能使用非锁定读,使用的是select ... lock in share mode。所以外键会增加死锁的概率。

锁的实现

InnoDB的三种行锁的实现

  • 记录锁 Record Lock : 单行记录上的锁
  • 间隙锁 Gap Lock : 锁定一个范围,不包括记录本身
  • Next-Key Lock : Record Lock + Gap lock,锁一个范围,包括记录本身

如果索引含有唯一性,Next-Key Lock 会降级为 Record Lock。
当有多个索引时吗,需要分别进行锁定。对于辅助索引,会对辅助索引的下一个健值加上间隙锁。

read committed 隔离级别只采用Record Lock
repeatable read 隔离级别采用Next-Key Lock

InnoDB使用页中的位图来实现锁,而不是一个记录一把锁。一个事务锁定一个页中一个记录还是多个记录,开销都差不多,所以InnoDB没必要做锁升级。

公众号.png

阅读 163发布于 1月6日

推荐阅读
目录