【MySQL】事务

文章目录

MySQL专栏链接https://blog.csdn.net/shun_hua/category_12685814.html?spm=1001.2014.3001.5482

前言

在上一篇关于表操作的文章当中,介绍了关于表的相关操作,主要是表的结构和内容的操作,分属于DDL(数据定义语言)以及DML(数据操纵语言),不熟悉的同学可以通过专栏链接进行复习,此处就不再赘述了。本篇文章的内容就来给事务的内容画下一个句号,即学习事务,主要通过查询和写入进行体现,属于DCL(数据控制语言)。话不多说,马上开始!

正文

一、事务

概念

  • 事务,即先后执行且存在逻辑的一批表操作,这批表操作 被视为一个整体 ,要么被统一执行,要么失败进行回滚
    • 当这批操作执行过程中失败时,将数据恢复到没有对表进行操作的状态,这被称之为回滚。
    • 这批表操作通常指的是DML(数据操纵语言),比如insert,delete,select,update,增删查改。
    • 在多人协作的场景下,可能会存在对同一张表多次并发写入的类似情况,因此事务显得尤为重要。
    • MySQL面向应用层和客户,其保证事务是为了解决在实际应用中不同客户端并发执行一批SQL语句的时间段内,发现表的数据跟操作对不上,导致执行错误或者执行的结果不符合预期。

性质

  • 事务的保证通常是通过四个方面来保证的,将这其首字母提出来大写简称ACID
    • Atomicity,原子性,最终的执行结果要么成功,要么失败进行回滚,比较符合事务的概念。
    • Consistency,一致性,表在事务执行前一个状态,执行后一个状态,执行中表的数据对其它用户保持不变。
    • Isolation,隔离性,不同用户同时执行事务时,都看不到对方干了啥,即独立互不影响。
    • Durability,持久性,执行成功之后的状态不发生改变,通常被永久保留。

应用场景

  • 在并发度较高以及数据量庞大和对表操作的内容比较复杂的情况下,通常会使用事务来保证表数据的安全性。
  • MySQL在5.5.8版本之前采用的默认存储引擎MyISAM不支持事务,在之后变为InnoDB支持事务。

补充:MyISAM采用非聚集索引,即数据和索引分开进行存储。InnoDB采用采用聚集索引,数据和索引放在一起。

二、隔离

概念

  • 隔离,即保证事务在执行的过程中尽量不受到干扰。
  • 对于隔离通常谈论是隔离级别 ,如下简称3R1S
    • 从隔离级别的程度由低到高分为RU,RC,RR,S,共四种级别。
    • R ead Uncommitted,读未提交,可以读到其它事务没有提交的结果。
    • R ead Committed,读提交,可以读到其它事务提交的结果。
    • R epeatable Read,可重复读,重复读的是事务之前的表数据。
    • Serializable,串行化,强制事务的执行是串行化的,由间隙锁(防插)和行锁(只读)进行实现。

图解:

补充:串行化指的是在事务前强制对事务进行排序,通常使用锁来讲进行实现,比如两个人同时执行一次事务,小红和小明,假如小红抢到了锁先执行事务,那么小红拿的是之前的表,小明拿的是小红执行事务后的表。

  • 不同程度的隔离级别,适用于不同的场景,但也有着不同的缺陷。
    • 读未提交,适用于内容动态的更新,比如金铲铲,在玩的过程中更新了一些活动,但是版本没有更新;但不适用于计算汇率,当通过表公式计算时,查找时表的数据发生了变化,导致通过公式计算有误,这被称之为脏读问题。
    • 读提交,适用于游戏更新bug,比如金铲铲热门阵容削弱了,赌狗赌不成了,但幸好我不是赌狗^ _ ^;但不适用于证券交易,比如在进行交易前,股票跌了买入,但是交易中,股票又涨了,那么交易如果按最新的价格就会失败,这被称之为不可重复读问题。
    • 可重复读,适用于游戏添加新英雄,比如金铲铲小小英雄^ _ ^。但不适用于医生进行病因诊断,比如你没给医生说你对抗生素过敏,然后医生给你开了抗生素的药,导致你GG,这被称之为幻读问题。
    • 串行化,基本适用于所有场景,但如果对性能要求很高的话,还是灵活选择如上的几种隔离方案吧!

实操

  • 版本支持

说明:如下的演示环境为Linux下Ubuntu系统,安装的是最新版本的mysql,大家跟我使用一样的进行实操同步性更好一些。

  • 命令
