在 MySQL 中 Innodb 存储引擎是支持事务的,事务处理可以用来维护数据库的完整性,要么全部执行,要么全部不执行。本文从InnoDB的基础简介、关键特性、事务特性、事务分类等几方面学习和介绍InnoDB的事务的原理、实现和重要作用。
InnoDB存储引擎是基于磁盘存储的,并将其中的记录按照页的方式进行管理。因此可将其视为基于磁盘的数据库系统(Disk-base Database)。在数据库系统中,由于CPU速度与磁盘速度之间的鸿沟,基于磁盘的数据库系统通常使用缓冲池技术来提高数据库的整体性能。
缓冲池简单来说就是一块内存区域,通过内存的速度来弥补磁盘速度较慢对数据库性能的影响。在数据库中进行读取页的操作,首先将从磁盘读到的页存放在缓冲池中,这个过程称为将页“FIX”在缓冲池中。下一次再读相同的页时,首先判断该页是否在缓冲池中。若在缓冲池中,称该页在缓冲池中被命中,直接读取该页。否则,读取磁盘上的页。
对于数据库中页的修改操作,则首先修改在缓冲池中的页,然后再以一定的频率刷新到磁盘上。这里需要注意的是,页从缓冲池刷新回磁盘的操作并不是在每次页发生更新时触发,而是通过一种称为CheckPoint的机制刷新回磁盘。同样,这也是为了提高数据库的整体性能。
缓冲池中缓存的数据页类型有:索引页、数据页、undo页、插入缓冲(insert buffer)、自适应哈希索引(adaptive hash index)、InnoDB存储的锁信息(lock info)、数据字典信息(data dictionary)等。不能简单地认为,缓冲池只是缓存索引页和数据页,它们只是占缓冲池很大的一部分而已。
数据库中的缓冲池是通过LRU(Latest Recent Used,最近最少使用)算法来进行管理的。即最频繁使用的页在LRU列表的前端,而最少使用的页在LRU列表的尾端。当缓冲池不能存放新读取到的页时,将首先释放LRU列表中尾端的页。
参数 | 功能 |
---|---|
innodb_buffer_pool_size | 缓存池容量大小。 |
innodb_buffer_pool_instances | 缓冲池实例数量。每个页根据哈希值平均分配到不同缓冲池实例中。这样做的好处是减少数据库内部的资源竞争,增加数据库的并发处理能力 |
首先,先来了解下数据插入原理,如下图
整理数据插入的性能,如下
聚簇索引 | 聚簇索引插入方式 | 非聚簇索引 | 非聚簇索引插入方式 | 插入性能 |
---|---|---|---|---|
存在 | 顺序插入 | - | - | 性能高 |
存在 | 无序插入 | - | - | 性能低 |
存在 | 顺序插入 | 非唯一索引 | 顺序插入 | 性能高 |
存在 | 顺序插入 | 唯一索引 | - | 唯一索引需要随机IO检查是否唯一 |
存在 | 顺序插入 | 非唯一索引 | 无序插入 | 数据顺序排放,由于非聚簇索引无序,该部分需要随机读取,性能低 |
综上总结可知,大部分时间是无法保证数据的绝对或相对的顺序插入的,因此为了提高数据插入的性能,InnoDB存储引擎通过insert Buffer这个特有功能来解决随机插入带来的性能问题,可以大幅度提高数据库中非唯一辅助索引的插入性能。
Insert Buffer的使用需要同时满足以下两个条件:
Insert Buffer的使用有以下优点:
在写密集的情况下,插入缓冲会占用过多的缓冲池内存(innodb_buffer_pool),默认最大可以占用到1/2的缓冲池内存。
InnoDB从1.0.x版本开始引入了Change Buffer,可将其视为Insert Buffer的升级。从这个版本开始,InnoDB存储引擎可以对DML操作——INSERT、DELETE、UPDATE都进行缓冲,他们分别是:Insert Buffer、Delete Buffer、Purge buffer。
innodb_change_buffering参数控制Change Buffer缓存支持
参数值 | 含义 |
---|---|
inserts | 支持插入操作 |
deletes | 支持删除操作 |
purges | 支持update操作 |
changes | 支持插入、删除操作 |
all | 支持全部 |
none | 不开启 |
innodb_change_buffer_max_size参数控制占用缓冲池大小,默认25,即占用1/4,最大有效值50,即最大占用1/2
Insert Buffer的数据结构是一棵B+树,在最新的版本中,全局只有一棵Insert Buffer B+树,负责对所有的表的辅助索引进行Insert Buffer。而这棵B+树存放在共享表空间中,默认也就是ibdata1中。
TODO数据结构、原理分析
doublewrite(两次写)带给InnoDB存储引擎的是数据页的可靠性。
如上图,由于MySQL中数据页大小默认是16K(参数innodb_page_size控制),而操作系统的文件存储数据页大小、操作系统的磁盘块大小都不是一致的,因此当从缓冲池刷进磁盘时,要进行多次IO操作写入,也就是说多次刷盘才可以完成脏页写入到磁盘进行最终的持久化,这个过程不是一个原子操作,当遇到异常中断,会出现部分写入(绿色)、部分丢失(红色),造成数据不完整的问题。
doublewrite由两部分组成,一部分为内存中的doublewrite buffer,其大小为2MB,另一部分是磁盘上共享表空间(ibdata1…n)中连续的128个页,即2个区(extent),大小也是2M。
Innodb_dblwr_pages_written / Innodb_dblwr_writes
doublewrite是默认开启的(innodb_doublewrite参数控制),每次脏页刷新必须要先写doublewrite,而doublewrite存在于磁盘上的是两个连续的区,每个区(1MB)由连续的页组成,一般情况下一个区最多有64个页(1024KB区块大小/16KB页大小=64页),所以一次IO写入应该可以最多写64个页。
而根据以上系统Innodb_dblwr_pages_written与Innodb_dblwr_writes的比例来看,越接近64就越说明缓冲池脏页刷盘压力大。
如果操作系统在将页写入磁盘的过程中发生崩溃,在恢复过程中,innodb存储引擎可以从共享表空间的doublewrite中找到该页的一个最近的副本,将其复制到表空间文件,再应用redo log,就完成了恢复过程。因为有副本所以也不担心表空间中数据页是否损坏。
Redo Log记录了数据页修改的物理记录,但是拿不到数据页的真实数据,个人理解这里是Redo Log记录了数据页的物理操作记录,即操作页是哪个,具体偏移量操作是多少,但是没有记录数据页数据内容,就好比你记录了某个人的乘车轨迹,但是你不知道这个人的容貌、穿着
因为Redo Log File文件的刷盘是按照512字节进行填充磁盘的,调用fsync()函数刷盘能一次IO填满磁盘块,要么成功要么失败,不存在非原子多次IO写问题,能保证完整性
哈希查找的时间复杂度是O(1),而B+树的查找速度取决于树深度,生产环境一般为3-4层,即需要查找3-4次,索引往往都是基于B+树进行构建的,为了进一步优化查询速度,InnoDB存储引擎会自动根据访问的频率和模式来自动地为某些热点页建立哈希索引。AHI通过缓冲池的B+树页构造,建立的速度很快,而且不需要对整张表构建哈希索引。MySQL是默认开启的(innodb_adaptive_hash_index参数控制),哈希索引仅支持等值查询。
为了提高磁盘操作性能,当前的数据库系统都采用异步IO(Asynchronous IO,AIO)的方式来处理磁盘操作。InnoDB存储引擎也是。AIO在较早的InnoDB版本中是通过存储引擎的代码模拟实现的,后续则提供了内核级Native AIO支持,依赖操作系统对AIO的支持,目前Windows、Linux都支持,MacOSX未提供支持。
例如要读取表中的三个连续的数据页(space=5,page_no=1)、(space=5,page_no=2)、(space=5,page_no=3),只发送一个IO请求读取3个page共48KB数据即可
其工作原理为:当刷新一个脏页时,InnoDB存储引擎会检测该页所在区(extent)的所有页,如果是脏页,那么一起进行刷新。这样做的好处显而易见,通过AIO可以将多个IO写入操作合并为一个IO操作,故该工作机制在传统机械磁盘下有着显著的优势。(innodb_flush_neighbors参数控制,默认开启)
充分考虑以下问题:
综上可以梳理出的事务特性,就是我们常说的ACID,即原子性、一致性、隔离性、持久性。
特性 | 内容 |
---|---|
原子性 | 一个事务(transaction)中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节。事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。 |
一致性 | 在事务开始之前和事务结束以后,数据库的完整性没有被破坏。这表示写入的资料必须完全符合所有的预设规则,这包含资料的精确度、串联性以及后续数据库可以自发性地完成预定的工作。 |
隔离性 | 数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。事务隔离分为不同级别,包括读未提交(Read uncommitted)、读提交(read committed)、可重复读(repeatable read)和串行化(Serializable)。 |
持久性 | 事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。 |
MySQL中对ACID的实现如下:
特性 | 实现手段 |
---|---|
原子性 | undo log |
持久性 | redo log |
隔离性性 | 锁、mvcc |
一致性性 | 通过原子性、持久性、隔离性的具体实现共同保证 |
MySQL是利用Innodb的undo log作为原子性的实现。
undo log名为回滚日志,是实现原子性的关键,当事务回滚时能够撤销所有已经成功执行的sql语句,他需要记录你要回滚的相应日志信息。
例如
undo log记录了这些回滚需要的信息,当事务执行失败或调用了rollback,导致事务需要回滚,便可以利用undo log中的信息将数据回滚到修改之前的样子。
MySQL利用Innodb的redo log来实现持久性,在InnoDB存储引擎中,通过Force Log At Commit机制预写日志的Write-Ahead-Log(WAL)技术实现事务的持久性,即当事务提交时,必须先将该事务的所有日志记录写入redo log重做日志文件进行持久化。在某种程度上讲,在MySQL中实际存储数据的库表文件并不是最重要的,日志文件却是第一位的!
组成 | 持久性 | 数据结构 | 性能 | 存储位置 | 功能 |
---|---|---|---|---|---|
重做日志缓存(redo log buffer) | 内存容易丢失 | Log Block数组 | 内存操作快 | (用户空间)InnoDB存储引擎内存 | 由于内存操作,加快了Redo Log File 写磁盘速度 |
重做日志文件(redo log file) | 持久存储 | Log Block数组 | 磁盘操作慢 | (磁盘空间)系统磁盘 | 由于是追加顺序写,比数据文件的随机写高效; 实现WAL技术的手段 |
Log Block 即日志块。无论Redo Log Buffer还是Redo Log File都是以Log Block的形式存在的。
CheckPoint技术解决了以下问题:
在InnoDB存储引擎中,Checkpoint发生的时间、条件及脏页的选择等都非常复杂。而Checkpoint所做的事情无外乎是将缓冲池中的脏页刷回到磁盘。不同之处在于每次刷新多少页到磁盘,每次从哪里取脏页,以及什么时间触发Checkpoint。
CheckPoint类型 | 描述 |
---|---|
Sharp Checkpoint | 发生在数据库关闭时将所有的脏页都刷新回磁盘,这是默认的工作方式,即参数innodb_fast_shutdown=1 |
Fuzzy Checkpoint | 只刷新一部分脏页,而不是刷新所有的脏页回磁盘 |
Fuzzy Checkpoint的触发情况:
触发方式 | 触发时机 | 作用 |
---|---|---|
Master Thread Checkpoint | 定时轮做 | 差不多以每秒或每十秒的速度从缓冲池的脏页列表中刷新一定比例的页回磁盘。这个过程是异步的,即此时InnoDB存储引擎可以进行其他的操作,用户查询线程不会阻塞 |
FLUSH_LRU_LIST Checkpoint | 空闲页数量不足时,可以通过参数innodb_lru_scan_depth控制LRU列表中可用页的数量,该值默认为1024 | InnoDB存储引擎要保证LRU列表中有足够的空闲页,若不足会将LRU列表末端页进行移除,若这些末端页中存在脏页会触发CheckPoint InnoDB1.1版本之前,检查LRU列表是否有足够空闲页是在用户的查询线程中,因此该操作会阻塞用户查询操作 InnoDB1.2版本之后,这个检查被放在了一个单独的Page Cleaner线程中进行 |
Async/Sync Flush Checkpoint | Redo Log已满时 | 指的是重做日志文件不可用的情况,这时需要强制将一些页刷新回磁盘,而此时脏页是从脏页列表中选取的 它的作用是为了保证重做日志的循环使用的可用性 在InnoDB 1.2.x版本之前,Async Flush Checkpoint会阻塞发现问题的用户查询线程,而Sync Flush Checkpoint会阻塞所有的用户查询线程,并且等待脏页刷新完成 从InnoDB 1.2.x版本开始,这部分的刷新操作同样放入到了单独的Page Cleaner Thread中,故不会阻塞用户查询线程 |
Dirty Page too much Checkpoint | 其可由参数innodb_max_dirty_pages_pct控制,默认75,即脏页达到75%时 | 即脏页的数量太多,导致InnoDB存储引擎强制进行Checkpoint。其目的总的来说还是为了保证缓冲池中有足够可用的页 |
对于InnoDB存储引擎而言,其是通过LSN(Log Sequence Number) 来标记版本的。而LSN是8字节的数字,其单位是字节。每个页有LSN,重做日志中也有LSN,Checkpoint也有LSN。可以通过命令SHOW ENGINE INNODB STATUS查看
Redo Log 刷盘策略 | 性能 | 安全性 |
---|---|---|
0 | 高 | 无法确保事务完整性,极端情况有innodb_flush_log_at_timeout(默认1)秒数据丢失 |
1 | 低 | 确保事务完整性,主从复制架构确保数据一致性必须设置sync_binlog=1和该值为1 |
2 | 较高 | 无法确保事务完整性,极端情况有innodb_flush_log_at_timeout(默认1)秒数据丢失,由于每次事务提交都刷新OS Buffer,比0策略相对安全 |
以上性能对比,0最高,2次之,1最低。最耗时的操作在调用fsync()刷磁盘操作上,2和0的区别并不大,2比0多了一步就是每次事务提交commit时顺带将Redo Log Buffer刷进OS Buffer,这其实是内存迁移操作并没有太多性能消耗。
重做日志在下列三种情况下会将重做日志缓冲中的内容刷新到外部磁盘的重做日志文件中:
参数 | 功能 |
---|---|
innodb_log_file_size | 该参数决定着mysql事务日志文件(ib_logfile0)的大小,即Log Group中Redo Log File大小 |
innodb_log_files_in_group | 该参数控制日志文件数。默认值为2。mysql 事务日志文件是循环覆写的 |
innodb_log_buffer_size | 该参数确保有足够大的日志缓冲区来保存脏数据在被写入到日志文件之前 |
innodb_log_group_home_dir | 在事务被提交并写入到表空间磁盘文件上之前,事务日志存储在InnoDB的redo日志文件里。为了获得最佳性能,建议分离innodb_data_home_dir和innodb_log_group_home_dir到单独的物理存储阵列上,这样可以保证IO资源不起冲突,利于服务器处理大量高并发连接 |
关于事务隔离级别,一般分为以下四种:
事务隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
读未提交(read-uncommitted) | 是 | 是 | 是 |
不可重复读(read-committed) | 否 | 是 | 是 |
可重复读(repeatable-read) | 否 | 否 | 是 |
串行化(serializable) | 否 | 否 | 否 |
MySQL利用的是锁和MVCC机制来实现隔离性的。
InnoDB存储引擎在事务隔离性上的策略
事务隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
读未提交(read-uncommitted) | 是 | 是 | 是 |
不可重复读(read-committed) | 否 | 是(读undo日志文件最新快照数据) | 是 |
可重复读(repeatable-read) | 否 | 否(读undo日志文件最早快照数据) | 否(next key解决) |
串行化(serializable) | 否 | 否 | 否 |
\ | Lock | Latch |
---|---|---|
对象 | 事务 | 线程 |
保护 | 数据库内容 | 内存数据结构 |
持续时间 | 整个事务过程 | 临界资源 |
模式 | 行锁、表锁、意向锁 | 读写锁、互斥量 |
死锁 | waits for graph、time out等机制进行死锁检查与处理 | 无死锁检查与处理机制,仅通过加锁顺序保证 |
存在于 | Lock Manager的哈希表中 | 每个数据结构的对象中 |
可通过SHOW ENGINE INNODB MUTEX命令查看Latch信息,含义如下:
名称 | 说明 |
---|---|
count | mutex被请求的次数 |
spin_waits | spin_lock自旋锁的次数,InnoDB在latch无法获取锁时会进行自旋,若还不能获取到则进入等待状态 |
spin_rounds | 自选内部循环的总次数,每次自选的内部循环是一个随机数。spin_rounds/spin_waits表示平均每次自旋内部所需的循环次数 |
os_waits | 表示操作系统等待的次数。当spin_lock通过自旋还不能获得latch时,则会进入操作系统等待状态,等待被唤醒 |
os_yields | 进行os_thread_yield唤醒操作的次数 |
os_wait_times | 操作系统等待的时间,单位是ms |
可以看到,latch产生竞态条件时,选择了先进行自旋解决,而不是先选择会引起用户态和内核态切换的系统等待。
从数据库操作角度可划分为读锁、写锁
锁类型 | 是否可读 | 是否可写 | 类别 |
---|---|---|---|
读锁 | 可读 | 不可写 | 乐观、共享锁 |
写锁 | 不可读 | 不可写 | 悲观、排他锁 |
从颗粒度角度可以划分为表锁,行锁、间隙锁
存储引擎 | 表锁 | 行锁 | 间隙锁(next-key locking) |
---|---|---|---|
MyISAM | 支持 | 不支持 | 不支持 |
Innodb | 支持 | 支持 | 支持 |
锁类型 | 开销 | 颗粒度 | 并发冲突 |
---|---|---|---|
表锁 | 小 | 大 | 高 |
行锁 | 大 | 小 | 小 |
间隙锁 | 大 | 中 | 中 |
大部分MySQL存储引擎都支持表级锁
InnoDB存储引擎实现了如下两种标准的行级锁:
行级锁的排它锁和共享锁的兼容性
\ | X | S |
---|---|---|
X | 不兼容 | 不兼容 |
S | 不兼容 | 兼容 |
MySQL在Server层面支持的是表级锁,而InnoDB存储引擎支持行级锁,为了支持事务在行级上的锁和表级上的锁同时存在,使得多层次颗粒度的锁,InnoDB存储引擎提供了意向锁的实现。
锁类型 | IS | IX | S | X |
---|---|---|---|---|
IS | 兼容 | 兼容 | 兼容 | 不兼容 |
IX | 兼容 | 兼容 | 不兼容 | 不兼容 |
S | 兼容 | 不兼容 | 兼容 | 不兼容 |
X | 不兼容 | 不兼容 | 不兼容 | 不兼容 |
通过命令SHOW ENGINE INNODB STATUS及information_schema架构下的表INNODB_TRX、INNODB_LOCKS、INNODB_LOCK_WAITS来观察锁的信息
表INNODB_TRX信息
字段名 | 说明 |
---|---|
trx_id | InnoDB存储引擎内部唯一的事务ID |
trx_state | 当前事务的状态 |
trx_started | 事务的开始时间 |
trx_requested_lock_id | 等待事务的锁ID |
trx_wait_started | 事务等待开始的时间 |
trx_weight | 事务的权重。反映一个事务修改和锁住的行数。发送死锁需要回滚时,InnoDB存储引擎会选择该值最小的进行回滚 |
trx_mysql_thread_id | 执行事务的线程ID,SHOW PROCESSLIST显示的结果 |
trx_query | 事务允许的SQL语句 |
表INNODB_LOCKS信息
字段名 | 说明 |
---|---|
lock_id | 锁的ID |
lock_trx_id | 事务ID |
lock_mode | 锁的模式 |
lock_type | 锁的类型,表锁、行锁 |
lock_table | 要加锁的表 |
lock_index | 锁住的索引 |
loack_space | 锁对象的space_id |
lock_page | 事务锁定页的数量。若是表锁,则为NULL |
lock_rec | 事务锁定行的数量。若是表锁,则为NULL |
lock_data | 事务锁定记录的主键值,若是表锁,则为NULL |
表INNODB_LOCK_WAITS信息
字段名 | 说明 |
---|---|
request_trx_id | 申请锁资源的事务ID |
request_lock_id | 事务的锁的ID |
blocking_trx_id | 阻塞的事务ID |
blocking_lock_id | 阻塞的锁的ID |
在锁的兼容性一节可知,行级锁中的X排它锁会阻塞其他事务的读写操作,但是不同的事务隔离级别有不同的数据读取隔离性要求,InnoDB存储引擎通过多版本控制(Multi Version Concurrency Control,MVCC)来解决这种行级排它锁阻塞其他事务读操作的情况,InnoDB通过undo日志来进行数据回滚,因此若事务读取的数据行正在进行UPATE、DELETE事务,转而选择读取它对应的undo日志快照数据而无需阻塞等待排他锁占用事务的完成,因为undo日志是历史数据不会被修改。非锁定读会极大的提高数据库的并发性能。
非锁定一致读仅在度读已提交READ_COMMITTED和可重复读REPEATABLE_READ两种隔离级别下应用
事务隔离级别 | 快照数据读取方式 | 产生问题 |
---|---|---|
读已提交 READ COMMITTED | 总是读取被锁定行的最新一份快照数据 | 不可重复读、幻读 |
可重复读 REPEATABLE READ | 总是读取事务开始时的行数据版本 | 幻读(这里是为了对比,InnoDB通过Next Key Lock解决了REPEATABLE READ隔离级别下的幻读问题) |
InnoDB存储引擎对于SELECT语句支持两种一致性的锁定读(locking read)操作:
语句 | 锁类型 | 锁对象 | 阻塞\执行 |
---|---|---|---|
SELECT…FOR UPDATE | 排他锁 X | ①表排他锁(X) ②行排他锁(X) + 表意向排他锁(IX) |
参考锁兼容列表 |
SELECT…LOCK IN SHARE MODE | 共享锁 S | ①表共享锁(S) ②行共享锁(S) + 表意向共享锁(IS) |
参考锁兼容列表 |
SELECT…FOR UPDATE的锁类型会根据情况施加表级意向排他锁或者行级锁,具体如下:
下面是对SELECT…FOR UPDATE几种情况的测试思维导图,可以参考:
SELECT…LOCK IN SHARE MODE的锁类型会根据情况施加表级意向共享锁或者行级锁,具体如下:
下面是对SELECT…LOCK IN SHARE MODE几种情况的测试思维导图,可以参考:
行锁算法 | 描述 |
---|---|
行锁(Record Lock) | 单个行记录上的锁,总是会去锁住索引记录,如果InnoDB存储引擎表在建立的时候没有设置任何一个索引,那么这时InnoDB存储引擎会使用隐式的主键来进行锁定 |
间隙锁(Gap Lock) | 锁定一个范围,但不包含记录本身 |
间隙锁+行记录锁(Gap Lock+Record Lock)(Next-Key Lock) | 锁定一个范围,并且锁定记录本身,设计的目的是为了解决幻读问题(Phantom Problem) |
问题 | 描述 |
---|---|
幻读 | 在READ UNCOMMITTED、READ COMMITTED事务隔离级别下会产生,REPEATABLE(Next Key锁)、SERIALIZE(串行化)隔离级别下解决 |
不可重复读 | 在REPEATABLE事务隔离级别下,读取undo日志文件事务开始阶段快照解决 |
丢失更新 | 增加排它锁进行数据锁定进行船串行化来解决 |
阻塞 | 锁存在时,其他事务会话需要等待 |
死锁 | 超时机制; 等待图(wait-for graph)锁的信息链表、事务等待链表,发现链表回路则进行事务回滚,一般回滚undo量最小的事务,死锁检测使用的是深度优先查找算法 |
问题:唯一索引冲突后,事务回滚未插入真实数据,自增键值回滚了吗?
答案:没有回滚
对于外键值的插入或更新,首先需要查询父表中的记录,即SELECT父表。但是对于父表的SELECT操作,不是使用一致性非锁定读的方式,因为这样会发生数据不一致的问题,因此这时使用的是SELECT…LOCK IN SHARE MODE方式,即主动对父表加一个S锁
从数据库层面,数据库通过原子性、隔离性、持久性来保证一致性。也就是说ACID四大特性之中,C(一致性)是目的,A(原子性)、I(隔离性)、D(持久性)是手段,是为了保证一致性,数据库提供的手段。数据库必须要实现AID三大特性,才有可能实现一致性。例如,原子性无法保证,显然一致性也无法保证。
这部分内容摘自《MySQL技术内幕:InnoDB存储引擎》,大部分时间我们都使用的是扁平事务,而且个人看来,根据实际的业务场景来使用事务,尽管事务分类中支持了嵌套、链式等,还是尽量保持事务操作的简洁性和易读性,减少开发和调试难度。关于事务分类深入研究可以参考Jim Gray的《事务处理:概念与技术》
分布式事务一般是在分布式环境下的扁平事务
https://www.cnblogs.com/rjzheng/p/10841031.html
https://www.runoob.com/mysql/mysql-transaction.html
https://blog.csdn.net/zhaoyangjian724/article/details/53285925
https://www.cnblogs.com/wyaokai/p/10921323.html
http://blog.itpub.net/29654823/viewspace-2147683/
https://www.cnblogs.com/geaozhang/p/7241744.html
《MySQL技术内幕:InnoDB存储引擎》第2版
《事务处理:概念与技术》Jim Gray