9.1 ACID
把需要保证 原子性 、 隔离性 、 一致性 和 持久性 的一个或多个数据库操作称之为一个 事务 (英文名是: transaction )。事务 是一个抽象的概念,它其实对应着一个或多个数据库操作。
随着事务对应的数据库操作执行到不同阶段,事务的状态也在不断变化:
9.1.1 原子性(Atomicity)
现实世界中转账操作是一个不可分割的操作,也就是说要么压根儿就没转,要么转账成功,不能存在中间的状态,也就是转了一半的这种情况。设计数据库的把这种要么全做,要么全不做的规则称之为 原子性 。但是在现实世界中的一个不可分割的操作却可能对应着数据库世界若干条不同的操作,数据库中的一条操作也可能被分解成若干个步骤(比如先修改缓存页,之后再刷新到磁盘等),最要命的是在任何一个可能的时间都可能发生意想不到的错误(可能是数据库本身的错误,或者是操作系统错误,甚至是直接断电之类的)而使操作执行不下去。为了保证在数据库世界中某些操作的原子性,设计数据库的需要费一些心机来保证如果在执行操作的过程中发生了错误,把已经做了的操作恢复成没执行之前的样子。
9.1.2 一致性(Consistency)
我们生活的这个世界存在着形形色色的约束,比如身份证号不能重复,性别只能是男或者女,高考的分数只能在0~750之间,人民币面值最大只能是100,红绿灯只有3种颜色,房价不能为负的,只有符合这些约束的数据才是有效的,比如有个小孩儿跟你说他高考考了1000分,你一听就知道他胡扯呢。数据库世界只是现实世界的一个映射,现实世界中存在的约束当然也要在数据库世界中有所体现。如果数据库中的数据全部符合现实世界中的约束(all defined rules),我们说这些数据就是一致的,或者说符合 一致性 的。
9.1.3 隔离性(Isolation)
对于现实世界中状态转换对应的某些数据库操作来说,不仅要保证这些操作以 原子性 的方式执行完成,而且要保证其它的状态转换不会影响到本次状态转换,这个规则被称之为 隔离性 。
9.1.4 持久性(Durability)
当现实世界的一个状态转换完成后,这个转换的结果将永久的保留,这个规则被设计数据库的大叔们称为 持久性 。当把现实世界的状态转换映射到数据库世界时, 持久性 意味着该转换对应的数据库操作所修改的数据都应该在磁盘上保留下来,不论之后发生了什么事故,本次转换造成的影响都不应该被丢失掉。
9.2 事务提交和回滚
MySQL 事务(只有 InnoDB 和 NDB 存储引擎支持)允许你执行一组 MySQL 操作,以确保数据库从不包含部分操作的结果。在一组操作中,如果其中一个操作失败,就会发生回滚,将数据库恢复到其原始状态。如果没有发生错误,整个语句集就会提交到数据库中。
MySQL 为我们提供了以下重要语句来控制事务:
为了启动一个事务,你使用 START TRANSACTION 语句。BEGIN 或 BEGIN WORK 是 START TRANSACTION 的别名。START TRANSACTION 后面可以加修饰符:
- READ ONLY :标识当前事务是一个只读事务,也就是属于该事务的数据库操作只能读取数据,而不能修改数据
- READ WRITE :标识当前事务是一个读写事务,也就是属于该事务的数据库操作既可以读取数据,也可以修改数据
- WITH CONSISTENT SNAPSHOT :启动一致性
START TRANSACTION READ ONLY, WITH CONSISTENT;
要提交当前事务并使其变化永久化,你要使用 COMMIT 语句。
- 要回滚当前事务并取消其变化,你可以使用 ROLLBACK 语句。
要禁用或启用当前事务的自动提交模式(不开启事务,每一条语句都算作一个独立的事务,执行完自动提交),你可以使用 SET autocommit 语句。使用START TRANSACTION,自动提交将保持禁用状态,直到你使用COMMIT 或 ROLLBACK 结束事务。 自动提交模式然后恢复到之前的状态。
-- 设置不自动提交SET autocommit = 0;-- OR --SET autocommit = OFF-- 设置自动提交SET autocommit = 1;-- OR --SET autocommit = ON;
start transaction;insert into teachers(id,name,age,country)value(5,'Kansas',41,'UK');commit;
9.3 隐式提交
当我们使用 START TRANSACTION 或者 BEGIN 语句开启了一个事务,或者把系统变量 autocommit 的值设置为 OFF时,事务就不会进行 自动提交 ,但是如果我们输入了某些语句之后就会 悄悄的 提交掉,就像我们输入了
COMMIT 语句了一样,这种因为某些特殊的语句而导致事务提交的情况称为 隐式提交 ,这些会导致事务隐式提交的语句包括:定义或修改数据库对象的数据定义语言(Data definition language,缩写为: DDL )
所谓的数据库对象,指的就是 数据库 、 表 、 视图 、 存储过程 等等这些东西。当我们使用 CREATE 、ALTER 、 DROP 等语句去修改这些所谓的数据库对象时,就会隐式的提交前边语句所属于的事务。
BEGIN;SELECT ... # 事务中的一条语句UPDATE ... # 事务中的一条语句... # 事务中的其它语句CREATE TABLE ... # 此语句会隐式的提交前边语句所
- 隐式使用或修改 mysql 数据库中的表
当我们使用 ALTER USER 、 CREATE USER 、 DROP USER 、 GRANT 、 RENAME USER 、 REVOKE 、 SET PASSWORD 等语句时也会隐式的提交前边语句所属于的事务。
事务控制或关于锁定的语句
- 当我们在一个事务还没提交或者回滚时就又使用 START TRANSACTION 或者 BEGIN 语句开启了另一个事务时,会隐式的提交上一个事务。
- 或者当前的 autocommit 系统变量的值为 OFF ,我们手动把它调为 ON 时,也会隐式的提交前边语句所属的事务。
- 或者使用 LOCK TABLES 、 UNLOCK TABLES 等关于锁定的语句也会隐式的提交前边语句所属的事务。
SELECT ... # 事务中的一条语句UPDATE ... # 事务中的一条语句... # 事务中的其它语句BEGIN; # 此语句会隐式的提交前边语句所属
加载数据的语句
比如我们使用 LOAD DATA 语句来批量往数据库中导入数据时,也会隐式的提交前边语句所属的事务。
- 关于 MySQL 复制的一些语句
使用 START SLAVE 、 STOP SLAVE 、 RESET SLAVE 、 CHANGE MASTER TO 等语句时也会隐式的提交前边语句所属的事务。
- 其它的一些语句
使用 ANALYZE TABLE 、 CACHE INDEX 、 CHECK TABLE 、 FLUSH 、 LOAD INDEX INTO CACHE 、 OPTIMIZE TABLE 、 REPAIR TABLE 、 RESET 等语句也会隐式的提交前边语句所属的事务。
9.4 保存点
在调用 ROLLBACK 语句时可以指定会滚到哪个点,而不是回到最初的原点。
SAVEPOINT 保存点名称; // 创建保存点ROLLBACK [WORK] TO [SAVEPOINT] 保存点名称; // 回滚到某个保存点RELEASE SAVEPOINT 保存点名称; // 删除保存点
9.5 数据库事务隔离级别
9.5.1 设置数据库事务隔离级别
SHOW VARIABLES like '%ISOLATION'; // 查看数据库事务隔离级别SET [GLOBAL|SESSIO] TRANSACTION ISOLATION LEVEL SERIALIZAB; // 设置数据库事务隔离级别