powershell 复制代码
 show engines;
 #查看搜索引擎的基本属性
  • 从表中可以看到关于数据库引擎属性Transation,即是否支持事务,XA,即是否支持一致性,Savepoints,即是否支持保存点。
  • 在事务中可能要执行的mysql语句可能过多,害怕出错,因此分批执行,给每一批打一个标记就是所谓的保存点了。
  • 从以上所说的三栏来看,InnoDB全部支持了,而其它的要么就没有提供这些功能,要么就将三项默认设置为关闭状态。
  • 综上,MySQL默认采用的是InnoDB,所以MySQL(最新版本)默认支持事务,一致性,保存点。其它引擎要么没有要么默认不支持。

关于如下实验操作的图解:

读未提交

  • 金铲铲活动数据库

说明:请你想象左边是金铲铲的管理服务器的人员,而右边是玩家正在查看是否有白嫖的活动,^ _ ^。

补充:

  1. 这里还要设置一下会话隔离与全局隔离同步比较好,比如我的机器上就是会话隔离的优先级大于全局隔离,下面就不再赘述了。
  2. 每个会话的autocommit默认都为ON,得每个会话都把autocommit设置为0。
  • 详解服务器管理员与玩家擦肩而过的过程------

此时读未提交不是问题,但是在比如说银行用公式计算时就会出大问题,用数据代入到公式里面计算汇率之类的金融数据,不能一个数据一个数据改,公式中所有的数据都要统一进行改,一旦别人要用就拿到了正在改动,即错误的数据,这种问题我们称之为脏读问题。解决其实也很简单,设置隔离模式为读提交即可,此时再代入上述场景使用公式计算的数据可能不是最新的,但也不是错误的。

读提交

说明:请你想象左边是金铲铲的管理服务器的人员,而右边是玩家。

  • 金铲铲的修复热门补丁

此时读提交不是问题,银行把数据全部替换完毕之后,使用最新的数据用公式计算也没有问题;但是在证劵交易所进行股票交易的过程中,你的策略是低买高卖,如果你在交易中原本是低的,但在交易过程中如果按照隔离方式按照读提交,突然涨了,你的交易就会不符合预期,从而可能会导致亏本问题,这被称之为不可重复读问题。

可重复读

说明:请你想象左边是官方金融的管理服务器的人员,而右边是股民。

  • 股市交易

此时进行股票交易就没有问题,但是如果要进行看病,假如你没给医生说你对啥过敏,医生开过药才知道,那你不是白去医院看了,弄不好,你就要GG重开了。

串行化,最高的隔离级别。

  • 首先建立一张库存表,然后设立两个会话以及全局的隔离等级为序列化。

  • 然后仓库进了两件潮牌男衣的衣服。

  • 接着有两个客户过来要买衣服,先要看一下有没有,然后再考虑买或者不买的。

    说明:此时读是可以并发访问的,我这里少写一个commit不是很完整,读者读到这里自己进行实验时要及时进行思考和补充。

  • 最后两个客户考虑要买,于是库存是少买两件的。

网上购物的库存是必须要按照序列化的隔离程度进行实现的,而且我这里只是简单的介绍一个例子,帮助大家进行理解,望各位读者不要死扣逻辑是否完整,符合现实之类的,只是简单地帮助各位举例理解序列化而已。但是在其它场景下,比如同时对银行数据做更新操作,两个事务同时进行更新时,如果按照串行化就会出大问题,因为两个事物都是基于一个旧的数据进行更新的。


综上,我们四个隔离程度的例子就举好了,希望大家能够基于现实角度进一步地理解这些知识,其次,事务中的一些操作我这里就咱不赘述了,那张图其实表述的已经很明白了(下面会贴出来的),我们接着从事务的四个性质进一步理解不同隔离程度下的区别。

  • 原子性,要么执行成功,要么失败进行回滚,在保存点操作以及四个隔离程度都有所体现。
  • 持久性,在四个隔离程度下都有所体现,即数据都会在事务执行后永久保留。
  • 一致性,表在事务的执行中状态的变化,其实在不同的隔离级别都有所不同。
  • 隔离性,不同的隔离级别,事务之间彼此的影响程度是不一样的。

图解:

三、多版本控制

概念

  • 用来解决读写冲突的无锁并发 实现的一种机制
    • 并发场景一般来说有三种,读-读并发,读-写并发,写-写并发。
      • 读-读,即都在读而不修改,一般来说是没有线程安全问题的。
      • 读-写,即有在读表的,有在向表中写入数据,根据不同的隔离程度,会有脏读,不可重复读,幻读问题。
      • 写-写,即都在向表中写入数据,会存在更新丢失,具体一点就是回滚丢失和覆盖丢失,下面会进行补充和说明。
    • 机制的实现一般是采用undo日志中的快照实现的,具体涉及的对象为ReadView。

