1 Star 0 Fork 246

sherlock / java-construct

forked from 古春波 / java-construct 
加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
克隆/下载
事务详解.md 12.71 KB
一键复制 编辑 原始数据 按行查看 历史
古春波 提交于 2022-06-17 17:52 . 更新图床

本文架构

1585901494537

[TOC]

事务就是一组原子性的SQL査询,或者说一个独立的工作单元。如果数据库引擎能够成功地对数据库应用该组査询的全部语句,那么就执行该组查询。如果其中有任何一条语句因为崩溃或其他原因无法执行,那么所有的语句都不会执行。也就是说,事务内的语句,要么全部执行成功,要么全部执行失败。

1. 事务的ACID特性

银行应用是解释事务必要性的一个经典例子。假设一个银行的数据库有两张表:支票(checking)表和储蓄( savings)表。现在要从用户Jane的支票账户转移200美元到她的储蓄账户,那么需要至少三个步骤

1584276392783

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

1585817639262

单纯的事务概念并不是故事的全部。试想一下,如果执行到第四条语句时服务器崩溃了,会发生什么?天知道,用户可能会损失200美元。再假如,在执行到第三条语句和第四条语句之间时,另外一个进程要删除支票账户的所有余额,那么结果可能就是银行在不知道这个逻辑的情况下白白给了Jane 200美元。

系统通过严格的ACID测试,否则空谈事务的概念是不够的。ACID表示原子性(atomicity)、一致性(consistency)、隔离性(isolation)和持久性(durability)。一个运行良好的事务处理系统,必须具备这些标准特征。

  • 原子性(atomicity),一个事务必须被视为一个不可分割的最小工作单元,整个事务中的所有操作要么全部提交成功,要么全部失败回滚,对于一个事务来说,不可能只执行其中的一部分操作,这就是事务的原子性

  • 一致性(consistency),数据库总是从一个一致性的状态转换到另外一个一致性的状态。在前面的例子中,如果一致性确保了,即使在执行第三、四条语句之间时系统崩溃,支票账户中也不会损失200美元,因为事务最终没有提交,所以事务中所做的修改也不会保存到数据库中。

  • 隔离性(isolation),通常来说,一个事务所做的修改在最终提交以前,对其他事务是不可见的。在前面的例子中,当执行完第三条语句、第四条语句还未开始时,此时有另外一个账户汇总程序开始运行,则其看到的支票账户的余额并没有被减去200美元。后面我们讨论隔离级别(Isolation level)的时侯,会发现为什么我们要说“通常来说”是不可见的。

  • 持久性(durability),一旦事务提交,则其所做的修改就会永久保存到数据库中。此时即使系统崩溃,修改的数据也不会丢失。

事务的ACID特性可以确保银行不会弄丢你的钱。而在应用逻辑中,要实现这一点非常难,甚至可以说是不可能完成的任务。一个兼容ACID的数据库系统,需要做很多复杂但可能用户并没有觉察到的工作,需要增大许多的系统开销,才能确保ACID的实现。可以根据是否需要事务,来决定使用什么储存引擎。

一个事务不是总能够实现ACID特性!

2. 事务的隔离级别

SQL标准中定义了四种隔离级别,每一种级別都规定了个事务中所做的修改,哪些在事务内和事务间是可见的,哪些是不可见的。较低级别的隔离通常可以执行更高的并发,系统的开销也更低。

1. READ UNCOMMITTED(读未提交)

在 *READ UNCOMMITTED*级别,事务中的修改,即使没有提交,对其他事务也都是可见的。事务可以读取未提交的数据,这也被称为脏读(Dirty Read)。这个级别会导致很多问题,从性能上来说,READ UNCOMMITTED不会比其他的级别好太多,但却缺乏其他级别的很多好处,除非真的有非常必要的理由,在实际应用中一般很少使用。

2. READ COMMITTED(读已提交)

