数据库系列:InnoDB 下实现高并发控制
1 介绍
并发控制是为了防止多用户并发使用数据库时造成数据错误和程序运行错误,保证数据的完整性。当多个事务并发地存取数据库时,就会产生同时读取和/或修改同一数据的情况。若对并发操作不加控制就可能会存取和存储不正确的数据,破坏数据库的一致性(Consistency)。因此,数据库中间件必须提供并发控制(Concurrency Control)机制能力,而 MySQL 的 InnoDB 引擎,很好的支持了这一块。
2 InnoDB 并发控制
MySQL 是一个流行的关系型数据库管理系统,它支持多用户并发访问。并发控制是确保数据库一致性和完整性的重要机制。在 MySQL 中,有几种方法可以实施并发控制:
读写锁(Read-Write Locks):MySQL 使用了读写锁来控制对数据的并发访问。读写锁是共享的,多个客户端可以同时持有读锁,但只有一个客户端可以持有写锁。当一个客户端获得写锁时,其他客户端无法获得读锁或写锁,直到写锁被释放。
事务隔离级别 :MySQL 提供了不同的事务隔离级别,包括读未提交(Read Uncommitted)、读已提交(Read Committed)、可重复读(Repeatable Read)和串行化(Serializable)。较低的隔离级别允许更多的并发访问,但可能导致数据不一致;较高的隔离级别可以确保数据一致性,但会限制并发访问。
锁等待和死锁:MySQL 提供了锁等待和死锁检测机制。当一个事务尝试获取一个已经被其他事务持有的锁时,MySQL 会阻塞等待,直到锁被释放。如果事务之间形成死锁,MySQL 会检测到并终止其中一个事务以解除死锁。
分段锁定(Segmented Locking):MySQL 还支持分段锁定,它允许对数据库的特定部分进行锁定,而不是对整个数据库进行锁定。这对于处理大型数据集时非常有用,因为它可以减少锁定范围,提高并发性能。
乐观并发控制(Optimistic Concurrency Control):MySQL 还支持乐观并发控制,它假设冲突不太可能发生,因此不会立即锁定数据。而是在更新时检查是否存在冲突,如果存在冲突,则进行适当的处理,比如回滚或重试。
多版本并发控制(MVCC):MVCC 允许在事务隔离级别下执行一致性读操作,以提高并发性能。
通过合理配置和使用上述机制,可以在 MySQL 中实现有效的并发控制,来保证在数据库中执行一致性和数据完整性。下面详细来说说通过并发控制保证数据一致性的常见手段:
锁(Locking)
数据多版本(Multi Versioning)
3 锁的基本实现
MySQL 使用锁来保持数据的一致性。在并发控制中,锁是用来防止多个事务同时对同一数据进行修改或删除,以保持数据的一致性。MySQL 中的锁机制包括共享锁和排他锁。锁的基本实现,一般是这样的:
当用户对数据进行操作前,锁住,实施互斥,不允许其它任务的操作;
当前操作完成后,释放锁之后,其他任务才可以执行;
但是存在一个问题,他的执行本质是串行的,无论读写,都无法并行,这样性能太差了,也不符合互联网高并发需求。于是 MySQL 中的锁机制实现了共享锁和排他锁:
共享锁(Shared Lock):多个事务可以同时持有共享锁,用于读取数据,但不允许修改数据。共享锁允许并发读取,提高了并发性能。
排他锁(Exclusive Lock):只有一个事务可以持有排他锁,用于修改数据,不允许其他事务同时修改。排他锁会阻塞其他事务的读写操作,降低了并发性能。
简而言之就是:
共享锁之间不互斥,即读读可以并行,这样提高了数据读取的并发能力
排他锁与任何锁互斥,所以写读,写写不可以并行,其他线程的读写操作都在锁释放之后才能执行,这样对并发度是有较大影响的
所以说,单纯的锁机制,还是不满足需求,为了保证写任务没有完成之时,其他读的任务也可以并发执行,我们就需要使用另外一个能力来补充。那就是数据多版本(Multi Versioning)
4 数据多版本的实现原理
MySQL 的并发控制是通过多版本并发控制(MVCC)实现的。MVCC 允许在事务隔离级别下执行一致性读操作,以提高并发性能。在 MySQL 的 MVCC 中,每个数据行都有多个版本,每个版本都表示该行在不同时间点的状态。当一个事务读取数据时,它只看到该事务开始之前存在的数据版本,而不是当前最新的数据版本。这种方式允许并发读取多个数据版本,而不会相互阻塞,进一步提高并发的效果。详细拆分开来,读写同步执行的原理是这样的:
执行写任务发时,Clone 一份数据,打上新的版本号,与原版本号区分
写任务实际操作的是克隆那个版本的数据,直至操作并提交完成后
读任务可以并发执行,持续读取,读的是原版本的数据,并不会造成阻塞
如图所示,可以分成这几个步骤去解读:
初始数据版本为 V1.0
T1 发起了一个写任务,这时候把数据 clone 了一份,进行修改,版本变为 V2.0,这时候修改进行中,任务还未完成
T2 并发了一个读任务,依然读的是 V1.0 版本的数据
T3 又并发了一个读任务,依然不会阻塞,读的还是 V1.0 的版本
这时候数据修改,V1.0 的数据变为 V2.0 的数据
T4 这时候发起的度任务,读到的就是 V2.0 的数据了
从这边可以看出,数据多版本,读写之间不需要阻塞,能够极大提高任务的并发能力。
普通意义上的锁机制,本质是串行执行,效率十分低下
读写锁,可以实现读读并发,但是写读依然是互斥的,也不符合互联网的机制
数据多版本(Multi Versioning),才是实现读写并发的要素
5 MySQL 数据多版本的相关实现
5.1 概念介绍
在 MySQL 的 InnoDB 存储引擎中,使用 MVCC(多版本并发控制,Multiversion Concurrency Control)实现多版本控制。MVCC 的实现主要基于 undo 日志、redo 日志、rollback segment 回滚存储区间、和 read view。undo 日志用于回滚操作,而 read view 用于生成数据行的历史版本。通过这种方式,InnoDB 实现了非阻塞的一致性读操作。
redo 日志数据库事务提交后,必须将更新的数据刷到磁盘上,以保证 ACID 特性。磁盘随机写性能较低,且过度频繁的刷盘,会极大影响数据库的吞吐量。优化方式是将修改行为先写到 redo 日志里,这样随机就变成了有序性,再按照时间周期将数据持久化到磁盘上,极大提高了性能。另外一方面,即使数据库崩溃,恢复之后也可以从 redo 日志里面获取操作 Log,重新提交事务操作,然后刷盘,最终保证数据的一致性。
undo 日志数据库事务未提交时,会将事务修改数据的 Mirror Data(修改前的版本 )存放到 Undo Log 中,它的主要作用是在事务执行过程中,如果发生错误或者需要回滚操作,可以通过 Undo Log 中的记录来撤销已经执行的操作,恢复数据到事务开始之前的状态。另外一方面,数据库奔溃时,也可以使用 undo 日志,撤销未提交事务,保证事务的 ACID 特性。
insert 操作:undo 日志存储新数据的 PK(ROW_ID),回滚时执行删除即可。
delete/update 操作:undo 日志存储旧数据 row(整行数据),回滚时直接恢复。
rollback segmentRollback Segment(回滚存储区)是数据库中的一部分存储空间,用于临时保存当数据库数据发生改变时的先前值。它主要有两个作用:
通过 Rollback 操作来取消数据操作,使之恢复到改变之前的原始值,在 transaction 的过程才有效。如果执行了 commit 命令,那么 Rollback Segment 里面的值就会标识为失效,数据的改变将永久化。
select 读取的同时另一个事务也在修改这个表的值,那么 select 出来的数据是修改前的值,因为修改之前的原数据存入到了 Rollback Segment 中,所以不会被阻塞到。
5.2 示例说明
5.2.1 初始数据
先初始化一个默认的表,里面模拟几条数据。此时没有任何的事务未提交操作,所以回滚段是空的,如上图。
5.2.2 事务操作示例
还未执行 commit 或者 rollback,所以事务处于未提交的状态
综上,我们可以看出 Commit 之前我们进行如下操作:
正式提交删除前,id=1 的数据作为旧版本的数据,进入了回滚存储区;
正式提交修改前,id=2 的数据作为旧版本的数据,进入了回滚存储区;
新插入的数据 ('Lili', 1, 18), id= 4,在正式提交之前,也进入了回滚段;
我们上面说了,不是所有的操作最终都会 commit,如果失败,事务 rollback,就可以通过回滚存储区中的 undo 日志对操作进行回滚。
如果成功 commit,则整体提交成功了
可以看到:
id=1 数据删除成功;
id=2 字段更新成功;
id=4 数据行插入成功;
回滚存储区相关日志清掉
如果失败并执行 rollback,则全部回滚
数据删除的恢复了
被修改的旧数据也恢复
新增写入的数据删除
回滚存储区相关日志清掉
6 总结
MySQL 实现并发控制,保证数据一致性的方法有锁,数据多版本等
普通锁串行,读写锁读读并行,Multi Versioning 读写并行;
redo 日志保证已提交事务的 ACID 特性, undo 日志用来回滚未提交的事务,rollback segment 为临时回滚存储区;
InnoDB 是基于多版本并发控制的存储引擎;
InnoDB 用的多版本是快照读不加锁,所有 select 都是快照读,这些数据不会被修改,并发性能特别高;
文章转载自:Hello-Brand
评论