补充:

  1. 回滚丢失,打个比方,比如我们要做一个实验创建了一张表,然后存放了一些数据,此时称为表1,然后启动两个事务,分别为事务A和事务B,事务A操作完成功,此时的表称为表2;事务B操作的时候失败,如果回滚到表1,而不是表2;此类问题被称为回滚丢失。
  2. 覆盖丢失,还是类似的例子,比如我们要做一个实验创建了一张表,然后存放了一些数据,此时称为表1,然后启动两个事务,分别为事务A和事务B,事务A操作完成功,此时的表称为表2;如果事务B操作成功,覆盖表2,此时最新的表为表3;此类问题被称为覆盖丢失。
  3. 机制的实现博主简单画一个图,方便先大家进行认识机制,具体地下面会详谈。

拓展:MySQL三大日志

  • undo log,也叫做撤销日志,即主要负责数据的撤销,隔离等,主要保证事务的原子性,即执行失败回滚;隔离性,即对同一张表增删查改的结果是不一样的。
  • redo log,也叫做重做日志,即主要负责数据库服务崩溃时进行数据的恢复等,从而保证事务的持久性。
  • bin log,也叫做逻辑日志,即主要负责主从数据备份时进行数据同步等,从而保证事务的一致性。

补充一下,所谓的主从数据备份,打个比方,我们日常生活应该用过使用云服务的软件,比如说有道云笔记,moji日记等等,那么完整的讲有三端,即电脑端,云端,手机端,那么可以把云端看成主数据库,手机端和电脑端看成事从数据库,一旦主数据库更新,那么软件必须要让你马上看到手机端和电脑端的变化,这就很好的体现了数据库的一致性。

undo日志

  • 隐藏字段,如下的第一个用来进行数据库索引,剩下三个主要与undo日志有关。
    • DB_ROW_ID,隐藏主键,6字节,数据库中用来标识唯一行的id,如果没有设置默认主键,即索引ID的话,此ID会设置为默认主键。
    • DB_TRX_ID,事务ID,6字节,用于标识每一个事务,基于undo日志中保存的最新ID自增一得来。
    • DB_ROLL_PTR,事务回滚指针,7字节,主要用于进行回滚操作,一般使用链表的形式组织起来,方便直接回滚。
    • Flag,标志,1字节,用于标识操作的状态以及事务的状态(begin or commit)等。

如下是关于隐藏字段的举例解释,方便大家看图解进行理解(实际还是要看源码),不在此处发挥作用的隐藏主键我就忽略不写了,大家知道它有啥作用就行。

  • 首先,我们创建了一张关于股票价格的表,并已经在里面插入了数据,此会被undo日志进行记录在内。
  • 其次,股票价格更新,事务开始,处于事务中的阶段,但是还没有提交。
  • 然后,股票价格更新,事务结束,将操作记录到undo中,然后表中的DB_ROLL_PTR要进行清空。
  • 接着,股票价格又更新了,事务开始,处于事务中的阶段,但是还没有提交。(往下有点冗余,看会的可以直接跳过)
  • 最后,股票价格更新,事务结束,将操作记录到undo中,然后表中的DB_ROLL_PTR要进行清空。

补充:

  1. 在事务的执行期间,如果要删除数据,那么只需要将Flag设置为DELETE,表示为删除,然后写入到undo日志中,那么失败进行回滚,即将数据恢复的过程中,就是将Flag恢复到原来的UPDATE或者INSERT之类的状态。
  2. 对表的增删改查,即不断地修改属性信息,然后将其写入到undo日志中形成版本链的过程。

综上,undo日志关于事务记录的内容,就讲解完毕了,我们接下来进一步介绍关于读写并发的机制,即快照。


快照

  • 概念, 快照是数据库在某一时刻的完整数据状态的副本,原理是通过Read View进行实现。

说明:

  1. Read View是一个结构体,里面记录者关于事务的相关信息,下面会把主要源码贴出来,方便大家进行理解。
  2. Read View中存放着关于时间的信息,一般来说为时间戳,提取快照一般来说是根据时间戳提取的。
  3. 上述的undo日志的例子只是简单的Read View的体现,而具体的体现我们如下细讲。

补充:

  1. Read View源码
cpp 复制代码
class ReadView {
	// 省略...
private:
	/*
	* 高水位:大于等于这个ID的事务均不可见
	*/
	trx_id_t m_low_limit_id;
	
