1. 事务
1.1 事务概述
事务用来保障数据库的完整性——要么都批改,要么都不批改。事务必须满足 ACID 四个个性。
- 原子性(atomicity),指整个数据库事务是不可分割的工作单位。只有使事务中所有的数据库操作执行都胜利,才算整个事务胜利。如果事务中任何一个 SQL 语句执行失败,那么曾经执行胜利的 SQL 语句也必须撤销,数据库状态应该退回到执行事务前的状态。
- 一致性(consistency),指事务将数据库从一种状态转变为下一种统一的状态,在事务开始之前和事务完结当前,数据库的完整性束缚没有被毁坏。
- 隔离性(isolation),一个事务的影响在该事务提交前对其余事务都不可见——这通过锁来实现。
- 持久性(durability),事务一旦提交,其后果就是永久性的。即便产生宕机等故障,数据库也能将数据恢复。
1.2 事务隔离级别
ISO 和 ANIS SQL 规范制订了四种事务隔离级别的规范:
- READ UNCOMMITEED,读未提交,会呈现脏读问题。
- READ COMMITTED,读已提交,会呈现幻读问题。
- REPEATABLE READ,可反复读(InnoDB 存储引擎的默认隔离级别)
- SERIALIZABLE,会给每一个读操作加一个共享锁,不反对一致性的非锁定读,隔离性最高。
2. 锁的粒度和类型
InnoDB 既反对行级锁,也反对表级锁,默认状况下是采纳行级锁。
InnoDB 存储引擎实现了两种规范的行级锁:
- 共享锁(S Lock),容许事务读一行数据,不容许其余事务获取排他锁。
- 排他锁(X Lock),容许事务删除或者更新一行数据,不容许其余事务取得共享锁或排他锁。
InnoDB 存储引擎反对多粒度锁定,容许行级锁和表级锁同时存在。为了反对在不同粒度上进行加锁操作,InnoDB 存储引擎提供了意向锁。
意向锁是表级别的锁,其设计目标次要是为了在一个事务中揭示下一行将被申请的锁的类型。InnoDB 存储引擎反对两种意向锁:
- 动向共享锁(IS Lock),事务想要取得一个表中某几行的共享锁。
- 动向排他锁(IX Lock),事务想要取得一个表中某几行的排他锁。
意向锁不会阻塞除全表扫描以外的任何申请。
3. 一致性的非锁定读
一致性的非锁定读(consistent nonlocking read)是指 InnoDB 存储引擎通过 多版本控制(multi versioning)
的形式来读取以后执行工夫数据库中行的数据。如果读取的行正在执行 DELETE、UPDATE 操作,这是读取操作不会因而而期待行上锁的开释,相同,InnoDB 存储引擎会去读取行的一个快照数据。如下图所示:
上图能够看出,读取的数据是一份快照数据,快照数据是指执行以后更改操作前的数据,该实现是通过 undo 段来实现的,所以快照数据自身是没有额定的开销的。因为快照数据是一份历史数据,是只读的,所以不须要上锁。
快照可能有多个版本,也就是说可能有多份不同的快照数据,这种技术称为 行多版本技术
,由此带来的并发管制称为 多版本并发管制(Multi Version Concurrency Control,MVCC)
。
一致性非锁定读是 InnoDB 存储引擎默认的读取形式,然而不同的事务隔离级别下,读取的形式不同,并不是每个事务隔离级别下读取的都是一致性读,即便都是一致性读,不同的事务隔离级别读取的快照数据也不同。例如 Read Commited 和 Repeatable Read 下,应用的都是一致性的非锁定读,但它们读取的是不同的快照数据。在 Read Commited 级别下,读取的总是被锁定行的最新一份快照数据,而在 Repeatable Read 级别下,读取的是事务开始时的第一份快照数据。
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 语句加锁有两种操作:
- 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 失落更新
多个事务同时批改同一行记录,可能呈现失落更新的问题。
- 事务 A 查问了一行数据。
- 事务 B 也查问了这行数据。
- 事务 A 依据它的查问后果批改数据,并提交事务。
- 事务 B 依据它的查问后果批改数据,并提交事务。
能够看出,事务 1 的更新操作失落了。防止失落更新的做法,就是事务在查问数据的时候加上排他锁。
6.2 脏读
脏读是指事务 A 读到了事务 B 中还没有提交的更新(违反了数据库的隔离性)。脏读只在隔离级别为 Read UnCommitted 的状况下才会产生。防止脏读的方法,就是把事务的隔离级别至多设置成 Read Committed。
6.3 幻读(不可反复读)
幻读是指在一个事务中屡次读同一数据,拿到的后果不同(违反了数据库的一致性)。例如,事务 A 读了一行数据,事务 B 批改了这行数据并提交了,事务 A 再读这行数据,拿到的后果与事务 A 前一次读取的后果不同。
幻读个别是能够接管的,因为它读到的的确是其余事务曾经提交的数据。
InnoDB 存储引擎中,通过应用 Next-Key Lock 算法来防止幻读问题。在 Next-Key Lock 算法下,对于索引的扫描,不仅仅锁住的是扫描到的索引,而且还锁住这些索引笼罩的范畴(gap),因而在这个范畴内的批改都是不容许的。