文章目录
- 前置知识
- 1. 了解存储引擎
- 1.1 什么是存储引擎
- 1.2 存储引擎的作用
- 2. 了解 MySql 架构
- 2.1 MySql--架构组成
- 2.1.1 MySql--架构图
- 2.1.2 MySql--架构组件
- 2.1.3 MySql--最大特点
- 2.1.4 MySql--语句执行步骤
- 2. 详解引擎--InnoDB
- 2.1 InnoDB--特点
- 2.2 InnoDB--实现架构详解
- 2.2.1 InnoDB 实现架构图示
- 2.2.2 InnoDB 实现架构图--内存池详解
- 2.2.3 InnoDB 实现架构图--内存池--详解缓冲池
- 2.2.3.1 详解--Checkpoint 机制
- 2.2.4 InnoDB 实现架构图--内存池--详解重做日志缓存
- 2.2.4.1 详解--重做日志
- 2.2.5 InnoDB 实现架构图--内存池--详解额外的内存池
- 2.2.6 InnoDB 实现架构图--线程
- 3. 详解引擎--MyISAM
- 3.1 MyISAM--特点
- 3.2 MyISAM--锁相关
- 3.3 MyISAM--存储格式
- 4. 详解--事务
- 4.1 事务是什么
- 4.2 事务的目的
- 4.3 事务的四个特性
- 4.4 事务并发存在的问题
- 4.4.1 脏读
- 4.4.2 不可重复读
- 4.4.3 幻读
- 4.5 解决事务并发问题--四大隔离级别
- 4.5.1 四大隔离级别--读未提交
- 4.5.2 四大隔离级别--读已提交
- 4.5.3 四大隔离级别--可重复读
- 4.5.4 四大隔离级别--串行化
- 4.5.5 总结--四大隔离级别分别存在哪些并发问题
- 4.5.6 了解数据库是如何保证事务的隔离性的--MVCC
- 5. 详解 MVCC
- 5.1 了解什么是 MVCC
- 5.2 了解 MVCC 原理
- 5.3 了解 MVCC 实现原理
- 5.3.1 事务版本号
- 5.3.2 隐式字段
- 5.3.3 undo log
- 5.3.4 版本链
- 5.3.5 快照读和当前读
- 5.3.6 Read View
- 5.4 MVCC 举例说明
- 6. 详解 锁
- 6.1 什么是锁
- 6.2 MySql 锁分类
- 6.3 详解--粒度锁
- 6.3.1 全局锁
- 6.3.1.1 概念
- 6.3.1.2 **实现方式**
- 6.3.1.3 **风险**
- 6.3.2 表级锁
- 6.3.2.1 概念
- 6.3.2.2 详解表级锁--表锁
- 6.3.2.3 详解表级锁--元数据锁
- 6.3.3 页级锁
- 6.3.3.1 概念
- 6.3.4 行级锁
- 6.3.4.1 概念
- 6.3.4.2 实现方式
- 6.4 乐观锁和悲观锁
- 6.4.1 乐观锁
- 6.4.1.1 概念
- 6.4.1.2 应用场景
- 6.4.1.3 实现方式
- 6.4.1.4 实例
- 6.4.2 悲观锁
- 6.4.2.1 概念
- 6.4.2.2 应用场景
- 6.4.2.3 实现方式
- 6.4.2.4 实例
- 6.5 共享锁 和 排它锁
- 6.5.1 共享锁
- 6.5.1.1 概念
- 6.5.1.2 实现方式
- 6.5.1.3 行级共享锁使用实例
- 6.5.2 排它锁
- 6.5.2.1 概念
- 6.5.2.2 应用场景
- 6.5.2.3 行级排它锁使用实例
- 6.5.2 排它锁--间隙锁、临键锁、记录锁
- 6.5.2.1 概念
- 6.5.2.2 排它锁--记录锁
- 6.5.2.3 排它锁--间隙锁
- 6.5.2.4 排它锁--临键锁
- 6.6 意向锁
- 6.6.1 概念
- 6.6.2 作用
- 6.6.3 意向锁和表级锁互斥性
- 6.6.4 实例
- 7. 详解 索引
- 7.1 索引由谁实现
- 7.2 索引建立时间
- 7.3 MySql 索引数据结构
- 8. binlog
前置知识
-
数据库:存储数据的物理操作系统文件或其他形式文件的集合
- MySql 是一个单进程多线程架构的数据库
-
数据库实例:MySql 数据库实例由后台线程以及一个共享内存区组成,负责操作数据库文件。
- MySql 数据库实例在系统上表现就是一个进程。
1. 了解存储引擎
1.1 什么是存储引擎
- MySQL中的数据用各种不下同的技术存储在文件中,每一种技术都使用不同的存储机制、索引技巧、锁定水平并最终提供不同的功能和能力,这些不同的技术以及配套的功能在MySQL中称为存储引擎。
1.2 存储引擎的作用
-
存储引擎是MySQL将数据存储在文件系统中的存储方式或者存储格式。
-
存储引擎是MySQL数据库中的组件,负责执行实际的数据I/O操作。
-
MySQL系统中,存储引擎处于文件系统之上
- 在数据保存到数据文件之前会传输到存储引擎,之后按照各个存储引擎的存储格式进行存储。
2. 了解 MySql 架构
2.1 MySql–架构组成
2.1.1 MySql–架构图

