首页 > 数据库 > 数据库系列:事务的4种隔离级别

数据库系列:事务的4种隔离级别

时间:2023-11-17 14:46:15浏览次数:40  
标签:事务 隔离 幻读 数据库 提交 级别 读取

数据库系列:MySQL慢查询分析和性能优化
数据库系列:MySQL索引优化总结(综合版)
数据库系列:高并发下的数据字段变更
数据库系列:覆盖索引和规避回表
数据库系列:数据库高可用及无损扩容
数据库系列:使用高区分度索引列提升性能
数据库系列:前缀索引和索引长度的取舍
数据库系列:MySQL引擎MyISAM和InnoDB的比较
数据库系列:InnoDB下实现高并发控制

1 事务概念和必要性

在MySQL中,事务是一个数据库操作的最小执行单元,它由一个或多个SQL语句组成,这些SQL语句要么全部执行成功,要么全部失败回滚。
所以,事务是一种机制,用来保证一系列操作要么全部执行成功,要么全部失败回滚,从而保持数据库的一致性和完整性。MySQL中只有使用支持事务的存储引擎(如InnoDB)才能使用事务功能。

如果数据库中没有事务机制,那会怎么样呢?
★ 超级典型的金融案例,案例改编自《高性能MySQL》第四版:
假设银行对两个用户账号进行转账:操作用户账户表(包括转账源头 和 转账目标)。现在要从用户A的账户转账 1000 元到用户B的账户中,那么需要至少三个步骤:

  1. 检查账户A的余额高于 1000 元。
  2. 从账户A余额中减去 1000 元。
  3. 在账户B的余额中增加 1000 元。

上述三个步骤的操作必须打包在一个事务中,任何一个步骤失败,则必须回滚所有的步骤。

可以用 START TRANSACTION 语句开始一个事务,然后要么使用 COMMIT 提交事务将修改的数据持久保留,要么使用 ROLLBACK 撤销所有的修改。
事务SQL的样本如下:

/* 开始事务 */
START TRANSACTION;
/* 检查账户A(123456)的余额高于 1000 元 */
SELECT balance FROM acount WHERE customer_id=123456;
/* 从账户A(123456)余额中减去 1000 元 */
UPDATE acount SET balance=balance-1000.00 WHERE customer_id=123456;
/* 在账户B(123457)余额中增加 1000 元 */
UPDATE acount SET balance=balance+1000.00 WHERE customer_id=123457;
/* 提交事务 */
COMMIT;

解读下这个SQL脚本:

  • 如果执行到第四条语句时服务器崩溃了,用户A可能会损失1000元,而用户B也没有接收到1000元。
  • 如果执行到第三条语句和第四条语句之间时,另外一个进程要消费掉A账户的所有余额,那么结果可能就是银行在不知道这个逻辑的情况下白白给了B账户1000元。

所以,金融类系统需要有严格的ACID测试,ACID是指原子性 (atomicity)、一致性(consistency)、隔离性(isolation)和持久性durability)。一个运行良好的事务处理系统,必须具备这些标准特征。

2 事务的四个特性(ACID)

一般来说,衡量事务必须满足四个特性:ACID,即 原子性(Atomicity,或称不可分割性)、一致性(Consistency)、隔离性(Isolation,又称独立性)、持久性(Durability)。

  • 原子性(Atomicity):一个事务(transaction)中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节。事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。
  • 一致性(Consistency):在事务开始之前和事务结束以后,数据库的完整性没有被破坏。这表示写入的资料必须完全符合所有的预设规则,这包含资料的精确度、串联性以及后续数据库可以自发性地完成预定的工作。
  • 隔离性(Isolation):数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。事务隔离分为不同级别,包括读未提交(read uncommitted)、读提交(read committed)、可重复读(repeatable read)和串行化(Serializable),下面会详细说明。
  • 持久性(Durability):事务处理结束后,对数据的修改就是永久的,会持久化到硬盘上,即便系统故障也不会丢失。

3 如何保证事务的隔离性

3.1 数据库并发下事务的三种现象

3.1.1 脏读

读取事务未提交数据

脏读就是指当一个事务A正在访问数据,并且对数据进行了修改,而这种修改还没有提交到数据库中,这时,另外一个事务B也访问这个数据,读了未提交事务操作的数据,然后使用了这个脏数据。举个例子:

时间序列 A事务 B事务
T1 开始事务
T2 开始事务
T3 查询B账户有100元余额
★SELECT balance FROM acount WHERE customer_id=123456;
T4 B账户增加1000元转账(未提交)
★UPDATE acount SET balance=balance+1000.00 WHERE customer_id=123457;
T5 查询B账户有1100元余额(读脏数据)
T6 入账失败,回滚1000元转账款
T7 提交事务
★commit;

3.1.2 不可重复读

前后多次读取数据不一致

不可重复读指的是在事务A中先后多次读取同一个数据,读取的结果不一样,因为另外一个事务也访问该同一数据,并且可能修改这个数据,这种现象称为不可重复读。
脏读与不可重复读的区别在于:前者读到的是其他事务未提交的数据,后者读到的是其他事务已提交的数据。

