《MySQL45讲》读书笔记(六):数据库事务概述[通俗易懂]

《MySQL45讲》读书笔记(六):数据库事务概述[通俗易懂]此文为极客时间:MySQL实战45讲的3、8、18、19节事务相关部分的总结一、事务的启动方式mysql主要有两种事务的启动方式:begin或starttransaction显式启动事

大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。

Jetbrains全系列IDE使用 1年只要46元 售后保障 童叟无欺

此文为极客时间: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 从一致性视图中判断该版本对本事务是否可见;

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

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

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

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/170787.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • centos 日志审计_CentOS7 – 审计日志[通俗易懂]

    centos 日志审计_CentOS7 – 审计日志[通俗易懂]1、auditctl:即时控制审计守护进程的行为的工具,比如如添加规则等等。audtitctl-l#查看规则auditctl-D#清空规则2、aureport:查看和生成审计报告的工具。aureport-l#生成登录审计报告3、ausearch:查找审计事件的工具ausearch-i-p40964、autrace:一个用于跟踪进程的命令。autrace-r/u…

    2022年5月23日
    379
  • visual studio2010注释快捷键_html中注释的快捷键

    visual studio2010注释快捷键_html中注释的快捷键VS注释快捷键操作:  注释:先CTRL+K,然后CTRL+C  取消注释:先CTRL+K,然后CTRL+U

    2022年8月15日
    6
  • linux redis重启,互联网常识:linux下重启redis的方法

    linux redis重启,互联网常识:linux下重启redis的方法跟大家讲解下有关 linux 下重启 redis 的方法 相信小伙伴们对这个话题应该也很关注吧 现在就为小伙伴们说说 linux 下重启 redis 的方法 小编也收集到了有关 linux 下重启 redis 的方法的相关资料 希望大家看到了会喜欢 导语 已经将 redis 加入到 etc 下此时服务器启动 redis 也启动但是却连不上 redis 所有有了以下的过程 学习视频分享 redis 视频教程 查看 redis 状态 syst

    2025年6月1日
    3
  • UNIX命令

    UNIX命令 一、UNIX命令格式1、UNIX命令提示符   在命令行下,操作系统会显示一提示符,提示用户在此提示符后可以输入一行命令。不同的Shell有不同的缺省提示符:       BShell和KShell的缺省提示符为”$”;       CShell的缺省提示符为”%”;       但当以root用户登录时,系统提示符统一缺省为”#”。用户可以更改自己的缺省Shell和提

    2022年6月1日
    129
  • Maven – 解决Maven下载依赖包速度慢问题

    Maven – 解决Maven下载依赖包速度慢问题

    2021年6月29日
    67
  • hashmap面试题简书_三年php面试题

    hashmap面试题简书_三年php面试题这篇文章仅限小编个人的理解,小编不是Java方向的,只是对Java有很高的学习兴趣如果有什么不对的地方还望大佬指点HashMap的底层是数组+链表,(很多人应该都知道了)JDK1.7的是数组+链表(1.7只是一个例子,以前的话也是这样后面就以1.7为例子了)首先是一个数组,然后数组的类型是链表元素是头插法JDK1.8的是数组+链表或者数组+红黑树首先是一个数组,然后数组的类型是链表在链表的元素大于8的时候,会变成红黑树在红黑树的元素小于6的时候会变成链表元素进行尾插HaspM.

    2022年8月10日
    6

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号