2.1.2 MySql–架构组件
由上图架构图知:
-
连接池组件 – Connection Pool
-
管理服务和工具组件 – Management Services & utilities
-
SQL接口组件 – SQL Interface
-
查询分析器组件 – Praser
-
优化器组件 – Optimizer
-
缓冲组件 – Caches & Buffers
-
插件式表存储引擎 – Pluggable Storage Enginess
-
物理文件 – Files & Logs
2.1.3 MySql–最大特点
- MySQL 区别于其他数据库的一个最重要的特点是
插件式存储引擎。- 即 MySql 存储引擎是
基于表的,而不是数据库。
- 即 MySql 存储引擎是
2.1.4 MySql–语句执行步骤
本节结合上文 MySql 架构图理解更佳。

-
连接器(即架构图中连接池)
负责客户端与服务端进行连接的工作。 -
查询缓存
- MySQL收到一个查询请求后,会先到查询缓存里检查一下,之前是不是执行过这条语句。
- 注意:
MySQL8.0 已经移除了查询缓存功能。
-
词法分析器
- 查询缓存没有命中的话,就会开始真正执行SQL语句了。也就是第三步:词法分析器。
- SQL经过分析器分析以后会生成类似如下的一个语法树:

- 至此分析器的工作任务也基本完成了,接下来进入到优化器。
-
优化器
- SQL语句经过分析器分析后,在开始执行之前,还要先经过优化器的处理。
- 优化器是在表里面有多个索引的时候,决定使用哪个索引;
或者在一个语句有多表关联(join)的时候,决定各个表的连接顺序;
以及一些mysql自己内部的优化机制。 - 最终 优化器会选择一个最优的查询路径。
-
执行器
这里面包含两步:- 发送
- 把查询优化器选择的结果作为任务发送给底层的存储引擎去真正的执行。
- 我们的数据放在内存或者磁盘,存储引擎其实就是执行 sql 语句的,他会按照一定的步骤查询内存缓存数据,更新磁盘数据等。
也就是说 索引什么的都是存储引擎这里用的。下文详解。
- 我们的数据放在内存或者磁盘,存储引擎其实就是执行 sql 语句的,他会按照一定的步骤查询内存缓存数据,更新磁盘数据等。
- 把查询优化器选择的结果作为任务发送给底层的存储引擎去真正的执行。
- 接收
- 不断接收 存储引擎已经执行过的语句的结果,返回给相应的任务(即对应的查询请求任务)。
- 发送
由 MySql 语句执行步骤可知存储引擎至关重要。
2. 详解引擎–InnoDB
MySQL从 5.5.5 版本开始,默认的存储引擎为InnoDB
2.1 InnoDB–特点
- 独立表空间
- 支持
MVCC 行锁设计,提供一致性非锁定读- 支持外键
- 插入缓冲,二次写,自适应哈希索引,预读
- 使用聚集的方式存储数据,每张表的存储都是按主键顺序存放。
2.2 InnoDB–实现架构详解
2.2.1 InnoDB 实现架构图示

2.2.2 InnoDB 实现架构图–内存池详解
- 主要作用
1. 维护所有进程/线程需要使用的多个内部数据结构
2. 缓存磁盘上的数据,方便快速地读取,同时对磁盘文件数据修改之前在这里缓存
3. 重做日志缓存 - 内存池构成

