MySQL事务

MySQL事务

定义

  • 数据库事务是数据库管理系统执行过程中的一个逻辑单位,由一个有限的数据库操作序列构成。

  • 事务的使用是数据库管理系统区别文件系统的重要特征之一。

  • 数据库事务指的是一组数据操作,事务内的操作要么就是全部成功,要么就是全部失败,什么都不做,

  • InnoDB 引擎支持事务,MyISAM 引擎是不支持事务。

事务ACID

原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)、持久性(Durability),人们习惯称之为 ACID 特性。下面我逐一对其进行解释。

  • 原子性 (Atomicity)

    事务开始后所有操作,要么全部做完,要么全部不做,不可能停滞在中间环节。事务执行过程中出错,会回滚到事务开始前的状态,所有的操作就像没有发生一样。例如,如果一个事务需要新增 100 条记录,但是在新增了 10 条记录之后就失败了,那么数据库将回滚对这 10 条新增的记录。也就是说事务是一个不可分割的整体,就像化学中学过的原子,是物质构成的基本单位。

  • 一致性 (Consistency)

    指事务将数据库从一种状态转变为另一种一致的的状态。事务开始前和结束后,数据库的完整性约束没有被破坏。例如工号带有唯一属性,如果经过一个修改工号的事务后,工号变的非唯一了,则表明一致性遭到了破坏。

  • 隔离性 (Isolation)

    要求每个读写事务的对象对其他事务的操作对象能互相分离,即该事务提交前对其他事务不可见。 也可以理解为多个事务并发访问时,事务之间是隔离的,一个事务不应该影响其它事务运行效果。这指的是在并发环境中,当不同的事务同时操纵相同的数据时,每个事务都有各自的完整数据空间。由并发事务所做的修改必须与任何其他并发事务所做的修改隔离。例如一个用户在更新自己的个人信息的同时,是不能看到系统管理员也在更新该用户的个人信息(此时更新事务还未提交)。

    注:MySQL 通过锁机制来保证事务的隔离性。

  • 持久性 (Durability)

    事务一旦提交,则其结果就是永久性的。即使发生宕机的故障,数据库也能将数据恢复,也就是说事务完成后,事务对数据库的所有更新将被保存到数据库,不能回滚。这只是从事务本身的角度来保证,排除 RDBMS(关系型数据库管理系统,例如 Oracle、MySQL 等)本身发生的故障。

    注:MySQL 使用 redo log 来保证事务的持久性。

并发控制 (1)DBS(数据库系统)一个明显的特点是多个用户共享数据库资源,尤其是多个用户可以同时存取相同数据。

串行控制:如果事务是顺序执行的,即一个事务完成之后,再开始另一事务。 并行控制:如果DBMS可以同时接受多个事务,并且这些事务在时间上可以重叠执行。 (2)并发控制概述

事务是并发控制的基本单位,保证事务ACID的特性是事务处理的重要任务,而并发操作有可能会破坏其ACID特性。

DBMS并发控制机制的责任:对并发操作进行正确调度,保证事务的隔离更一般,确保数据库的一致性。

数据的不一致性

如果没有锁定且多个用户同时访问一个数据库,则当他们的事务同时使用相同的数据时可能会发生问题。由于并发操作带来的数据不一致性包括:丢失数据更新、读“脏”数据(脏读)、不可重复读。

(1)更新丢失 两个事务都同时更新一行数据,一个事务对数据的更新把另一个事务对数据的更新覆盖了。这是因为系统没有执行任何的锁操作,因此并发并没有被隔离开来。

(2)脏读 一个事务读取到了另一事务未提交的数据操作结果。

(3)不可重复读 不可重复读(Non-repeatable Reads):一个事务对同一行数据重复读取两次,但是却得到了不同的结果。

包括以下情况:

  • 虚读:事务T1读取某一数据后,事务T2对其做了修改,当事务T1再次读取该数据时得到与前一次不同的值。

  • 幻读:事务在操作过程中进行两次查询,第二次查询的结果包含了第一次查询中未出现的数据或者缺少了第一次查询中出现的数据。这是因为在两次查询过程中有另外一个事务插入数据造成的。

不可重复读和幻读的区别:

  • 不可重复读重点在于update和delete,

  • 幻读的重点在于insert。

在可重复读中,该sql第一次读取到数据后,就将这些数据加锁,其它事务无法修改这些数据,就可以实现可重复读了。但这种方法却无法锁住insert的数据,所以当事务A先前读取了数据,或者修改了全部数据,事务B还是可以insert数据提交,这时事务A就会发现莫名其妙多了一条之前没有的数据,这就是幻读,不能通过行锁来避免。需要Serializable隔离级别 ,读用读锁,写用写锁,读锁和写锁互斥,这么做可以有效的避免幻读、不可重复读、脏读等问题,但会极大的降低数据库的并发能力。

事务隔离级别

为了避免上面出现的几种情况,在标准SQL规范中,定义了4个事务隔离级别,不同的隔离级别对事务的处理不同。

  • 读未提交(Read Uncommitted):在并发事务A,B中,事务A能读取到事务B的未提交修改操作数据;

  • 读提交(Read Committed):在并发事务A,B中,事务A只能读取到事务B的已提交的数据;读提交解决了脏读的问题,但是无法解决可重复读、幻读。

  • 可重复读取(Repeatable Read):处理更新丢失、脏读和不可重复读取。读取数据的事务将会禁止写事务,但允许读事务,写事务则禁止任何其他事务。可通过“共享读锁”和“排他写锁”实现。

  • 序列化(Serializable):提供严格的事务隔离。要求失去序列化执行,事务只能一个接一个地执行,不能并发执行。仅仅通过“行级锁”是无法实现事务序列化的,必须通过其他机制保证新插入的数据不会被刚执行查询操作的事务访问到。

    隔离级别越高,越能保证数据的完整性和统一性,但是对并发性能的影响也越大。对于多数应用程序,可以优先考虑把数据库系统的隔离级别设为Read Committed。它能够避免脏读,而且具有较好的并发性能。尽管它会导致不可重复读、幻读和第二类丢失更新这些并发问题,在可能出现这类问题的个别场合,可以由应用程序采用悲观锁或乐观锁来控制。

