事务处理是保证数据安全的重要机制,事务有四个重要属性 ,根据它们的英文名称可以记为ACID:

  • 原子性(Atomic): 事务操作是不可分割的; 事务只存在已执行和未执行两种状态,不存在只执行了部分指令的情况
  • 一致性(Consistency): 数据库总是从一个一致的状态转换到另一个一致状态
  • 隔离性(Isolation): 同时执行的事务之间相互隔离,不会互相影响。
  • 持久性(Durability): 事务成功提交后, 其写入的数据直到被覆盖永久有效

我们以银行转账操作为例理解事务:

START TRANSACTION;
UPDATE account_balance SET balance = balance - 200.00 WHERE customer_id = 1;
UPDATE account_balance SET balance = balance + 200.00 WHERE customer_id = 2;
COMMIT;

上述事务执行前后数据库只可能有两种状态: 账户1、2的余额未变化, 账户1余额减少200元账户2余额增加200元。不可能存在账户1余额减少而账户2余额不变的状态。

减少账户1余额和增加账户2余额是一个连续的过程, 不允许在事务执行过程中对账户1、2余额进行其它操作。

事务的原子性体现在两方面:

  • 事务执行过程中不允许插入其它操作。 减少账户1余额和增加账户2余额是一个连续的过程, 不允许在事务执行过程中对账户1、2余额进行其它操作。
  • 事务中的所有更改要么都发生要么都不发生, 不存在部分完成的情况。 减少账户1余额和增加账户2余额要么都发生,要么都不发生

事务一致性体现在: 事务执行前后数据库总是维持在一致状态, 转账开始前到转账结束(无论转账成功或失败)的整个过程中, 账户1、2的总余额始终不变。

事务隔离性体现在: 在转账事务减少账户1余额后提交之前,另一个事务查询到的账户1余额仍是减少之前的。

并发事务的潜在问题

  • 脏读: 事务A修改了一个数据,但未提交,事务B读到了事务A未提交的更新结果(即脏数据)。 如事务A在执行转账操作,从转出账户扣除了余额但未修改转入账户余额,此时事务B读取了转入账户的余额, 即发生了脏读。

  • 不可重复读: 在同一个事务中,对于同一条数据两次查询读到的结果不一致。比如,在事务A两次查询中间事务B修改了某条记录,那么事务A两次查询会读到不同的结果。

  • 幻读: 在同一个事务中,对于同一个查询返回的记录数不一致。造成这种现象的原因是在事务A的两次查询中间事务B添加或删除了记录,导致事务A两次查询读到不同的结果。

幻读和不可重复读的区别在于,不可重复读是对已存在记录的修改导致的只需要对某一条记录加锁即可,幻读增删记录导致的必须对全表加锁。

事务隔离级别

MySQL提供四级事务隔离级别:

  • Read Uncommitted: 禁止多个事务同时修改同一条记录,其它事务可以读取未提交的修改。 隔离级别最低,并发性能最高,会出现脏读,不可重复读和幻读。

  • Read Committed: 禁止多个事务同时修改同一条记录, 修改在提交前其它事务只能读取修改前的版本。不会出现脏读,但会出现不可重复读和幻读。

  • Repeated Read: 禁止多个事务同时修改同一条记录, 事务提交前会锁定所有读取到的行,禁止其它事务修改它正在读取的行。默认隔离级别,不会出现脏读和不可重复读,但会出现幻读。

  • Serializable: 串行化执行,会锁定所有涉及的数据表。可以解决脏读、不可重复读和幻读, 隔离级别最高,并发性能最低。

在实际应用中我们需要根据需要选择合适的事务隔离级别。

SET TRANSACTION语句可以设置事务隔离级别:

-- SET [SESSION | GLOBAL] TRANSACTION ISOLATION LEVEL {READ UNCOMMITTED | READ COMMITTED | REPEATABLE READ | SERIALIZABLE}
SET GLOBAL TRANSACTION ISOLATION LEVEL REPEATABLE READ; -- 设置所有新连接的事务隔离级别
SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED; -- 设置当前连接的事务隔离级别
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE; -- 设置下一个事务的隔离级别

事务并发控制原理

