共计 3274 个字符,预计需要花费 9 分钟才能阅读完成。
一. 事务隔离机制介绍
事务具有 原子性、一致性、隔离性、持久性 四大特性
而 隔离性 顾名思义指的就是事务彼此之间隔离开, 多个事务在同时处理一个数据时彼此之间互相不影响, 如如果隔离的不够好就有可能会产生 脏读、不可重复度、幻读等读 现象
二. 隔离性的四个级别
1. 等级 (隔离程度) 由低到高
- Read uncommitted (未提交读)
- Read committed (提交读)
- Repeatable read (可重复读) (mysql 默认)
- Serializable (可序列化)
2. 依次解决的读现象
-
✔ : 可能出现
-
❌ : 不会出现
脏读 | 不可重复读 | 幻读 | |
Read uncommitted | √ | √ | √ |
Read committed | × | √ | √ |
Repeatable read(mysql 默认) | × | × | √ |
Serializable | × | × | × |
需要强调的是 : 我们确实可以采用提高事务的隔离级别的方式来解决脏读、不可重复读、幻读等问题, 但与此同时, 事务的隔离级别越高, 并发能力也就越低; 所以, 还需要读者根据业务需要进行权衡
三. 四种级别介绍
1. 未提交读 (Read uncommitted)
- 定义
是最低的隔离级别, 在这种事务隔离级别下, 一个事务可以读到另外一个事务未提交的数据
- 数据库加锁情况(实现原理)
事务在读数据的时候并未对数据加锁
事务在修改数据的时候只对数据增加行级共享锁
- 现象解释
事务 1 读取某行记录时, 事务 2 也能对这行记录进行读取、更新, 并且因为事务一并未对数据增加任何锁
当事务 2 也对该记录进行更新时, 事务 1 再次读取该记录, 能读到事务 2 对该记录的修改版本 (因为事务 2 只增加了共享读锁, 事务 1 可以再增加共享读锁读取数据), 即使该修改尚未被提交, 若此时事务 2 回滚, 那事务 1 读到的就脏数据了, 这就引发了脏读现象
事务 1 更新某行记录时, 事务 2 不能对这行记录做更新, 直到事务 1 结束 (因为事务 1 对数据增加了共享读锁, 事务 2 不能增加排他写锁进行数据的修改)
2. 提交读 (Read committed)
- 定义
可以理解成都已提交, 在一个事务修改数据过程中, 如果事务还没提交, 其他事务不能读该数据
- 数据库加锁情况
事务对当前被读取的数据增加行级共享锁(读到时才加锁), 一旦读完该行, 立即释放该行行级共享锁
事务在更新某数据的瞬间(在更新的瞬间), 必须先对其加行级排它锁, 直到事务结束才释放
- 现象解释
事务 1 在读取某行记录的整个过程中, 事务 2 都可以对该行记录进行读取 (因为事务一对该行记录增加行级共享锁的情况下, 事务二同样可以对该数据增加共享锁来读数据)
事务 1 读取某行的一瞬间, 事务 2 不能修改该行数据, 但是, 只要事务 1 读取完改行数据, 事务 2 就可以对该行数据进行修改 (事务一在读取的一瞬间会对数据增加共享锁, 任何其他事务都不能对该行数据增加排他锁; 但是事务一只要读完该行数据, 就会释放行级共享锁, 一旦锁释放, 事务二就可以对数据增加排他锁并修改数据)
事务 1 更新某行记录时, 事务 2 不能对这行记录做更新, 直到事务 1 结束 (事务一在更新数据的时候, 会对该行数据增加排他锁, 知道事务结束才会释放锁, 所以, 在事务二没有提交之前, 事务一都能不对数据增加共享锁进行数据的读取; 所以, 提交读可以解决脏读的现象)
3. 可重复读 (Repeatable reads)
- 定义
由于提交读隔离级别会产生不可重复读的读现象, 所以比提交读更高一个级别的隔离级别就可以解决不可重复读的问题, 这种隔离级别就叫可重复读
- 数据库锁情况
事务在读取某数据的瞬间 (就是开始读取的瞬间), 必须先对其加行级共享锁, 直到事务结束才释放
事务在更新某数据的瞬间 (就是发生更新的瞬间), 必须先对其加行级排他锁, 直到事务结束才释放
- 现象解释
事务 1 在读取某行记录的整个过程中, 事务 2 都可以对该行记录进行读取 (因为事务一对该行记录增加行级共享锁的情况下, 事务二同样可以对该数据增加共享锁来读数据)
事务 1 在读取某行记录的整个过程中, 事务 2 都不能修改该行数据 (事务一在读取的整个过程会对数据增加共享锁, 直到事务提交才会释放锁, 所以整个过程中, 任何其他事务都不能对该行数据增加排他锁; 所以, 可重复读能够解决不可重复读的读现象)
事务 1 更新某行记录时, 事务 2 不能对这行记录做更新, 直到事务 1 结束 (事务一在更新数据的时候, 会对该行数据增加排他锁, 知道事务结束才会释放锁, 所以, 在事务二没有提交之前, 事务一都能不对数据增加共享锁进行数据的读取; 所以, 提交读可以解决脏读的现象)
4. 可序列化 (Serializable)
- 定义
是最高的隔离级别, 前面三种隔离级别都无法解决的幻读, 在可序列化的隔离级别中可以解决
- 数据库锁情况
事务在读取数据时, 必须先对其加表级共享锁, 直到事务结束才释放
事务在更新数据时, 必须先对其加表级排他锁, 直到事务结束才释放
- 现象解释
事务 1 正在读取 A 表中的记录时, 则事务 2 也能读取 A 表, 但不能对 A 表做更新、新增、删除, 直到事务 1 结束 (因为事务一对表增加了表级共享锁, 其他事务只能增加共享锁读取数据, 不能进行其他任何操作)
事务 1 正在更新 A 表中的记录时, 则事务 2 不能读取 A 表的任意记录, 更不可能对 A 表做更新、新增、删除, 直到事务 1 结束 (事务一对表增加了表级排他锁, 其他事务不能对表增加共享锁或排他锁, 也就无法进行任何操作)
- 序列化事务产生的效果
- 无法读取其他事务已经修改单位提交的记录
- 在当前事务完成之前, 其他事务不能修改当前事务已经读取的记录
- 在当前事务完成之前, 其他事务插入的新记录, 其索引键值不能在当前事务的任何语句所读取的索引键范围中
5. 隔离级别的选择
四种事务隔离级别从隔离程度上越来越高, 但同时在并发性上也就越来越低; 之所以有这么几种隔离级别, 就是为了方便开发人员在开发过程中根据业务需要选择最合适的隔离级别
四. 修改事务隔离级别
1. 查看当前事务隔离级别
- 可以通过模糊匹配变量名
show variables like "%tx_isolation";
- 直接查看变量 tx_isolation
select @@tx_isolation;
- 也可以通过以下语句查看
select @@global.tx_isolation; # 查看全局事务隔离级别
select @@session.tx_isolation; # 查看当前会话事务隔离级别
ps : 在 MySQL 8.0.3 中, tx_isolation
变量被 transaction_isolation
变量替换了; 在 MySQL 8.0.3 版本中查询事务隔离级别, 只要把上述查询语句中的 tx_isolation
变量替换成 transaction_isolation
变量即可
2. 修改事务隔离级别
Mysql 提供了 set transaction
语句来改变单个会话或者全局会话的事务隔离级别
set [session|global] transaction isolation level
[read ununcommitted|read committed|repeatable read|serializable]
- session : 表示修改的事务隔离级别将应用于当前 session(当前 cmd 窗口) 内的所有事务
- global : 表示修改的事务隔离级别将应用于所有 session (全局) 中的所有事务, 且当前已经存在的 session 不受影响
- 如果省略 session 和 global, 则表示修改的事务隔离级别将应用于当前 session 内的下一个还未开始的事务
3. 用户权限问题
任何用户都能改变会话的事务隔离级别, 但是只有拥有 super 权限的用户才能改变全局的事务隔离级别
- 验证修改全局事务隔离级别, 当前会话不受影响
select @@global.tx_isolation; # 查看全局事务隔离级别
select @@session.tx_isolation; # 查看当前会话事务隔离级别
set global transaction isolation level read committde;
# 修改全局事务隔离级别
- 可以通过直接修改 tx_isolation 变量来修改当前 session 的事务隔离级别
set tx_isolation="read-committed";
select @@session.tx_isolation;
---end---