	/*
	* 低水位:小于这个ID的事务均可见 
	*/
	trx_id_t m_up_limit_id;
	
	/*
	*事务ID
	*/
	trx_id_t m_creator_trx_id;
	
	/* 创建视图时的活跃事务id列表*/
	ids_t m_ids;
	
	/* 
	* 配合purge,标识该视图不需要小于m_low_limit_no的UNDO LOG,
	* 如果其他视图也不需要,则可以删除小于m_low_limit_no的UNDO LOG
	*/
	trx_id_t m_low_limit_no;
	
	/*
	* 标记视图是否被关闭
	*/
	bool m_closed;
	// 省略...
};
  1. 隔离策略
c 复制代码
bool changes_visible(trx_id_t id, const table_name_t& name) const 
	MY_ATTRIBUTE((warn_unused_result))
{
	ut_ad(id > 0);
	//1、事务id小于m_up_limit_id(已提交)或事务id为创建该Read View的事务的id,则可见
	if (id < m_up_limit_id || id == m_creator_trx_id) {
		return(true);
	}
	check_trx_id_sanity(id, name);
	//2、事务id大于等于m_low_limit_id(生成Read View时还没有启动的事务),则不可见
	if (id >= m_low_limit_id) {
		return(false);
	}
	//3、事务id位于m_up_limit_id和m_low_limit_id之间,并且活跃事务id列表为空(即不在活跃列表中),则可见
	else if (m_ids.empty()) {
		return(true);
	}
	const ids_t::value_type* p = m_ids.data();
	//4、事务id位于m_up_limit_id和m_low_limit_id之间,如果在活跃事务id列表中则不可见,如果不在则可见
	return (!std::binary_search(p, p + m_ids.size(), id));
}

图解:

  • 在不同的隔离程度下,形成的Read View会有所不同,基本上差别都在于活跃事务和新来事务的判断。
    • 对于读未提交,那么已提交事务能看到,未提交的结果也会看到。
    • 对于读提交,那么已提交事务能看到,未提交的结果不会看到。
    • 对于可重复读,那么已提交事务不能看到,未提交的结果更不能看到。
    • 对于序列化,那么已提交的事物能看到,除了读读场景,未提交的结果得等到提交之后才能看到,否则会陷入阻塞。
  • 究其根本就在于对于不同隔离程度下m_up_limit_id以及 m_low_limit_id的划分,以及对应的技术的实现,比如锁之类的。

补充:

  1. 快照读,即定格在最开始的表数据中,而不对其它事务对表的修改做更新,其实就是隔离级别为可重复读的事务中的一次结果。
  2. 当前读,其实就是读取最新的事务的结果,即随时都可能被其它事务的修改而进行更新。
  3. 要在可重复读的事物中读取按照当前读的形式进行读表可以使用select * from [表名] lock in share mode; 进行读。
  4. RR,即Repeatable Read,RC,即Read Committed两者的区别就在于一个在快照读一个在当前读。

尾序

最后,后续的MySQL文章可能没有时间进行更新了,因为博主要转入考研的准备工作,然后开始考研了,但是我会把考研总结的知识点以博客的形式全部发布出来的,敬请期待。如果觉得内容不完整的话,博主在这里推荐一位CSDN的一位大佬------2021dragon,去看人家MySQL专栏的文章,个人觉得写的还是比较全的,我也是有一部分参考人家的文章进行思考写的。好了,本文到这里就结束了,我们下篇文章再见吧!我是理工小羊,期待你的关注!

相关推荐
宛唐羁客15 分钟前
ODBC连接PostgreSQL数据库后,网卡DOWN后,客户端进程阻塞问题解决方法
网络·数据库
吴半杯44 分钟前
Redis-monitor安装与配置
数据库·redis·缓存
是桃萌萌鸭~2 小时前
mysqldbcompare 使用及参数详解
数据库·mysql
gma9992 小时前
brpc 与 Etcd 二次封装
数据库·c++·rpc·etcd
Yz98762 小时前
hive复杂数据类型Array & Map & Struct & 炸裂函数explode
大数据·数据库·数据仓库·hive·hadoop·数据库开发·big data
小鹿撞出了脑震荡2 小时前
SQLite3语句以及用实现FMDB数据存储的学习
数据库·学习·sqlite
小草儿7992 小时前
gbase8s之mysql的show命令实现
数据库·mysql
斯普信专业组2 小时前
MongoDB调优利器:掌握性能分析工具mongostat
数据库·mongodb·mongostat
.2 小时前
接口 测试
数据库·oracle
码到成功>_<3 小时前
Spring Boot实现License生成和校验
数据库·spring boot·后端