1 一致性(consistency)
一旦事务完成(不管成功还是失败),系统必须确保它所建模的业务处于一致的状态,而不会是部分完成部分失败。在现实中的数据不应该被破坏。
1.1 undo log的机制
- undo log存放在数据库内部的一个特殊段(segment)中,这个段称为undo段(undosegment)。
- undo log是逻辑日志,因此只是将数据库逻辑地恢复到原来的样子。
- undo log的另一个作用是MVCC,即在InnoDB存储引擎中MVCC的实现是通过undo来完成。
- undo log的产生会伴随着redo log的产生,这是因为undo log也需要持久性的保护。
1.2 undo log与redo log的区别
- redo log通常是物理日志,记录的是页的物理修改操作。
- undo log是逻辑日志,根据每行记录进行记录。
2 隔离性(Isolation)
可能有许多事务会同时处理相同的数据,因此每个事务都应该与其他事务隔离开来,防止数据损坏。
- 为了性能往往不能100%兼顾隔离性。
2.1 通过lock(锁)实现
2.1.1 粒度
- 行锁
- 表锁
2.1.2 类型
- 共享锁(S):与IX/X锁不兼容
行锁,允许事务读一行数据 - 排他锁(X):与任何锁都不兼容
行锁,允许事务删除或更新一行数据 - 意向共享锁(IS):与X锁不兼容
表锁,事务想要获得一张表中某几行的共享锁 - 意向排他锁(IX):与S/X锁不兼容
表锁,事务想要获得一张表中某几行的排他锁
总结:
- X锁与任何锁都不兼容
- IX锁与S/X锁不兼容
2.1.3 机制
-
无锁
MVCC(Multi-Version Concurrency Control):对于正在更新的数据,InnoDB会去读取该行的一个快照数据(undo log) -
加锁
X锁:SELECT ... FOR UPDATE
S锁:SELECT ... LOCK IN SHARE MODE
2.1.4 算法
- Record Lock:单个行记录上的锁,总是会去锁住(聚簇/主键)索引记录
- Gap Lock:间隙锁,锁定一个范围,但不包含记录本身
- Next-Key Lock:锁定一个范围,并且锁定记录本身
2.1.5 问题
2.1.5.1 读取
- 脏读:某一个事务,读取了另外一个事务中未提交的数据(绝对要避免)
- 不可重复读:某一个事务,对同一个数据前后读取的结果不一致(有时也可以接受,但是要尽力杜绝)
- 幻读:某一个事务,对同一个表前后查询到的行数不一致(往往可以接受,但是尽量避免)
2.1.5.2 更新
- 第一类丢失更新:某一个事务的回滚,导致另外一个事务已更新的数据丢失了
- 第二类丢失更新:某一个事务的提交,导致另外一个事务已更新的数据丢失了
说明:任何隔离级别都不会发生这类问题,因为对于DML操作需要先加IX锁,它会直接阻塞事务2的行为。
2.1.6 死锁
2.1.6.1 场景
事务1:
update ... where id=1;
update ... where id=2;
事务2:
update ... where id=2;
update ... where id=1;
2.1.6.2 解决
- 超时回滚(被动):innodb_lock_wait_timeout,当一个等待时间超过设置的某一阈值时,其中一个事务进行回滚,另一个等待的事务就能继续进行。
- 死锁检测(主动):wait-for graph,采用等待图的方式来进行死锁检测,这是一种更为主动的死锁检测方式。
2.1.7 升级
InnoDB存储引擎不存在锁升级的问题。
因为其不是根据每个记录来产生行锁的,相反,其根据每个事务访问的每个页对锁进行管理的,采用的是位图的方式。因此不管一个事务锁住页中一个记录还是多个记录,其开销通常都是一致的。
2.2 隔离级别
2.2.1 READ UNCOMMITTED
- 未解决脏读、不可重复读、幻读问题
2.2.2 READ COMMITTED
- 采用Record Lock算法,解决了脏读问题
- 采用MVCC,总是读取被锁定行的最新一份快照数据
2.2.3 REPEATABLE READ
- 是默认的隔离级别;
- 采用Next-Key Lock算法,解决了脏读、不可重复读、幻读问题
- 采用MVCC,总是读取事务开始时的行数据版本
2.2.4 SERIALIZABLE
- 解决了脏读、不可重复读、幻读问题(SELECT ... LOCK IN SHARE MODE)
3 持久性(Durability)
一旦事务完成,无论发生什么系统错误,它的结果都不应该受到影响,这样就能从任何系统崩溃中恢复过来。通常情况下,事务的结果被写到持久化存储器中。
使用redo log来解决
3.1 redo log的机制
- 当事务提交时,必须先将该事务的所有日志写入到redo log进行持久化,待事务的COMMIT操作完成才算完成。
- redo log是顺序写的,在数据库运行时不需要对该文件进行读取操作。
- 每次写入redo log文件后,InnoDB引擎都需要调用一次fsync操作。
- redo log通常是物理日志,记录的是页的物理修改操作。
3.2 redo log与bin log的区别
- redo log是在存储引擎层产生,bin log是在数据库的上层产生的,并且bin log不仅仅针对于InnoDB存储引擎,MySQL中任何存储引擎对于数据库的更改都会产生二进制日志。
- bin log 是一种逻辑日志,其记录的是对应的SQL语句,而InnoDB存储引擎层面的redo log是物理格式日志,其记录的是对于每个页的修改。
- bin log只在事务提交完成后进行一次写入,而redo log在事务进行中不断地被写入,这表现为日志并不是随事务提交的顺序进行写入的。
4 原子性(Atom)
事务是一个原子操作,由一系列动作组成。事务的原子性确保动作要么全部完成,要么完全不起作用。
一致性、隔离性和持久性的实现是原子性的保证。