大家好,我是程序媛雪儿,今儿咱聊聊mysql事务。
咱们先看一下什么是事务。
事务是什么
事务是一组操作的集合,是一个不可分割的工作单位,事务会把所有的操作作为一个整体向系统提交或者撤销操作,也就是说,这些操作要么同时成功,要么同时失败。
特性(ACID)
原子性(Atomicity):事务是不可分割的最小操作单元(要么都成功,要么都失败)
一致性(Consistency):事务完成时,必须使所有数据保持一致
隔离性(Isolation):数据库提供隔离机制,保证事务不受外部并发操作影响,在一个独立环境下运行
持久性(Durability):事务一旦提交或回滚,对数据库的改变是永久的,也就是说会保存到磁盘上
并发事务问题
有哪些
脏读
一个事务读到另外一个事务还没有提交的数据
事务A更新完数据还没提交,事务B此时读到了A还没提交的数据
不可重复读
一个事务先后读取同一条记录,但两次读取的数据不同
事务A第一次读到的是旧数据,第二次读这个数据前,这个数据已经被事务B改了,所以事务A第二次读到的数据和第一次读到的同一条数据不一样
幻读
一个事务按照条件查询数据的时候,没有对应的数据,但是在插入数据的时候,又发现这行数据已经存在了,好像出现了幻影
当事务A查数据库,发现id为1的数据没有,此时事务B把id为1的数据已经插入到数据库中,事务A再插入id为1的数据报错,再查id为1的数据,还是没有(因为解决了不可重复读,保证了两次数据一致)
解决方案
|----------|----|-------|----|
| 隔离级别 | 脏读 | 不可重复读 | 幻读 |
| 未提交读 | √ | √ | √ |
| 读已提交 | × | √ | √ |
| 可重复读(默认) | × | × | √ |
| 串行化 | × | × | × |
注:事务隔离级别越往下安全性越高,但是性能越差
事务的隔离级别是如何实现的?
本质还是老两套
锁:排他锁(一个事务获得了某一行,其他事务就不能再获取该行的锁了)
mvcc:多版本并发控制
什么是MVCC?
维护一个数据的多个版本,使得读写操作没有冲突
来看下面这个例子
MVCC的实现,主要依赖数据库记录中的隐式字段、undo log日志、readView
隐藏字段
|-------------|-------------------------|
| 隐藏字段 | 含义 |
| DB_TRX_ID | 最近修改事务ID,最后修改这条记录的事务ID |
| DB_ROLL_PTR | 回滚指针,指向是这条记录的上个版本 |
| DB_ROW_ID | 隐藏主键,表结构中如果没有主键就会生成这个字段 |
undo log日志中的版本链
事务对同一条记录修改,就会生成记录版本链,链表头部是最新的数据记录,尾部是最老的数据记录
readview
readview是快照读SQL执行时提取数据的依据。
这里聊到了一个概念,快照读,我们先搞明白什么是当前读和快照读
当前读:读取的是记录的最新版本,并且保证其他并发事务不能修改当前的记录(加锁)
快照读:读取的是记录数据的可见版本,可能是历史数据(不加锁、非阻塞)
|----------------|-----------------------------|
| 字段 | 含义 |
| m_ids | 当前活跃的事务ID集合(活跃的事务指的是未提交的事务) |
| min_trx_id | 最小活跃事务ID |
| max_trx_id | 最大事务ID+1 |
| creator_trx_id | 当前事务ID,readview创建的事务ID |
访问规则
trx_id代表的当前事务,在undo log日志当中,会依次遍历DB_TRX_ID,去找第一个符合的版本数据返回给用户
不同的隔离级别,生成的readview不同
read committed (读已提交):每次执行快照读时生成readview
事务5中查询id为30的记录,根据访问规则+生成的readview,第一次查询读到的是事务2,第二次查询读到的是事务3
repeatable read(可重复读):仅在事务第一次执行快照读时生成readview,后续复用该readview,保证在一个事务中读到的版本是一样的
事务5中查询id为30的记录,根据访问规则+生成的readview,第一次读到的是事务2,第二次查询读到的还是事务2,因为复用的第一个ReadView。
欢迎大家关注我的微信公众号,程序媛雪儿,雪儿会在上面发布编程的知识碎片,也有雪儿博客地址,上面有详细系统的笔记,雪儿是全栈,但是公众号目前主要还是发后端的技术,以后可能也会涉及到一些前端的知识,我们下期见,拜拜~