** 什么 是 事务 ? ** (重中之重)
事务 是 逻辑上的操作 ,要么 都执行 ,要么 都不执行。
假如小明要给小红转账1000元,这个转账会涉及到两个关键操作就是:将小明的余额减少1000元,将小红的余额增加1000元。万一在这两个操作之间突然出现错误比如银行系统崩溃,导致小明余额减少而小红的余额没有增加,这样就不对了。
事务就是要保证 这两个操作 ,要么都成功 ,要么都失败。
事务的特征 ACID
原子性 事务是最小的执行单位, 不允许分割。
事务的原子性 确保 动作 要么 全部完成 ,要么完全不起作用。
一致性 执行 事务 前后 ,数据保持一致。
例如 转账业务中 ,无论 事务是否 成功 ,收款人 和 转账人的 总额 始终保持不变。
隔离性
并发访问数据库时, 一个用户的 事务 不被 其他事务 所干扰 ,各并发 事务 之间的 数据库 是独立的。
持久性
一个事务被提交后 , 它对 数据库 中的 数据 的 改变 是持久的, 即使 数据库发生 故障 也不应该 对其有任何 影响。
** 并发事务带来的问题 **
多个事务并发执行,经常性会操作相同的 数据 来完成 各自的 任务。
(多个用户对统一的数据进行操作)
1.脏读
当一个事务正在访问数据并且对数据进行了修改, 但这种修改 还没提交到 数据库中 ,这时候 另外 一个 事务 也访问了 这个数据 ,然后使用了这个数据。
但是这个 数据 还没有 提交, 那么 另外一个 事务 读到这个 数据 是 “脏数据” ,根据 “脏数据 ” 操作 可能是不正确的。
2.丢失修改
一个事务 读取了 一个 数据 , 另外一个 事务 也 访问了 这个数据 , 然后 第一个 事务 对这个数据 进行了修改 后 ,第二个 数据 也修改了 这个数据 ,
这样一来 第一个事务 修改结果 就 丢失了 ,这就是 丢失修改。
例如:事务1读取某表中的数据A=20,事务2也读取A=20,事务1修改A=A-1,事务2也修改A=A-1,最终结果A=19,事务1的修改被丢失。
3.不可重复读
在一个事务内 多次 读取 同一个数据。
在这个事务 还没有结束时, 另一个 事务也访问了 该数据。
那么,在 第一个事务 中 的 两次 读取 数据 之间 , 由于 第二个事务的修改
导致了 第一个事务 两次 读取的数据 可能不一样。
这就 发生了 在 一个事务内 两次 读的数据 是不一样的 情况, 这就是 不可重复读。
4.幻读
与不可重复读类似。
在一个事务 读取了 几行数据 , 另外一个并发事务 插入了 一些 数据 ,
在随后的查询中, 第一个 事务 就会 发现 多了 一些 原本 不存在的 数据。
就好像 发生了 幻觉一样 ,称为幻读。
** 事务隔离级别 **
1.读未提交
最低的隔离级别 允许读取尚未提交的数据变更。
(可能会导致 脏读 幻读 不可重复读)
2.读已提交
允许并发事务 已经提交的数据。
(可以阻止 脏读 但是 幻读 不可重复读 可能发生)
3.可重复读
对同一个字段 的 读取 是一致的 , 除非 数据 是被本身 事务 所修改。
(可以阻止 幻读 不可重复读 , 但 幻读可能存在)
4.可串行化
最高的隔离级别 , 完全 服从 ACID 的 隔离级别。
所有的 事务 逐个执行 , 这样 事务 之间 完全 不可能 产生 干扰。
(可以防止 脏读 不可重复读 幻读)
innodb 默认的 隔离级别 是 可重复读 。
select @@tx_isolation 查看
InnoDB 存储引擎的锁的算法有三种:
Record lock:记录锁,单个行记录上的锁
Gap lock:间隙锁,锁定一个范围,不包括记录本身
Next-key lock:record+gap 临键锁,锁定一个范围,包含记录本身
** MySQL InnoDB 的 REPEATABLE-READ(可重读)并不保证避免幻读,需要应用使用加锁读来保证。而这个加锁度使用到的机制就是 Next-Key Locks(临建锁)。 **
因为 隔离性越低 事务请求的 锁越少。
大部分数据库系统的 隔离级别 是 读未提交 (READ-COMMITTED)
但 innodb 引擎 默认 使用 可重复读 (REPEATABLE-READ),并不会造成性能损失。
InnoDB 存储引擎在 分布式事务 的情况下一般会用到 SERIALIZABLE(可串行化) 隔离级别。
扩展
InnoDB 存储引擎提供了对 XA 事务的支持,并通过 XA 事务来支持分布式事务的实现。分布式事务指的是允许多个独立的事务资源(transactional resources)参与到一个全局的事务中。事务资源通常是关系型数据库系统,但也可以是其他类型的资源。全局事务要求在其中的所有参与的事务要么都提交,要么都回滚,这对于事务原有的 ACID 要求又有了提高。另外,在使用分布式事务时,InnoDB 存储引擎的事务隔离级别必须设置为 SERIALIZABLE。
实际情况演示
在下面我会使用 2 个命令行mysql ,模拟多线程(多事务)对同一份数据的脏读问题。
MySQL 命令行的默认配置中事务都是自动提交的,即执行SQL语句后就会马上执行 COMMIT 操作。如果要显式地开启一个事务需要使用命令:START TARNSACTION。
我们可以通过下面的命令来设置隔离级别。
SET [SESSION|GLOBAL] TRANSACTION ISOLATION LEVEL [READ UNCOMMITTED|READ COMMITTED|REPEATABLE READ|SERIALIZABLE]
我们再来看一下我们在下面实际操作中使用到的一些并发控制语句:
START TARNSACTION |BEGIN:显式地开启一个事务。
COMMIT:提交事务,使得对数据库做的所有修改成为永久性。
ROLLBACK:回滚会结束用户的事务,并撤销正在进行的所有未提交的修改。
脏读(读未提交)
避免脏读(读已提交)
不可重复读
还是刚才上面的读已提交的图,虽然避免了读未提交,但是却出现了,一个事务还没有结束,就发生了 不可重复读问题。
可重复读
防止幻读(可重复读)
一个事务对数据库进行操作,这种操作的范围是数据库的全部行,然后第二个事务也在对这个数据库操作,这种操作可以是插入一行记录或删除一行记录,那么第一个是事务就会觉得自己出现了幻觉,怎么还有没有处理的记录呢? 或者 怎么多处理了一行记录呢?
幻读和不可重复读有些相似之处 ,但是不可重复读的重点是修改,幻读的重点在于新增或者删除。