隔离级别

SQL 标准定义的四种隔离级别被 ANSI(美国国家标准学会)和 ISO/IEC(国际标准)采用,每种级别对事务的处理能力会有不同程度的影响。

mysql 有四级事务隔离级别 每个级别都有字符或数字编号

读未提交 READ-UNCOMMITTED | 0:存在脏读,不可重复读,幻读的问题

读已提交 READ-COMMITTED | 1:解决脏读的问题,存在不可重复读,幻读的问题

可重复读 REPEATABLE-READ | 2:解决脏读,不可重复读的问题,存在幻读的问题,默认隔离级别,使用 MMVC机制 实现可重复读

序列化 SERIALIZABLE | 3:解决脏读,不可重复读,幻读,可保证事务安全,但完全串行执行,性能最低

幻读会在 RU / RC / RR 级别下出现,SERIALIZABLE 则杜绝了幻读,但 RU / RC 下还会存在脏读,不可重复读,故我们就以 RR 级别来研究幻读,排除其他干扰。

各个隔离级别下产生的一些问题
隔离级别脏读不可重复读幻读实现方式性能
读未提交(RU)OOO未加锁最好
读提交(RC)xOO读不加锁,写入、修改和删除加锁Oracle默认隔离级别
可重复读(RR)xxOMVCCInnoDB默认隔离级别
串行化(Serialser)xxx读加共享锁,写加排它锁最差

在MVCC并发控制中,读操作可以分成两类:快照读 (snapshot read)与当前读 (current read)。快照读,读取的是记录的可见版本 (有可能是历史版本),不用加锁。当前读,读取的是记录的最新版本,并且,当前读返回的记录,都会加上锁,保证其他事务不会再并发修改这条记录。

NextKey锁

  • MySQL 通过行锁+间隙锁的方式 解决了RR级别下解决了幻读的问题;

快照读VS当前读

在一个支持MVCC并发控制的系统中,哪些读操作是快照读?哪些操作又是当前读呢?以MySQL InnoDB为例:

快照读:简单的select操作,属于快照读,不加锁。

1
select * from table where ?;

当前读:特殊的读操作,插入/更新/删除操作,属于当前读,需要加锁。

1
2
3
4
5
select * from table where ? lock in share mode;
select * from table where ? for update;
insert into table values ();
update table set ? where ?;
delete from table where ?;

所有以上的语句,都属于当前读,读取记录的最新版本。并且,读取之后,还需要保证其他并发事务不能修改当前记录,对读取记录加锁。其中,除了第一条语句,对读取记录加S锁 (共享锁)外,其他的操作,都加的是X锁 (排它锁)。

Serializable

这个级别很简单,读加共享锁,写加排他锁,读写互斥。使用的悲观锁的理论,实现简单,数据更加安全,但是并发能力非常差。如果你的业务并发的特别少或者没有并发,同时又要求数据及时可靠的话,可以使用这种模式。

这里要吐槽一句,不要看到select就说不会加锁了,在Serializable这个级别,还是会加锁的!

MVCC在MySQL的InnoDB中的实现

在InnoDB中,会在每行数据后添加两个额外的隐藏的值来实现MVCC,这两个值一个记录这行数据何时被创建,另外一个记录这行数据何时过期(或者被删除)。

在实际操作中,存储的并不是时间,而是事务的版本号,每开启一个新事务,事务的版本号就会递增。

在可重读Repeatable reads事务隔离级别下:

  • SELECT时,读取创建版本号<=当前事务版本号,删除版本号为空或>当前事务版本号。
  • INSERT时,保存当前事务版本号为行的创建版本号
  • DELETE时,保存当前事务版本号为行的删除版本号
  • UPDATE时,插入一条新纪录,保存当前事务版本号为行创建版本号,同时保存当前事务版本号到原来删除的行

通过MVCC,虽然每行记录都需要额外的存储空间,更多的行检查工作以及一些额外的维护工作,但可以减少锁的使用,大多数读操作都不用加锁,读数据操作很简单,性能很好,并且也能保证只会读取到符合标准的行,也只锁住必要行。

MVCC在mysql中的实现依赖的是undo log与read view

MVCC在mysql中的实现依赖的是undo log与read view

  • undo log :undo log 中记录某行数据的多个版本的数据。
  • read view :用来判断当前版本数据的可见性

前面讲的重做日志,回滚日志以及锁技术就是实现事务的基础。

  • 事务的原子性A是通过 undo log 来实现的;
  • 事务的持久性D是通过 redo log 来实现的;
  • 事务的隔离性I是通过 (读写锁+MVCC)来实现的;
  • 而事务的终极大 boss 一致性是通过原子性,持久性,隔离性来实现的!!!

参考

  1. Innodb中的事务隔离级别和锁的关系 - 美团技术团队

  2. 『浅入深出』MySQL 中事务的实现 - 面向信仰编程

  3. 深入学习MySQL事务:ACID特性的实现原理 - 编程迷思 - 博客园

updatedupdated2024-05-102024-05-10