2.2.3 InnoDB 实现架构图–内存池–详解缓冲池
-
InnoDB 将表数据拆分为若干固定大小的页。
-
InnoDB 是基于磁盘存储(外存)的,并将其中的记录按照
页的方式进行管理(即一个数据库表分为多个页加载进来)。 -
缓冲池就是一块内存区域,主要缓冲数据页和索引页。
-
InnoDB 的对页(可以理解为数据)的读取
- 首先判断该页是否在缓冲池中 ,
若在,直接读取该页,
若不在则从磁盘读取页数据,并存放在缓冲池中。
- 首先判断该页是否在缓冲池中 ,
-
InnoDB 对页(可以理解为数据)的修改操作
- 首先修改在缓冲池中的页,
再以一定的频率(Checkpoint机制)刷新到磁盘。
参数:innodb_buffer_pool_size设置缓冲池大小
- 首先修改在缓冲池中的页,
-
缓冲池的管理
- 通过LRU(Latest Recent Used,最近最少使用)算法进行管理。
最频繁使用的页在LRU列表前端,最少使用的页在尾端,当缓冲池不能存放新读取的页时,首先释放LRU列表尾端的页(页数据刷新到磁盘,并从缓冲次中删除)。 - InnoDB对于新读取的页,不是放到LRU列表最前端,而是放到midpoint位置(默认为5/8处)。
这是因为一些SQL操作会访问大量的页(如全表扫描),读取大量非热点数据,如果直接放到首部,可能导致真正的热点数据被移除。
- 通过LRU(Latest Recent Used,最近最少使用)算法进行管理。
2.2.3.1 详解–Checkpoint 机制
InnoDB 对于对于DML语句操作(如Update或Delete),事务提交时只需在缓冲池中中完成操作,然后再通过 Checkpoint 将修改后的脏页数据刷新到磁盘。
InnoDB 有两种 Checkpoint:
-
Sharp Checkpoint:数据库关闭时将所有脏页刷新会磁盘
-
Fuzzy Checkpoint:
- Master Thread Checkpoint
Master Thread每个1秒或10秒按一定比例将缓存池的脏页列表刷新会磁盘 - FLUSH LRU LIST Checkpoint
Page Cleaner线程发现LRU列表中可用页数量少于innodb_lru_scan_depth(1024),就将LRU列表尾端移除,如果这些页中有脏页,就需要Checkpoint - Async/Sync Flush Checkpoint
重做日志文件空间不可以用时,将一部分脏页刷新到磁盘。 - Dirty Page too much Checkpoint:
脏页数量太多(超过比例innodb_max_dirty_pages_pct,默认75),执行Checkpoint。
- Master Thread Checkpoint
2.2.4 InnoDB 实现架构图–内存池–详解重做日志缓存
- 重做日志先放到这个缓冲区,然后按一定频率刷新到重做日志文件。
参数:innodb_log_buffer_size- 刷新规则:
Master Thread每秒将一部分重做日志缓冲刷新到重做日志文件
每一事务提交时会将重做日志刷新到重做日志文件(如果配置了)
重做日志缓冲区使用空间大于1/2
- 刷新规则:
2.2.4.1 详解–重做日志
-
重做日志是为了保证事务的原子性,持久性。
InnoDB采用 Write Ahread Log 策略,事务提交时,先写重做日志,再修改页。- 数据库宕机重启时通过执行重做日志恢复数据。
但由于Checkpoint机制,数据库宕机重启并不需要重做所有的日志,因为Checkpoint之前的页都刷新到磁盘了,只需执行最新一次Checkpoint后的重做日志进行恢复,这样可以缩短数据库的恢复时间。
- 数据库宕机重启时通过执行重做日志恢复数据。
-
InnoDB中重做日志文件是循环使用的。
当页被Checkpoint刷新到磁盘后,对应的重做日志就不需要使用 ,其空间可以被覆盖重用。
如果待写入的重做日志文件空间不可用(脏页还没有刷新到磁盘),就需要强制产生Checkpoint,将缓冲池中的页至少刷新到当前重做日志的位置。 -
InnoDB 1.2.x(MySql 5.6)后,FLUSH LRU LIST Checkpoint 以及 Async/Sync Flush Checkpoint 操作放到 Page Cleaner 线程,以免阻塞用户线程。
2.2.5 InnoDB 实现架构图–内存池–详解额外的内存池
额外的内存池, 即内存堆,对InnoDB内部使用的数据结构对象进行管理
2.2.6 InnoDB 实现架构图–线程
-
主要作用:
- 负责刷新内存池中的数据,保证缓冲池的内存缓冲的是最近的数据
- 已修改的数据文件刷新到磁盘文件
- 保证数据库发生异常的情况下InnoDB能恢复到正常状态。
-
InnoDB运行时主要有以下线程
-
Master Thread
负责将缓冲池中的数据异步刷新到磁盘,保证数据的一致性,包括脏页的刷新,合并插入缓冲(INSERT BUFFER),UNDO页的回收等。 -
IO Thread
- 负责AIO请求的回调处理。
参数:innodb_read_io_threads,innodb_write_io_threads
- 负责AIO请求的回调处理。
-
Purge Thread
- 事务提交后,undo log可能不再需要,由Purge Thread负责回收并重新分配的这些已经使用的undo页。
注意:Purge Thread需要离散地读取undo页。
- 事务提交后,undo log可能不再需要,由Purge Thread负责回收并重新分配的这些已经使用的undo页。
-
Page Cleaner Thread
- InnoDB 1.2.x引入,将Master Threader中刷新脏页的工作移至该线程,如上面说的FLUSH LRU LIST Checkpoint以及Async/Sync Flush Checkpoint。
-
Master Thread
- Master Thread具有最高的线程优先级别,内部由多个循环组成:主循环(loop),后台循环(backgroup loop),刷新循环(flush loop),暂停循环(suspend loop),Master Thread根据数据库运行状态在以上循环切换。
-
3. 详解引擎–MyISAM
3.1 MyISAM–特点
- MylSAM 不支持事务,
- 不支持**外键约束
- 只支持全文索引
- 缓冲池只缓存索引文件,不缓冲数据文件
- 由MYD和MYI文件组成,MYD用来存放数据文件,MYI用来存放索引文件
3.2 MyISAM–锁相关
- 表级锁定形式,数据在更新时锁定整个表。
- 读写相关
数据库在读写过程中相互阻塞:- 数据写入的过程会阻塞用户数据的读取
- 数据读取的过程中也会阻塞用户的数据写入
3.3 MyISAM–存储格式
-
支持3种不同的存储格式
-
静态(固定长度)表
- 静态表是默认的存储格式。
- 静态表中的字段都是非可变字段,这样每个记录都是固定长度的,这种存储方式的优点是存储非常迅速,容易缓存,出现故障容易恢复;缺点是占用的空间通常比动态表多。
-
动态表
- 动态表包含可变字段,记录不是固定长度的,这样存储的优点是占用空间较少,但是频繁的更新、删除记录会产生碎片,需要定期执行OPTIMIZE TABLE语句或myisamchk-r命令来改善性能,并且出现故障的时候恢复相对比较困难(因为会产生磁盘碎片,而且存储空间不是连续的)。
-
压缩表
压缩表由 myisamchk 工具创建,占据非常小的空间,因为每条记录都是被单独压缩的,所以只有非常小的访问开支。(压缩的过程中会占用CPU性能)
-
4. 详解–事务
4.1 事务是什么
- 事务,由一个有限的数据库操作序列构成,这些操作要么全部执行,要么全部不执行,是一个不可分割的工作单位。
4.2 事务的目的
- 是为了保证数据的最终一致性。
4.3 事务的四个特性
即 ACID
- 原子性(Atomicity)
事务作为一个整体被执行,包含在其中的对数据库的操作要么全部都执行,要么都不执行。 - 一致性(Consistency)
指在事务开始之前和事务结束以后,数据不会被破坏。
假如A账户给B账户转10块钱,不管成功与否,A和B的总金额是不变的。 - 隔离性(Isolation)
多个事务并发访问时,事务之间是相互隔离的,一个事务不应该被其他事务干扰,多个并发事务之间要相互隔离。 - 持久性(Durability)
表示事务完成提交后,该事务对数据库所作的操作更改,将持久地保存在数据库之中
4.4 事务并发存在的问题
事务并发会 引起脏读、不可重复读、幻读问题。
4.4.1 脏读
-
什么是脏读
如果一个事务读取到另一个未提交事务修改过的数据,我们就称发生了脏读现象。
-
例子
假设现在有两个事务 A、B:- 假设现在 A 的余额是 100,事务 A 正在准备查询Jay的余额
- 事务 B 先扣减 Jay 的余额,扣了 10,但是还没提交
- 最后 A 读到的余额是 90,即扣减后的余额