使用 GLOBAL 关键字(在全局范围影响)
- 只对执行完该语句之后产生的会话起作用。
- 当前已经存在的会话无效
使用 SESSION 关键字(在会话范围影响)
- 对当前会话的所有后续的事务有效
- 该语句可以在已经开启的事务中间执行,但不会影响当前正在执行的事务。
- 如果在事务之间执行,则对后续的事务有效
上述两个关键字都不用(只对执行语句后的下一个事务产生影响)
- 只对当前会话中下一个即将开启的事务有效。
- 下一个事务执行完后,后续事务将恢复到之前的隔离级别。
-
9.5.2 事务并发遇到的问题
9.5.2.1 脏写(Dirty Write)
如果一个事务修改了另一个未提交事务修改过的数据,那就意味着发生了 脏写。

Session A 和 Session B 各开启了一个事务, Session B 中的事务先将 number 列为 1 的记录的 name 列更新为 ‘关羽’ ,然后 Session A 中的事务接着又把这条 number 列为 1 的记录的 name 列更新为张飞 。如果之后 Session B 中的事务进行了回滚,那么 Session A 中的更新也将不复存在,这种现象就称之为 脏写 。9.5.2.2 脏读(Dirty Read)
如果一个事务读到了另一个未提交事务修改过的数据,那就意味着发生了 脏读 。

