数据库事务初步综述

100 阅读5分钟

事务将多个sql组合在一起,并保证每个sql执行的结果相同。事务具有四个基本特性

  1. 原子性 事务中的所有sql的执行结果相同,均为成功或失败,不存在部分成功。
  2. 隔离性 事务的中间态不会被其他事务所感知,事务与事务之间不会知晓彼此的执行。
  3. 一致性 事务从开始到结束的事件内,保持所操作的数据一致,既不会凭空消失也不会突然产生。 比如在转账操作中,事务将保证两个账户内的资金总量在事务开始和结束时一致。
  4. 持久性 事务成功提交后将永久保留改动,即使发生系统故障也应当保证这一点,确保事务入库。

如何保证事务原子性

在数据库中,实际只有单独sql是原子的,由多条sql集合构成的事务并不具备原子特性,所以需要额外通过回滚日志(undo log)来保证事务的原子性。 undo log中记录了如何撤销一条由事务改动的数据,当事务回滚时,通过undo log将该事务所有的改动撤销。此外,如果有其他事务想访问由该事务修改前的数据,也通过undo log进行撤销。

如何保证事务隔离

事务的隔离性可以细分为不用的隔离等级,不同的隔离等级决定其它事务对当前事务做出修改的可访问性。以mysql8.0为例

事务的隔离等级

  • REPEATABLE READ 可重复读是InnoDB的默认隔离等级。 此时在同一事务的read,都将读取该事务第一次read操作时所构建的快照(snapshot),从而避免被其他事务影响。 对于带锁查询(SELECT ... FOR UPDATE/SHARE)、DELETE和UPDATE,锁类型主要取决于查询条件: 如果查询中有唯一索引,则使用行锁(record lock); 如果其他条件,则会使用gap lock或者next-key lock阻止其他事务的操作待修改数据区间 在UPDATE语句执行时,首先会获取表中每一行的互斥锁,即使不满足UPDATE条件的行也不会释放。锁将保留到事务提交或者回滚,因此这个级别的事务不会出现不可重复读的问题。
  • READ COMMITTED 此时同一事务中的read不再统一快照版本,由各自read操作的时间重新构建快照。 带锁查询、DELETE和UPDATE,仅使用行锁。gap lock仅在检查外键约束和重复主键时使用。由于没有gap lock的保护,其他事务也可以在被锁记录的附近查入数据,因此会导致幻读的产生。 此外,对于UPDATE操作,只会获取需要符合调价的行的互斥锁,不满足条件的行不会被锁,所以其他事务也可以同时操作这个表的其他数据,从而出现不可重复读的问题。
  • READ UNCOMMITTED 读未提交已无锁的方式运行,是所有隔离等级中最低的一个,由于读到的数据可能会被其他事务回滚,因此有脏读的问题。
  • SERIALIZABLE 将并发事务强制串行,执行效率低。在autocommit没有开启的情况下,SELECT语句会被解释为SELECT .. FOR SHARE;否则查询作为一个单独的事务执行。

不同隔离等级可能出现的问题

  • 幻读 本事务插入一条,符合其他事务查询条件的记录。 事务A

    SELECT * FROM employee WHERE salary > 30000
    ...
    SELECT * FROM employee WHERE salary > 30000
    

    事务B

    INSERT INTO employee(empno, firstnme, midinit,lastname,job,salary) VALUES ('000350', 'NICK','A','GREEN','LEGAL COUNSEL',35000)
    

    当B在A执行过程中执行,则A的两次查询结果不同,产生幻读。

  • 不可重复读 本事务中读取到由其他事务修改的记录。 事务A

    SELECT * FROM employee WHERE empno = '000090'
    

    事务B

    UPDATE employee SET salary = 30100 WHERE empno = '000090'
    

    事务B修改的数据满足事务A的查询,如果B在A执行过程中提交,造成事务A的不可重复读

  • 脏读 本事务读取到其他事务未提交的数据

Note: 不同隔离等级与问题的对应关系

transaction isolation leveldirty readnon-repeatable readPhantom reading
READ UNCOMMITTED (READ-UNCOMMITTED)
READ COMMITTED (READ-COMMITTED)×
Repeated read (REPEATABLE-READ)××
Serialization (SERIALIZABLE)×××

如何保事务证持久性

事务持久性通过redo log得以实现。redo log记录所有对数据库的操作,包括修改数据对segment的改动,undo数据等。在事务提交时,会首先将缓存中的日志写入redo log文件,这样即使出现系统故障,也可以从物理存储的redo log中进行恢复。 在 InnoDB 中,重做日志都是以 512 字节的块的形式进行存储的,同时因为块的大小与磁盘扇区大小相同,所以重做日志的写入可以保证原子性,不会由于机器断电导致重做日志仅写入一半并留下脏数据。


参考

[1]: Transaction isolation [2]: ACID properties of transactions [3]: 14.6.7 Undo Logs - Oracle [4]: What Is the Redo Log? [5]: Isolation levels and concurrency [6]: MySQL :: MySQL 8.0 Reference Manual :: 15.7.2.1 Transaction Isolation Levels