MySQL:事务(概念&操作&隔离)

目录

事务概念

事务操作

事务提交

事务回滚

事务隔离

隔离级别

读未提交

读提交

可重复读

串行化

MVCC

read view


事务概念

在MySQL中,我们把共同完成一个功能而组成的多条SQL语句,称为一个事务

一个事务最重要的问题就是:不能被打断,即原子性,一个事务要么还没开始,要么已经完成

另外的,一个数据库中,一般不止一个用户在操作,如果一个用户正在修改数据,另一个用户在读取数据,也会导致错误。此时就需要对多个事务进行隔离。

一个完整的事务,包含以下四个特性:

  • 原子性 Atomicity:一个事务中的所有操作,要么全部完成,要么还没开始,不允许在中间环节结束。
  • 隔离性 Isolation:数据库允许多个并发事务同时对其数据进行增删查改,而不出现错误。
  • 持久性 Durability :事务结束后,对数据进行持久化保存,即存储在硬盘中。
  • 一致性 Consistency:事务前后,数据库的完整性没有被破坏,即写入/查询的数据,必须符合业务要求。

以上四个特性简称为ACID


事务操作

通过show engines指令,查看各个引擎的信息:

在这里插入图片描述

其中Transactions表示事务,主流引擎中InnoDB支持事务,而MyISAM不支持事务。

  • 查看自动提交:
show variables like 'autocommit';

ON表示打开自动提交,自动提交情况下每一条语句都会被视为一个事务,为了让我们的事务正常执行起来,此时要关闭自动提交。

set autocommit = 0;

操作事务的示例表结构如下:

create table testTransaction(
id int primary key,
name varchar(20),
tel varchar(11)
) engine = 'InnoDB';
事务提交
  • 开始事务

开始事务的方法有两种:start transaction 或 begin:

此时通过begin开始了一个事务,随后插入了两条数据,最后直接退出MySQL。只要事务没有提交,那么就不算一个完整的事务,按照之前对事务的理解,这两条数据应该没有被插入。

在这里插入图片描述

重新进入数据库后,发现刚刚的两条数据没有被插入,也就是回到了事务开始前。

  • 提交事务

如果一个事务已经执行完所有语句,那么最后需要使用commit来提交事务。


事务回滚
  • 设置保存点

在事务执行过程中,可以设置保存点,保存点用于回滚。

savepoint 保存点名;

  • 回滚

回滚用于在一个事务内部进行撤销操作,即取消之前的某些操作。

rollback to 保存点;

在这里插入图片描述

回滚到s2后,可以发现少了一条数据,这就是因为最后一次操作被回滚覆盖了,也就是被撤销了。

也可以直接使用rollback,直接回滚到事务的最开始:

在这里插入图片描述要注意的是,回滚只能在一个事物内部使用,一旦事务提交,就无法回滚了。


事务隔离

数据库需要面对并发场景,很可能同时有多个事务在操作数据库,事务是由多条SQL语句构成的,那么多个事务就有可能互相访问到不完整的数据。因此数据库需要事务,让每个事务都具有原子性,而原子性的实现,就是基于隔离

隔离级别

事务隔离分为四个级别:

  • 读未提交 Read Uncommitted

  • 读提交 Read Commited

  • 可重复读 Repeatable Read

  • 串行化 Serializable

查看隔离级别

可以通过指令select @@session.tx_isolation查看当前对话的隔离级别,通过select @@global.tx_isolation查看全局默认隔离级别。

在这里插入图片描述

在MySQL中,事务的默认隔离级别是可重复读。

  • 设置隔离级别
set 范围 transaction isolation level 隔离级别;

范围:

  • session:当前对话
  • global:全局

隔离级别:

  • read uncommitted:读未提交
  • read committed:读提交
  • repeatable read:可重复读
  • serializable:串行化

读未提交

在读未提交隔离级别下,所有事务都可以看到其他事务未提交的执行结果,也就是相当于没有任何隔离性

如图,左右是两个终端,用于模拟两个操作,左侧开启事务后,插入了两条数据,右侧则将隔离级别设置为了读未提交。可以发现左侧每插入一个数据,右侧都可以直接看到。

一个事务读取到别的事务未提交的数据,称为脏读在实际生产中不会使用这个隔离级别,因为这个级别相当于没有隔离。 


读提交

读提交是大多数数据库的默认隔离级别,一个事务只能看到其它事务已经提交的数据

如图,左侧对话开启了一个事务,右侧对话设置隔离级别为读提交,并开启了一个事务。在左侧更新数据后,右侧事务查询了一次,但是查询不到更新结果,因为左侧事务没有提交。 

