sql事务特性简介

pphh发布于2018年10月5日

Sql事务有原子性、一致性、隔离性、持久性四个基本特性,要实现完全的ACID事务,是以牺牲事务的吞吐性能作为代价的。在有些应用场景中,通过分析业务数据读写,使得可以降低事务的隔离性,容忍相应出现的数据一致性问题,实现事务的高并发、高吞吐、低时延性,这是sql事务优化的最佳实践。本文对sql标准中隔离性级别定义和可能会出现的问题进行一一介绍,最后通过Mysql数据库进行相应的演示。

目录

  • 1. Sql事务特性
  • 2. Sql事务特性:原子性
  • 3. Sql事务特性:一致性、并发性和隔离性
  • 4. Sql隔离级别和问题
    • 4.1 脏读
    • 4.2 不可重复读
    • 4.3 幻读
    • 4.4 不可重复读和幻读的区别
  • 5. 演示
    • 5.1 准备工作
    • 5.2 事务的原子性
    • 5.3 事务的脏读
    • 5.4 事务的不可重复读
    • 5.5 幻读
  • 6. 小结
  • 7. 参考资料

1. Sql事务特性

业界常用字母缩写ACID描述Sql事务的四个基本特性,这四个字母分别代表为,

  • Atomicity 原子性
  • Consistency 一致性
  • Isolation 隔离性
  • Durability 持久性

下面对这四个特性进行介绍,

  1. 原子性:在一个事务中sql的操作,要不成功提交,要不回滚。当一个事务对数据库有做多项改动,这些改动要不全部一起提交进入数据库,要不就全部回滚,数据库无变化。
  2. 一致性:在事务执行的过程中,数据库一直保持一致性的状态,无论事务是否成功提交或者回滚,或者事务在执行中。当一个事务改动了多个表的记录,查询时要不看到所有变化后的新记录,要不就看到变化前的老记录,不会出现查询出新老记录混合出现的场景。
  3. 隔离性:各个事务之间相互隔离,互不影响,隔离性是通过数据库锁机制实现。要说明的是,隔离性是相对的,在数据库使用过程中,为了实现高并发,会降低事务之间的隔离性,并牺牲一定的数据一致性。更详细的讨论见下文。
  4. 持久性:事务的执行结果具有持久性,一旦事务提交后,其结果将被安全持久化到存储设备上,无论遇到电力中断、系统崩溃、关机、或者其它潜在威胁。

这四个特性中,原子性是事务最基本的特性,现代数据库都支持完整的原子性事务,而对于一致性、隔离性、持久性,在面对高可用性、高并发、高吞吐时会进行相应的取舍。

2. Sql事务特性:原子性

原子性是事务最基本的特性,根据其定义可以知道事务的执行分为三个阶段,

  • uncommitted 未提交,当前事务在执行中。
  • commited 已提交,当前事务做的改动被数据库接受,已安全持久化到数据库存储中。
  • rollback 已回滚,当前事务所做的操作被撤销,对数据库无改动。

一个执行中的事务只能以commited/rollback两者状态之一作为结束。

3. Sql事务特性:一致性、并发性和隔离性

数据库事务中,保持数据一致性是需要代价的,若要保证绝对一致性,则相关联的事务只能以串行执行(serializability),这是一种严格的隔离方式。在这种隔离方式下,有数据关联性的几个事务操作,只能一个一个按顺序执行,事务的并发被完全限制,数据库的事务吞吐将大为降低,一个写入操作甚至会被一个只读查询操作阻塞,等待读操作完成之后才可以进行下一步写操作。

在有些通用场景中,对读数据的准确性和时效性要求没有那么高,但希望有高吞吐量,能快速获取查询结果,在数据库操作高并发的同时,实现低时延性、快速的响应。为了实现这个目的,数据库专家提出了不同的数据隔离性级别,通过降低事务的隔离性,从而使得数据库的并发吞吐能够获得最佳的效率。

在sql-1992标准中,对数据库实现的隔离级别和隔离性提出了相关的规范定义,其中隔离级别包括四种,隔离性按低往高排序分别为,

  • READ UNCOMMITTED 读未提交:可以允许读未提交的事务数据
  • READ COMMITTED 读提交:只允许读已提交的事务数据
  • REPEATABLE READ 可重复读:保证读取的数据不会出现不一致的情况
  • SERIALIZABLE 串行:保证数据读取和写入的绝对一致性

现代数据库基本都实现了上述四个级别的事务隔离配置,供不同场景下使用。

4. Sql隔离级别和问题

