Mysql的InnoDB存储引擎锁机制

1. 事务

1.1 事务概述

  事务用来保证数据库的完整性——要么都修改,要么都不修改。事务必须知足ACID四个特性。mysql

  • 原子性(atomicity),指整个数据库事务是不可分割的工做单位。只有使事务中全部的数据库操做执行都成功,才算整个事务成功。若是事务中任何一个SQL语句执行失败,那么已经执行成功的SQL语句也必须撤销,数据库状态应该退回到执行事务前的状态。
  • 一致性(consistency),指事务将数据库从一种状态转变为下一种一致的状态,在事务开始以前和事务结束之后,数据库的完整性约束没有被破坏。
  • 隔离性(isolation),一个事务的影响在该事务提交前对其余事务都不可见——这经过锁来实现。
  • 持久性(durability),事务一旦提交,其结果就是永久性的。即便发生宕机等故障,数据库也能将数据恢复。

1.2 事务隔离级别

  ISO和ANIS SQL标准制定了四种事务隔离级别的标准:算法

  • READ UNCOMMITEED,读未提交,会出现脏读问题。
  • READ COMMITTED,读已提交,会出现幻读问题。
  • REPEATABLE READ,可重复读(InnoDB存储引擎的默认隔离级别)
  • SERIALIZABLE,会给每个读操做加一个共享锁,不支持一致性的非锁定读,隔离性最高。

2. 锁的粒度和类型

  InnoDB既支持行级锁,也支持表级锁,默认状况下是采用行级锁。
InnoDB存储引擎实现了两种标准的行级锁:sql

  • 共享锁(S Lock),容许事务读一行数据,不容许其余事务获取排他锁。
  • 排他锁(X Lock),容许事务删除或者更新一行数据,不容许其余事务得到共享锁或排他锁。

  InnoDB存储引擎支持多粒度锁定,容许行级锁和表级锁同时存在。为了支持在不一样粒度上进行加锁操做,InnoDB存储引擎提供了意向锁。意向锁是表级别的锁,其设计目的主要是为了在一个事务中揭示下一行将被请求的锁的类型。InnoDB存储引擎支持两种意向锁:数据库

  • 意向共享锁(IS Lock),事务想要得到一个表中某几行的共享锁。
  • 意向排他锁(IX Lock),事务想要得到一个表中某几行的排他锁。

  意向锁不会阻塞除全表扫描之外的任何请求。并发

3. 一致性的非锁定读

  一致性的非锁定读(consistent nonlocking read)是指InnoDB存储引擎经过多版本控制(multi versioning)的方式来读取当前执行时间数据库中行的数据。若是读取的行正在执行DELETE、UPDATE操做,这是读取操做不会所以而等待行上锁的释放,相反,InnoDB存储引擎会去读取行的一个快照数据。以下图所示:测试

      image

  上图能够看出,读取的数据是一份快照数据,快照数据是指执行当前更改操做前的数据,该实现是经过undo段来实现的,因此快照数据自己是没有额外的开销的。由于快照数据是一份历史数据,是只读的,因此不须要上锁。
  快照可能有多个版本,也就是说可能有多份不一样的快照数据,这种技术称为行多版本技术,由此带来的并发控制称为多版本并发控制(Multi Version Concurrency Control, MVCC)
  一致性非锁定读是InnoDB存储引擎默认的读取方式,可是不一样的事务隔离级别下,读取的方式不一样,并非每一个事务隔离级别下读取的都是一致性读,即便都是一致性读,不一样的事务隔离级别读取的快照数据也不一样。例如Read Commited和Repeatable Read下,使用的都是一致性的非锁定读,但它们读取的是不一样的快照数据。在Read Commited级别下,读取的老是被锁定行的最新一份快照数据,而在Repeatable Read级别下,读取的是事务开始时的第一份快照数据。atom

step 1. 初始化测试表
mysql> create table t (id int,
    -> primary key (id))
    -> engine innodb;
Query OK, 0 rows affected (0.62 sec)
mysql> insert into t values(1);
mysql> select * from t;
+----+
| id |
+----+
|  1 |
+----+
1 row in set (0.00 sec)