我们通常有3种思路进行并发控制:

  • 悲观锁: 在事务进行过程中数据总是处于被锁定状态。

    悲观锁对数据被其它事务修改的可能性持悲观态度(倾向于可能发生), 常用于数据争用激烈的情景。我们通常使用的锁即是悲观锁。

  • 乐观锁: 在事务执行过程中数据不被锁定, 在事务提交时会对是否发生数据争用进行判断,若未发生冲突则完成提交, 否则回滚事务。

    乐观锁认为数据争用发生的可能性较小, 常用于数据争用比较少的情景。CAS原语是乐观锁的一个典型示例。

  • 快照: 所有对数据的修改都是在原有数据上产生了一个新的版本, 对数据的读取是在快照(历史版本)上进行的。写操作产生新的版本不会影响在旧版本执行的读操作。

MySQL默认使用的InnoDB存储引擎使用悲观锁和快照(多版本并发控制, Multi Version Concurrent Control, MVCC)来实现事务的并发控制。

InnoDB采用两阶段锁协议, 即事务分为扩张阶段和收缩阶段, 扩张阶段只允许加锁不能释放锁, 收缩阶段只能释放锁不能加锁。

MVCC

InnoDB提供了全局唯一且有序的事务序列号, 以修改数据的事务序列号作为数据版本号。并为每条记录维护两个版本号: 最近修改版本号, 删除事务号。

以 REPEATABLE READ 隔离级别下 MVCC 机制为例:

  • SELECT: 被检索的行必须同时满足两个条件:

    • 行的修改版本号必须小于或等于当前事务序列号

    • 行的删除版本号为空或者大于当前事务序列号

    当前事务读取到的数据总是事务开始前的版本或事务进行中修改的版本, 更晚开始的事务的修改不会被读取。这种读取方式称为快照读。

  • INSERT: 将当前事务序列号作为修改版本号

  • UPDATE: 插入一行新的记录并使用当前事务序列号作为修改版本号, 并将当前事务序列号作为旧记录的删除事务号(标记为已删除)。

  • DELETE: 将当前事务序列号作为记录的删除事务号(标记为已删除)。

因为快照读不会读取到更晚开始事务的修改, 因此不会产生不可重复读和幻读的问题。

在不同事务隔离级别下,快照读的一致性是不同的:

  • READ COMMITTED: 每次SELECT时生成快照。SELECT 可以看到其它已提交事务的修改
  • REPEATABLE READ: 事务开始时生成快照,事务内的更改会修改快照,SELECT 语句看不到其它事务的修改。

GAP 锁

快照读的缺陷在于只能读取事务开始前的版本, 而对于修改操作而言必须读取最新版本。

读取最新版本的需求被InnoDB称为当前读(Locking Read), 使用当前读的语句包括 UPDATE, DELETE 和 SELECT ... IN SHARE MODE, SELECT ... FOR UPDATE。

InnoDB使用锁来解决当前读的问题, InnoDB 中存在三种行级锁:

  • Record Lock: 单条行记录上的锁
  • Gap Lock:间隙锁,锁定一个范围,但不包括记录本身
  • Next-Key Lock: Record Lock + Next-Key Lock

用一个示例来说明GAP锁:

1> START TRANSACTION;
1> DELETE FROM user WHERE age < 18;

在执行 DELETE 语句时 GAP LOCK 锁定了所有 age < 18 的行。我们在另一个会话中开始另一个事务, 此时事务1尚未提交:

2> START TRANSACTION;
2> INSERT INTO user (age) VALUES (17);
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction

可以看到事务2等待锁超时, 在事务1释放 GAP LOCK 之前不能插入 age < 18 的行, 原有的 age < 18 的行也无法修改。

InnoDB 锁定索引而非锁定数据行, BTREE索引是有序的。GAP LOCK 锁定了索引树中 age < 18 的空间(即索引间的空隙), 被锁定的区间不能插入记录也不能修改已有记录。

在 REPEATABLE READ 隔离级别下不出现幻读是 InnoDB 存储引擎的特性不是 MySQL 的要求, 在使用其它存储引擎时仍可能出现幻读问题。

更多关于读一致性的内容可以参考 InnoDB 官方文档: innodb-consistent-read

