SQL执行逻辑
引自《极客时间 mysql45讲》
事务
Innodb引擎引入事务,可以认为事务是一组SQL语句的序列,事务具有ACID特性。
ACID
- 原子性
一个事务要么全部提交成功,要么全部失败回滚,不能只执行其中的一部分操作。 - 持久性
一旦事务提交,那么它对数据库中的对应数据的状态的变更就会永久保存到数据库中。 - 隔离性
在并发环境中,并发的事务时相互隔离的,一个事务的执行不能不被其他事务干扰。 - 一致性
一个事务在执行之前和执行之后,数据库都必须处于一致性状态。
快照读
InnoDB在每行数据都增加几个隐藏字段,一个记录创建的事务id,一个指针指向上个版本的数据,在不同的隔离级别下MVVC会在不同的事件发生来创建快照来保证事务的隔离性。
- 默认快照读
SELECT * FROM t_record WHERE id = 2
- 非快照读
SELECT * FROM t_record WHERE id = 2 FOR UPDATE
隔离级别
- 读已提交:读取到未提交的数据(脏读)
- 读未提交:读SQL执行前的快照,所以在一个事务中可能读取到同个记录的不同值(不可重复读)
- 可重复读:读事务执行前的快照,一个事务的两次查询一致,但是会读到其他事务新增的数据(幻读)
- 串行化:SQL语句串行化执行
幻读的解决方式 (MVVC、锁)
在可重复读级别下,如果事务A产生查询,如果包含唯一索引会产生行锁,不包含唯一索引会产生间隙锁,锁住当前记录和下一条记录中间的区域。
# table(id,value) id为主键索引 value非索引
INSERT INTO t_record VALUE (1,5)
INSERT INTO t_record VALUE (5,10)
SessionA | SessionB | 备注 |
---|---|---|
SELECT * FROM t_record WHERE id = 2 | ||
INSERT INTO t_record VALUE(2,15) | 不会阻塞 | |
SELECT * FROM t_record WHERE id = 2 | 读不到数据 | |
SELECT * FROM t_record WHERE value = 5 | ||
INSERT INTO t_record VALUE(3,6) | 不会阻塞 | |
SELECT * FROM t_record WHERE value = 6 | 读不到数据 |
SessionA | SessionB | 备注 |
---|---|---|
SELECT * FROM t_record WHERE id = 2 FOR UPDATE | ||
INSERT INTO t_record VALUE(2,15) | 阻塞 | |
SELECT * FROM t_record WHERE value = 5 FOR UPDATE | ||
INSERT INTO t_record VALUE(3,6) | 阻塞 | |
commit; | 插入执行 |
在快照读时,不会影响其他事务插入操作。FOR UPDATE 读取当前最新记录,会导致其他事务插入的阻塞。
锁
共享锁 (S):读锁允许其他事务读,不允许写
排他锁 (X):写锁独占资源阻塞其他操作
记录锁(行锁):唯一索引,单条记录上锁
间隙锁:解决幻读问题,可重复读隔离级别下,在数据范围内上锁
undo log、redo log、binlog
如果每次查库都进行一次磁盘寻址,效率会很低,因此数据库在很多地方使用了缓存,为了解决缓存和磁盘数据不一致的问题,一般采用日志预写方案(WAL)来保证事务的特性。
undo log 回滚日志
保存了事务发生之前的数据的一个版本,可以用于回滚,同时可以提供多版本并发控制下的读(MVCC),即非锁定读。
redo log 重做日志
确保事务的持久性。防止在发生故障的时间点,尚有脏页未写入磁盘,在重启mysql服务的时候,根据redo log进行重做,从而达到事务的持久性这一特性。
binlog
用于复制,在主从复制中,从库利用主库上的binlog进行重播,实现主从同步,或是用于数据库的基于时间点的还原。
区别
undo log | redo log | binlog | |
---|---|---|---|
实现 | innodb | innodb | mysql |
数据 | 逆语句 | 数据页 | SQL语句 |
作用 | 原子性 | 持久性 | 主从同步 恢复数据 |
事务特性的保证
- 原子性 undo log回滚
- 持久性 redo log commit 写入磁盘
- 隔离性 锁 MVVC
- 一致性 回滚、恢复、并发隔离