目录

前言
看下面的一个例子,思考一下:CURD不加控制,会有什么问题?
这是一个经典的多线程并发导致数据不一致的问题,MySQL既然提供数据存储服务,那么它也要想办法解决上面的问题。
那CURD满足什么属性,能解决上述问题?
- 买票的过程得是原子的吧。(原子性)
- 买票互相应该不能影响吧。 (隔离性)
- 买完票应该要永久有效吧。 (持久性)
- 买前和买后都要是确定的状态吧。(一致性)
1.事务
1.1.什么是事务?
事务就是一组DML语句组成,这些语句在逻辑上存在相关性,这一组DML语句要么全部成功,要么全部失败,是一个整体。MySQL提供一种机制,保证我们达到这样的效果。事务还规定不同的客户端看到的数据是不相同的。
事务主要用于处理操作量大,复杂度高的数据。比如转账就涉及多条SQL语句,包括查询余额(select)、在当前账户上减去指定金额(update)、在指定账户上加上对应金额(update)等,将这多条SQL语句打包便构成了一个事务。
1.2.事务的特性?
MySQL同一时刻可能存在大量事务,如果不对这些事务加以控制,在执行时就可能会出现问题。比如单个事务内部的某些SQL语句执行失败,或是多个事务同时访问同一份数据导致数据不一致的问题,以及也会存在执行到一半出错或者不想再执行的情况,那么已经执行的怎么办呢?
因此一个完整的事务并不是简单的SQL集合,事务还需要满足如下四个属性,这四个属性简称为ACID:
原子性(Atomicity)
原子性是指事务中的所有操作要么全部完成,要么全部不完成,不会停留在中间某个状态。这意味着,如果事务中的任何一个操作失败,整个事务都会被回滚到事务开始前的状态,就像这个事务从未执行过一样。这是通过数据库系统的事务日志(如MySQL的Undo Log)来实现的,当事务失败时,数据库系统会使用这些日志来撤销已执行的操作,使数据库恢复到事务开始前的状态。
一致性(Consistency)
一致性是指事务执行前后,数据库的完整性约束没有被破坏。事务开始时,数据库必须处于一致性状态;事务结束时,数据库也必须处于一致性状态。这要求事务必须遵守所有业务规则和数据完整性约束,如主键约束、外键约束、唯一性约束等。如果事务结束时数据库的状态不满足一致性要求,那么事务就被认为是失败的,数据库系统会进行回滚操作。
隔离性(Isolation)
隔离性是指多个并发事务之间不会互相干扰,每个事务都像是独立运行的。数据库系统通过事务隔离级别来控制并发事务之间的交互程度。MySQL支持四种事务隔离级别:
- 读未提交(Read Uncommitted):事务可以读取到其他未提交事务的修改,这可能导致脏读问题。脏读是指一个事务读取了另一个未提交事务的修改,如果这些修改在另一个事务回滚时被撤销,那么读取到的数据就是无效的。
- 读已提交(Read Committed):事务只能读取到已提交事务的修改,这避免了脏读问题。但是,如果一个事务在读取数据后,另一个事务对该数据进行了修改并提交,那么该事务再次读取数据时可能会得到不同的结果,这称为不可重复读。
- 可重复读(Repeatable Read):事务在开始时创建一个一致性快照,事务期间读取的数据都基于该快照。这避免了脏读和不可重复读问题。但是,如果另一个事务在读取数据后插入了新数据并提交,那么该事务再次查询时可能会得到更多的数据行,这称为幻读。MySQL的InnoDB存储引擎通过多版本并发控制(MVCC)来实现可重复读隔离级别。
- 串行化(Serializable):最高的隔离级别,事务完全串行化执行,避免了脏读、不可重复读和幻读问题。但是,这种隔离级别会严重影响并发性能,因为每个事务都需要等待前一个事务完成后才能开始执行。
持久性(Durability)
持久性是指事务一旦提交,其对数据库的修改就是永久性的,即使系统发生故障也不会丢失。这是通过数据库系统的持久化存储机制来实现的,如将事务日志写入磁盘。当事务提交时,数据库系统会确保所有修改都已写入持久化存储介质中,这样即使系统崩溃或断电,也不会丢失已提交的事务。
1.3.为什么会出现事务
事务被MySQL 编写者设计出来,本质是为了当应用程序访问数据库的时候,事务能够简化我们的编程模型,不需要我们去考虑各种各样的潜在错误和并发问题。
可以想一下当我们使用事务时,要么提交,要么回滚,我们不会去考虑网络异常了,服务器宕机了,同时更改一个数据怎么办?因此事务本质上是为了应用层服务的,而不是伴随着数据库系统天生就有的。
1.4.事务的版本支持
在 MySQL 中只有使用了Innodb 数据库引擎的数据库或表才支持事务,MyISAM 不支持事务的。
通过show engines命令可以查看数据库引擎和相关说明:
show engines;
说明:
- Engine: 表示存储引擎的名称。
- Support: 表示mysql服务器对存储引擎的支持级别,YES表示支持,NO表示不支持,DEFAULT表示数据库默认使用的存储引擎,DISABLED表示支持引擎但已将其禁用。
- Comment: 表示存储引擎的简要说明。
- Transactions: 表示存储引擎是否支持事务,可以看到InnoDB存储引擎支持事务,而MyISAM存储引擎不支持事务。
- XA: 表示存储引擎是否支持XA事务。
- Savepoints: 表示存储引擎是否支持保存点。
2.事务的操作
2.1.事务的提交
通过show命令查看autocommit全局变量,可以查看事务的自动提交是否被打开
show variables like 'autocommit';
说明一下: autocommit的值为ON表示自动提交被打开,值为OFF表示自动提交被关闭,即事务的提交方式为手动提交。
2.2.设置事务的提交方式
- 通过set命令设置autocommit全局变量的值,可以打开或关闭事务的自动提交。
- 将autocommit的值设置为1表示打开自动提交,设置为0表示关闭自动提交,相当于将事务提交方式设置为手动提交。
set autocommit=0;
可以看到我们已经关闭了自动提交:
2.3.不同提交方式的事务的演示
准备工作
我们先创建一个银行用户表,表中包含用户的id、姓名和账户余额。如下:
create table balance(
id int primary key auto_increment,
name varchar(20) not null,
balance decimal(10,2) not null default 0.0
);
- 自动提交
事务是可以由一条或则多条SQL组成的,在我们开启自动提交的情况下我们每一条执行过SQL都会被提交。
例如我们打开两个客户端进行连接,客户端1使用下面的SQL进行插入数据,插入完毕以后,然后客户端2立即进行查看。
insert into balance(name, balance) values('张三',1000.2);
我们发现输入完成,按回车之后,就MySQL就自动commit然后保存了
- 手动提交
我们将自动提交进行关闭,
set autocommit=0;
show variables like 'autocommit';
然后继续进行上面的实验:
insert into balance(name, balance) values('李四',1010.2);
这一次我们让客户端1使用下面的SQL进行插入数据,插入完毕以后,立即使用客户端2立即进行查看
有没有发现,左边那个终端就能看到我们的李四的信息,但是右边那个终端却查询不到。
这就是手动提交的特点!!!
- 使用rollback
rollback;
我们发现使用rollback之后,左边这个终端的李四也不见了,右边的也是没有任何反应。
- 使用commit
时间回到下面这张图的时候
接下来我执行
commit;
有没有发现不一样的!!!
我们发现右边也出现了!!!这说明了我们已经保存成功了!!
这里如果还不懂的话就去下面看看:
2.4.事务常见使用方式
1. 开启事务:START TRANSACTION;
- 作用:手动开启一个事务。
- 背景:在许多数据库系统中,默认情况下,每个SQL语句都被视为一个独立的事务并自动提交(autocommit模式)。为了执行一系列相互关联的SQL操作作为一个整体(即一个事务),需要手动开启事务。
- 影响:一旦开始事务,后续的SQL操作将不会立即生效到数据库中,直到执行COMMIT或ROLLBACK。
2. 设置保存点:SAVEPOINT 名称;
- 作用:在事务中设置一个可以回滚到的点。
- 背景:在复杂的事务中,可能需要在某个中间状态设置一个回滚点,以便在出现错误或需要撤销部分操作时,能够回滚到该点而不是整个事务的开始。
- 影响:设置保存点后,可以使用ROLLBACK TO 保存点名称;命令回滚到该点。
3. 回滚到保存点:ROLLBACK TO 保存点名称;
- 作用:将事务回滚到指定的保存点。
- 影响:从保存点到当前事务结束之间的所有操作都将被撤销,但保存点之前的操作仍然保留。
4. 回滚事务:ROLLBACK;
- 作用:回滚整个事务。
- 影响:从START TRANSACTION;开始到当前时刻的所有操作都将被撤销,数据库将恢复到事务开始之前的状态。
5. 提交事务:COMMIT;
- 作用:提交事务,使事务中的所有操作生效。
- 影响:一旦事务被提交,所有在事务期间执行的SQL操作都将永久保存到数据库中,并且无法再被回滚。
2.5.事务的常规操作
在上面的演示中我们直接进行了事务操作,这是因为InnoDB中的每一条SQL都会默认被封装成事务。
但是我们有些时候我们需要一个由多条SQL构成的事务,于是我们可以手动开启一个事务。
BEGIN
- 语法:BEGIN; 或 BEGIN WORK;(在某些数据库系统中,WORK 是可选的)
- 作用:标记一个事务的开始。在此之后的SQL操作将被视为该事务的一部分,直到执行COMMIT或ROLLBACK。
START TRANSACTION
- 语法:START TRANSACTION;
- 作用:与BEGIN相同,也是用来开启一个新事务。
- 额外选项:在某些数据库系统中,START TRANSACTION 命令可以接受额外的选项,如设置事务的隔离级别(ISOLATION LEVEL)、是否只读(READ ONLY 或 READ WRITE)等。例如:START TRANSACTION ISOLATION LEVEL SERIALIZABLE;
- 使用begin或者start transaction开启一个事务
start transaction;
当我们执行 START TRANSACTION; 命令时,我们显式地开启了一个新的事务。
从这个点开始,直到遇到 COMMIT; 或 ROLLBACK; 命令为止,所有执行的SQL语句都将被视为这个事务的一部分。
- COMMIT;:当事务中的所有操作都成功执行,并且您希望这些更改永久保存到数据库中时,您应该执行 COMMIT; 命令。这将提交事务,使所有更改生效。
- ROLLBACK;:如果在事务执行过程中发生错误或异常情况,您可能希望撤销已经执行的所有操作,以恢复数据库到事务开始之前的状态。这时,您可以执行 ROLLBACK; 命令。这将回滚事务,撤销所有在事务中执行的更改。
如果在这个事务中间突然出现异常情况,mysql会自动回滚到事务的最开始,也就是刚创建事务时的状态。
在数据库事务管理中,SAVEPOINT 命令用于在事务中创建一个保存点(也称为检查点或回滚点)。这个保存点标记了事务中的一个特定状态,允许我们在之后的某个时刻回滚到这个状态,而不是回滚到整个事务的开始。这对于处理复杂事务特别有用,因为它提供了更细粒度的控制,使得我们可以撤销部分操作而不是整个事务。
- 使用 SAVEPOINT 创建保存点
语法:
SAVEPOINT 保存点名称;
保存点名称:这是一个用户定义的名称,用于标识创建的保存点。在之后回滚到该保存点时,需要使用这个名称。
- 使用 ROLLBACK TO SAVEPOINT 回滚到保存点
语法:
ROLLBACK TO SAVEPOINT 保存点名称;
这将回滚事务到指定的保存点,撤销从该保存点创建到当前时刻之间的所有操作。但是,保存点之前的操作仍然保留。
- 释放保存点(可选)
在某些数据库系统中,您还可以选择释放(删除)一个保存点,以便在之后不再需要它时清理资源。但是,请注意,释放保存点并不会影响已经执行的操作或事务的状态;它只是删除了保存点本身的引用。
语法(如果支持):
RELEASE SAVEPOINT 保存点名称;
示例
假设我们有一个事务,其中包含多个步骤,我们希望在某个中间步骤创建一个保存点,以便在后续步骤出错时能够回滚到这个中间状态。
sql复制代码 START TRANSACTION; -- 执行一些SQL操作 UPDATE 表1 SET 列1 = 值1 WHERE 条件; -- 创建一个保存点 SAVEPOINT my_savepoint; -- 执行更多SQL操作 UPDATE 表2 SET 列2 = 值2 WHERE 条件; -- 假设这里发生了错误,我们决定回滚到保存点 ROLLBACK TO SAVEPOINT my_savepoint; -- 如果决定继续事务并提交剩余的操作(注意:这取决于具体的应用逻辑) -- 则需要再次执行从保存点之后应该执行的操作(在这个例子中,没有更多的操作) -- 然后提交事务 -- COMMIT;
在这个例子中,如果UPDATE 表2操作失败,我们可以回滚到my_savepoint,这样UPDATE 表1的更改仍然保留,但UPDATE 表2的更改被撤销了。然后,我们可以根据应用逻辑决定是继续事务还是完全回滚事务。
- 使用savepoint创建保存点,方便我们进行回滚
savepoint s1;
然后我们在这个事务中插入一些数据,插入一条建立一个保存点:
insert into balance(name, balance) values('李四', 1523.4);
savepoint s2;
insert into balance(name, balance) values('王五', 2002.4);
savepoint s3;
我们现在查看一下我们的balance
我们去另外一个终端看看
- 使用rollback to进行回滚
假设我们将王五的数据插入时搞错了,这时我们可以回滚到s2状态,即王五的信息还没有被插入时的状态。
rollback to s2;
如果这个时候,我们突然发现王五的数据好像是对的,我们可能会执行下面这个语句
rollback to s3;
但事实上它是不会允许的!
难道这个事务结束了吗?事实上并没有!!
ROLLBACK TO 命令在数据库事务管理中用于将事务回滚到指定的保存点(Savepoint),但它本身并不直接标志着事务的结束。事务的结束通常是通过 COMMIT 或 ROLLBACK(不带 TO 子句)命令来实现的。
事实上,这是因为回滚到s2保存点时,MySQL会撤销从s2创建到当前时刻之间的所有操作。
- 使用commit提交事务
假设现在就是我们想要的表,我们这个时候客户端1不能直接退出,这是我们手动开启的事务,必须要求我们手动进行提交,不然mysql会默认给我们回滚到事务的最开始。
commit;
我们去另外一个终端里面查看一下
在我们使用完了commit也代表着我们这个事务结束了,如果我们还要再手动创建事务,那么我们还要使用begin或则start transaction
InnoDB的事务特性
- 默认封装成事务:
- 对于InnoDB存储引擎,每一条SQL语句都默认被封装成一个事务。这意味着,如果没有显式地开启一个更大的事务(通过BEGIN或START TRANSACTION),那么每条SQL语句都会自动提交(如果autocommit被设置为1,这是MySQL的默认设置)。
- 持久化与自动提交:
- 持久性是指事务一旦提交,其对数据库的修改就是永久性的。在InnoDB中,这通常是通过将数据写入磁盘的redo log来实现的。
- autocommit设置决定了事务是否自动提交。如果autocommit被设置为0,那么需要手动通过COMMIT来提交事务,否则事务中的更改将不会被永久保存到数据库中。
- 手动开启的事务:
- 当使用BEGIN或START TRANSACTION显式地开启一个事务时,该事务中的所有操作都将被视为一个整体,直到显式地通过COMMIT提交或ROLLBACK回滚。
- 回滚机制:
- 事务可以手动回滚,使用ROLLBACK命令。如果事务中的某个操作失败或需要撤销已执行的更改,可以使用回滚来恢复到事务开始前的状态。
- 当操作异常时,MySQL通常会自动回滚到事务的开始点,但这取决于具体的异常类型和MySQL的配置。
- 原子性:
- 事务的原子性保证了事务中的所有操作要么全部执行成功,要么全部失败回滚。这是通过undo log来实现的,它记录了事务开始前的数据库状态,以便在需要时回滚到该状态。
事务操作注意事项
- 保存点的使用:
- 在复杂的事务中,可以使用SAVEPOINT命令来设置保存点。这样,如果事务中的某个部分需要回滚,可以回滚到特定的保存点,而不是回滚整个事务。
- 需要注意的是,当回滚到某个保存点时,该保存点之后的所有操作(包括其他保存点的创建和之后的操作)都将被撤销。
- 提交与回滚的时机:
- 一旦事务被提交(COMMIT),则无法再回滚(ROLLBACK)。因此,在提交事务之前,需要确保所有操作都已经正确执行,并且符合预期的业务逻辑。
- 如果事务还没有提交,并且需要撤销已执行的更改,可以使用ROLLBACK命令。
- 存储引擎的支持:
- InnoDB支持事务,而MyISAM不支持。因此,在选择存储引擎时,需要根据具体的业务需求来决定。如果需要事务支持,那么应该选择InnoDB。
3.事务的隔离级别
3.1.为什么要有隔离?隔离级别?
MySQL服务可能会同时被多个客户端进程(线程)访问,访问的方式以事务的方式进行。
一个事务可能由多条SQL语句构成,也就意味着任何一个事务,都有执行前、执行中和执行后三个阶段,而所谓的原子性就是让用户层要么看到执行前,要么看到执行后,执行中如果出现问题,可以随时进行回滚,所以单个事务对用户表现出来的特性就是原子性。
原子性(Atomicity)
原子性是指事务中的所有操作要么全部完成,要么全部不完成,不会停留在中间某个状态。这意味着,如果事务中的任何一个操作失败,整个事务都会被回滚到事务开始前的状态,就像这个事务从未执行过一样。这是通过数据库系统的事务日志(如MySQL的Undo Log)来实现的,当事务失败时,数据库系统会使用这些日志来撤销已执行的操作,使数据库恢复到事务开始前的状态。
但毕竟每个事务都有一个执行的过程,在多个事务各自执行自己的多条SQL时,仍然可能会出现互相影响的情况,比如多个事务同时访问同一张表,甚至是表中的同一条记录。
数据库为了保证事务执行过程中尽量不受干扰,于是出现了隔离性的概念。
但为了满足不同的应用场景和性能需求,数据库系统并不总是要求事务之间完全隔离。因此,数据库引入了隔离级别的概念,允许事务在执行过程中受到不同程度的干扰。这些隔离级别为数据库管理员和开发者提供了灵活的选择,以便在数据一致性和系统性能之间找到最佳平衡点。
3.2.MySQL的隔离级别
先来了解三个概念
脏读:
- 读到其他事务未提交的数据;
- 指当一个事务正在访问数据,并且对数据进行了修改,而这种数据还没有提交到数据库中,这时,另外一个事务也访问这个数据,然后使用了这个数据。因为这个数据还没有提交那么另外一个事务读取到的这个数据我们称之为脏数据。
不可重复读:
- 前后读取的数据不一致;
- 指在一个事务内,多次读同一数据。在这个事务还没有执行结束,另外一个事务也访问该同一数据,那么在第一个事务中的两次读取数据之间,由于第二个事务的修改第一个事务两次读到的数据可能是不一样的,这样就发生了在一个事务内两次连续读到的数据是不一样的,这种情况被称为是不可重复读。
幻读:
- 前后读取的记录数量不一致。
- 一个事务先后读取一个范围的记录,但两次读取的纪录数不同,我们称之为幻象读。(两次执行同一条 select 语句会出现不同的结果,第二次读会增加一数据行,并没有说这两次执行是在同一个事务中)
MySQL提供了四种事务隔离级别,每种级别对事务的隔离程度不同,具体如下:
-
读未提交(Read Uncommitted)
- 在此隔离级别下,一个事务可以读取另一个事务尚未提交的数据。
- 可能导致脏读、不可重复读和幻读等并发问题。
- 由于缺乏足够的隔离性,实际生产环境中通常不会使用此隔离级别。
-
读已提交(Read Committed)
- 此隔离级别下,一个事务只能读取其他已经提交的事务所做的改变。
- 可以避免脏读,但可能导致不可重复读和幻读。
- 这种隔离级别会引起不可重复读,即一个事务执行时,如果多次 select, 可能得到不同的结果。
- 是许多数据库系统的默认隔离级别,但不是MySQL的默认隔离级别。
-
可重复读(Repeatable Read)
- MySQL的默认隔离级别。
- 在此隔离级别下,同一个事务在执行过程中多次读取同一数据时,会看到相同的数据行(除非该事务本身对数据进行了修改)。
- 可以避免脏读和不可重复读,但可能导致幻读。
- MySQL通过多版本并发控制(MVCC)和间隙锁(GAP Lock)等技术来实现此隔离级别。
-
串行化(Serializable)
- 最高的隔离级别。
- 通过强制事务按顺序执行来避免所有并发问题,包括脏读、不可重复读和幻读。
- 但由于事务按顺序执行,可能导致性能下降和锁竞争。
- 实际生产环境中很少使用此隔离级别,因为它过于严格且影响性能。
说明:
- 虽然数据库事务的隔离级别有以上四种,但一个稳态的数据库只会选择这其中的一种,作为自己的默认隔离级别。只是因为数据库默认的隔离级别有时可能并不满足上层的业务需求,因此数据库提供了这四种隔离级别,可以让我们根据需要自行设置。
- 隔离级别基本上都是通过加锁的方式实现的,不同的隔离级别对锁的使用是不同的,常见的有表锁、行锁、写锁、间隙锁(GAP)、Next-Key锁(GAP+行锁)等。
3.3.查看与设置隔离性
- 查看全局隔级别
使用命令:
select @@global.transaction_isolation;
我们看到是REPEATABLE_READ——可重复读。这个是mySQL的默认隔离级别。
注意:
- MySQL 8.0 不再支持 tx_isolation 作为系统变量名。
在 MySQL 5.7.20 版本中,transaction_isolation 被添加为 tx_isolation 的别名,但随后在 MySQL 8.0 中,tx_isolation 被弃用并最终删除。因此,在 MySQL 8.0 及更高版本中,你应该使用 transaction_isolation 来查询和设置事务隔离级别。
所以像下面这3个命令就不要写了!!
select @@session.tx_isolation; select @@tx_isolation; select @@global.tx_isolation;
- 查看会话隔离级别
注意:这里的“会话”指的是当前与MySQL服务器建立连接的客户端会话。
可以这么理解:
- 会话的开始:从你登陆上了mySQL开始
- 会话的结束:你退出了mySQL的登陆
例如说,下面就是两个对话!!
当他们退出登陆时,会话也就结束了
可以使用下面这个命令。
SELECT @@SESSION.transaction_isolation;
SELECT @@transaction_isolation;
我们看到是REPEATABLE_READ——可重复读。这个是mySQL的默认隔离级别。
默认情况下,我们当前会话的隔离级别是继承自全局的隔离级别,当然最终产生作用的还是当前会话的隔离级别。
3.4.设置会话隔离等级
设置会话隔离级别的语法如下:
SET [SESSION | GLOBAL] TRANSACTION ISOLATION LEVEL
{
READ UNCOMMITTED |
READ COMMITTED |
REPEATABLE READ |
SERIALIZABLE
}
解释
SET:这是用于更改系统变量或事务属性的 SQL 语句。
[SESSION | GLOBAL]:这是一个可选的关键字,用于指定更改是应用于当前会话还是全局范围。
- SESSION:如果指定,更改将仅应用于当前会话(连接)。这是默认值,如果省略此关键字,则假定为 SESSION。
- GLOBAL:如果指定,更改将应用于所有新创建的会话(连接),但不会影响当前已经在进行中的会话。
TRANSACTION ISOLATION LEVEL:这是一个固定的短语,用于指定要更改的事务隔离级别。
{READ UNCOMMITTED | READ COMMITTED | REPEATABLE READ | SERIALIZABLE}:这是一个枚举列表,表示可用的事务隔离级别。
- READ UNCOMMITTED:允许读取未提交的数据,可能会导致脏读。
- READ COMMITTED:只能读取已提交的数据,避免脏读,但可能会出现不可重复读。
- REPEATABLE READ:确保在同一事务中多次读取同一数据时结果一致,避免不可重复读(在 MySQL 的 InnoDB 存储引擎中,还通过其他机制尽量避免幻读)。
- SERIALIZABLE:提供最高的事务隔离级别,通过强制事务串行执行来避免脏读、不可重复读和幻读,但性能开销最大。
- 设置全局隔离级别
我们可以把全局隔离级别设置为串行化(Serializable)
set global transaction isolation level serializable;
说明: 设置全局隔离级别会影响后续的新会话,但当前会话的隔离级别没有发生变化,如果要让当前会话的隔离级别也改变,则需要重启会话。
- 设置会话隔离级别
我们把它设置为读已提交(Read Committed)
set session transaction isolation level read committed;
说明: 设置会话的隔离级别只会影响当前会话,新起的会话依旧采用全局隔离级。
3.5.四种隔离级别的演示
首先,我们这4种隔离级别都是在手动提交的情况下进行的
- 读未提交(Read Uncommitted)
启动两个终端,登陆同一个mySQL账户,然后都将隔离级别都设置为读未提交,并查看此时testA表中的数据。如下:
set autocommit=1;
set session transaction isolation level read uncommitted;
SELECT @@SESSION.transaction_isolation;
select * from testA;
首先我们先把事务提交改成手动提交,然后在两个终端各自启动一个事务(模拟事务并发运行),左终端中的事务所作的CRUD操作在没有提交之前,右终端中的事务就已经能够看到了。如下:
set autocommit=0;
start transaction;
说明:
- 读未提交是事务的最低隔离级别,几乎没有加锁,虽然并发的效率很高,但是问题比较多,所以严重不建议使用。
- 一个事务在执行过程中,读取到另一个执行中的事务所做的修改,而且这个修改还没有进行提交,这种现象叫做脏读。
到这里大家可以在两边都执行rollback;以便下面实验的进行!!
- 读已提交(Read Committed)
启动两个终端,登陆同一个mySQL账户,然后都将隔离级别都设置为读提交,并查看此时testA表中的数据。如下:
set autocommit=1;
set session transaction isolation level read committed;
SELECT @@SESSION.transaction_isolation;
select * from testA;
特别注意我们这里的第一句autocommit=1;这句是非常重要的,它决定了下面3句都是立即commit生效的!!!
怎么样!
首先我们先把事务提交改成手动提交,然后在两个终端各自启动一个事务,左终端中的事务所作的修改在没有提交之前,右终端中的事务无法看到。如下:
set autocommit=0;
start transaction;
我们看到左边的还没有commit,右边的是看不到的!!
我们左边执行commit,右边就可以看到了!
说明:
- 一个事务在执行过程中,两个相同的select查询得到了不同的数据,这种现象叫做不可重复读。
- 显然在读提交的隔离级别下,存在不可重复读现象。
- 可重复读(Repeatable Read)
启动两个终端,登陆同一个mySQL账户,然后都将隔离级别都设置为可重复读,并查看此时testA表中的数据。如下:
set autocommit=1;
set session transaction isolation level repeatable read;
SELECT @@SESSION.transaction_isolation;
select * from testA;
特别注意我们这里的第一句autocommit=1;这句是非常重要的,它决定了下面3句都是立即commit生效的!!!
首先我们先把事务提交改成手动提交,然后在两个终端各自启动一个事务,左终端中的事务所作的修改在没有提交之前,右终端中的事务无法看到。如下:
set autocommit=0;
start transaction;
并且当左终端中的事务提交后,右终端中的事务仍然看不到修改后的数据。
只有当右终端中的事务提交后再查看表中的数据,这时才能看到修改后的数据。如下:
说明:
- 在可重复读隔离级别下,一个事务在执行过程中,相同的select查询得到的是相同的数据,这就是所谓的可重复读。
- 一般的数据库在可重复读隔离级别下,update数据是满足可重复读的,但insert数据可能会存在幻读问题,因为隔离性是通过对数据加锁完成的,而新插入的数据原本在表中是不存在的,因此一般的加锁无法屏蔽这类问题。
- 一个事务在执行过程中,相同的select查询得到了新增的数据,如同出现了幻觉,这种现象叫做幻读。
- MySQL解决了可重复读隔离级别下的幻读问题,比如上面我们新插入数据,在右终端是没有办法看到的。
- MySQL是通过Next-Key锁(GAP+行锁)来解决幻读问题的。
- 串行化(Serializable)
启动两个终端,登陆同一个mySQL账户,然后都将隔离级别都设置为串行化,并查看此时testA表中的数据。如下:
set autocommit=1;
set session transaction isolation level serializable;
SELECT @@SESSION.transaction_isolation;
select * from testA;
特别注意我们这里的第一句autocommit=1;这句是非常重要的,它决定了下面3句都是立即commit生效的!!!
在两个终端各自启动一个事务,如果这两个事务都对表进行的是读操作,那么这两个事务可以并发执行,不会被阻塞。如下:
set autocommit=0;
start transaction;
select * from testA;
但如果这两个事务中有一个事务要对表进行写操作,那么这个事务就会立即被阻塞。如下:
久一点还会出现这个错误
直到访问这张表的其他事务都提交后,这个被阻塞的事务才会被唤醒,然后才能对表进行修改操作。如下:
说明:
-
串行化是事务的最高隔离级别,多个事务同时进行读操作时加的是共享锁,因此可以并发执行读操作,但一旦需要进行写操作,就会进行串行化,效率很低,几乎不会使用。
-
串行化不是串行化的SQL,而是串行化的事务。
3.6.隔离级别总结
隔离级别 | 脏读 | 不可重复读 | 幻读 | 加锁读 | 备注 |
---|---|---|---|---|---|
读未提交(READ UNCOMMITTED) | √ | √ | √ | 不加锁 | 允许读取未提交的数据,可能导致数据不一致 |
读已提交(READ COMMITTED) | X | √ | √ | 不加锁 | 只能读取已提交的数据,防止脏读,但可能产生不可重复读和幻读 |
可重复读(REPEATABLE READ) | X | X | √(InnoDB避免) | 一般情况下不加锁,但间隙锁可能用于防止幻读 | MySQL InnoDB的默认隔离级别,防止脏读和不可重复读,通过间隙锁避免幻读(在某些情况下) |
可串行化(SERIALIZABLE) | X | X | X | 加锁 | 最高的隔离级别,通过加锁确保事务完全按照顺序执行,防止所有类型的不一致 |
补充说明
- 脏读(Dirty Read):
- 读未提交隔离级别允许读取未提交的数据,这可能导致数据不一致,因为其他事务可能回滚其更改。
- 读已提交、可重复读和可串行化隔离级别都防止脏读。
- 不可重复读(Non-repeatable Read):
- 读未提交和读已提交隔离级别都可能产生不可重复读,因为其他事务可以在当前事务多次读取之间更改数据。
- 可重复读隔离级别通过在同一事务期间使用一致性视图来防止不可重复读。
- 幻读(Phantom Read):
- 幻读是指在同一事务中,其他事务插入了新行,导致当前事务再次查询时得到了不同的结果集。
- 读未提交和读已提交隔离级别都可能产生幻读。
- 可重复读隔离级别在MySQL InnoDB中通过间隙锁(Gap Lock)来避免幻读,但这取决于具体的查询和索引使用情况。
- 可串行化隔离级别通过加锁完全防止幻读。
- 加锁读:
- 读未提交和读已提交隔离级别通常不加锁进行读操作。
- 可重复读隔离级别在大多数情况下也不加锁进行读操作,但可能会使用间隙锁来防止幻读。
- 可串行化隔离级别在读取数据时加锁,以确保事务之间的完全隔离。
- 并发性能:
- 隔离级别越严格,数据库的并发性能通常越低,因为需要更多的锁和同步机制来确保数据一致性。
- 在选择隔离级别时,需要根据具体的应用场景和性能需求进行权衡。
- 默认隔离级别:
- MySQL InnoDB存储引擎的默认隔离级别是可重复读。
- 其他存储引擎可能有不同的默认隔离级别。
- 事务一致性:
- 隔离级别越高,事务之间的一致性越好,但可能以牺牲并发性能为代价。
- 在需要高一致性的应用场景中,可以选择更高的隔离级别。
关于事务的一致性,以下是整理后的内容:
3.7.一致性(Consistency)
事务执行的结果必须确保数据库从一个一致性状态转变到另一个一致性状态。这意味着,在事务开始之前和事务结束之后,数据库中的数据都必须满足所有的完整性约束和业务规则。
3.7.1.一致性的保障要素:
- 原子性(Atomicity):
- 事务是一个不可分割的工作单元,要么全部执行成功,要么全部回滚到事务开始之前的状态。
- 如果事务在执行过程中发生错误,则需要自动回滚到事务最开始的状态,就像这个事务从来没有执行过一样。
- 原子性通过确保事务的完整性来保障一致性。
- 持久性(Durability):
- 事务一旦提交,其对数据库的修改就是永久性的,即使系统发生故障也不会丢失。
- 持久性通过确保事务的修改被永久保存到数据库中来保障一致性。
- 隔离性(Isolation):
- 多个事务并发执行时,一个事务的执行不应该影响其他事务的执行结果。
- 隔离性通过确保事务之间的独立性和互不干扰来保障一致性。
- 不同的隔离级别(如读未提交、读已提交、可重复读、可串行化)提供了不同程度的事务隔离和一致性保障。
- 正确的业务逻辑:
- 一致性还与用户的业务逻辑紧密相关。
- 如果用户本身的业务逻辑存在问题,即使数据库提供了原子性、持久性和隔离性的保障,最终也可能导致数据库处于一种不一致的状态。
- 因此,上层用户需要编写出正确的业务逻辑来保障一致性。
3.7.2.MySQL的一致性保障:
- MySQL通过其内部的事务管理机制(如InnoDB存储引擎)来提供原子性、持久性和隔离性的保障。
- MySQL还通过自动增量ID(Auto-Increment ID)等机制来辅助保障一致性,但这些机制主要关注的是数据的唯一性和顺序性,而不是业务逻辑的一致性。
- 对于业务逻辑的一致性,MySQL主要依赖上层应用来保障。上层应用需要编写正确的业务逻辑,并通过事务管理来确保这些逻辑在并发环境下能够正确执行。