Session A 和 Session B 各开启了一个事务, Session B 中的事务先将 number 列为 1 的记录的 name 列更新为 ‘关羽’ ,然后 Session A 中的事务再去查询这条 number 为 1 的记录,如果du到列 name 的值为 ‘关羽’ ,而 Session B 中的事务稍后进行了回滚,那么 Session A 中的事务相当于读到了一个不存在的数据,这种现象就称之为 脏读 。9.5.2.3 不可重复读(Non-Repeatable Read)
如果一个事务只能读到另一个已经提交的事务修改过的数据,并且其他事务每对该数据进行一次修改并提交后,该事务都能查询得到最新值,那就意味着发生了 不可重复读。

我们在 Session B 中提交了几个隐式事务(注意是隐式事务,意味着语句结束事务就提交了),这些事务都修改了 number 列为 1 的记录的列 name 的值,每次事务提交之后,如果 Session A 中的事务都可以查看到最新的值,这种现象也被称之为 不可重复读 。9.5.2.4 幻读(Phantom)
如果一个事务先根据某些条件查询出一些记录,之后另一个事务又向表中插入了符合这些条件的记录,原先的事务再次按照该条件查询时,能把另一个事务插入的记录也读出来,那就意味着发生了 幻读。

Session A 中的事务先根据条件 number > 0 这个条件查询表 hero ,得到了 name 列值为 ‘刘备’ 的记录;之后 Session B 中提交了一个隐式事务,该事务向表 hero 中插入了一条新记录;之后 Session A 中的事务再根据相同的条件 number > 0 查询表 hero ,得到的结果集中包含 Session B 中的事务新插入的那条记录,这种现象也被称之为 幻读 。
如果 Session B 中是删除了一些符合 number > 0 的记录而不是插入新记录,那 Session A 中之后再根据 number > 0 的条件读取的记录变少了,这种现象不属于 幻读 。其实这相当于对每一条记录都发生了不可重复读的现象, 幻读 强调的是一个事务按照某个相同条件多次读取记录时,后读取时读到了之前没有读到的记录。9.5.3 SQL标准中的四种隔离级别
脏写 > 脏读 > 不可重复读 > 幻读
我们上边所说的舍弃一部分隔离性来换取一部分性能在这里就体现在:设立一些隔离级别,隔离级别越低,越严重的问题就越可能发生。因为脏写这个问题太严重了,不论是哪种隔离级别,都不允许脏写的情况发生。- READ UNCOMMITTED :未提交读。可能发生 脏读 、 不可重复读 和 幻读 问题。
- READ COMMITTED :已提交读。可能发生 不可重复读 和 幻读 问题,但是不可以发生 脏读 问题。
- REPEATABLE READ :可重复读。可能发生 幻读 问题,但是不可以发生 脏读 和 不可重复读 的问题。
- SERIALIZABLE :可串行化。各种问题都不可以发生
