事务具有四个特性
- 原子性,要么全成功要么全失败,通过undo log实现
- 持久性,不会因为断电等特殊情况造成数据丢失,通过redo log实现
- 隔离性,事务之间互相不干扰,通过MVCC实现
- 一致性,事务执行前后整体一致,通过原子性,持久性,隔离性一起保证
下面着重介绍隔离性
并发读写数据会产生三种异常情况
- 脏读,读到了别的事务没有提交的数据,没有提交随时可能回滚,所以容易出错
- 幻读,两次读取的结果集不同,事务执行期间别的事物添加了新的数据
- 不可重复读,两次读取内容不同,事务执行期间别的事务修改了这条数据的内容
事务隔离级别
- 读未提交,可以读到没有提交的数据,存在脏读、幻读、不可重复读问题
- 读提交,只能读到提交的数据,存在幻读、不可重复度问题
- 可重复读,读的数据一直是事务开始时的状态,存在幻读问题(InnoDB默认隔离级别)
- 串行化,没有问题,但是并发性太差
读未提交显然就是不采用任何机制控制,串行化就是利用锁控制事务一个一个执行,这两种一般情况下不会使用,重点是读提交和可重复读,这两种隔离级别都是通过read view实现的,不同点在于read view的时机不同,读提交是在每个任务执行前创建一个read view而可重复读则是事务开始时创建一个read view然后整个事务执行期间都使用这一个read view。
Read View 在 MVCC 里如何工作的?
read view 四个字段
creator_trx_id | m_ids | min_trx_id | max_trx_id |
---|---|---|---|
创建该read view的事务的事务id | 创建read view时,当前数据库中【活跃且未提交】的事务id列表 | 创建read view时当前数据库中活跃且未提交的事务中最小事务id | 创建read view时当前数据库中应该给下一个事务的id |
- 聚簇索引记录中两个事务相关的隐藏列(trx_id/roll_ptr)
trx_id:产生当前数据的事务id
roll_ptr:指向undo log中上一个版本的数据
使用当前数据(最新/undo log)对应的事务id和这几个参数进行对比可以快速的确定该读哪个版本的事务数据,同时roll_ptr下维持有一个相同结构的链表因此可以快速的找到具体数据。
这种通过「版本链」来控制并发事务访问同一个记录时的行为就叫 MVCC(多版本并发控制),利用这种机制快照读可以有效的避免一些幻读现象。
然而只有一些简单的查询语句可以使用这种快照读,更多情况下使用的是当前读,比如update的时候如果另一个事务delet掉了这条数据使用快照读最终是提交不了的,交叉使用快照读和当前读非常容易产生幻读情况,为了处理这种情况引入了间隙锁,当进行当前读的时候范围加锁避免其它事务提交而造成数据不一致,但是这种情况并不能完全解决幻读情况,比如:
- 事务A执行select查询不到(快照读未加间隙锁)
- 事务Binsert了可以被select查询到的记录并提交了
- 事务B修改可以查询到的数据,尽管前面没有查询到,但是现在因为事务B的行为可以进行修改了
- 于是产生了幻读情况。