image.png

基本概念

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

经典案例之银行转账

image.png
上述过程必须是原子性操作,要么全部执行成功,要不任何一个执行失败,都需要回滚所有步骤,保证数据的一致性。

ACID

一个运行良好的事务处理系统,必须具备这些标准特征。

原子性(Atomicity)

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

一致性(Consistency)

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

隔离性(isolation)

通 常 来 说 , 一 个 事 务 所 做 的 修 改 在 最 终 提 交 以 前 , 对 其 他 事 务 是 不 可 见 的 。 在 前 面 的 例 子 中 , 当 执 行 完 第 三 条 语 句 、 第 四 条 语 句 还 未 开 始 时 , 此 时 有 另 外 一 个 账 户 汇 总 程 序 开 始 运 行 , 则 其 看 到 的 支 票 账 户 的 余 额 并 没 有 被 减 去 200 美 元 。

持久性(Durability)

一 旦 事 务 提 交 , 则 其 所 做 的 修 改 就 会 永 久 保 存 到 数 据 库 中 。 此 时 即 使 系 统 崩 溃 , 修 改 的 数 据 也 不 会 丢 失 。 持 久 性 是 个 有 点 模 糊 的 溉 念 , 因 为 实 际 上 持 久 性 也 分 很 多 不 同 的 级 别 。 有 些 持 久 性 策 略 能 够 提 供 非 常 强 的 安 全 保 障 , 而 有 些 则 未 必 。 而 且 不 可 能 有 能 做 到 100 % 的 持 久 性 保 证 的 策 略 ( 如 果 数 据 库 本 身 就 能 做 到 真 正 的 持 久 性 , 那 么 备 份 又 怎 么 能 增 加 持 久 性 呢 ? ) 。

事务的ACID特性可以保证上述业务不会出错,但实现这一点也需要负责较大的成本。这就会给系统带来更大的开销。一个实现了ACID的数据库,相比没有实现的需要更强的CPU处理能力、更大的内存和更多的磁盘空间。Mysql可以借助存储引擎架构让用户根据自己的业务来选择合适的存储引擎。 对于一些不需要事务的查询类应用,选择非事务型的存储引擎(Myslam),可以获取更高的查询性能。

隔离级别

READ UNCOMMITTED

一个事务还没有提交时,它做的变更就会被别的事务发现。事务可以读取未提交的数据,这也被称为脏读(Dirty Read)。 基本不用!

READ COMMITTED

一个事务提交之后,它做的变更才会被其他事务看见。也被称为不可重复读(unrepeatable read), 因为执行两次同样的查询,可能会得到不一样的结果。

REPEATABLE READ

Mysql 默认的隔离级别。
REPEATBALE READ 解决了脏读的问题。该级别保证了在同一个事务中多次读取同样记录的结果是一致的。但仍在幻读的问题(Phantom Read)。

幻读指的是当某个事务在读取某个范围内的记录时,另外一个事务又在该范围内插入了新的记录,当之前的事务再次读取该范围的记录时,会产生幻行(Phantom Row)。

InnoDB和XtraDB存储引擎通过多版本并发控制(MVCC)解决了幻读的问题。

SERIALIZABLE

SERIALIZABLE是最高的隔离级别。在读取的每一行数据上加锁,强制事务串行执行。出现冲突的时候,后访问的事务必须等前一个事务执行完成才能继续执行。但会给系统带来较多系统开销(大量的超时以及锁争用。适用于业务要求强一致性,一般不使用。

隔离级别 脏读 不可重复读 幻读
READ UNCOMMITTED
READ COMMITTED
REPEATABLE READ
SERIALIZABLE

实现原理

在实现上,数据库里面会创建一个试图,访问的时候以视图的逻辑结果为准。
在RR级别下,这个视图是在事务启动时创建的,整个事务存在期间都用这个视图。
在RC级别下,这个视图是在每个SQL语句开始执行的时候创建的。(MVCC视图会在每一个语句前创建一个,所以下个事务是可以看到另外一个事务已经提交的内容,因为它在每一次查询前都会重新给予最新的数据创建一个新的MVCC视图。)
在RU级别下,直接返回记录上的最新值,没有视图概念。
在Serial,通过加锁的方式来避免并行访问。

死锁

死锁是指两个或者多个事务在同一资源上相互占用,并请求锁定对方占用的资源,从而导致恶性循环的现象。当多个事务试图以不同的顺序锁定资源时,就可能会产生死锁。

事务日志

事 务 日 志 可 以 帮 助 提 高 事 务 的 效 率 。 使 用 事 务 日 志 , 存 储 引 擎 在 修 改 表 的 数 据 时 只 需 要 修 改 其 内 存 拷 贝 , 再 把 该 修 改 行 为 记 录 到 持 久 在 硬 盘 上 的 事 务 日 志 中 , 而 不 用 每 次 都 将 修 改 的 数 据 本 身 持 久 到 磁 盘 。 事 务 日 志 采 用 的 是 追 加 的 方 式 , 因 此 写 日 志 的 操 作 是 磁 盘 上 一 小 块 区 域 内 的 顺 序 1 / 0 , 而 不 像 随 机 , ℃ 需 要 在 磁 盘 的 多 个 地 方 移 动 磁 头 , 所 以 采 用 事 务 日 志 的 方 式 相 对 来 说 要 快 得 多 。 事 务 日 志 持 久 以 后 , 内 存 中 被 修 改 的 数 据 在 后 台 可 以 慢 慢 地 刷 回 到 磁 盘 。 目 前 大 多 数 存 储 引 擎 都 是 这 样 实 现 的 , 我 们 通 常 称 之 为 预 写 式 日 志 ( Write-Ahead Logging) , 修 改 数 据 需 要 写 两 次 磁 盘 。

多版本并发控制

在 MySQL 中,实际上每条记录在更新的时候都会同时记录一条回滚操作。记录上的最新值,通过回滚操作,都可以得到前一个状态的值。(除了记录变更记录,还会记录一条变更相反的回滚操作记录,前者记录在redo log,后者记录在undo log)
image.png
在查询这条记录的时候,不同时刻启动的事务会有不同的 read-view。如图中看到的,在视图 A、B、C 里面,这一个记录的值分别是 1、2、4,同一条记录在系统中可以存在多个版本,就是数据库的多版本并发控制(MVCC)。

可以认为MVCC是行级锁的一个变种,但是它在很多情况下避免了加锁操作,因此开销更低。虽然实现机制有所不同,但大都实现了非阻塞的读操作,写操作也只锁定必要的行。

MVCC的是实现,是通过保存数据在某个时间点的快照来实现的。也就是说,不管需要执行多长时间,每个事务看到的数据都是一致的。根据事务开始的时间不同,每个事务对同一张表,同一时刻看到的数据可能是不一样的。

多版本并发控制解决了哪些问题

1. 读写之间阻塞的问题

通过 MVCC 可以让读写互相不阻塞,即读不阻塞写,写不阻塞读,这样就可以提升事务并发处理能力。

提高并发的演进思路:

  • 普通锁,只能串行执行;
  • 读写锁,可以实现读读并发;
  • 数据多版本并发控制,可以实现读写并发。

2. 降低了死锁的概率

因为 InnoDB 的 MVCC 采用了乐观锁的方式,读取数据时并不需要加锁,对于写操作,也只锁定必要的行。

3. 解决一致性读的问题

一致性读也被称为快照读,当查询数据库在某个时间点的快照时,只能看到这个时间点之前事务提交更新的结果,而不能看到这个时间点之后事务提交的更新结果。