大多数数据库系统的默认隔离级别都是 READ COMMITTED(但 MYSQL不是)。READ COMMITTED满足前面提到的隔离性的简单定义:A事务开始时,只能“看见”其它事务如B事务已经提交所做的修改。换句话说,B事务从开始直到提交之前,所做的任何修改对其他事务如A事务都是不可见的。但也可能会造成以下问题:如果A两次执行同样的査询,可能会得到不一样的结果。【一个事务A在读c表的一条数据某个时间后,事务B修改那条数据,并且进行了事务的提交,然后事务A再次读取c表的那条数据,却发现其读出的数据已经发生了改变、或某些记录已经被删除了!这种现象就叫做“不可重复读”。一句话:事务A读取到了另一个事务B对c表的已经提交的修改数据,不符合隔离性。】视频讲解

3. REPEATABLE READ(可重复读)

该级别保证了在同一个事务中多次读取同样记录的结果是一致的。但是理论上,可重复读隔离级别还是无法解决另外一个幻读(Phantom Read)的问题。所谓幻读,指的是当某个事务在读取某个范围内的记录时,另外一个事务又在该范围内插入了新的记录,当之前的事务再次读取该范围的记录另外一个事务又在该范围内插入了新的记录,当之前的事务再次以相同条件读取该范围的记录时,会产生幻行(Phantom Row)。 INNODB存储引擎通过多版本并发控制(MVCC, Multiversion Concurrency Control)解决了幻读的问题。稍后会做进一步的讨论,可重复读是 MYSQL的默认事务隔离级别。

4. SERIALIZABLE(可串行化)

SERIALIZABLE是最高的隔离级别。它通过强制事务串行执行,避免了前面说的幻读的问题。简单来说, SERIALIZABLE会在读取的每一行数据上都加锁,所以可能导致大量的超时和锁争用的问题。实际应用中也很少用到这个隔离级别,只有在非常需要确保数据的一致性而且可以接受没有并发的情况下,才考虑采用该级别。

**不可重复读和幻读区别:**不可重复读的重点是修改比如多次读取一条记录发现其中某些列的值被修改,幻读的重点在于新增或者删除比如多次读取一条记录发现记录增多或减少了

3. 数据库实现事务隔离的方式

MYSQL提供了两种事务型的存储引擎: InnodbNDB ClusterMYSQL服务器层不管理事务,事务是由下层的存储引擎实现的。

数据库实现事务隔离的方式,基本上可分为以下两种。

  • 一种是在读取数据前,对其加锁,阻止其他事务对数据进行修改。
  • 另一种是不用加任何锁,通过一定机制生成一个数据请求时间点的一致性数据快照(Snapshot),并用这个快照来提供一定级别(语句级或事务级)的一致性读取。从用户的角度来看,好象是数据库可以提供同一数据的多个版本,因此,这种技术叫做数据多版本并发控制(MultiVersion Concurrency Control,简称 MVCCMCC),也经常称为多版本数库。

4 个事务隔离级别,每个级别的隔离程度不同,允许出现的副作用也不同,应用可以根据自己的业务逻辑要求,通过选择不同的隔离级别来平衡“隔离”与“并发”的矛盾。

隐式和显式锁定

INNODB采用的是两阶段锁定协议(two-phase locking protocol)。在事务执行过程中,随时都可以执行锁定,锁只有在执行 COMMIT或者 ROLLBACK的时侯才会释放,并且所有的锁是在同一时刻被释放。一般的锁定都是隐式锁定,INNODB会根据隔离级别在需要的时侯自动加锁。另外,INNODB也支持通过特定的语句进行显式锁定,但是应该尽量避免使用。

- 共享锁(S):`SELECT * FROM table_name WHERE ... LOCK IN SHARE MODE`
- 排他锁(X)`SELECT * FROM table_name WHERE ... FOR UPDATE`

MYSQL也支持LOCK TABLESUNLOCK TABLES语句,这是在服务器层实现的,和存储引擎无关。它们有自己的用途,但并不能替代事务处理。如果应用需要用到事务,还是应该选择事务型存储引擎。经常可以发现,应用已经将表从 MYISAM转换到 INNODB,但还是显式地使用LOCK TABLES语句。这不但没有必要,还会严重影响性能,实际上 INNODB的行级锁工作得更好。

