事务及ACID数据
事务是一组操作要么全部成功,要么全部失败,目的是保证数据最终的一致性。
事务具有以下四种属性,通常简称为事务的ACID属性。
- 原子性(Atomicity):当前事务的操作要么同时成功,要么同时失败,原子性由undo log日志实现
- 一致性(Consistent):使用事务的最终目的,由其他三个特性以及业务代码正确逻辑来实现。
- 隔离性(Isolation):在事务并发执行时,他们内部的操作不能互相干扰。隔离性由Mysql的各种锁及MVCC机制来实现。
- 持久性(Durable):一旦提交了事务,它对数据库的改变就应该是永久性的。持久性由redo log日志来实现。
并发事务处理带来的问题
更新丢失(Lost Update)或脏写
当两个或者多个事务同时对一行数据进行修改,有可能发生丢失问题,即最后的更新覆盖了由其他事务所做的更新。
脏读(Dirty Reads)
事务A读取到事务B已经修改但为提交的数据
不可重复读(Non-Repeatable Reads)
事务A在内部的相同查询语句在不同时刻查询出来的结果不一致
幻读(Phantom Reads)
事务A读取到了事务B提交的新增数据
事务隔离级别
隔离界别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
读未提交(Read Uncommitted) | 可能 | 可能 | 可能 |
读已提交(Read Committed) | 不可能 | 可能 | 可能 |
可重复读(Reaetable Read) | 不可能 | 不可能 | 可能 |
串行化(Serializable) | 不可能 | 不可能 | 不可能 |
查看当前数据库的事务隔离级别: show variables like ‘tx_isolation’;
设置事务隔离级别:
SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ;
SET tx_isolation = ‘REPEATABLE-READ’;
Mysql默认的事务隔离级别是可重复读。
事务隔离级别案例分析
读未提交:
对account 表添加三条数据
事务1
# 设置事务级别为读未提交
SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
# 查询表数据
BEGIN;
select * from account;
数据结果:
此时存在另外一个事务2
# 设置隔离级别为读未提交并且更新account.blance数据
SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
BEGIN;
update account set balance = balance - 50 where id = 1;
select * from account;
# 执行回滚操作
# ROLLBACK;
此时事务2查询到的数据已经更新,且事务1查询的结果也是如此
当事务1拿到这个数据结果并执行逻辑运算的时候时候,事务2由于某种原因并未提交事务而是进行了ROLLBACK回滚事务操作,那么事务1拿到的数据将会是脏数据。
不可重复读
事务1
# 设置事务为读已提交
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
BEGIN;
select * from account;
commit;
事务2
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
BEGIN;
update account set balance = balance - 50 where id = 1;
select * from account;
# 未提交
# COMMIT;
事务2查询结果:
此时事务1查询结果为:
已经解决了脏读问题
事务1
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;
BEGIN;
# 事务2未提交前进行查询,结果为450
select * from account;
# 事务2提交后进行查询,结果为400
select * from account;
commit;
存在不可重复读的问题
可重复读
事务1
SET tx_isolation = 'REPEATABLE-READ';
BEGIN;
select * from account ;
commit;
先执行查询:
事务2
set tx_isolation = 'repeatable-read';
BEGIN;
update account set balance = balance - 50 where id = 1;
select * from account;
COMMIT;
执行结果:
此时在事务1中只执行查询语句发现结果仍没有变化,解决了不可重复读的问题:
在事务2中执行插入语句
set tx_isolation = 'repeatable-read';
BEGIN;
update account set balance = balance - 50 where id = 1;
insert account (id, name ,balance) VALUES (4, 'zhangsan', '5000');
select * from account;
COMMIT;
在执行事务1的时候,结果如下:
同时在其他事务中执行整个库中其他表的数据的时候,在事务1中查询的仍是在最开始执行查询一瞬间数据的快照(历史版本);
insert、update和delete是读当前版本,且update时会对操作行添加一个行锁。
幻读验证
事务1中查询数据:
此时在事务2中新插入一条数据
set tx_isolation = 'repeatable-read';
BEGIN;
insert account (id, name ,balance) VALUES (4, 'zhangsan', '5000');
select * from account;
COMMIT;
执行结果:
此时事务1查询的时候数据仍为三条;
但是在事务1中直接对id=4的数据进行更新时,仍可以更新成功且再执行查询时可以查询得到;
update account set balance = balance - 500 where id = 4;
即幻读问题仍存在。
串行化
事务1
SET tx_isolation = 'SERIALIZABLE';
BEGIN;
select * from account where id = 1;
当事务1设置模式为Serializable时,事务2进行更新id = 1 的操作将会阻塞等待,但是更新id = 2 的操作仍可以成功,说明在串行化模式下innodb的查询也会被加上行锁。