时间序列 A事务 B事务
T1 开始事务
T2 开始事务
T3 查询B账户有100元余额
★SELECT balance FROM acount WHERE customer_id=123456;
T4 B账户增加1000元转账(未提交)
★UPDATE acount SET balance=balance+1000.00 WHERE customer_id=123457;
T5 提交事务
★commit;
T6 查询B账户有1100元余额(不可重复读)

按照正确逻辑,事务B前后两次读取到的数据应该一致,这边一次读到的是100元,一次读到的是1100元,得到了不同的结果。

3.1.3 幻读

前后多次读取,数据不一致

在事务A中按照某个条件先后两次查询数据库,两次查询结果的条数不同,这种现象称为幻读。不可重复读与幻读的区别可以通俗的理解为:前者是数据变了,后者是数据的行数变了。通俗点就是已提交事务B对事务A产生的影响,导致B执行有误,这个影响叫做“幻读”。

时间序列 A事务 B事务
T1 开始事务 开始事务
T2 第一次查询数据库账户表有2条数据,键 pay_id是1和2
T3 给账户表转账1000元,所以新增一条 pay_id为3的转账数据
T4 提交事务成功
T5 因为上面查到的pay_id=2, 所以这边新增一条pay_id=3的消费数据,insert的时候提示key冲突

按照正确逻辑,事务B前后两次读取到的数据总量应该一致

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

  • 不可重复读是读取了其他事务更改的数据,针对update操作
    ★ 解决:使用行级锁,锁定该行,事务A多次读取操作完成后才释放该锁,这个时候才允许其他事务更改刚才的数据。

  • 幻读是读取了其他事务新增的数据,针对insert与delete操作
    ★ 解决:使用表级锁,锁定整张表,事务A多次读取数据总量之后才释放该锁,这个时候才允许其他事务新增数据。

幻读和不可重复读都是指的一个事务范围内的操作受到其他事务的影响了。只不过幻读是重点在插入和删除,不可重复读重点在修改

所以,从上面的几个案例可以看到,并发的事务可能导致其他事务的问题包括:

  • 读脏数据(最后事务并未提交成功)
  • 不可重复读
  • 幻读

如何解决,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。事务隔离分为不同级别,包括

  • 读未提交(read uncommitted)
  • 读提交(read committed)、可重复读(repeatable read)和串行化(Serializable)

3.2 事务的隔离级别

SQL92标准中事务的隔离性(Isolation)定义了四种隔离级别,并规定了每种隔离级别下上述几个(脏读、不可重复读、幻读)问题是否被解决。
一般来说,隔离级别越低,系统开销越低,可支持的并发越高,但隔离性也越差。隔离级别与数据库读的3个问题的关系如下:

隔离级别 脏读 不可重复读 幻读
读未提交:Read Uncommitted ×
读已提交:Read Committed × ×
可重复读:Repeatable Read × ×
串行化:Serializable × × ×

不同事务的隔离级别,实际上是一致性与并发性的一个权衡与折衷,它本质上也是InnoDB不同的锁策略(Locking Strategy)产生的效果 。接下来我们对这几种事务隔离机制详细介绍下:

3.2.1 读未提交(Read Uncommitted)

读未提交情况下,可以读取到其他事务还未提交的数据,多次读取结果不一样,出现了脏读、不可重复读的情况。
这种情况下select语句不加锁:SELECT statements are performed in a nonlocking fashion.,所以这是并发最高,一致性最差的隔离级别。

3.2.2 读已提交(Read Committed)

这是大多数数据库系统的默认隔离级别(但不是MySQL默认的)。读已提交情况下,无法读取到其他事务还未提交的数据,可以读取到其他事务已经提交的数据,多次读取结果不一样,不会出现脏读和幻读,但出现不可重复读。
这种隔离级别用的比较多,也是互联网业务最常用的隔离级别,在Read Committed 下:

  • 普通读是快照读取
  • 加锁的select, update, delete等语句,除了在外键约束检查(foreign-key constraint checking)以及重复键检查(duplicate-key checking)时会封锁区间,其他时刻都只使用记录锁;

3.2.3 串行化(Serializable)

这种事务的隔离级非常严格,在这种串行情况下不存在脏读、不可重复读、幻读的问题了。
所有select请求语句都会被隐式的转化为:

select ... in share mode.

这可能导致,如果有未提交的事务正在修改某些行,所有读取这些行的select都会被阻塞住,直到之前的事务执行完成。
这是一致性最好的,但并发性最差的隔离级别。执行顺序参考如下:

时间 窗口A 窗口B
T1 start transaction;
T2 select * from classes;
T3 start transaction;
T4 insert into classes values(9,'初三九班');
T5 select * from classes;
T6 commit;
T7 commit;

这个明显效率太慢了,在大数据量,大并发量的互联网场景下,基本上是不会使用上述这种隔离级别。

3.2.4 可重复读(Repeated Read, RR)