鱼和熊掌不可兼得,面对隔离性和数据一致性,便是这样的选择题。追求高并发吞吐,必然低隔离性,数据一致性问题则愈严重。了解sql不同隔离级别定义和相应会出现的一致性问题,是进行隔离性级别优化选择的前提。

下表对Sql隔离级别和问题进行简要说明(依据sql-1992标准),

隔离级别 脏读 dirty read 不可重复读 non-repeatable read 幻读 phantom 并发吞吐性
读未提交 可能 可能 可能
读提交 不会 可能 可能 中等
可重复读 不会 不会 可能
串行 不会 不会 不会 串行

上表中,有列出三种数据不一致的问题,

  • 脏读
  • 不可重复读
  • 幻读

下面对这三个问题一一进行讲解,然后给出mysql数据库中的三种问题的演示。

4.1 脏读

在一个事务T1中对某个数据记录进行了修改。若在事务T1提交之前,T2中此刻读取这个数据记录,随后T1进行了回滚操作,则T2将读取到一个未提交的无效数据。这个问题就叫做脏读。

脏读的问题在于,读取到错误的、无效的数据。

4.2 不可重复读

在一个事务T1中读取了某个数据记录,若此时事务T2对这个数据记录进行了修改和删除并提交,随后T1再尝试重复读取同一数据记录,这个时候T1发现数据有变化(或者发现已经不存在)。这种在一个事务中,重复读取数据却获取到不一致的查询结果,就叫做不可重复读的问题。

不可重复读主要问题在于,在一个事务中同一数据记录多次读取,会有前后不一致的问题(尽管前后读取的数据都是准确的)。

4.3 幻读

在一个事务T1中读取了一系列满足指定查询条件的数据记录,若此时事务T2执行一些操作,若T2操作会更新某些数据记录,而这些数据记录刚好落入T1事务中的查询条件,则当T1再次读取同一查询条件的数据记录,发现数据记录有不一样。

幻读的主要问题在于,在一个事务中数据记录读取的准确性依赖查询条件,其数据集合是当前事务所涉及的数据记录的超集。

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

一个常见的疑问是,不可重复读和幻读的区别。从事务的控制角度,不可重复读针对的是当前事务所操作的数据记录,幻读针对的是符合当前事务查询条件的所有数据记录,后者是前者的超集。从解决方案来说,对于不可重复读的问题,只要锁住当前事务操作的数据记录即可,或者读取快照,两种方法都可以有效地避免前后读取不一致的问题;而对于幻读,则需要锁住所有符合查询条件的记录,其范围是无限扩大的,有时候甚至需要锁住整张表。

举个例子来说,下面的sql语句,将状态为NEW的记录进行更新,若表中符合NEW状态的记录有5个,

update `order` set `status`='PAID' where `status`='NEW';

则,

  • 要解决不可重复读的问题,只要锁住当前表中那5条记录即可,或者留存快照,当前事务不受其它事务影响即可。
  • 要解决幻读的问题,则要锁住所有可能出现NEW状态的其它事务操作,包括插入和更新操作。解决幻读问题,本质问题是需要了解其它事务对数据库的更新变化,一旦发现对当前事务有影响,则对外部其它事务进行阻塞,保证当前事务的优先执行权。

5. 演示

下面通过Mysql演示Sql的不同隔离级别和出现的问题,演示中使用的Mysql版本为5.7.16。

5.1 准备工作

在数据库中,执行如下语句,创建测试数据库和表order。