- 因为事务 A 读取到事务 B 未提交的数据,这就是脏读。
4.4.2 不可重复读
-
什么是不可重复读
同一个事务内,前后多次读取,读取到的数据内容不一致 -
例子
假设现在有两个事务 A 和 B:-
事务 A 先查询Jay的余额,查到结果是100
-
这时候事务 B 对 Jay 的账户余额进行扣减,扣去 10 后,提交事务
-
事务 A 再去查询 Jay 的账户余额发现变成了 90

-
事务 A 被事务 B 干扰到了!在事务 A 范围内,两个相同的查询,读取同一条记录,却返回了不同的数据,这就是不可重复读。
-
4.4.3 幻读
-
什么是幻读
如果一个事务先根据某些搜索条件查询出一些记录,在该事务未提交时,另一个事务写入了一些符合那些搜索条件的记录(如insert、delete、update),就意味着发生了幻读。 -
例子
假设现在有两个事务A、B:- 事务A先查询id大于2的账户记录,得到记录id=2和id=3的两条记录
- 这时候,事务B开启,插入一条id=4的记录,并且提交了
- 事务A再去执行相同的查询,却得到了id=2,3,4的3条记录了。

- 事务 A 查询一个范围的结果集,另一个并发事务 B 往这个范围中插入新的数据,并提交事务,然后事务 A 再次查询相同的范围,两次读取到的结果集却不一样了,这就是幻读。
4.5 解决事务并发问题–四大隔离级别
为了解决并发事务存在的脏读、不可重复读、幻读等问题,数据库大叔设计了四种隔离级别。分别是读未提交,读已提交,可重复读,串行化(Serializable)。
4.5.1 四大隔离级别–读未提交
- 读未提交隔离级别,只限制了两个数据不能
同时修改,但是修改数据的时候,即使事务未提交,都是可以被别的事务读取到的;
这级别的事务隔离有脏读、重复读、幻读的问题。
4.5.2 四大隔离级别–读已提交
- 读已提交隔离级别,当前事务只能读取到其他事务
提交的数据。
所以这种事务的隔离级别解决了脏读问题,但还是会存在重复读、幻读问题。
4.5.3 四大隔离级别–可重复读
- 可重复读隔离级别,限制了读取数据的时候,不可以进行修改,所以解决了重复读的问题。
但是读取范围数据的时候,是可以插入数据,所以还会存在幻读问题;
4.5.4 四大隔离级别–串行化
- 事务最高的隔离级别,在该级别下,所有事务都是进行串行化顺序执行的。可以避免脏读、不可重复读与幻读所有并发问题。但是这种事务隔离级别下,事务执行很耗性能。
4.5.5 总结–四大隔离级别分别存在哪些并发问题
| 隔离级别 | 脏读 | 不可重复读 | 幻读 |
|---|---|---|---|
| 读未提交 | √ | √ | √ |
| 读已提交 | × | √ | √ |
| 可重复读 | × | × | √ |
| 串行化 | × | × | × |
4.5.6 了解数据库是如何保证事务的隔离性的–MVCC
- 数据库是通过加锁,来实现事务的隔离性的。这就好像,如果你想一个人静静,不被别人打扰,你就可以在房门上加上一把锁。
- 加锁确实好使,可以保证隔离性。比如串行化隔离级别就是加锁实现的。但是频繁的加锁,导致读数据时,没办法修改,修改数据时,没办法读取,大大降低了数据库性能。
- 如何解决加锁后的性能问题?
答案就是,MVCC(Multi-Version Concurrency Control ,多版本并发控制)!- 它实现读取数据不用加锁,可以让读取数据同时修改。修改数据时同时可读取。
5. 详解 MVCC
参考链接
5.1 了解什么是 MVCC
- MVCC,即Multi-Version Concurrency Control (多版本并发控制)。
- 它是一种并发控制的方法,一般在数据库管理系统中,实现对数据库的并发访问,在编程语言中实现事务内存。
- 用更好的方式去处理读写冲突,能有效提高数据库并发性能。
5.2 了解 MVCC 原理
- 通俗的讲,数据库中同时存在多个版本的数据,并不是整个数据库的多个版本,而是某一条记录的多个版本同时存在
在某个事务对其进行操作的时候,需要查看这一条记录的隐藏列事务版本 id,比对事务 id 并根据事物隔离级别去判断读取哪个版本的数据。
5.3 了解 MVCC 实现原理
相关知识点如下
5.3.1 事务版本号
事务每次开启前,都会从数据库获得一个 自增长的事务ID,可以从事务ID判断事务的执行先后顺序。
这就是事务版本号。
5.3.2 隐式字段
对于 InnoDB 存储引擎,
每一行记录都有两个隐藏列 trx_id、roll_pointer,
如果表中没有主键和非 NULL 唯一键时,则还会有第三个隐藏的主键列 row_id。
| 列名 | 是否必须 | 描述 |
|---|---|---|
| row_id | 否 | 单调递增的行ID,不是必需的,占用6个字节。 |
| trx_id | 是 | 记录操作该数据事务的事务ID |
| roll_pointer | 是 | 这个隐藏列就相当于一个指针,指向回滚段的undo日志 |
5.3.3 undo log
-
什么是 undo log
- undo log,回滚日志,用于记录数据被修改前的信息。
在表记录修改之前,会先把数据拷贝到undo log里,如果事务回滚,即可以通过 undo log来还原数据。 - 可以这样认为
- 当 delete 一条记录时,undo log 中会记录一条对应的 insert 记录
- 当 update 一条记录时,它记录一条对应相反的update记录。
- undo log,回滚日志,用于记录数据被修改前的信息。
-
undo log 的作用
- 事务回滚时,保证原子性和一致性。
- 用于MVCC快照读。
5.3.4 版本链
- 多个事务并行操作某一行数据时,不同事务对该行数据的修改会产生多个版本。
然后通过回滚指针(roll_pointer),连成一个链表,这个链表就称为版本链。
如下:

- 通过版本链,我们就可以看出事务版本号、表格隐藏的列和 undo log 它们之间的关系。
我们再来小分析一下,例子:- 假设现在有一张 core_user 表,表里面有一条数据,id为1,名字为孙权:

- 现在开启一个事务 A:
对 core_user 表执行update core_user set name ="曹操" where id=1,会进行如下流程操作- 首先获得一个事务 ID=100
- 把 core_user 表修改前的数据,拷贝到 undo log
- 修改 core_user 表中,id=1的数据,名字改为曹操
- 把修改后的数据事务 Id=101 改成当前事务版本号,并把 roll_pointer 指向 undo log 数据地址。
下面这个图有问题,指错了。
- 假设现在有一张 core_user 表,表里面有一条数据,id为1,名字为孙权:
5.3.5 快照读和当前读
-
快照读
- 读取的是记录数据的可见版本(有旧的版本)。不加锁,普通的select语句都是快照读,如:
select * from core_user where id > 2;
- 读取的是记录数据的可见版本(有旧的版本)。不加锁,普通的select语句都是快照读,如:
-
当前读
- 读取的是记录数据的最新版本,显式加锁的都是当前读。如
select * from core_user where id > 2 for update; select * from account where id>2 lock in share mode;
- 读取的是记录数据的最新版本,显式加锁的都是当前读。如
5.3.6 Read View
-
什么是 Read View
它就是事务执行SQL语句时,产生的读视图。实际上在innodb中,每个SQL语句执行前都会得到一个Read View。 -
Read View有什么用
它主要是用来做可见性判断的,即判断当前事务可见哪个版本的数据~
那么Read View是如何保证可见性判断的:- m_ids : 当前系统中那些活跃(未提交)的读写事务 ID, 它数据结构为一个 List。
- min_limit_id : 表示在生成 ReadView 时,当前系统中活跃的读写事务中最小的事务 id,即 m_ids 中的最小值。
- max_limit_id : 表示生成 ReadView 时,系统中应该分配给下一个事务的 id 值。
- creator_trx_id : 创建当前 read view 的事务 ID
-
Read view 匹配条件规则如下:
-
如果数据事务 ID
trx_id<min_limit_id,表明生成该版本的事务在生成Read View前,已经提交(因为事务 ID 是递增的),所以该版本可以被当前事务访问。 -
如果
trx_id>=max_limit_id,表明生成该版本的事务在生成 ReadView 后才生成,所以该版本不可以被当前事务访问。 -
如果
min_limit_id=<trx_id<max_limit_id,需腰分 3 种情况讨论- (1).如果m_ids包含trx_id,则代表Read View生成时刻,这个事务还未提交,但是如果数据的trx_id等于creator_trx_id的话,表明数据是自己生成的,因此是可见的。
- (2)如果 m_ids 包含 trx_id,并且 trx_id 不等于 creator_trx_id,则 Read View 生成时,事务未提交,并且不是自己生产的,所以当前事务也是看不见的;
- (3).如果 m_ids 不包含 trx_id,则说明你这个事务在 Read View 生成之前就已经提交了,修改的结果,当前事务是能看见的。
-
5.4 MVCC 举例说明
参考链接–第 4 节
6. 详解 锁
参考链接
6.1 什么是锁
锁是计算机在执行多线程或线程时用于并发访问同一共享资源时的同步机制。
MySQL 中的锁是在 服务器层或者存储引擎层 实现的,保证了数据访问的一致性与有效性。
6.2 MySql 锁分类

