事物

ACID

  • 原子性:由redo提供
  • 一致性:由undo提供
  • 隔离性:由锁提供
  • 持久性:由redo提供

隔离级别

  1. 读未提交:当前事物所作的任何操作都随时可被其他事物看见,对于中间状态和失败事物的数据如果被其他事物读取,即读到了脏数据,因此会产生脏读
  2. 读已提交:当前事物提交后,其他事物即可看到。在其他事物第一次与第二次查询同一条记录的期间,记录被事物更改且被提交,从而导致其他事物单个事物内两次查询到的记录不同,造成不可重复读
  3. 可重复读:当事物开始时,事物对数据加锁,其他事物无法更改数据,但是可以插入新数据,因此可能造成幻读。但在mysql中使用了MVCC机制,事物开始后只能看到当前版本下的数据,从而避免了幻读。
  4. 序列化:完全串行化操作。

控制语句

  • start transaction | begin : 准备开启事物(真正开启是在之后的第一次访问数据库时才开启的
  • commit :提交事物
  • rollback : 回滚事物
  • savepoint id :保存回滚点,设置id名称
  • release savepoint id : 删除id回滚点
  • rollback to [savepoint] id :回滚至id回滚点
  • set transaction :设置事物隔离级别

innodb的事物支持

redo

innodb专属,属于引擎日志,为事物提供原子性和持久性。事物在提交前,必须将操作全部写入到redo日志中后才能提交完成事物。redo是物理日志。redo日志的持久化由配置参数innodb_flush_log_at_trx_commit控制:

  • 0:每秒一次将日志持久化到日志文件中,由专门的线程进行操作。
  • 1:每次事物提交时将日志持久化到文件中
  • 2:事物提交时将日志写入操作系统文件缓存中,由OS决定何时刷新到磁盘上

mysql进行在执行事物过程中发生宕机时,有以下几种情形:

  • 对于已经成功提交的事物,没有影响
  • 对于事物执行但没来得及提交的,redo中记录了其操作,根据redo进行重新执行。

redo的写入是跟随事物的进行持续写入redo日志缓冲区,且为顺序写入,缓冲区同步磁盘时以512字节为单位原子性的写入磁盘(磁盘扇区为512字节,可以保证原子性的写入)

undo

innodb专属,属于引擎日志,为事物提供原子性支持。 undo用于记录事物执行之前的状态,当事物执行失败时会根据undo内容进行回滚操作。undo是逻辑日志。undo不存在与日志文件中,而是存在与数据库的一个特殊的undo segment

undo产生的同时也会产生对于的redo。

undo还提供着MVCC的功能。当事物读取记录时,如果当前记录被其他事物锁定占用,当前事物可以通过undo查询之前版本的信息,从而无需等待,实现无锁定读。

当事物提交时,undo不会被马上删除,因为可能存在其他事物仍需要undo读取之前版本的信息。因此undo会被放入一个列表中等待专门的线程清理。

不同操作的undo
  1. insert

只需要记住新的id,undo时删除这个id对应记录即可

  1. delete

先将记录标志为已删除,当事务提交时,再将记录加入到垃圾链表中,同时修改页中的相关信息。undo时将删除标志回复即可

  1. update

更新内容不影响空间大小的,在原纪录上原地更新。
造成空间改变的,先删除(加入垃圾链表并修改页信息,真正的删)再新增。如果新记录空间不超过旧记录,直接复用旧空间,否则需要在页中新申请空间。
如果更新了主键,则先标记删除旧记录,再根据新主键定位位置,在新位置查询新记录。
更新操作的undo存储了各列的信息

同一个事务的多次操作造成的undo会组成一个链表。不同类型(delete,insert,update)的操作undo分开维护

事务id分配

有全局变量来维护,每次分配后自增1,当是256倍数时持久化更新(8字节)。下次系统启动时读取该值并加上256作为起始id。
只有第一次修改数据时才会分配事务id
只读事务的事务id为默认值0

update流程

1、从磁盘读取记录放到内存。
2、记录undo log 日志。
3、记录redo log (预提交状态)
4、修改内存中的记录。
5、记录binlog,写入文件缓存或者刷盘
6、提交事务,写入redo log (commit状态)

1、在第一步、第二步、第三步执行时据库崩溃:因为这个时候数据还没有发生任何变化,所以没有任何影响,不需要做任何操作。
2、在第四步修改内存中的记录时数据库崩溃:因为此时事务没有commit,所以这里要进行数据回滚,所以这里会通过undo log进行数据回滚。
3、第五步写入binlog时数据库崩溃:这里和第四步一样的逻辑,此时事务没有commit,所以这里要进行数据回滚,会通过undo log进行数据回滚。
4、执行第六步事务提交时数据库崩溃:如果数据库在这个阶段崩溃,那其实事务还是没有提交成功,但是这里并不能像之前一样对数据进行回滚,因为在提交事务前,binlog可能成功写入磁盘了,所以这里要根据两种情况来做决定。

  • 如果binlog存在事务记录:那么就“认为”事务已经提交了,这里可以根据redo log对数据进行重做。其实你应该有疑问,其实这个阶段发生崩溃了,最终的事务是没提交成功的,这里应该对数据进行回滚。 这里主要的一个考虑是因为binlog已经成功写入了,而binlog写入后,那么依赖于binlog的其它扩展业务(比如:从库已经同步了日志进行数据的变更)数据就已经产生了,如果这里进行数据回滚,那么势必就会造成主从数据的不一致。
  • binlog不存在事务记录,那么这种情况事务还未提交成功,所以会对数据进行回滚。

参考文献

MVCC

在可重复读的隔离级别下,在事务开始时会开启视图,后面所有查询均是在该视图下进行;在读提交隔离下,视图的创建是在语句执行时创建的。
大事物会导致系统中存在很老的视图,因此要避免大事物。
事务开启时还会拿到对自己可见的事务id列表,在进行查询时,根据read view判断是否是自己可见的,如果不可见则查找undo log来查历史版本,并依次判断事务id对自己是否可见,可见时才返回。
对于更新操作,需要拿到写锁,所以必须等其他事务的读写锁释放了才会进行,且操作的是最新数据。因此在事务中如果要取最新数据,可以直接加锁读。如果有写事务存在,则加锁读会等待写锁释放才能读且读最新。

read view
m_ids 生成read view时,当前活跃的事务id列表
min_trx_id m_ids最小值
max_trx_id 系统应该分配给下一个事务的事务id
creator_tx_id 当前事务id

对于被访问版本的事务id

  1. 若小于min_trx_id,代表已经提交过的事务,可见
  2. 若大于等于max_trx_id,代表未来事务,不可见
  3. 若等于creator_tx_id,代表自己的事务,可见
  4. 若在min_trx_id,max_trx_id之间,若在m_ids中,不可见,否则可见

对于读已提交和可重复读的mvcc的区别

  • 读已提交:每次读取数据时都创建read view
  • 可重复读:第一次读取数据时创建read view

MySQL · 引擎特性 · InnoDB 事务子系统介绍
MySQL · 引擎特性 · InnoDB 事务子系统介绍.pdf