数据库事务有不同的隔离级别,不同的隔离级别对锁的使用是不同的,锁的应用最终导致不同事务的隔离级别。

事务和锁机制是什么关系? 开启事务就自动加锁了吗?

1、事务与锁是不同的。事务具有ACID(原子性、一致性、隔离性和持久性),锁是用于解决隔离性的一种机制。

2、事务的隔离级别通过锁的机制来实现。另外锁有不同的粒度,同时事务也是有不同的隔离级别的。

3、开启事务就自动加锁。https://www.cnblogs.com/leijiangtao/p/11911644.html

4. 事务的自动提交

自动提交(AUTO COMMIT),MYSQL默认采用自动提交(AUTO COMMIT)模式。也就是说,如果不是显式地开始一个事务,则每个査询都被当作一个事务执行提交操作。在当前连接中,可以通过设置AUTO COMMIT变量来启用或者禁用自动提交模式:

1584282322099

1或者ON表示启用,0或者OFF表示禁用。当 AUTOCOMMIT=0时,所有的査询都是在一个事务中,直到显式地执行 COMMIT 提交或者 ROLLBACK回滚,该事务结束,同时又开始了另一个新事务。修改 AUTO COMMIT 对非事务型的表,比如 MYISAM或者内存表,不会有任何影响。对这类表来说,没有 COMMIT 或者 ROLLBACK的概念,也可以说是相当于一直处于 AUTO COMMIT启用的模式。

另外还有一些命令,在执行之前会强制执行 COMMIT提交当前的活动事务。典型的例子,在数据定义语言(DDL)中,如果是会导致大量数据改变的操作,比如 ALTER TABLE就是如此。另外还有 LOCK TABLES等其他语句也会导致同样的结果。如果有需要,请检査对应版本的官方文档来确认所有可能导致自动提交的语句列表。

5. 实战演示事务的隔离级别

由上面的讨论我们已经说过,因为默认每个查询都被当成一个事务自动提交的。所以我们要演示事务的隔离级别就要显式地开启一个事务,使用start transaction 命令(start transaction 有隐含的 设置autocommit为0 作用)。

我们可以通过下面的命令来设置隔离级别。

SET [SESSION|GLOBAL] TRANSACTION ISOLATION LEVEL [READ UNCOMMITTED|READ COMMITTED|REPEATABLE RE
# 创建支持事务的表!一定要选择支持事务的,不然,以下演示是不可能成功的
DROP TABLE IF EXISTS `transaction_test`;
CREATE TABLE `transaction_test`  (
  `id` int(10) NOT NULL AUTO_INCREMENT,
  `title` varchar(20)  DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB ROW_FORMAT = Dynamic;
INSERT INTO `transaction_test` VALUES (1, 'change_from');

1. 读未提交(发生脏读)

1585906229687

2. 读已提交(解决脏读)

1585909173576

3. 可重复读(解决不可重复读)

从上一张图可以看到,session_1中两次读取session_2的数据是不一样的,这就叫做不可重复读。

1585910088033

4. 可串行化

1585911688758

在上面所示的操作中,将session_1事务的隔离级别设置为可重复读也可以实现同样的效果,这印证了之前说的理论:Innodb通过某种方法,可以在可重复读的隔离级别实现可串行化的隔离级别的效果,减少了锁的使用,更加高效。

一个事务对数据库进行操作,这种操作的范围是数据库的全部行,然后第二个事务也在对这个数据库操作,这种操作可以是插入一行记录或删除一行记录,那么第一个是事务就会觉得自己出现了幻觉,怎么还有没有处理的记录呢? 或者怎么多处理了一行记录呢?

Java
1
https://gitee.com/sherlock7565/java-construct.git
git@gitee.com:sherlock7565/java-construct.git
sherlock7565
java-construct
java-construct
master

搜索帮助