6.3 详解–粒度锁
6.3.1 全局锁
6.3.1.1 概念
全局锁就是对整个数据库实例加锁。
6.3.1.2 实现方式
- MySQL 提供了一个加全局读锁的方法,命令是
Flush tables with read lock(FTWRL)。- 需要让整个库处于只读状态的时候,可以使用这个命令,之后其他线程的以下语句会被阻塞。
6.3.1.3 风险
-
如果在主库上备份,那么在备份期间都不能执行更新,业务基本上就能停止。
-
如果在从库上备份,那么备份期间从库不能执行主库同步过来的binlog,会导致主从延迟。
- 解决风险办法
mysqldump 使用参数--single-transaction,启动一个事务,确保拿到一致性视图。而由于 MVCC 的支持,这个过程中数据是可以正常更新的。
6.3.2 表级锁
6.3.2.1 概念
-
就是对当前操作的整张表加锁
-
最常使用的 MyISAM 与 InnoDB 都支持表级锁定。
-
MySQL 里面表级别的锁有两种:
- 一种是表锁
- 一种是元数据锁(
meta data lock,MDL)
-
注意:
一般行锁都有锁超时时间。但是MDL 锁没有超时时间的限制,只要事务没有提交就会一直锁住。
6.3.2.2 详解表级锁–表锁
- 语法
lock tables … read/write; - 例子:
例如lock tables t1 read, t2 write;命令,则其他线程写 t1、读写 t2 的语句都会被阻塞。
同时,线程 A 在执行 unlock tables 之前,也只能执行读 t1、读写 t2 的操作。
连写 t1 都不允许,自然也不能在 unlock tables 之前 访问其他表。
6.3.2.3 详解表级锁–元数据锁
-
实现方式
MDL 不需要显式使用,在访问一个表的时候会被自动加上,在 MySQL 5.5 版本中引入了 MDL- 当对一个表做增删改查操作的时候,加 MDL 读锁;
- 当要对表做结构变更操作的时候,加 MDL 写锁。
-
风险点 及 解决办法
参考链接
6.3.3 页级锁
6.3.3.1 概念
- 页级锁是 MySQL 中锁定粒度介于行级锁和表级锁中间的一种锁。
- 表级锁速度快,但冲突多
- 行级锁冲突少,但速度慢。
- 因此,采取了折衷的页级锁,一次锁定相邻的一组记录。
BDB 引擎支持页级锁。
6.3.4 行级锁
6.3.4.1 概念
-
行级锁是粒度最低的锁,发生锁冲突的概率也最低、并发度最高。
但是加锁慢、开销大,容易发生死锁现象。 -
MySQL 中只有 InnoDB 支持行级锁
-
行级锁分为 共享锁 和 排他锁 。
6.3.4.2 实现方式
- 在 MySQL 中,行级锁并不是直接锁记录,而是
锁索引。-
索引分为主键索引和非主键索引两种
- 如果一条 sql 语句操作了主键索引,MySQL 就会锁定这条主键索引;
- 如果一条 sql 语句操作了非主键索引,MySQL 会先锁定该非主键索引,再锁定相关的主键索引。
-
在 UPDATE、DELETE 操作时,MySQL 不仅锁定 WHERE 条件扫描过的所有索引记录,而且会锁定相邻的键值,即所谓的
next-key locking。
-
6.4 乐观锁和悲观锁
6.4.1 乐观锁
6.4.1.1 概念
- 乐观锁是相对悲观锁而言的,乐观锁假设数据一般情况下不会造成冲突,所以在数据进行提交更新的时候,才会正式对数据的冲突与否进行检测,如果发现冲突了,则返回给用户错误的信息,让用户决定如何去做。
6.4.1.2 应用场景
- 适用于读多写少,因为如果出现大量的写操作,写冲突的可能性就会增大,业务层需要不断重试,会大大降低系统性能。
6.4.1.3 实现方式
- 一般使用数据版本(Version)记录机制实现,
- 在数据库表中增加一个数字类型的“version”字段来实现。
- 当读取数据时,将version字段的值一同读出,数据每更新一次,对此version值加一。
- 当我们提交更新的时候,判断数据库表对应记录的当前版本信息与第一次取出来的version值进行比对,如果数据库表当前版本号与第一次取出来的version值相等,则予以更新,否则认为是过期数据。

