写点什么

理一理事务实现

作者:Zhang
  • 2023-04-11
    广东
  • 本文字数:2214 字

    阅读完需:约 7 分钟

理一理事务实现

事务是将多条 SQL 语句作为一个整体操作,要么全部执行成功,要么全部执行失败。主要用于确保数据间依赖型大的情况下,数据发生变动时能够整体变动,避免损失,比如转账操作,用户 A 给用户 B 执行了转账 100 块的操作,要是程序崩溃导致给用户 B 账号上新增 100 块的操作没能执行成功,这就会造成资产损失。


对于一些不需要事务查询类应用可以选择非事务型存储引擎以获取更高的性能,即便没有事务,但通过 lock tables 语句也能提供一定程度上的保护。

4 大特性

事务必须同时满足 4 大特性:ACID


  • A: Acomicity(原子性),事务内的操作要么全部成功提交,要么就全部失败回滚到事务开始前的状态。

  • C: Consistency(一致性),事务完成后,所有数据的状态都是一致的,比如上文的转账操作,用户 A 减掉了 100,用户 B 的账号必然要加上 100。

  • I: Isolation(隔离性),多个并发事务同时对某条数据读写操作,隔离性能防止事务交叉操作导致数据不一致。

  • D: Duration(持久性),事务完成后,数据会持久化存储到数据库,即使发生宕机等故障,数据也能恢复。


原子性、一致性和持久性的实现由 redo log 和 undo log 完成,而隔离性由锁完成。

undo log 和 redo log

undo log 和 redo log 作用分别是记录数据修改前后的状态,它们经常需要结合在一起工作,可以当作为一个整体,它们在事务中的工作过程如下:


假设有 A、B 两个数据,值分别为 1 和 2。


  1. 事务开始

  2. 记录 A=1 到 undo log

  3. 修改 A=3

  4. 记录 A=3 到 redo log

  5. 记录 B=2 到 undo log

  6. 修改 B=4

  7. 记录 B=4 到 redo log

  8. 将 redo log 写入磁盘

  9. 事务提交


undo log 确保了原子性,它记录数据的逻辑变化,主要用于事务回滚和 MVVC。事务回滚实际是根据 undo log 所记录的数据状态执行一遍反方向数据修改,比如执行了 insert 语句后需要回滚,那么就是通过执行一遍对应的 delete 语句操作让数据看起来没有变化过。


redo log 确保了持久性,redo log 在事务提交前要写入磁盘,而数据是晚于 redo log 写入磁盘的,它在事务提交前只是缓存于内存。


redo log 通过批量顺序追加方式尽量存在一个连续的空间上,通过顺序 I/O 提升性能,日志写入需要先通过写入到日志缓冲区,然后在事务提交前再一起写入到磁盘;记录只追加不删除,如果有事务回滚,redo log 记录也不会被删除; 如果是并发事务,它们的 redo log 记录按照语句执行顺序依次交替记录在一起,以更好地减少占用的空间。


记录1: <trx1, insert …>记录2: <trx2, update …>记录3: <trx1, delete …>记录4: <trx3, update …>记录5: <trx2, insert …>
复制代码

隔离级别

SQL 标准中定义了 4 种隔离级别,每种级别导致性能和可靠性都不同,其中 REPEATABLE READ 是 MySQL 默认事务隔离级别。


  • READ UNCOMMITTED(未提交读):在应用中一般很少使用,因为事务可以读取未提交的数据(也就是脏读),性能上不比其它级别好太多,还缺乏其它级别的很多好处。

  • READ COMMITTED(提交读):事务从开始到提交期间所作的修改,其它事务是不可见,所以也叫不可重复读,因为两次执行同样的查询操作,得到的结果有可能是不一样的。

  • REPEATABLE READ(可重复读):MySQL 默认事务隔离级别,它确保了同一事务中多次读取到的结构是一致的,但它也带来了另一个问题:幻读(Phantom Read),幻读是指事务 A 在读取某个范围内的数据时,事务 B 往这范围插入了新的记录,事务 A 再次读取时就会产生幻行(Phantom Row)。

  • InnoDB 存储引擎通过多版本并发控制(MVCC)解决幻读的问题。

  • SERIALIZABLE(可串行化):最高的隔离级别,在应用中也很少用到,因为它会给读取的每一行都加锁,虽说能避免幻读的问题,但这会导致大量的超时和锁争用的问题,所以说只有在非常确保数据一致性和接受没有并发的情况下才会选择。


事务的隔离性由锁机制完成,锁是常见的并发控制机制,可分为共享锁(shared lock)和排他锁(exclusive lock),也可以简单叫作读锁和写锁。读锁是共享的,多个用户都同时访问同一数据,而写锁是排他的,当用户修改某数据时,其它用户是不能读取和修改这条数据的。



死锁

当多个事务试图以不同顺序或同时锁定同一资源就可能会产生死锁,死锁会导致慢查询。


为了解决死锁问题,数据库会通过死锁检测和死锁超时机制,检测死锁的循环依赖,并立即返回错误,还有可以通过判断查询时间达到锁原先设置的等待超时时间就放弃锁请求,但这方法处理不是很好得方案。


InnoDB 处理死锁方法是:将持有最少行级排他锁的事务进行回滚(这是相对比较简单的死锁回滚算法)。

MVCC

大多数事务型存储引擎实现都不是简单的行级锁,基于提高并发性能的考虑,一般同时实现了多版本并发控制(MVCC)。


MVCC 可以说是行级锁的一个变种,开销更低,因为它在很多情况下避免了加锁操作。每个数据库系统实现机制各有差异,没有统一的实现标准,不过大多实现了非阻塞的读操作,写操作也只锁定必要的行。


如何实现:通过保存数据在某个时间点的快照实现。也就是说不管需要执行多长时间,每个事务看到的数据是一致的,事务开始的时间不同,每个事务对同一张表、同一时刻看到的数据可能不一样。


事务是门学问,数据库更是门大学问,文中目前只是描述事务实现的整体,后续会有更多的细节,底层知识虽说看起来有些枯燥乏味,但从长远来看是值的投入的,共勉。 :)

参考资料

发布于: 刚刚阅读数: 3
用户头像

Zhang

关注

还未添加个人签名 2018-10-18 加入

还未添加个人简介

评论

发布
暂无评论
理一理事务实现_MySQL_Zhang_InfoQ写作社区