当左侧事务提交后,右侧事务再查询,就可以看到更新结果了。

但是这会造成不可重复读问题:在一个事务内部,前一秒和后一秒读取相同内容,结果不一样,称为不可重复读


可重复读

可重复读是MySQL的默认隔离级别,它确保一个事务中,多次读取时不会出现不一样的数据

 

右侧对话先将隔离级别设置为可重复读,随后两边同时开始事务。右侧对话首先进行一次查询。

随后左侧对话更新数据后,插入数据:

在这里插入图片描述左侧完成整个事务并提交,右侧再查询,发现数据不变。也就是说在可重复读的情况下,当前事务会屏蔽其他事务提交的修改,从而保证前后查询的一致。

部分数据库在可重复读情况下,会有幻读问题:虽然可重复读可以屏蔽其它事务的update修改,但是无法屏蔽insertdelete这样的插入删除语句,这叫做幻读。

注意一下不可重复读与幻读的区别,不可重复读是指读取同一行数据,前后内容不一样,这是因为其它事务进行了update更新。而幻读是指前后读取出来的数据数目不同,也就是有其他事务进行了insertdelete

数据库实现可重复读的本质,是对一行数据进行加锁,但是如果一条数据原本就不存在,如何对其加锁?所以数据库无法屏蔽新插入的数据。不过MySQL解决了这个问题,在MySQL的可重复读级别下,幻读已经被解决了


串行化

串行化是最高的隔离级别,会对事务进行排序,同时只允许一个事务执行

在串行化下,事物之间不会并发,所以也就没有读取的问题了。但是由于只能同时执行一个事务,所以效率很低,不会使用串行化。

总结

  • 读未提交:所有事务都可以看到其他事务未提交的执行结果,也就是相当于没有任何隔离性
  • 读提交:大多数数据库的默认隔离级别,一个事务只能看到其它事务已经提交的数据
  • 可重复读:MySQL的默认隔离级别,它确保一个事务中,多次读取时不会出现不一样的数据
  • 串行化:对事务进行排序,同时只允许一个事务执行

读取问题:

隔离级别脏读不可重复读幻读
读未提交
读提交×
可重复读××
串行化×××

可以发现,这些隔离性大多数都和读相关,在数据库中并发分为:读-读读-写写-写。两个事务同时读,是不会互相影响的,而两个事务同时写极少发生,大部分时间产生问题的是读写并发,因此隔离性主要解决的是读写并发问题。 


MVCC

MySQL采用 MVCC + 锁 的方式来实现四种隔离性。

什么是MVCC?MVCC是用于解决读-写冲突的无锁并发控制,其可以解决以下问题:

  1. 实现读写并发,数据库可以同时进行读写操作,而互不影响
  2. 可以解决脏读,不可重复读,幻读的问题

事务ID

事务ID(Transaction ID,简称TID)是数据库系统为每个事务分配的唯一标识符。它通常是一个递增的整数,由数据库系统自动生成和管理。

每当一个新的事务开始时,数据库系统就会为其分配一个全局唯一的事务ID。这个ID在事务的整个生命周期中保持不变,直到事务结束。


隐藏列

在MySQL中,创建表的时候,并不是指定了哪些列,就存在哪些列,而是有几个所有表都存在的隐藏列,用于管理数据。

  • DB_TRX_ID:记录最近修改(insertdeleteupdate)该行的事务ID
  • DB_ROLL_PTR:回滚指针,指向历史版本undo log
  • DB_ROW_ID:隐藏主键,如果表中没有主键,则会有一个隐藏主键
  • ROW_DELETE:标识一个行有没有被删除,如果为0表示该行已经被删除,如果为1表示没有被删除
create table Stu(
name varchar(20),
age int
);

插入('张三', 18),假设该事务的事务ID为123,至少会包含以下列:

在这里插入图片描述

对于这条新数据,是通过事务123完成的插入,DB_TRX_ID = 123,由于没有历史版本,回滚指针 = NULLDB_ROW_ID则是隐藏主键,自行完成自增。ROW_DELETE = 0表示这一行是存在的。


undo log

MySQL作为一款数据库,自然是要把数据进行持久化保存,但是插入,删除,查询这样的操作,是要把数据加载到内存中计算的。对于一个事务,只有整个事务结束,才会把数据写入硬盘保存,在写入硬盘之前,会在内存中开辟一段缓冲区,用于保存历史版本以便回滚,这个缓冲区就叫做undo log

假设我们在一个事务中,将张三的名字改为李四,事务ID为199

在这里插入图片描述