- 在数据库表中增加一个数字类型的“version”字段来实现。
6.4.1.4 实例
- 订单order表中id,status,version分别代表订单ID,订单状态,版本号。
- 查询订单信息
select id,status,versionfrom order where id=#{id}; - 用户支付成功
- 修改订单状态
update set status=支付成功,version=version+1 where id=#{id} and version=#{ version};
- 查询订单信息
6.4.2 悲观锁
6.4.2.1 概念
- 悲观锁,正如其名,具有强烈的独占和排他特性,每次去拿数据的时候都认为别人会修改,对数据被外界(包括本系统当前的其他事务,以及来自外部系统的事务处理)修改持保守态度,因此,在整个数据处理过程中,将数据处于锁定状态。
6.4.2.2 应用场景
适用于并发量不大、写入操作比较频繁、数据一致性比较高的场景。
6.4.2.3 实现方式
- 在 MySQL 中使用悲观锁,必须关闭 MySQL 的自动提交,
set autocommit=0。 共享锁 和 排它锁 是 悲观锁 的不同的实现,它俩都属于悲观锁的范畴。
6.4.2.4 实例
- 商品goods表中id,name,number分别代表商品ID,商品名称,商品库存。
- 开启事务并关闭自动提交
setautocommit=0; - 查询商品信息
selectid,name,number from goods where id=1 for update; - 用户下单,生成订单
- 修改商品库存
updateset number= number-1 where id=1; - 提交事务
commit;
- 开启事务并关闭自动提交
- 说明:
- select…for update是MySQL提供的实现悲观锁的方式,属于排它锁,在goods表中,id为1的那条数据就被当前事务锁定了,其它的要执行select id,name,number from goods where id=1for update;的事务必须等本次事务提交之后才能执行。这样我们可以保证当前的数据不会被其它事务修改。
- 注意: MySQL
InnoDB默认行级锁。行级锁都是基于索引的,如果一条SQL语句用不到索引是不会使用行级锁的,会使用表级锁把整张表锁住。
6.5 共享锁 和 排它锁
四种粒度锁都有 共享锁 和 排它锁。
常用的是 行级 共享/排它锁, 表级 共享/排它锁。
6.5.1 共享锁
6.5.1.1 概念
- 共享锁,又称之为读锁,简称S锁。
- 当事务 A 对数据加上读锁后,其他事务只能对该数据加读锁,不能做任何修改操作,也就是不能添加写锁。只有当事务A上的读锁被释放后,其他事务才能对其添加写锁。
- 应用场景
- 共享锁主要是为了支持并发的读取数据而出现的。
- 读取数据时,不允许其他事务对当前数据进行修改操作,从而避免”不可重读”的问题的出现。
6.5.1.2 实现方式
select …lock in share mode
6.5.1.3 行级共享锁使用实例
-
session1
加读锁读,且未解锁

-
session2 的查询不受影响,但是update操作会被一直阻塞,直到超时

6.5.2 排它锁
6.5.2.1 概念
- 排它锁,又称之为写锁,简称X锁
- 当事务对数据加上写锁后,其他事务既不能对该数据添加读写,也不能对该数据添加写锁,写锁与其他锁都是互斥的。只有当前数据写锁被释放后,其他事务才能对其添加写锁或者是读锁。
MySQL InnoDB 引擎默认 update,delete,insert 都会自动给涉及到的数据加上排他锁,select 语句默认不会加任何锁类型。
6.5.2.2 应用场景
-
写锁主要是为了解决在修改数据时,不允许其他事务对当前数据进行修改和读取操作,从而可以有效避免”脏读”问题的产生。
-
实现方式
select …for update -
示例
-
session1 排它锁查询

-
session2也做排它锁查询会被阻塞。

-
6.5.2.3 行级排它锁使用实例
- session1 中先设置不自动提交事务
set autocommit=0;,然后更新,此时事务未提交

- 然后再操作 session 2

由于 session1 迟迟未提交事务,session2 在等待 session1 释放锁时出现了超过锁定超时的警告了。- 如果session2执行id=2的操作会不会成功呢?
- 执行id=2的操作是可以成功的。

