MySql的事务4个隔离级别
# MySql的事务4个隔离级别
# 4个隔离级别
MySQL 中事务的隔离级别一共分为四种,分别如下:
- 未提交读(READ UNCOMMITTED)
- 提交已读(READ COMMITTED)
- 可重复读(REPEATABLE READ)
- 序列化(SERIALIZABLE)——隔离级别最高
从上往下,隔离级别越来越高,其中序列化是最高的,同时也是效率最低的。下面从最低的读未提交开始往上介绍,四种不同的隔离级别:
# READ UNCOMMITTED——读未提交
READ UNCOMMITTED 提供了事务之间最小限度的隔离。除了容易产生虚幻的读操作和不能重复的读操作外,处于这个隔离级的事务可以读到其他事务还没有提交的数据,如果这个事务使用其他事务不提交的变化作为计算的基础,然后那些未提交的变化被它们的父事务撤销,这就导致了大量的数据变化。
# READ COMMITTED——读已提交
READ COMMITTED 隔离级别的安全性比 REPEATABLE READ 隔离级别的安全性要差。处于 READ COMMITTED 级别的事务可以看到其他事务对数据的修改。也就是说,在事务处理期间,如果其他事务修改了相应的表,那么同一个事务的多个 SELECT 语句可能返回不同的结果。
# REPEATABLE READ——可重复读
在可重复读在这一隔离级别上,事务不会被看成是一个序列。不过,当前正在执行事务的变化仍然不能被外部看到,也就是说,如果用户在另外一个事务中执行同条 SELECT 语句数次,结果总是相同的。(因为正在执行的事务所产生的数据变化不能被外部看到)。
# SERIALIZABLE——序列化
如果隔离级别为序列化,则用户之间通过一个接一个顺序地执行当前的事务,这种隔离级别提供了事务之间最大限度的隔离。
# 默认级别
在 MySQL 数据库种,默认的事务隔离级别是 REPEATABLE READ,也就是RR。
那为什么默认是可重复读呢?
# 相关操作
# 解决的问题
为什么会有四种隔离级别呢?那必然是出于对不同的安全和性能来考虑的。隔离级别越高,它的安全性就越好,同时它的性能也越差。下面就一起来看看,这个四种级别各自解决了哪些问题。
# 脏读
一个事务读到另外一个事务还没有提交的数据,称之为脏读。
只有读未提交
这种级别才存在脏读。而读已提交
则解决了脏读,但未解决不可重复读。
# 不可重复读
和脏读的区别在于,脏读是看到了其他事务未提交的数据,而不可重复读是看到了其他事务已经提交的数据(由于当前 SQL 也是在事务中,因此有可能并不想看到其他事务已经提交的数据)。
以下是一个简单的例子:
# 默认为 100
START TRANSACTION;
UPDATE order set account=account+100 where id='01';
COMMIT;
# 事务B
2
3
4
5
START TRANSACTION;
SELECT * from account where id='01';
SELECT * from account where id='01';
COMMIT;
# 事务A
2
3
4
5
事务A,第一次查询时,事务B还未开始,此时查到值为100,在进行第二次查询之前,事务B开始启动并完成。当事务A第二次查询时,就会发现account值为200,这就是所谓的不可重复读
。
而数据库的第三个级别——可重复读则是解决了这个问题,但它没解决幻读
。
# 幻读
假设我们有一个表 orders
,其中包含以下数据:
order_id | customer_id | amount |
---|---|---|
1 | 101 | 100 |
2 | 102 | 200 |
3 | 103 | 150 |
有两个事务 T1 和 T2,它们按以下步骤执行:
- 事务 T1 开始:
START TRANSACTION;
SELECT * FROM orders WHERE customer_id = 101;
2
结果:
| order_id | customer_id | amount |
|----------|-------------|--------|
| 1 | 101 | 100 |
2
3
事务 T2 插入一条新记录:
START TRANSACTION;
INSERT INTO orders (customer_id, amount) VALUES (101, 120);
COMMIT;
2
3
事务 T1 再次执行相同的查询:
SELECT * FROM orders WHERE customer_id = 101;
# 此时 你会惊奇的发现 结果有2条,似乎出现了 幻觉
#| order_id | customer_id | amount |
#|----------|-------------|--------|
#| 1 | 101 | 100 |
#| 4 | 101 | 120 |
2
3
4
5
6
7
你可能会说,它跟不可重复读
好像啊,的确很像,不过你仔细查看就会发现,它们的区别。
事务T2,做的操作可不是更新,而是新增。同样的,如果事务T2删除了数据,T1再次查看的话,也发现,没有数据。此时级别为可重复读,为什么两次查询结果不一样呢。
接下来再来一个例子,它可能更接近幻读。
两个窗口 A 和 B,将 B 窗口的隔离级别改为 READ COMMITTED
,
然后在 A 窗口输入如下测试 SQL:
START TRANSACTION;
insert into account(name,balance) values('zhangsan',1000);
COMMIT;
2
3
在 B 窗口输入如下测试 SQL:
START TRANSACTION;
SELECT * from account;
insert into account(name,balance) values('zhangsan',1000);
COMMIT;
2
3
4
测试方式如下:
- 首先执行 B 窗口的前两行 SQL,开启事务并查询数据,此时查到的只有 javaboy 和 itboyhub 两个用户。
- 执行 A 窗口的前两行 SQL,插入一条记录,但是并不提交事务。
- 执行 B 窗口的第二行 SQL,由于现在已经没有了脏读问题,所以此时查不到 A 窗口中添加的数据。
- 执行 B 窗口的第三行 SQL,由于 name 字段唯一,因此这里会无法插入。此时就产生幻觉了,明明没有 zhangsan 这个用户,却无法插入 zhangsan。
总结:某些锁只锁现在有的数据,对于新增的数据,或者删除的数据,是无法锁住的。这就是所谓的幻读,而可重复度只解决了已存在数据的问题。
# 隔离的实现原理
Mvcc +cas,mvcc也是一种乐观锁的实现
多版本控制,待补充
数据快照,保存着 更新前后的所有数据
每条数据都有额外的隐藏列,
- 事务id
- DB_Roll_PTR -数据指针
间隙锁?