MySQL 事务机制的更多相关文章

  1. MySQL事务机制

    事务机制的特性通常被概括为"ACID原则" A(Atomic) 原子性: 构成一个事务的所有语句应该是一个独立的逻辑单元,要么全部执行成功, 要么一个都不成功, 你不能只执行他们当 ...

  2. Mysql 预查询处理 事务机制

    预处理 PDO支持sql预处理功能,可以有效的防止sql注入的问题 例如: 以下操作会导致数据表中所有数据删除 $host = 'localhost'; $port = 3306; $dbname = ...

  3. MySQL事务、并发问题、锁机制

    MySQL事务,并发问题,锁机制 1.什么是事务 事务是一条或多条数据库操作语句的组合,具备ACID,4个特点. 原子性:要不全部成功,要不全部撤销 隔离性:事务之间相互独立,互不干扰 一致性:数据库 ...

  4. MySQL事务及事务隔离级别 锁机制

    什么是事务? 当多个用户访问同一份数据时,一个用户在更改数据的过程中可能有其他用户同时发起更改请求,为保证数据库记录的更新从一个一致性状态更改为另一个一致性状态,这样的操作过程就是事务.事务具有的AC ...

  5. Mysql事务隔离级别和锁机制

    一.Spring支持四种事务隔离级别: 1.ISOLATION_READ_UNCOMMITTED(读未提交):这是事务最低的隔离级别,它充许令外一个事务可以看到这个事务未提交的数据. 2.ISOLAT ...

  6. MySQL事务、锁机制、查询缓存

    MySQL事务 何为事务? 事务(Transaction)是访问并可能更新数据库中各种数据项的一个程序执行单元(unit). 一个事务可以是一条SQL语句,一组SQL语句或整个程序. 事务的特性: 事 ...

  7. mysql锁机制和事务隔离

    mysql事务 1.InnoDB事务原理 事务(Transaction)是数据库区别于文件系统的重要特性之一,事务会把数据库从一种一致性状态转换为另一种一致性状态. 在数据库提交时,可以确保要么所有修 ...

  8. MySQL的事务机制和锁(InnoDB引擎、MVCC多版本并发控制技术)

    一.事务(数据库的事务都通用的定义) 1.1 事务定义 事务是由一步或几步数据库操作序列组成逻辑执行单元,这系列操作要么全部执行,要么全部放弃执行.事务通常以 BEGIN TRANSACTION 开始 ...

  9. MySQL:事务机制

    为什么需要事务处理? 在执行SQL语句的时候,某些业务要求,一系列操作必须全部执行,而不能仅执行一部分. MySQL5.0后引入了事务机制,MySQL支持几种基本的数据库引擎,并非所有引擎都支持事务处 ...

随机推荐

  1. java中random的几个方法的使用Math.random()和random().

    random java中我们有时候也需要使用使用random来产生随机数,下面我来简单的介绍下java中random的使用方法 第一种:Math.random() public static doub ...

  2. HTML-表格-列表-结构标记-表单

    1.表格  1.表格语法    1.标记      1.表格        <table></table>      2.行        <tr></tr& ...

  3. string所在头文件

    使用string.wstring 头文件:#include <string> 命名空间:std

  4. spass按位置编码,进行排序题处理与分析

    本范例即需建立Q4_1至Q4_4 等四个变项, 各变量的数值则是排序的内容,共有0.1.2.3.4 等五种可能,0代表该选项没有被受测者选取,1.2.3.4分别代表被受测者指为第一至第四顺位. htt ...

  5. Reading | 《数字图像处理原理与实践(MATLAB版)》(未完待续)

    目录 一.前言 1.MATLAB or C++ 2.图像文件 文件头 调色板 像素数据 3.RGB颜色空间 原理 坐标表示 4.MATLAB中的图像文件 图像类型 image()函数 imshow() ...

  6. Go语言正则模块

    基本使用 import "bytes" import "fmt" import "regexp" func main() { //这个测试一 ...

  7. SRC列表收集

    阿里asrc https://security.alibaba.com/百度bsrc http://sec.baidu.com/views/main/index.html顺丰sfsrc http:// ...

  8. Javascript高级编程学习笔记(12)—— 引用类型(1)Object类型

    前面的文章中我们知道JS中的值分为两种类型 基础类型的值和引用类型的值 基础类型的值我已经大概介绍了一下,今天开始后面几天我会为大家介绍一下引用类型的值 Object类型 对象是引用类型的值的实例,在 ...

  9. JavaScript使用浏览器内置XML解析器解析DOM对象

    所有现代浏览器都内建了供读取和操作 XML 的 XML 解析器.解析器把 XML 转换为 XML DOM 对象 (可通过 JavaScript 操作的对象). 一.获取DOM对象 XMLHttpReq ...

  10. 用Nginx搭建IIS集群实现负载均衡

    长话短说,我们用Nginx来搭建一个简单的集群,实现Web应用的负载均衡,架构图如下: 两台Web服务器,一台静态资源服务器,因为是演示,我们以网站形式部署在本机IIS中 一台Nginx代理服务器,安 ...