并发事务问题
脏读
概念: 一个事务读到另外一个事务还没提交的数据
举例:
事务A:第一步:select 第二步:update 第三步:...
事务B:第一步:select 第二步:...
假设原本在数据库中的 数据C = 1,那么
- 事务A进行到第二步修改了数据库中的 数据C =2
- 此时事务B开始第一步,读取事务A修改后的 数据C=2
- 但是此时事务A未将事务提交,有可能会进行事务的回滚,数据C 被修改回原来的值 =1
- 而事务B此时持有的就是错误的 数据C =2
不可重复复
概念: 一个事务先后读取同一条记录,但两次读取到的数据不同
举例:
事务A:第一步:select 第二步:... 第三步:select
事务B:第一步:... 第二步:update 第三步:提交事务
- 事务A执行到第一步,查询 数据C =1
- 接着事务B 执行到第二步,修改了数据C =2
- 接着事务B 执行到第三步,提交了事务
- 接着事务A执行到第三步,再次查询数据C =2
- 但此时事务A发现,前后两次查询数据C的值不一样,这就出现了 不可重复读
幻读
概念: 一个事务按照查询条件查询数据时,没有对应的数据行,但是在插入数据时,又发现这行数据已经存在
举例:
事务A:第一步:select 第二步:insert 第三步:select
事务B:第一步:... 第二步:insert 第三步:提交事务
- 事务A要插入一条 id=1 的数据,第一步先查询数据库中是否有 id=1 的数据,发现没数据
- 接下来事务B 刚好执行到第二步,插入了一条 id=1 的数据
- 事务B执行到第三步,提交了事务
- 而事务A此时进行到第二步,要往数据库中插入 id=1 的数据,结果发现已经存在了
- 但是事务A接着执行第三步查询,结果查询到的结果还是没有
事务隔离级别设置方法
查看事务隔离级别
SELECT @@TRANSACTION_ISOLATION;
设置事务隔离级别
SET [ SESSION | GLOBAL ] TRANSACTION ISOLATION LEVEL { READ UNCOMMITTED |
READ COMMITTED | REPEATABLE READ | SERIALIZABLE }
其中的 [ SESSION | GLOBAL ] 选择是只对当前会话进行设置还是对全局进行设置
事务隔离级别详解
读未提交(Read uncommitted)
效果 -- 相当于没有隔离,三种并发事务问题都会发生
虽然 读未提交的隔离级别 的数据安全性最差,但是性能最高,因为其并发程度最高
读已提交(Read committed)
Oracle的默认隔离级别
效果 -- 解决了脏读问题
例如上面的脏读例子:
事务A:第一步:select 第二步:update 第三步:提交
事务B:第一步:select 第二步:... 第三步:select
假设原本在数据库中的 数据C = 1,那么
- 事务A进行到第二步修改了数据库中的 数据C = 2
- 此时事务B开始第一步,读取的不再是事务A修改后的 数据C ,而是原本的值 数据C=1
- 等事务A将事务提交
- 事务B再次读取 数据C,才会读取到事务A修改的 数据C=2
这就解决了脏读问题
可重复读
MySQL的默认隔离级别
效果 -- 解决了 脏读、不可重复读 问题
例如上面的不可重复读例子:
事务A:第一步:select 第二步:... 第三步:select
事务B:第一步:... 第二步:update 第三步:提交事务
- 事务A执行到第一步,查询 数据C =1
- 接着事务B 执行到第二步,修改了数据C =2
- 接着事务B 执行到第三步,提交了事务
- 接着事务A执行到第三步,再次查询到的不是事务B已经提交的数据C=2,还是之前的C=1
这就解决了不可重复读的问题
串行化
效果 -- 解决了 脏读、不可重复读、幻读 问题
串行化就是同步,不存在并发,那肯定就没有并发事务问题
但是没有并发,也就意味着,串行化对性能的影响很大
举例:
事务A:第一步:select 第二步:insert 第三步:select
事务B:第一步:insert 第二步:... 第三步:提交事务
- 事务A要插入一条 id=1 的数据,第一步先查询数据库中是否有 id=1 的数据,发现没数据
- 接下来事务B 刚要开始执行第一步插入id=1的数据,但是此时因为事务A在进行,事务B不能并发,会被阻塞
- 必须等事务A进行到第二步,要往数据库中插入 id=1 的数据,接着执行后面的步骤直到提交事务
- 事务A提交后,事务B才会开始进行第一步,但是这个时候事务B就会发现id=1的数据已经存在,插入失败
这就解决了幻读的问题
总结
事务隔离级别越高,数据越安全,但是性能越低