step 2. 开启一个会话A,并在会话A中开启一个事务,查看下测试表的数据,但不提交事务
# Session A
mysql> begin;
Query OK, 0 rows affected (0.03 sec)

mysql> select * from t;
+----+
| id |
+----+
|  1 |
+----+
1 row in set (0.00 sec)

step 3. 开启另外一个会话B,模拟并发的状况,在会话B中开启事务,修改测试表中id为1的数据,但不提交
# Session B
mysql> begin ;
Query OK, 0 rows affected (0.00 sec)
mysql> update t set id = 3 where id = 1;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1  Changed: 1  Warnings: 0

step 4. 在会话A中查看测试表数据,发现仍是修改以前的数据
# Session A
mysql> select * from t;
+----+
| id |
+----+
|  1 |
+----+
1 row in set (0.00 sec)

setp 5. 在会话B中提交事务
# Session B
mysql> commit;
Query OK, 0 rows affected (0.04 sec)

step 5. 再在会话A中查看测试数据,在Read Commited和Repeatable Read级别下获得的结果就不同了。对于Read Committed级别,它老是读取最新版本,因此它获得的结果是一个id为3的记录(幻读)。而Repeatable Read级别下,它老是读取事务开始时的行数据,因此它获得的结果仍然是一个id为1的记录。

4. Select ... For Update 和 Select ... Lock In Share Mode

  默认状况下,InnoDB存储引擎的Select操做使用的是一致性非锁定读,可是有些状况下,须要用户主动对读取操做进行加锁。InnoDB存储引擎对Select语句加锁有两种操做:spa

  • Select ... For Updata,对读取的行记录加一个X锁。其余事务想在这些行上加任何锁都会被阻塞。
  • Select ... Lock In Share Mode,对读取的行加一个S锁。其余事务能够向被锁定的行加S锁,可是若是加X锁会被阻塞。

  对于一致性非锁定读,即便读取的行已被使用Select ... For Update,也是能够读取的。Select ... For Update 和 Select ... Lock In Share Mode 必须在事务中使用,当事务提交了,锁也就释放了。设计

5. 锁的算法

  InnoDB存储引擎有3种行锁算法:版本控制

  • Record Lock,单个记录上的锁。Record Lock锁住的永远是索引而不是记录,若是在建表的时候没有设置索引,InnoDB存储引擎会使用隐式的主键来进行锁定。
  • Gap Lock,间隙锁,锁定一个范围,但不包含记录自己。Gap Lock主要是解决可重复度模式下的幻读问题。
  • Next-Key Lock,Gap Lock + Record Lock,锁定一个范围,而且包括记录自己。

6. 锁问题

6.1 丢失更新

  多个事务同时修改同一行记录,可能出现丢失更新的问题。

  1. 事务A查询了一行数据。
  2. 事务B也查询了这行数据。
  3. 事务A根据它的查询结果修改数据,并提交事务。
  4. 事务B根据它的查询结果修改数据,并提交事务。

  能够看出,事务1的更新操做丢失了。避免丢失更新的作法,就是事务在查询数据的时候加上排他锁。

6.2 脏读

  脏读是指事务A读到了事务B中尚未提交的更新(违反了数据库的隔离性)。脏读只在隔离级别为Read UnCommitted的状况下才会发生。避免脏读的办法,就是把事务的隔离级别至少设置成Read Committed。

6.3 幻读(不可重复读)

  幻读是指在一个事务中屡次读同一数据,拿到的结果不一样(违反了数据库的一致性)。例如,事务A读了一行数据,事务B修改了这行数据并提交了,事务A再读这行数据,拿到的结果与事务A前一次读取的结果不一样。  幻读通常是能够接收的,由于它读到的确实是其余事务已经提交的数据。  InnoDB存储引擎中,经过使用Next-Key Lock算法来避免幻读问题。在Next-Key Lock算法下,对于索引的扫描,不只仅锁住的是扫描到的索引,并且还锁住这些索引覆盖的范围(gap),所以在这个范围内的修改都是不容许的。

相关文章
相关标签/搜索