此时旧的数据会进入undo log保存起来,随后将name改为”李四“,这是用户表面上看到的操作。实际上还会更新事务ID为199,表示这条数据最后一次是事务199操作的,并且将回滚指针指向在undo log中上一个版本的数据。 

接着同一个事务199又把这一行删掉

在这里插入图片描述

在用户看来,这一行数据已经被删掉了,实际上这行数据还在,只是ROW_DELETE = 1,而原先ROW_DELETE = 0的版本,被保存在了undo log中。 

目前事务199还没有commit,此时还可以进行操作,比如说回滚!

如果想要回滚到删除数据前,由于undo log中有ROW_DELETE = 0的版本,此时可以直接通过回滚指针找到上一个版本,然后复原!

如果想要回滚到最开始,那么就从回滚指针一直往回找,直到找到NULL空指针为止!

只要事务199不提交,那么这个undo log就会一直在内存中,方便进行版本控制。当事务提交后,这个事务对应的undo log就会被MySQL自动释放,这也就是为什么事务结束后不能再回滚。


read view

在这里插入图片描述

回到这张图,可以发现一个事务在操作数据时,会生成多个版本。而MySQL中事务往往是并发的,其它事务来读取数据的时候,会读取到哪一个版本的数据呢?

mvcc通过快照读机制,读取某个固定的版本。

快照读基于读视图read view实现,在MySQL源码中,readview包含以下内容:

class ReadView {
 private:
  trx_id_t m_low_limit_id;   // 高水位
  trx_id_t m_up_limit_id;    // 低水位
  trx_id_t m_creator_trx_id; // 当前事务ID
  ids_t m_ids;               // 活跃事务ID
};

当一个事务99进行读取时,会对当前整个MySQL生成一个读视图,读视图分为三部分:

在这里插入图片描述

当一个事务进行读取时,可能有很多正在活跃的事务,此时整个数据库中就有如上三种事务。这三种事务通过ReadView记录:

  • m_ids:一个数组,存储当前所有活跃的事务ID
  • m_creator_trx_id:记录当前事务的事务ID,即哪一个事务创建了该读视图
  • m_low_limit_id:高水位,存储m_ids的最小值,低于该值的事务都已经提交
  • m_up_limit_id:低水位,存储当前出现事务id的最大值 + 1,大于等于该值的事务都是快照后产生的事务

当事务创建完读视图后,读取数据时依照这个读视图判断读取哪一个版本。

  • 已提交事务:

查询数据时,首先看DB_TRX_ID列,看上一次是哪一个事务对其进行了修改:

在这里插入图片描述

该数据的DB_TRX_ID = 92小于m_low_limit_id = 98,说明是已经提交的事务,那么该数据是可见的,最后就可以读取到李四这条数据。 

  • 活跃事务:

在这里插入图片描述该数据的DB_TRX_ID = 100大于m_low_limit_id = 98,小于m_up_limit_id = 106,在m_ids数组中,说明是活跃的事务,那么该数据不可见随后通过回滚指针找到上一个版本,发现上一个版本李四是已经提交的事务,所以最后看到李四这条数据

  • 后产生的事务:

在这里插入图片描述

该数据的DB_TRX_ID = 110大于m_up_limit_id = 106,说明是后产生的事务,那么该数据不可见。随后通过回滚指针找到上一个版本,发现上一个版本李四是已经提交的事务,所以最后看到李四这条数据。 

  • 当前事务

在这里插入图片描述

该数据DB_TRX_ID = 99m_creator_trx_id = 99,说明就是事务自己上一次修改的数据。可见,读取到孙七

每个事务创建快照的时机不同,那么看到的版本也就不同,如果在当前事务之后,有其他事务修改了数据,那么一定是活跃事务或者是后来的事务,此时当前事务会屏蔽掉这些内容,通过回滚指针找到自己可见的版本.

  1. 读未提交:事务可以读取到其他未提交事务的修改。这个级别不使用MVCC,因此没有快照读,事务总是读取到最新的数据。

  2. 读提交:事务只能读取到其他事务已经提交的修改。在读已提交级别下,每次读取操作都会创建一个新的快照,确保读取到的数据是其他事务提交后的数据。

  3. 可重复读整个事务只在第一次读取的时候创建一个快照,后续整个事务都使用同一张快照,保证前后读取到的内容一致,可重复读。

  4. 串行化:事务通过锁定来避免并发问题,MVCC的作用非常有限,因为数据访问通常是被锁定的,直到事务完成。

MVCC机制在可重复读和读已提交隔离级别中起作用,但在读未提交串行化隔离级别中,其作用有限或不适用。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值