RR(可重复读)是InnoDB默认的隔离级别,在R这种隔离级别下:

  • select操作使用快照读(snapshot read),这是一种不加锁的一致性读(Consistent Nonlocking Read),底层使用MVCC来实现,MVCC 参考作者的这篇:数据库系列:InnoDB下实现高并发控制

  • 加锁的 select(select ... in share mode / select ... for update), update, delete 等语句条件的实现,其实是依赖于它们是否在唯一索引(unique index)上使用,如:

    • 唯一的查询条件(unique search condition)
    • 范围查询条件(range-type search condition)

可重复读情况下,未出现脏读,未读取到其他事务已提交的数据,多次读取结果一致,即可重复读。但是可能导致“幻读”。

4 总结

  • 认识ACID(原子性、一致性、隔离性、持久性)特性及其实现原理
  • 了解事务的脏读、幻读、不可重复读
  • 了解InnoDB实现deSQL92标准中的四种隔离级别
  • InnoDB默认的隔离级别是RR,互联网用得最多的隔离级别是RC,它解决了脏读和幻读,保证了数据隔离性和一致性。

标签:事务,隔离,幻读,数据库,提交,级别,读取
From: https://www.cnblogs.com/wzh2010/p/15886907.html

相关文章

  • 浅析开源数据库MySQL架构
    数据库是所有应用系统的核心,故保证数据库稳定、高效、安全地运行是所有企业日常工作的重中之重。数据库系统一旦出现问题无法提供服务,有可能导致整个系统都无法继续工作。所以,一个成功的数据库架构在高可用设计方面也是需要充分考虑的。下面就为大家介绍一下如何构建一个高可用的M......
  • 精彩回顾|从架构到实践,AntDB融合型数据库揭秘
    当今社会中的信息除了“多”,人们对于“效率”和“速度”的要求也越来越高。譬如,对于很多企业决策者来说,在当前的经济形势下需要尽一切可能降本增效。过去每周看看经营报表的习惯,现在慢慢转变为实时可视化分析企业当前的经营状况。基于此,亚信安慧AntDB融合型数据库,为了支持用户对“......
  • 2023年11月中国数据库排行榜:OPO组合持续两月,亚信、中兴闯进前十
    长夜之中蓄力待,势如破晓初光披。 2023年11月的 墨天轮中国数据库流行度排行 火热出炉,本月共有283个数据库参与排名。本月排行榜前十名变动较大,TiDB上升一位居第4,达梦奋勇向前重归第6,亚信AntDB、中兴GoldenDB势如破竹进军10强。 墨天轮十巨头之争白热化,中国数据库行业将迎......
  • 记一次mysqlbinlog恢复数据库数据
    因为一些意外操作,用旧的备份覆盖了最新的数据库数据,导致最近几天内的数据被覆盖掉了。百度了一圈。。用mysqlbinlog恢复比较靠谱,网上查感觉操作也比较简单。。实际吧。费点劲。。 MySQL的Binlog是用于记录数据库中所有操作的日志文件。通过检查日志文件,可以找到误删除的......
  • SqlServer中获取数据库中每个表的行数
    SqlServer中获取数据库中每个表的行数CREATETABLE#RowCounts(NumberOfRowsBIGINT,TableNameVARCHAR(128))EXECsp_MSForEachTable'INSERTINTO#RowCountsSELECTCOUNT_BIG(*)ASNumberOfRows,''?''asTableNameFROM?'SELECTTableName,Numbe......
  • Lumen框架 之数据库迁移
    一、基本操作1、/database/migrations/目录下生成一个php文件,这个文件主要包括两个函数,在up()函数中根据你的需求定义数据库字段phpartisanmake:migrationcreate_users_table--create=users<?phpuseIlluminate\Database\Migrations\Migration;useIlluminate\Database\Sch......
  • 数据库对象命名规范
      译自:http://weblogs.asp.net/jamauss/articles/DatabaseNamingConventions.aspx 通用规则:命名长度不超过30个字符使用字母和下划线(避免使用数字)尽量少用下划线.以字母开头.(不要以下划线开头)避免简写(减少歧义或误解)避免缩写(缩写可能造成多义性比如见名知意避免......
  • 数据库大事记
    数据库分类分类方法为:按数据模型分类、按业务类型分类、按部署方式分类、按存储介质分类。按数据模型分类按业务类型分类按部署方式分类按存储介质分类喜欢点赞收藏转发,下期再见。......
  • 非结构化数据库的典型实例
    列举下列非结构化数据库的典型实例。键值数据库存储数据库——()列存储数据库——()文档型数据库——()图形数据库——()键值数据库是一种基于键值对存储的数据库,它将数据以键值对的形式进行存储和检索。以下是一些常见的键值数据库的典型实例:1.Redis:Redis是一个开源的内存键值数......
  • MySQL数据库高并发优化配置
    在Apache,PHP,mysql的体系架构中,MySQL对于性能的影响最大,也是关键的核心部分。对于Discuz!论坛程序也是如此,MySQL的设置是否合理优化,直接影响到论坛的速度和承载量!同时,MySQL也是优化难度最大的一个部分,不但需要理解一些MySQL专业知识,同时还需要长时间的观察统计并且根据经验进......