mysql面试事务问题
一:事务四大特性和实现原理
1:事务特性ACID
- 原子性: 事务作为一个整体被执行,包含在其中的对数据库的操作要么全部被执行,要么都不执行。
- 一致性: 指在事务开始之前和事务结束以后,数据不会被破坏,假如 A 账户给 B 账户转 10 块钱,不管成功与否,A 和 B 的总金额是不变的。
- 隔离性: 多个事务并发访问时,事务之间是相互隔离的,即一个事务不影响其它事务运行效果。简言之,就是事务之间是进水不犯河水的。
- 持久性: 表示事务完成以后,该事务对数据库所作的操作更改,将持久地保存在数据库之中
2:事务ACID的实现思想
- 原子性:是使用 undo log 来实现的,如果事务执行过程中出错或者用户执行了 rollback,系统通过 undo log 日志返回事务开始的状态。
- 持久性:使用 redo log 来实现,只要 redo log 日志持久化了,当系统崩溃,即可通过 redo log 把数据恢复。
- 隔离性:通过锁以及 MVCC, 使事务相互隔离开。
- 一致性:通过回滚、恢复,以及并发情况下的隔离性,从而实现一致性。
二:事务的隔离级别
1:为什么有隔离级别的概念?
- 当数据库上有多个事务同时执行的时候,就可能出现脏读、不可重复读、幻读的问题,为了解决这些问题。
- InnoDB 使用不同的锁策略 (Locking Strategy) 来实现不同的隔离级别。
2:脏读、幻读、不可重复读
- 脏读:读到了其他事务未提交的数据。
- 不可重复读:对于事务 A 来说,在同一个事务的不同时刻读取某一行记录,结果是不同的,这种现象就是不可重复读。
- 幻读:在读提交的事务隔离级别下,事务 B 提交了事务的前后,事务 A 分别读取到了不同的记录。这就是幻读现象。
3:读提交
- 别人改数据的事务尚未提交,我在我的事务中也能读到。
- select语句不加锁,会出现脏读,并发性最高,一致性最差的隔离级别。
4:串行化
- 对于同一行记录,写会加写锁,读会加读锁。当出现读写锁冲突的时候,后访问的事务必须等到前一个事务执行完成,才能继续执行。
- 一致性最好,并发性最差。在互联网大数据量,高并发量下几乎不会使用读提交和串行化。
5:可重复读(InnoDB默认)
- 别人改数据的事务已经提交,我在我的事务中也不去读。一个事务执行过程中看到的数据是一致的
- 普通读使用快照读,底层使用mvcc实现。
- 加锁的select, update, delete语句,在唯一索引上使用唯一的查询条件,会使用记录锁(record lock)。
- 加锁的select, update, delete语句,范围查询条件,会使用间隙锁(gap lock)和临键锁(next-key lock),锁住索引记录之间的范围,避免范围间插入记录,避免出现不可重读和幻读。
6:读提交(互联网常用的)
- 别人改数据的事务已经提交,我在我的事务中才能读到。
- 普通读使用快照读,底层使用mvcc实现。
- 加锁的select, update, delete语句,除了在外键约束和重复键检查会封锁区间,其他时刻都只使用记录锁,此时,其他事务的插入依然可以执行,就可能导致幻读。
三:MVCC底层原理
1:多版本并发控制
- innodb 对于同一个 ID,不同的事务创建或修改,每个事务都有自己的快照,会插入一条记录。
- innodb会在每行数据的最后加上两个隐藏列,创建时间事务id, 删除时间事务id,事务id是mysql自身维护自增的。
2:可重复读隔离级别
- 在一个事务内查询,只会查询当前事务id >= 创建事务id,这样可以确保这个行是在当前事务创建的或者是之前创建的;同时一个行的删除事务id没有定义(没删除),要么是比当前事务id大(在事务开启之后才被删除),满足这两个条件的数据都会被查出来。
- 某个事务执行期间,别的事务更新一条数据,在innodb中是插入了一行记录,然后将新插入的记录的创建时间设置为新的事务的 id,同时将这条记录之前的那个版本的删除时间设置为新的事务的 id。
3:demo
- 事务 ID=121 的事务,查询 ID=1 的这一行数据,当前事务id >=创建事务id
| id | name | 创建事务 id | 删除事务 id |
|---|
| 1 | 张三 | 120 | 空 |
- 事务 ID=122 的事务,将 ID=1 的这一行删除了,会将 id=1 的行的删除事务id 设置成 122
| id | name | 创建事务 id | 删除事务 id |
|---|
| 1 | 张三 | 120 | 122 |
- 事务 id=121 的事务,再次查询 ID=1 的那一行,当前事务id >=创建事务id,当前事务id < 删除事务id
| id | name | 创建事务 id | 删除事务 id |
|---|
| 1 | 张三 | 120 | 122 |
| 2 | 李四 | 119 | 空 |
- 事务 id=121 的事务,查询 id=2 的那一行,查到 name = 李四
- 事务 id=122 的事务,将 id=2 的那一行的 name 修改成 name = 小李四
| id | name | 创建事务 id | 删除事务 id |
|---|
| 1 | 张三 | 120 | 122 |
| 2 | 李四 | 119 | 空 |
| 2 | 小李四 | 122 | 空 |