每日激励:"不设限和自我肯定的心态:I can do all things。 --- Stephen Curry"
**绪论:
本章承接上文将讲到mysql中另一大底层细节---《事务》,事务其实在每次的sql语句中都存在,只不过因为它的特性你并没有感知,并且它该解决了当多个人并发执行数据库时的一些数据不一致问题,保证了数据库效率的同时还防止了脏读、不可重复读和幻读的并发问题,下一章将继续更新事务的隔离性底层到底是如何实现的(Read View 和 MVCC 多版本控制器),敬请期待~
若有看不懂如何登录Linux中的MySQL来执行具体代码的或者不知道如何创建表等一些列MySQL基础的的请看---》MySQL专栏(文章平均94分)早关注不迷路,话不多说安全带系好,发车啦(建议电脑观看)。**
什么是事务
事务就是一组DML语句组成,这些语句在逻辑上存在相关性,这一组DML语句要么全部成功,要么全部失败,是一个整体。MySQL提供一种机制,保证我们达到这样的效果。事务还规定不同的客户端看到的数据是不相同的。
简单来说事务是一种具有业务场景由一个或多个sql语句形成的业务处理语句。
例如:转账业务,他就需要事务由两条语句组成,一条删除转账的金额,一条增加转账的金额。
并且要么成功要么失败的原子性。
理解成事务并不是一种程序员术语,而是一种用户级的视角
但一个事务不仅仅只是简单的sql集合,还需要满足如下四个属性:
事务的四大属性:
(对于这四大特性,先有个了解即可,后续会通过实例进行细致的讲解)
- 原子性:一个事务(transaction)中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节。事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。
- 一致性:在事务开始之前和事务结束以后,数据库的完整性没有被破坏。这表示写入的资料必须完全符合所有的预设规则,这包含资料的精确度、串联性以及后续数据库可以自发性地完成预定的工作。
- 隔离性:数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。事务隔离分为不同级别,包括读未提交( Read、uncommitted )、读提交( read committed )、可重复读( repeatable read )和串行化( Serializable )
- 持久性:事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失
上述四大属性,又可以简称 ACID:
- 原子性(Atomicity,或称不可分割性)
- 一致性(Consistency)
- 隔离性(Isolation,又称独立性)
- 持久性(Durability)
通过上述四大属性 + 多条sql的封装 形成是事务,最终就能保证mysql的顺利执行
同样的事务在mysql是应该存在的,它同样也就是事务对象数据,然后mysql就能通过结构管理起来
为什么会出现事务
事务被 MySQL 编写者设计出来 ,本质是为了当应用程序访问数据库 的时候,事务能够简化我们的编程模型,不需要我们去考虑各种各样的潜在错误和并发问题。可以想一下当我们使用事务时,要么提交,要么回滚,我们不会去考虑网络异常了,服务器宕机了,同时更改一个数据怎么办对吧?因此事务本质上是为了应用层服务的。而不是伴随着数据库系统天生就有的。
事务的具体例子
通过CURD不加控制,看看会有什么问题?
对于这种情况是绝对不允许发生的,这本质就是数据不一致导致的,那么解决办法是:
- 买票的过程得原子的(原子性)
- 买票 互相应该不能影响(隔离性)
- 买完票应该要永久有效吧(持久性)
- 买前,和买后都要是确定的状态吧(一致性)
事务的版本支持
首先不是所有搜索引擎都支持事务(具体如下图)
其中常见的存储引擎:InnoDB支持事务,而MyISAM并不支持事务
事务的提交
事务的提交方式常见的有两种:
- 自动提交
- 手动提交
- 查看是否的提交方式:
sql
show variables like 'autocommit';
- 用 SET 来改变 MySQL 的自动提交模式:
sql
1. SET AUTOCOMMIT=0 禁止自动提交:
SET AUTOCOMMIT=0;
2. SET AUTOCOMMIT=1 开启自动提交:
SET AUTOCOMMIT=1;
事务的基本操作
提前准备
- 为了便于演示,我们将mysql的默认隔离级别设置成读未提交。
sql
set global transaction isolation level READ UNCOMMITTED;
- 重启终端,进行查看隔离级别:
sql
select @@tx_isolation;
- 通过两个客户端的形式进行访问同一MySQL进行并发情况
- MySQL可能会存在隔离性和隔离级别(调低了才能让两台机器并发的操作显示,即隔离性高了就看不到了)
新建表:
sql
create table if not exists account(
id int primary key,
name varchar(50) not null default '',
blance decimal(10,2) not null default 0.0
)ENGINE=InnoDB DEFAULT CHARSET=UTF8;
两台机器连接MySQL
查看mysql有几个用户连接:
show processlist;
- 启动事务(两种方法):
start transaction;
begin
- 设置保存点(这个保存点相对于一个标记,方便回滚)
savepoint 保存点名
- 插入信息后查看
- 在设置一个保存点 s2
- 再插入...
- 定向回滚到某个点
rollback 保存点
- 当我们回滚到某点后,数据就会改变,改变成回滚点时的情况。
- 若不进行设置保存点,那么将直接事务的开始:
- 直接回滚,回滚到事务开始
- 直接回滚,回滚到事务开始
commit
提交事务- 当我们提交事务后,数据就会永久保存
- 再次回滚就不会回到之前了
- 也就是回滚只能回滚到事务期间,当提交后就无法回滚了
事务碰到的异常情况
- 证明未commit,客户端崩溃,MySQL自动会回滚
- 当在事务插入过程中,客户端崩溃了(发送信号模拟:ctrl + \)
- 此时插入的数据本不会插入就去,而是系统自动回滚到事务开始
- 也就保证了,数据要么执行完成,要么失败(保证原子性)
- 相反只要commit了,就持久化存储了,后面即使再崩溃了,数据也已经持久化存储成功了!
- 其中虽然查看提交方式是自动提交事务(show variables like 'autocommit')
- 但注意的是:自动提交事务和本初的begin开启的事务并无关系
- 此处begin开启事务,就已经表示手动了(此时就需要手动 commit 了!)
- 单条sql和事务的关系
- 关闭自动提交 set autocommit=0
- 不使用事务,直接删除某条数据
- 然后直接关闭mysql
- 发现数据右回来了,说明:它本质也是事务,因为关闭了自动提交,所以导致并没有提交我们sql的事务
- 相反:在我们正常情况下,自动提交方法是打开的,那么我们删除过程中即使mysql崩溃了,它数据也是会自动提交成功的(所以我们执行的所有sql本质都是事务!!只不过开启了自动提交)
总结:
- 只要输入begin或者start transaction,事务便必须要通过commit提交,才会持久化,与是
否设置set autocommit无关。 - 事务可以手动回滚,同时,当操作异常,MySQL会自动回滚
- 对于 InnoDB 每一条 SQL 语言都默认封装成事务,自动提交。(select有特殊情况,因为MySQL 有 MVCC )
- 从上面的例子,我们能看到事务本身的原子性(回滚),持久性(commit)
- 如果没有设置保存点,也可以回滚,只能回滚到事务的开始。直接使用 rollback(前提是事务还没有提交)
- 如果一个事务被提交了(commit),则不可以回退(rollback)
- 可以选择回退到哪个保存点
- InnoDB 支持事务, MyISAM 不支持事务
- 开始事务可以使 start transaction 或者 begin
事务的隔离性理论(事务存在的意义详解)
首先理解:MySQL服务可能会同时被多个客户端进程(线程)访问,访问的方式以事务方式进行
而一个事务可能由多条SQL构成,也就意味着,任何一个事务,都有:
- 执行前
- 执行中
- 执行后的阶段。
- 而所谓的原子性,其实就是让用户层,要么看到执行前,要么看到执行后。执行中出现问题,
可以随时回滚。所以单个事务,对用户表现出来的特性,就是原子性。 - 但,毕竟所有事务都要有个执行过程,那么在多个事务各自执行多个SQL的时候,就还是有可能会出现互相影响的情况。比如:多个事务同时访问同一张表,甚至同一行数据。
那么也就引出了:
- 隔离性:为了保证事务执行过程中尽量不受干扰(语句值看自己时间线,也就表示着,谁先来谁先执行)
- 隔离级别:允许事务受不同程度的干扰(代表着隔离性的程度,要么不要隔离性改了数据立马看到(隔离级别最低))
结论:
- 隔离是必要的(时间范围,代表着运行中的事务进行的相互隔离或者说他们执行的先后)
- 在事务运行中"不会"出现互相干扰(隔离性)
- 根据影响程度的不同(隔离级别)
隔离级别
- 读未提交【Read Uncommitted】: 在该隔离级别,所有的事务都可以看到其他事务没有提交的执行结果。(实际生产中不可能使用这种隔离级别的),但是相当于没有任何隔离性,也会有很多并发问题,如脏读,幻读,不可重复读等,我们上面为了做实验方便,用的就是这个隔离性
- 读提交【Read Committed】 :该隔离级别是大多数数据库的默认的隔离级别 (不是 MySQL 默认的)。它满足了隔离的简单定义:一个事务只能看到其他的已经提交的事务所做的改变。这种隔离级别会引起不可重复读,即一个事务执行时,如果多次 select, 可能得到不同的结果。
- 可重复读【Repeatable Read】:只能当多个事务都结束后才能看到改变。 这是 MySQL 默认的隔离级别,它确保同一个事务,在执行中,多次读取操作数据时,会看到同样的数据行。但是会有幻读问题 。
- 串行化【Serializable】: 这是事务的最高隔离级别 ,它通过强制事务排序,使之不可能相互冲突,从而解决了幻读的问题。它在每个读的数据行上面加上共享锁。但是可能会导致超时和锁竞争(这种隔离级别太极端,实际生产基本不使用)
隔离级别如何实现:隔离,基本都是通过锁实现的,不同的隔离级别,锁的使用是不同的。常见有,表锁,行锁,读锁,写锁,间隙锁(GAP),Next-Key锁(GAP+行锁)等。不过,我们目前现有这个认识就行,先关注上层使用。
隔离级别的操作
- 查看隔离级别
- 查看全局隔离级别(也是提前设置好):
SELECT @@global.tx_isolation;
- 查看会话(当前)全局隔离级别(也是会话隔级别它会自动设置为全局隔级别):
SELECT @@session.tx_isolation;
- 会话的就是你自己登录到结束的过程使用的窗口
- 默认的隔离级别同上:
SELECT @@tx_isolation;
- 查看全局隔离级别(也是提前设置好):
- 设置隔级别
- 设置当前会话 or 全局隔离级别语法:
SET [SESSION | GLOBAL] TRANSACTION ISOLATION LEVEL {READ UNCOMMITTED | READ COMMITTED | REPEATABLE READ | SERIALIZABLE}
set [ session | global ] transaction isolation level { read uncommitted | read committed | repeatable read | serializable}
- 修改会话的隔离级别
- 一个用户设置了session隔离级别不影响其他用户
- 一个用户设置了session隔离级别不影响其他用户
- 修改全局的隔离级别
- 当设置了全局的隔离级别时会话的隔离级别暂时不会受影响(会话的隔离级别只会在登录时默认为全局的隔离级别)
- 注意要:重启mysql后就会变成全局的隔离级别
- 当设置了全局的隔离级别时会话的隔离级别暂时不会受影响(会话的隔离级别只会在登录时默认为全局的隔离级别)
- 设置当前会话 or 全局隔离级别语法:
事务隔离级别---读未提交Read UnCommitted
读未提交:几乎没有加锁,虽然效率高,但是问题太多,严重不建议采用
- 其中会发送的问题是:脏读(dirty read) :一个事务在执行中,读到另一个执行中事务的更新(或其他操作),但是该事务都还未commit数据(事务执行是有过程的)
- 只要一个用户修改了表,另外一个用户不用commit就能立马看到
- 也就是所谓的读未提交,当一个用户只要执行了sql还没提交,其他用户都能看到他在表上的操作
(提前启动设置读未提交)
事务隔离级别---读提交【Read Committed】
在读提交隔离级别中,在一个用户没有提交自己的事务的情况下,另外一个用户并不会看到该用户的操作
但当该用户进行commit提交后,即使另外一个用户没有结束自己的事务的前提下,也会看到该用户提交后修改的表:
但此时会发生的问题是:
不可重复读(non reapeatable read):此时还在当前事务中,并未commit,那么就造成了,同一个事务内,同样的读取,在不同的时间段(依旧还在事务操作中!),读取到了不同的值(这个是问题吗??
- 逻辑上一个事务提交后,另一个事务就能看到最新的数据
- 但不应该给并发的事务看到!
问题也很明显:
当有如下情况时:
- 当小张要查询多条数据(每个数据都有各自的范围)
- 而小王要进行修改数据,当小张查询一次数据后,小王修改了数据,就可能会导致小张在后再次查到(具体如下图)
事务隔离级别---可重复读【Repeatable Read】
总结上面的不难推出:
可重复读是:必须两个事务同时结束才能看到结果 (也是mysql默认的隔离级别 )
当该事务commit提交结束后就能看到其他用户的修改了!
- 其中比较符合可重复读
多次查看,发现终端A在对应事务中insert的数据,在终端B的事务周期中,也没有什么影响,也符合可重复的特点(mysql) 。但是,对于一般数据库 ,在可重复读情况的时候,无法屏蔽其他事务insert的数据
为什么?
因为隔离性实现是对数据加锁完成的,而insert待插入的数据因为并不存在,那么一般加锁无法屏蔽这类问题,会造成虽然大部分内容是可重复读的,但是insert的数据在可重复读情况被读取出来,导致多次查找时,会多查找出来新的记录,就如同产生了幻觉。这种现象,叫做幻读(phantom read)。
很明显,MySQL在RR级别的时候,是解决了幻读问题的解决的方式是用Next-Key锁 (GAP+行锁)解决的。
总结不可重复读和幻读的区别:
- 相信很多人和我一样都有点懵了,因为它们在概念上一致,都是在查询时出现了不同的情况
- 但他们本质的区别在于:不可重复度主要是针对于修改,而幻读是针对于插入和删除
- 不可重复度:关心的是同一数据项在两次读取时发生了变化
- 幻读:关心的是查询结果集在两次查询时发生了变化,通常是因为插入、删除或修改了满足查询条件的数据。
事务隔离级别---串行化【serializable】
对所有事务操作全部加锁,进行事务的串行化,但是只要串行化,效率很低,几乎完全不会被采用。
对于左边的事务来说,当他要进行删除表中数据的操作时,因为右边的事务前面进行了查操作,所以左边的事务就会阻塞式等待右边事务的完成(也就是所谓的串行化)
- 其中对于读来说就不会阻塞
当右边结束事务后,左边就能执行了:
所以具体情况可以如下图:
- 其中隔离级别越严格,安全性越高,但数据库的并发性能也就越低,往往需要在两者之间找一个平衡点。
- 不可重复读的重点是修改和删除:同样的条件, 你读取过的数据,再次读取出来发现值不一样了幻读的重点在于新增:同样的条件, 第1次和第2次读出来的记录数不一样
- 说明: mysql 默认的隔离级别是可重复读,一般情况下不要修改
- 上面的例子可以看出,事务也有长短事务这样的概念。事务间互相影响,指的是事务在并行执行的时候,即都没有commit的时候,影响会比较大。

事务隔离级别---一致性(Consistency)
- 事务执行的结果,必须使数据库从一个一致性状态,变到另一个一致性状态。当数据库只包含事务成功提交的结果时,数据库处于一致性状态。如果系统运行发生中断,某个事务尚未完成而被迫中断,而改未完成的事务对数据库所做的修改已被写入数据库,此时数据库就处于一种不正确(不一致)的状态。因此==一致性是通过原子性来保证(但并不完全)==的。
- 其实一致性和用户的业务逻辑强相关,一般MySQL提供技术支持,但是一致性还是要用户业务逻辑做支撑(也就是自己写的SQL是对的,如:转账一边增加,一边减少),也就是,一致性,是由用户决定的。
- 而技术上,通过AID保证C
update,insert,delete之间是会有加锁现象的,但是select和这些操作是不冲突的。这就
是通过读写锁(锁有行锁或者表锁)+MVCC完成隔离性
本章完。预知后事如何,暂听下回分解。
如果有任何问题欢迎讨论哈!
如果觉得这篇文章对你有所帮助的话点点赞吧!
持续更新大量MySQL细致内容,早关注不迷路。