- 执行id=2的操作是可以成功的。
- 如果session2执行id=2的操作会不会成功呢?
6.5.2 排它锁–间隙锁、临键锁、记录锁
6.5.2.1 概念
- 记录锁、间隙锁、临键锁都是排它锁,而记录锁的使用方法跟排它锁介绍一致。
6.5.2.2 排它锁–记录锁
-
记录锁是封锁记录,记录锁也叫 行锁
-
例如:
select *from goods where id = 1 for update;它会在 id=1 的记录上加上记录锁,以阻止其他事务插入,更新,删除 id=1 这一行。
6.5.2.3 排它锁–间隙锁
-
间隙锁 基于非唯一索引,它锁定一段范围内的索引记录。
使用间隙锁锁住的是一个区间,而不仅仅是这个区间中的每一条数据。 -
实例
- session1 使用 间隙锁,
select* from goods where id between 1 and 10 for update;
即所有在(1,10)区间内的记录行都会被锁住,所有id 为 2、3、4、5、6、7、8、9 的数据行的插入会被阻塞,但是 1和 10 两条记录行并不会被锁住。

- session2 就会阻塞

- session1 使用 间隙锁,
6.5.2.4 排它锁–临键锁
-
概念
- 临键锁,是记录锁与间隙锁的组合,它的封锁范围,既包含索引记录,又包含索引区间,是一个左开右闭区间。
-
临键锁的主要目的
- 也是为了避免幻读(Phantom Read)。如果把事务的隔离级别降级为RC,临键锁则也会失效。
-
临键锁位置
- 每个数据行上的非唯一索引列上都会存在一把临键锁,当某个事务持有该数据行的临键锁时,会锁住一段左开右闭区间的数据。
- 需要强调的一点是,InnoDB 中行级锁是基于索引实现的,
临键锁只与非唯一索引列有关,在唯一索引列(包括主键列)上不存在临键锁。
-
实例
- session1
- goods 表中隐藏的临键锁有:
(-∞, 96],(96, 99],(99, +∞] - session1 在对 number 为 96 的列进行 update 操作的同时,也获取了
(-∞, 96],(96, 99]这两个区间内的临键锁。
- goods 表中隐藏的临键锁有:
- session2 尝试 插入 97,出现等待超时。
- 在根据非唯一索引对记录行进行
UPDATE \ FOR UPDATE \LOCK IN SHARE MODE操作时,InnoDB 会获取该记录行的临键锁,公式为:左gap lock + record lock + 右gap lock。
即session1在执行了上述的 SQL 后,最终被锁住的记录区间为 (-∞, 99)。
这也是 session2 超时原因。
- 在根据非唯一索引对记录行进行
- session1
6.6 意向锁
6.6.1 概念
- 意向锁是表锁,为了协调行锁和表锁的关系,支持多粒度(表锁与行锁)的锁并存。
6.6.2 作用
- 当有事务 A 有行锁时,MySQL 会自动为该表添加意向锁
事务B如果想申请整个表的写锁,那么不需要遍历每一行判断是否存在行锁,而直接判断是否存在意向锁,增强性能。
6.6.3 意向锁和表级锁互斥性
意向锁不会与 行级排它/共享锁 互斥。
| 意向共享锁(IS) | 意向排它锁(IX) | |
|---|---|---|
| 表级-共享锁(S) | 兼容 | 互斥 |
| 表级-排它锁(X) | 互斥 | 互斥 |
6.6.4 实例
-
session1 获取了某一 行的排他锁,并未提交:
select*from goods where id=1 for update;
此时 goods 表存在两把锁:- goods 表上的 意向排它锁
- id 为 1 的数据 行级排它锁。

-
session2 想要获取 goods 表的共享锁:
LOCK TABLES goods READ;- 此时 session2 检测session1 持有 goods 表的意向排他锁,就可以得知 session1 必然持有该表中某些数据行的排他锁,
- 那么 session2 对 goods 表的加锁请求就会被排斥(阻塞),而无需去检测表中的每一行数据是否存在排它锁。

7. 详解 索引
参考链接1
参考链接2
参考链接3
7.1 索引由谁实现
- 索引机制最终是由存储引擎实现,因此不同存储引擎下的索引文件,其保存在本地的格式也并不相同。
7.2 索引建立时间
- 建立索引的时间, 在表数据越少时越好。
- 原因
假设表中有一千万条数据,那创建索引时,就需要将索引字段上的 1000W 个值全部拷贝到本地索引文件中,同时做好排序并与表数据产生映射关系。
7.3 MySql 索引数据结构
- MySQL索引常用的数据结构如下:
B+Tree类型:MySQL中最常用的索引结构,大部分引擎支持,有序。Hash类型:大部分存储引擎都支持,字段值不重复的情况下查询最快,无序。R-Tree类型:MyISAM引擎支持,也就是空间索引的默认结构类型。T-Tree类型:NDB-Cluster引擎支持,主要用于MySQL-Cluster服务中。
- MySQL索引支持的数据结构还有
R+、R*、QR、SS、X树等结构。
但为何后续的一些索引结构大家没听说过呢?这是因为索引到