CREATE SCHEMA IF NOT EXISTS `test` DEFAULT CHARACTER SET utf8mb4;
DROP TABLE IF EXISTS `test`.`order` ; CREATE TABLE IF NOT EXISTS `test`.`order` (
`id` BIGINT(20) NOT NULL AUTO_INCREMENT,
`name` VARCHAR(64) NOT NULL DEFAULT '未知',
`quantity` INT NOT NULL DEFAULT '',
`price` DOUBLE NOT NULL DEFAULT '0.0',
`status` VARCHAR(64) NOT NULL DEFAULT 'NEW' COMMENT '订单状态:NEW-新订单,PAID-订单已付,CLOSE-订单结束',
`date` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`))
ENGINE = InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订单'; INSERT INTO `test`.`order` (`id`, `name`, `quantity`, `price`, `status`) VALUES ('', 'apple', '', '5.0', 'NEW');
select * from `test`.`order`;

下面是一些基本的sql事务查询语句,

  • 查询事务:SELECT * FROM information_schema.INNODB_TRX\G;
  • 查询当前隔离级别:select @@tx_isolation;
  • 设置当前隔离级别:set session transaction isolation level read uncommitted;

演示中会启动两个sql连接,分别为session1和session2,方便演示两个session之间的相互影响。

5.2 事务的原子性

事务的提交,

start transaction;
update `test`.`order` set `price`='7.0' where `id`='1';
commit;

事务的回滚,回滚后数据的修改被撤销,

start transaction;
update `test`.`order` set `price`='8.0' where `id`='1';
rollback;

5.3 事务的脏读

请按照下表执行相应的演示步骤,

step session 1 session 2
1 use test; use test;
2 set session transaction isolation level read uncommitted;  
3 start transaction;  
4 select * from `order`;  
5   start transaction;
6   update `order` set `price`='10.0' where `id`='1';
7 select * from `order`;  
8   rollback;
9 select * from `order`;  
10 commit; ;

其中,

  • session-1 中在第2步设置了隔离级别为:读未提交。
  • session-1 中在第7步读取到的数据为session-2中未提交的数据,之后session-2在第8步进行了回滚,使得该数据失效。

请见session-1的输出,

mysql> set session transaction isolation level read uncommitted;
Query OK, 0 rows affected (0.00 sec) mysql> start transaction;
Query OK, 0 rows affected (0.00 sec) mysql> select * from `order`;
+----+-------+----------+-------+--------+---------------------+
| id | name | quantity | price | status | date |
+----+-------+----------+-------+--------+---------------------+
| 1 | apple | 1 | 7 | NEW | 2018-09-13 22:44:29 |
+----+-------+----------+-------+--------+---------------------+
1 row in set (0.00 sec) mysql> select * from `order`;
+----+-------+----------+-------+--------+---------------------+
| id | name | quantity | price | status | date |
+----+-------+----------+-------+--------+---------------------+
| 1 | apple | 1 | 10 | NEW | 2018-09-13 22:46:49 |
+----+-------+----------+-------+--------+---------------------+
1 row in set (0.00 sec) mysql> select * from `order`;
+----+-------+----------+-------+--------+---------------------+
| id | name | quantity | price | status | date |
+----+-------+----------+-------+--------+---------------------+
| 1 | apple | 1 | 7 | NEW | 2018-09-13 22:44:29 |
+----+-------+----------+-------+--------+---------------------+
1 row in set (0.00 sec) mysql> commit;
Query OK, 0 rows affected (0.00 sec)

可以看到在session-1中第7步第二次查询时,获得了无效的数据,这就是脏读。解决脏读,可以提高隔离级别到:读已提交。

set session transaction isolation level read committed;

请见接下来的演示。

5.4 事务的不可重复读

请按照下表执行相应的演示步骤,

step session 1 session 2
1 use test; use test;
2 set session transaction isolation level read committed;  
3 start transaction;  
4 select * from `order`;  
5   start transaction;
6   update `order` set `price`='11.0' where `id`='1';
7 select * from `order`;  
8   commit;
9 select * from `order`; ;
10 commit; ;

其中,

  • session-1 中在第2步设置了隔离级别为:读已提交。
  • session-1 中在第9步读取到的数据为session-2中已提交的数据,该数据和前两次查询的数据不一致。

请见session-1的输出,

mysql> set session transaction isolation level read committed;
Query OK, 0 rows affected (0.00 sec) mysql> start transaction;
Query OK, 0 rows affected (0.00 sec) mysql> select * from `order`;
+----+-------+----------+-------+--------+---------------------+
| id | name | quantity | price | status | date |
+----+-------+----------+-------+--------+---------------------+
| 1 | apple | 1 | 7 | NEW | 2018-09-13 22:44:29 |
+----+-------+----------+-------+--------+---------------------+
1 row in set (0.00 sec) mysql> select * from `order`;
+----+-------+----------+-------+--------+---------------------+
| id | name | quantity | price | status | date |
+----+-------+----------+-------+--------+---------------------+
| 1 | apple | 1 | 7 | NEW | 2018-09-13 22:44:29 |
+----+-------+----------+-------+--------+---------------------+
1 row in set (0.00 sec) mysql> select * from `order`;
+----+-------+----------+-------+--------+---------------------+
| id | name | quantity | price | status | date |
+----+-------+----------+-------+--------+---------------------+
| 1 | apple | 1 | 11 | NEW | 2018-09-13 22:52:45 |
+----+-------+----------+-------+--------+---------------------+
1 row in set (0.00 sec) mysql> commit;
Query OK, 0 rows affected (0.00 sec)

可以看到在session-1中第9步第三次查询时,获得了不一致的数据,这就是不可重复读的问题。解决不可重复读,可以提高隔离级别到:可重复读。

set session transaction isolation level repeatable read;

请见接下来的演示。

5.5 幻读

请按照下表执行相应的演示步骤,

step session 1 session 2
1 use test; use test;
2 set session transaction isolation level repeatable read;  
3 start transaction;  
4 select * from `order` where `status`='new';  
5   insert into `order` (`name`, `status`) VALUES ('apple', 'NEW');
6 select * from `order` where `status`='new';  
7 update `order` set `status`='PAID' where `status`='NEW';  
8 select * from `order` where `status`='PAID';  
9 commit; ;

其中,

  • session-1 中在第2步设置了隔离级别为:可重复读。
  • session-1 中在第6步读取到NEW状态的数据记录为一条。
  • session-1 中在第7步更新了NEW状态的数据记录,状态设置为PAID。
  • session-1 中在第8步查询更新结果,发现实际上更新操作影响了两条数据记录。

请见session-1的输出,

mysql> start transaction;
Query OK, 0 rows affected (0.00 sec) mysql> select * from `order` where `status`='new';
+----+-------+----------+-------+--------+---------------------+
| id | name | quantity | price | status | date |
+----+-------+----------+-------+--------+---------------------+
| 1 | apple | 1 | 5 | NEW | 2018-09-14 17:20:24 |
+----+-------+----------+-------+--------+---------------------+
1 row in set (0.00 sec) mysql> select * from `order` where `status`='new';
+----+-------+----------+-------+--------+---------------------+
| id | name | quantity | price | status | date |
+----+-------+----------+-------+--------+---------------------+
| 1 | apple | 1 | 5 | NEW | 2018-09-14 17:20:24 |
+----+-------+----------+-------+--------+---------------------+
1 row in set (0.00 sec) mysql> update `order` set `status`='PAID' where `status`='NEW';
Query OK, 2 rows affected (0.00 sec)
Rows matched: 2 Changed: 2 Warnings: 0 mysql> select * from `order` where `status`='PAID';
+----+-------+----------+-------+--------+---------------------+
| id | name | quantity | price | status | date |
+----+-------+----------+-------+--------+---------------------+
| 1 | apple | 1 | 5 | PAID | 2018-09-14 17:22:18 |
| 2 | apple | 0 | 0 | PAID | 2018-09-14 17:22:18 |
+----+-------+----------+-------+--------+---------------------+
2 rows in set (0.00 sec) mysql> commit;
Query OK, 0 rows affected (0.00 sec)

可以看到在session-1中第7步进行更新操作时,更新了当前事务并未看见的另外一条数据记录,这就是幻读所面临的问题。解决幻读问题,可以提高隔离级别到:串行。

set session transaction isolation level serializable;

若在上述演示中,在session-1中的第2步设置隔离级别为串行,则session-2中的第5步insert操作会被阻塞,直到session-1完成事务。

6. 小结

保证绝对的数据一致性,是以并发吞吐的下降为代价的。在很多时候,牺牲一定的隔离性,在有些应用场景下可以容忍一定的数据不一致问题,从而保障高并发的需求。了解sql隔离级别定义和相应会出现的问题,是进行隔离性级别优化选择的前提,根据不同的应用场景,选择合适的隔离级别,是数据库性能调优的重要手段。

7. 参考资料

  1. sql1992规范
  2. sql2011规范
  3. Mysql 8.0用户手册 - ACID
  4. Mysql 8.0用户手册 - 15.5.2.1 Transaction Isolation Levels
  5. SqlServer 2017用户手册 - Transaction Isolation Levels
  6. Oracle Database - Data Concurrency and Consistency
  7. wiki 文档 - sql standard

030.[转] sql事务特性的更多相关文章

  1. 【概念原理】四种SQL事务隔离级别和事务ACID特性

    摘要: SQL事务隔离级别和事务的ACID特性 事务是一组读写操作,并且具有只有所有操作都成功才算成功的特性.   事务隔离级别 SQL事务隔离级别由弱到强分别是:READ_UNCOMMITTED.R ...

  2. SQL—— 事务

    SQL 事务: 1.  定义: 事务是作为单个逻辑单元执行的一系列操作. 多个操作作为一个整体向系统提交,要么执行.要么都不执行,事务是一个不可分割的工作逻辑单元.这特别适用于多用户同时操作的数据通信 ...

  3. SQL 事务及实例演示

    简介 事务,英文名称是transaction.是在对数据库进行管理操作过程中一个逻辑单位,由有限的操作序列构成. 其实这个概念很好懂,简单理解就是:事务就是在使用数据库中的一个操作,由一些操作放到一起 ...

  4. 网络协议 finally{ return问题 注入问题 jdbc注册驱动问题 PreparedStatement 连接池目的 1.2.1DBCP连接池 C3P0连接池 MYSQL两种方式进行实物管理 JDBC事务 DBUtils事务 ThreadLocal 事务特性 并发访问 隔离级别

    1.1.1 API详解:注册驱动 DriverManager.registerDriver(new com.mysql.jdbc.Driver());不建议使用 原因有2个: >导致驱动被注册2 ...

  5. 事务特性,事务的隔离级别,并发事务可能出现的问题,spring事务 数据库锁

    1.0 事务特性(ACID) Atomicity:原子性,一个事务不可以被拆分 Consistency:一致性,在事务执行前数据库的数据处于正确的状态,而事务执行完成后数据库的数据还是处于正确的状态, ...

  6. 浅析Postgres中的并发控制(Concurrency Control)与事务特性(上)

    转载:https://www.cnblogs.com/flying-tiger/p/9567213.html#4121483#undefined PostgreSQL为开发者提供了一组丰富的工具来管理 ...

  7. 浅析Postgres中的并发控制(Concurrency Control)与事务特性(上)(转)

    这篇博客将MVCC讲的很透彻,以前自己懂了,很难给别人讲出来,但是这篇文章给的例子就让人很容易的复述出来,因此想记录一下,转载给更多的人 转自:https://www.cnblogs.com/flyi ...

  8. MySQL InnoDB如何保证事务特性

    如果有人问你"数据库事务有哪些特性"?你可能会很快回答出原子性.一致性.隔离性.持久性即ACID特性.那么你知道InnoDB如何保证这些事务特性的吗?如果知道的话这篇文章就可以直接 ...

  9. sql事务的使用及其技巧整理

    sql事务的使用及其技巧整理 概述: 在实际项目开发中,为了确保数据操作结果的一致性等要求,事务是一个必不可少的解决利器. 根据SQLSERVER实现原理,其实,SQLSERVER的每一条执行语句都是 ...

随机推荐

  1. Flask 教程 第七章:错误处理

    本文翻译自The Flask Mega-Tutorial Part VII: Error Handling 这是Flask Mega-Tutorial系列的第七部分,我将告诉你如何在Flask应用中进 ...

  2. Display a Detail View with a List View 主子视图-列表视图与详细信息视图同时显示

    In this lesson, you will learn how to display a Detail View together with a List View. For this purp ...

  3. QT总结

    作为一个QT(C++/linux/windows)开发工程师,把自己在工作中遇到的一些QT问题持续总结给大家,一起分享: 一.隐藏鼠标:QApplication::setOverrideCursor( ...

  4. Python入门基础学习(模块,包)

    Python基础学习笔记(五) 模块的概念:模块是python程序架构的一个核心概念 每个以拓展名py结尾的python源代码文件都是一个模块 模块名同样也是一个标识符,需要符合标识符的命名规则 在模 ...

  5. css3的新属性 新增的颜色--- 透明度---两种渐变---定义多张背景图--background-size

    css31==>颜色的6种表示的方法有6种表示颜色的方法 关键字 rgb rgba(css3) 16进制 hsl hsla hsla h=>是色相,值为360, s=>饱和度,0%- ...

  6. Less(4)

    1.先判断注入类型 (1)首先看到要求,要求传一个ID参数,并且要求是数字型的:?id=1 (2)再输入?id=1' 界面无变化 (3)再输入?id=1'' 界面还是无变化, (4)再输入?id=1 ...

  7. poj 3069 Saruman's Army 贪心 题解《挑战程序设计竞赛》

    地址 http://poj.org/problem?id=3069 题解 题目可以考虑贪心 尽可能的根据题意选择靠右边的点 注意 开始无标记点 寻找左侧第一个没覆盖的点 再来推算既可能靠右的标记点为一 ...

  8. WPF 绑定属性 XAML 时间格式化

    原文:WPF 绑定属性 XAML 时间格式化 XAML 时间格式化{Binding Birthday,StringFormat='yyyy-MM-dd '} public class AssetCla ...

  9. 【2019.10.7 CCF-CSP-2019模拟赛 T2】绝对值(abs)(线段树细节题)

    找规律 设\(p_i=a_{i+1}-a_i\),则答案就是\(\sum_{i=1}^{n-1}p_i\). 考虑若将\(a_i\)加上\(x\)(边界情况特殊考虑),就相当于是将\(p_{i-1}\ ...

  10. Keras保存模型并载入模型继续训练

    我们以MNIST手写数字识别为例 import numpy as np from keras.datasets import mnist from keras.utils import np_util ...