• 《MySQL45讲》读书笔记(六):数据库事务概述


    此文为极客时间:MySQL实战45讲的 3、8、18、19节事务相关部分的总结

    一、事务的启动方式

    mysql 主要有两种事务的启动方式:

    1. beginstart transaction显式启动事务。对应的提交语句是 commit ,回滚是 rollback
    2. set autocommit = 0关闭自动提交,然后在执行第一条 sql 的时候启动事务,这个事务会一直持续到你主动 commit 或者 rollback,或者断开连接才会结束。

    有一些客户端连接框架会在连接成功后默认修改设置,这可能导致意外的长事务。因此,显示启动事务明显是比较安全的,但是对于一些需要频繁使用事务的业务,每次都需要调用 begin 然后再 commit。对于这种情况,可以使用 commit work and chain,当 autocommit = 1时,使用该语句可以在提交以后自动开启下一个新事务

    这样省去了再次执行 begin 语句的开销,而且可以明确地知道每个语句是否处于事务中。

    除此之外,我们还可以使用 sql 去在 information_schema 库的 innodb_trx 这个表中查询长事务:

    select * from information_schema.innodb_trx where TIME_TO_SEC(timediff(now(),trx_started)) > 60
    

    比如上面这条语句,就是用于查找持续时间超过 60s 的事务

    二、事务的隔离级别

    我们知道事务有四大特性(ACID):原子性,一致性,隔离性,持久性。

    针对隔离性,我们有:

    1. 读未提交:一个事务还没提交时,它做的变更就能被别的事务看到。
    2. 读已提交:一个事务提交之后,它做的变更才会被其他事务看到。
    3. 可重复读:一个事务执行过程中看到的数据,总是跟这个事务在启动时看到的数据是一致的。当然在可重复读隔离级别下,未提交变更对其他事务也是不可见的。
    4. 串行化:顾名思义是对于同一行记录,“写”会加“写锁”,“读”会加“读锁”。当出现读写锁冲突的时候,后访问的事务必须等前一个事务执行完成,才能继续执行。

    简单的理解:

    1. 读未提交:别人改数据的事务尚未提交,我在我的事务中也能读到
    2. 读已提交:别人改数据的事务已经提交,我在我的事务中才能读到
    3. 可重复读:别人改数据的事务已经提交,我在我的事务中也不去读
    4. 串行化:我的事务尚未提交,别人就别想改数据。

    以这张图为例:

    image-20200930144400423

    1. 读未提交: 则 V1 的值就是 2。这时候事务 B 虽然还没有提交,但是结果已经被 A 看到了。因此,V2、V3 也都是 2。
    2. 读已提交:则 V1 是 1,V2 的值是 2。事务 B 的更新在提交后才能被 A 看到。所以, V3 的值也是 2。
    3. 可重复读:则 V1、V2 是 1,V3 是 2。之所以 V2 还是 1,遵循的就是这个要求:事务在执行期间看到的数据前后必须是一致的。
    4. 串行化:则在事务 B 执行“将 1 改成 2”的时候,会被锁住。直到事务 A 提交后,事务 B 才可以继续执行。所以从 A 的角度看, V1、V2 值是 1,V3 的值是 2。

    我们不难看出,读已提交和可重复读,最大的区别在于,当一个查询的事务尚未提交,另一个修改的事务的提交是否会影响到这次查询结果。

    三、事务隔离的实现

    1.脏读,幻读,不可重复读

    说起事务,就不得不提到三种错误读:

    1. 脏读(读到了RoolBack):表示一个事务能够读取另一个事务中还未提交的数据。这个未提交数据就是脏读(Dirty Read)。
    2. 幻读(读到了insert):指同一个事务内多次查询返回的结果集不一样。
    3. 不可重复读(读到了update):是指在一个事务内,多次读同一数据。
      • 第一类丢失更新:两个事务更新同一条数据资源,后做的事务撤销,发生回滚造成已完成事务的更新丢失
      • 第二类丢失更新:两个事务更新同一条数据资源,后完成的事务会造成先完成的事务更新丢失

    2.事务隔离的实现

    在实现上,数据库里面会创建一个视图,当访问的时候以视图的逻辑结果为准。

    这里需要注意一下,这里的视图区别于我们自己创建的 View :

    innodb 创建的,用于实现 MVCC 时的一致性读视图,即 consistent read view,用于支持 RC(Read Committed,读提交)和 RR(Repeatable Read,可重复读)隔离级别。

    1. 读未提交:直接返回记录上的最新值,没有视图概念;
    2. 读已提交:这个视图是在每个 SQL 语句开始执行的时候创建的。
    3. 可重复读:这个视图是在事务启动时创建的,整个事务存在期间都用这个视图。
    4. 串行化:直接用加锁的方式来避免并行访问。

    这里单独对读已提交和可重复读的逻辑做一个区分:

    • 在可重复读隔离级别下,只需要在事务开始的时候创建一致性视图,之后事务里的其他查询都共用这个一致性视图
    • 在读提交隔离级别下,每一个语句执行前都会重新算出一个新的视图
    隔离级别 脏读 不可重复读 幻读
    读未提交
    读已提交 ×
    可重复读 × ×
    串行化 × × ×

    四、MVCC

    1.概述

    MVCC 即是并发版本控制。拿可重复读举个例子:

    我们知道 innodb 有个 undo log ,每条记录在更新的时候都会在 undo log 中记录一条回滚操作,通过日志记录可以回滚到上一状态的值。

    假设一个值从 1 被按顺序改成了 2、3、4,在回滚日志里面就会有类似下面的记录。

    image-20200930145105580

    当前的值是4,但是对于不同时间段启动的事务创建的视图ABC而言,分别为1,2,4,这时就算把4再改成5,对于ABC三个视图也不会有影响。

    同一条记录在系统中可以存在多个版本,就是数据库的多版本并发控制(MVCC)

    2.一致性读视图的实现

    当在可重复读隔离级别下时,事务在启动的时候就给整库“拍了个快照”,这个快照就是我们在事务的隔离提到过一致性读视图。这个视图是逻辑上的,用于描述事务之间的可见性。

    在 innodb 里,每个事务都有独有的 transaction id,这是在事务开始的时候向系统申请的,是严格递增的。

    而每行数据也有多个版本,每次事务更新数据的时候都会把 id 赋给对应版本数据的 row trx_id。

    image-20201023102713922

    如上图,我们可以看到这一行数据被三个事务进行了修改,现在有四个版本,每个版本更新前都会记录一条回滚的语句在 undo log。

    事实上,V1,V2这些版本的数据并不是真实存在的,而是在需要的时候才通过 undo log 计算获取。比如需要 V2,就从 V4 经过 U3 和 U2 获得。

    现在我们知道数据版本是如何跟事务绑定的,那么事务的隔离就很好理解了:当一个事务启动的时候,获取事务 id,事务id比他小的说明是在他之前就产生的,这些事务对应的版本就是被本事务承认的,反之,则这些数据是不被本事务承认的,要向前找到可以承认的数据版本。

    为此,innodb 会在事务启动的时候,为事务创建一个数组,这个数字里会存放所有当前启动了但是还没提交的事务的 id。这个数组里最小的视为低水位,最大的+1视为高水位,从低水位到高水位中间的这块区域,就是当前事务的一致性视图。这段操作是在锁保护性进行的。

    3.数据版的一致性读

    假如我们只在事务里面进行查询,而暂时不涉及到更新,那么基于一致性视图,当前事务就可以根据数据版本id,也就是 row trx_id 来判断当前数据版本对于自己而言是否可见:

    image-20201023104832814

    1. 如果落在绿色部分,表示这个版本是已提交的事务或者是当前事务自己生成的,这个数据是可见的;
    2. 如果落在红色部分,表示这个版本是由将来启动的事务生成的,是肯定不可见的;
    3. 如果落在黄色部分,那就包括两种情况
      • 若 row trx_id 在数组中,表示这个版本是由还没提交的事务生成的,不可见;
      • 若 row trx_id 不在数组中,表示这个版本是已经提交了的事务生成的,可见。

    当然,可能存在这么一种情况:如果有一个事务在未提交事务的区间,但是在当前事务获

    仍然以上图为例,假如有一个事务,他的低水位是18,也就是说在他启动的时候,row trx_id 是17 的V3是最新的版本,在他查询的时候,而最新的版本变成了 row trx_id 是25的V4,那么对他而言V4是不可见的,于是通过 undo log U3 计算得到V3,V3低于他的低水位,所以V3是可见的,故对于该事务而言值就是V3的值。

    可以看到,事务开始前和事务开始后读到的数据都一致的,这个就是一致性读。可重复读依赖这个隔离级别核心依赖于此。

    4.数据的当前读

    当事务里只进行查询的时候一致性读可以保证读取的正确性,但是如果进行的是更新,那么一致性读反而会导致错误。我们以下图为例:

    image-20201023110829190

    原本 k 是2,事务C进行了更新并且率先提交,对于事务C而言,此时k是3,但是事务B又进行了一次更新,那么等到提交的时候,k该是3还是4?

    这里涉及到一个规则。因为更新总是需要先读后改,所以更新的读必须要读最新的数据,也就是当前读

    值得一提的是,如果是 select 语句,如果加了读锁或者写锁,也是当前读:

    # 加读锁
    select k from t where id=1 lock in share mode;
    # 加写锁
    select k from t where id=1 for update;
    

    然后,我们在前面了解了行锁,而行锁有一个两阶段锁的机制:事务里的有对某一行数据的更新,那么sql执行前就会去获取行锁,然后执行完sql之后不释放,等到事务提交之后才会去释放锁。由于事务C先获取了行锁,那么事务B的更新就会等待事务C释放锁以后才会得到锁。反映到执行上,就是事务B的 update 等到 事务C提交了才会继续进行。

    也就说,而行锁的两阶段锁保证了更新的顺序进行,当前读机制保证的更新语句总是能拿到最新的数据。

    5.一致性视图与可重复读和读已提交

    MVCC 实现的核心在于一致性视图,可重复读和读已提交建立视图的机制决定了他们实现效果的不同:

    • 在可重复读隔离级别下,只需要在事务开始的时候创建一致性视图,之后事务里的其他查询都共用这个一致性视图;
    • 在读提交隔离级别下,每一个语句执行前都会重新算出一个新的视图。

    6.为什么要避免长事务

    1. 占用日志空间:因为一致性视图需要通过 undo log 去计算旧版本的数据,而 undo log 只有在没有比某条日志更早的一致性视图时才会删除。所以如果存在长事务,可能就会导致数据库的视图存在很长时间,直到这些视图删除前日志都会一直保留,这将会导致占用大量存储空间。
    2. 影响版本控制计算性能:在可重复读这个隔离级别下,如果其他事务对某条数据进行了非常多次的操作,最后会导致本事务读取的时候必须要通过 undo log 计算非常多次才能找到最初的数据版本。
    3. 占用锁资源:长事务还会可能会占用锁资源,比如只有等事务提交才能释放的行锁。

    五、总结

    1.事务的启动

    • beginstart transaction显式启动事务。对应的提交语句是 commit ,回滚是 rollback
    • set autocommit = 0`关闭自动提交,然后在执行第一条 sql 的时候启动事务,这个事务会一直持续到你主动 commit 或者 rollback,或者断开连接才会结束。

    2.事务的隔离级别

    • 读未提交:别人改数据的事务尚未提交,我在我的事务中也能读到。会脏读,幻读,不可重复读;
    • 读已提交:别人改数据的事务已经提交,我在我的事务中才能读到。会幻读,不可重复读;
    • 可重复读:别人改数据的事务已经提交,我在我的事务中也不去读。会不可重复读;
    • 串行化:我的事务尚未提交,别人就别想改数据。加锁,不会错误读。

    3.并发版本控制(MVCC):

    • 每个事务的更新都会产生一个新版本数据,每个数据版本有自己的 row trx_id,对应更新他们的事务的 transaction id;

    • 事务启动时 innodb 为事务创建一个数组,这个数字里会存放所有当前启动了但是还没提交的事务的 id。这个数组里最小的视为低水位,最大的+1视为高水位,从低水位到高水位中间的这块区域,就是当前事务的一致性视图。根据事务版本 id 从一致性视图中判断该版本对本事务是否可见;

    • 可重复读和读已提交建立视图的机制决定了他们实现效果的不同:

      可重复读隔离级别下,只需要在事务开始的时候创建一致性视图,之后事务里的其他查询都共用这个一致性视图;

      读提交隔离级别下,每一个语句执行前都会重新算出一个新的视图。

  • 相关阅读:
    selenium—用NoSuchElementException异常判断页面元素是否存在
    CentOS7 Nginx安装及配置反向代理
    CentOS7 安装 jexus-5.8.2-x64
    Windows Server 2008 R2远程协助选项 灰色
    IIS8.5 Error Code 0x8007007e HTTP 错误 500.19的解决方法
    记一次 windows server 2012R2 上安装 MSSQL2005 及网站发布
    记一次《系统集成实施的相关知识》培训自己感悟
    MySql 远程连接的条件
    CentOS7 下安装mysql历程
    VirtualBox虚拟机网络设置(四种方式)
  • 原文地址:https://www.cnblogs.com/Createsequence/p/13952060.html
Copyright © 2020-2023  润新知