MySQL的事务隔离级别
事务
1. 原则(ACID)
| atomicity | 原子性 |
|---|---|
| consistency | 一致性 |
| isolation | 隔离性 |
| durability | 持久性 |
1.1 原子性
一组事务是不能被拆分的;
要么都执行成功,要么都失败。
1.2 一致性
例如转账:转之前和转之后,钱的总数是不会变的
1.3 隔离性
两组事务同时执行,彼此之间不会影响
1.4 持久性
如果事务执行失败,就是执行之前的状态;
如果事务执行成功,那么状态也会持久化到数据库。
2. 事务的隔离级别
现存在这样一张数据库表:
| id | name | amount |
|---|---|---|
| 1 | 张三 | 1000 |
| 2 | 李四 | 1000 |
| 3 | 王五 | 1000 |
2.1 ead UnCommitted(读取未提交)
有可能导致的问题:
脏读(dirty read)
- 事务1:
-- 设置事务的隔离级别为 Read Uncommitted;
SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
-- 启动一个事件;
START TRANSACTION;
-- 修改张三的金额为700;
UPDATE account SET amount = 700 WHERE id = 1;
-- 此时事务并没有提交
- 事务2:
-- 设置事务的隔离级别为 Read Uncommitted;
SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
-- 查询account表
SELECT * FROM account;
- 此时查询到的结果,发现张三的钱已经变成了700;
- 如果紧接着,由于某些原因,事务1出现了问题,然后回滚了
ROLLBACK;
- 这个时候,事务2再去查询就发现,张三的金额又变回了1000;
- 那么就说明,事务2之前读取到的数据,是一个脏数据(dirty data),是事务1没有提交之前的数据。
2.2 Read Committed(读取提交)
解决脏读问题:
将事务的隔离级别设置为 Read Committed之后,另外一个事务在读取数据时,就不会读取到脏数据,但是依然避免不了 ——
不可重复读:
- 事务1开始:
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
START TRANSACTION;
UPDATE account SET amount = 700 WHERE id = 1;
-- 此时,事务1并未提交
- 事务2开始:
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
START TRANSACTION;
SELECT * FROM account;
-- 此时,事务2读取到的结果还是1000
- 但是,一旦事务1提交了
commit;
- 事务2在接下来的语句中查询到的结果就会变成700;
- 那么,就会在一个事务中,对于同一个数据,有不同的值;
- 再如果,事务1没有执行完的时候,事务3又提交改变了这个数据,事务1中的该数据就有前后不一的3个值;
- 以此类推;
- 那么,我们希望要达到的效果就是,让我们的事务2在执行的时候,存在的那些数据,是可重复读的。
- 它们不会随着事件2的提交而改变掉。
2.3 Repeatable Read(可重复读)
解决不可重复读问题:
就上述例子而言,我们可以将事务2的隔离级别设置为Repeatable Read;
那么,在事务2执行的过程中,就算事务1进行了提交commit,事务2中的数据也不会受到影响;
变成了可重复读的数据。
但是,依然不能解决——
幻读(虚读):
- 事务1开始
SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
START TRANSACTION;
INSERT INTO account value(4,'王二麻子',500);
-- 此时事务1插入了,主键id为4的一条数据
- 事务2开始
SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
START TRANSACTION;
SELECT * FROM account;
-- 此时事务2的查询结果中,没有该条数据
- 如果对事务1进行提交;
- 会在后来的查询中发现又添加显示了该条数据;
- 那么还是会造成一个事务中数据不一致的问题(数据行数变了);
- 注:在MySQL中,可能观察不到事务2中数据行数变多的情况,但这只是MySQL做的一些优化,实际上,它的数据行数还是变化了的,可以在事务2中添加一行主键id同样为4的数据,它会提示主键冲突。
2.4 Serializable(序列化)
解决上述的一切问题:
将事务的级别设置为Serializable后,就相当于,该事务就变成了同步的;
- 当有别的事务在执行插入等修改数据的操作,并且还没有提交时;被序列化的事务,启动后,就会休眠等待,一直到它所等待的事务提交完毕,自己才会执行。(等待的时候自己连查询也做不了)
- 当自己在启动后,别的事务就不能执行对于数据的改变操作(但是可以查询),如果另一个事务也是被序列化的,当然它也会等待;如果别的事务(未被序列化的)想要执行修改、插入等操作,就会排队等待。
3. MySQL事务的常用功能
3.1 MySQL默认
我们发现,在我们执行SQL语句的时候,即使没有手动开启和提交事务;数据的改变也是持久性的。
说明MySQL是默认开启事务的,并且在执行完后,会默认自动提交事务(除非手动开启事务,需要手动提交);
可以通过 SET autocommit = 0;来设置关闭自动提交;或者通过start transaction;手动开启一个事务。
3.2 开启事务
3.2.1 使用默认开启的事务
要想手动提交,必须设置 autocommit = 0; || autocommit = off;
3.2.2 手动开启(start transaction)
此方式必须手动提交
3.3 提交事务
3.3.1 使用默认提交
3.3.2 手动提交(commit;)
3.4 事务回滚(rollback;)
3.4.1 默认回滚到事务开始
3.4.2 设置回滚点,回滚指定位置
SET autocommit = 0;
UPDATE account SET amount = 700 WHERE id = 1;
savepoint p1;
UPDATE account SET amount = 500 WHERE id = 1;
savepoint p2;
UPDATE account SET amount = 300 WHERE id = 1;
-- 此时查询到的amount值应该为300
rollback to p1; -- 或者rollback to savepoint p1;两种写法都可以。
-- 回滚之后,我们再查询amount的值,就回到了p1回滚点,也就是700;
3.4.3 删除回滚点(一般没用)
release savepoint p1; -- 删除p1回滚点
浙公网安备 33010602011771号