From c098f3c7ec350e27c4617275c9bd5fabc9f1f73c Mon Sep 17 00:00:00 2001 From: "ruyu.li" Date: Sat, 21 Aug 2021 17:11:45 +0800 Subject: [PATCH] JMM --- Architecture.md | 1 + Database.md | 258 ++++++++++++++++++++++++++---------------------- 2 files changed, 139 insertions(+), 120 deletions(-) diff --git a/Architecture.md b/Architecture.md index 381f1d2..341aadd 100644 --- a/Architecture.md +++ b/Architecture.md @@ -1729,6 +1729,7 @@ https://wx.com/oauth/token? ## 简化模式 简化模式(Implicit Grant)。 + ![简化模式](images/Architecture/简化模式.png) - 第一步:用户访问页面时,重定向到认证服务器 diff --git a/Database.md b/Database.md index c056ae6..21b5928 100644 --- a/Database.md +++ b/Database.md @@ -604,13 +604,21 @@ WHERE A.EMP_SUPV_ID = B.EMP_ID; 从粒度上来说就是**表锁、页锁、行锁**。表锁有意向共享锁、意向排他锁、自增锁等。行锁是在引擎层由各个引擎自己实现的。但并不是所有的引擎都支持行锁,比如 `MyISAM`引擎就`不支持行锁`。 +## 全局锁 + + + + + ## 行锁(Record Locks) 在 `InnoDB` 事务中,行锁通过给索引上的索引项加锁来实现。即只有通过索引条件检索数据,`InnoDB` 才使用行级锁,否则将使用表锁。行级锁定同样分为两种类型:`共享锁` 和`排他锁`,以及加锁前需要先获得的 `意向共享锁` 和 `意向排他锁`。 行锁是在需要的时候才加上的,但并不是不需要了就立刻释放,而是要等到事务结束时才释放。这个就是两阶段锁协议。 -### 共享锁(Shared Locks) + + +**共享锁(Shared Locks)** 共享锁又称为 `S锁` 或 `读锁`。若事务T对数据对象A加上 `S锁`,则事务T `只能读A`;其他事务只能再对A加S锁,而不能加X锁,直到T释放A上的S锁。这就保证了其他事务可以读A,但在T释放A上的S锁之前不能对A做任何修改。 @@ -618,7 +626,7 @@ WHERE A.EMP_SUPV_ID = B.EMP_ID; -### 排它锁(Exclusive Locks) +**排它锁(Exclusive Locks)** 排它锁又称为 `X锁` 或 `写锁`。若事务T对数据对象A加上X锁,则只允许T读取和修改A,其它任何事务都不能再对A加任何类型的锁,直到T释放A上的锁。它防止任何其它事务获取资源上的锁,直到在事务的末尾将资源上的原始锁释放为止。在更新操作(INSERT、UPDATE 或 DELETE)过程中始终应用排它锁。 @@ -628,9 +636,46 @@ WHERE A.EMP_SUPV_ID = B.EMP_ID; -## 行锁实现算法 +**行锁实现算法** + +- Record Lock(记录锁) +- Gap Lock(间隙锁) +- Next-Key Lock(间隙锁) + + + +**MVCC** + +**MVCC**主要是通过**版本链**和**ReadView**来实现的。在Mysql的InnoDB引擎中,只有**已提交读(READ COMMITTD)**和**可重复读(REPEATABLE READ)**这两种隔离级别下的事务采用了MVCC机制。 + +- **版本链** + + 在InnoDB引擎表中,它的每一行记录中有两个必要的隐藏列: + + - `DATA_TRX_ID`:表示插入或更新该行的最后一个事务的事务标识符,同样删除在内部被视为更新,在该更新中,行中的特殊位被设置为将其标记为已删除。行中会有一个特殊位置来标记删除。 + - `DATA_ROLL_PTR`:存储了一个指针,它指向这条记录的上一个版本的位置,通过它来获得上一个版本的记录信息。 + + **作用**:解决了读和写的并发执行。 + +- **ReadView** + + ReadView主要存放的是当前事务操作时,系统中仍然活跃着的事务(事务开启后,没有提交或回滚的事务)。 + + - **ReadView数据结构**:ReadView是MySQL底层使用C++代码实现的一个结构体,主要的内部属性如下: + - `trx_ids`:数组,存储的是创建readview时,活跃事务链表里所有的事务ID + - `low_limit_id`:存储的是创建readview时,活跃事务链表里最大的事务ID + - `up_limit_id`:存储的是创建readview时,活跃事务链表里最小的事务ID + - `creator_trx_id`:当前readview所属事务的事务版本号 -### Record Lock(行锁) + - **ReadView创建策略**:对于读提交和可重复读事务隔离级别来说,ReadView创建策略是不同的,这样才能保证隔离性不同 + - `可重复读隔离级别`:事务开启后,第一次查询的时候创建,之后一直不变,直到事务结束 + - `读提交隔离级别`:事务开启后,每一次读取都重新创建 + + 也就是说已提交读隔离级别下的事务在每次查询的开始都会生成一个独立的ReadView,而可重复读隔离级别则在第一次读的时候生成一个ReadView,之后的读都复用之前的ReadView。 + + + +### Record Lock(记录锁) 单个行记录上的锁,总是会去锁住索引记录。 @@ -660,36 +705,30 @@ WHERE A.EMP_SUPV_ID = B.EMP_ID; -## MVCC +## 表级锁 -**MVCC**主要是通过**版本链**和**ReadView**来实现的。在Mysql的InnoDB引擎中,只有**已提交读(READ COMMITTD)**和**可重复读(REPEATABLE READ)**这两种隔离级别下的事务采用了MVCC机制。 +MySQL 里面表级别的锁有这几种: -### 版本链 +- 表锁 +- 元数据锁(MDL) +- 意向锁 +- AUTO-INC 锁 -在InnoDB引擎表中,它的每一行记录中有两个必要的隐藏列: +### 表锁 -- `DATA_TRX_ID`:表示插入或更新该行的最后一个事务的事务标识符,同样删除在内部被视为更新,在该更新中,行中的特殊位被设置为将其标记为已删除。行中会有一个特殊位置来标记删除。 -- `DATA_ROLL_PTR`:存储了一个指针,它指向这条记录的上一个版本的位置,通过它来获得上一个版本的记录信息。 -**作用**:解决了读和写的并发执行。 +### 元数据锁(MDL) -### ReadView -ReadView主要存放的是当前事务操作时,系统中仍然活跃着的事务(事务开启后,没有提交或回滚的事务)。 +### 意向锁 -- **ReadView数据结构**:ReadView是MySQL底层使用C++代码实现的一个结构体,主要的内部属性如下: - - `trx_ids`:数组,存储的是创建readview时,活跃事务链表里所有的事务ID - - `low_limit_id`:存储的是创建readview时,活跃事务链表里最大的事务ID - - `up_limit_id`:存储的是创建readview时,活跃事务链表里最小的事务ID - - `creator_trx_id`:当前readview所属事务的事务版本号 -- **ReadView创建策略**:对于读提交和可重复读事务隔离级别来说,ReadView创建策略是不同的,这样才能保证隔离性不同 - - `可重复读隔离级别`:事务开启后,第一次查询的时候创建,之后一直不变,直到事务结束 - - `读提交隔离级别`:事务开启后,每一次读取都重新创建 -也就是说已提交读隔离级别下的事务在每次查询的开始都会生成一个独立的ReadView,而可重复读隔离级别则在第一次读的时候生成一个ReadView,之后的读都复用之前的ReadView。 +### AUTO-INC 锁 + + @@ -1065,38 +1104,6 @@ SELECT * FROM products WHERE id LIKE '3' FOR UPDATE; # 数据库 -## 存储引擎 - -InnoDB 和 MyISAM 的比较: - -- 事务:InnoDB 是事务型的,可以使用 Commit 和 Rollback 语句 -- 并发:MyISAM 只支持表级锁,而 InnoDB 还支持行级锁 -- 外键:InnoDB 支持外键 -- 备份:InnoDB 支持在线热备份 -- 崩溃恢复:MyISAM 崩溃后发生损坏的概率比 InnoDB 高很多,而且恢复的速度也更慢 -- 其它特性:MyISAM 支持压缩表和空间数据索引 - -### InnoDB - -- InnoDB 是 MySQL 默认的事务型存储引擎,只要在需要它不支持的特性时,才考虑使用其他存储引擎。 -- InnoDB **采用 MVCC 来支持高并发**,并且**实现了四个标准隔离级别**(未提交读、提交读、可重复读、可串行化)。其默认级别时可重复读(REPEATABLE READ),在可重复读级别下,通过 MVCC + Next-Key Locking 防止幻读。 -- 主索引时聚簇索引,在索引中保存了数据,从而避免直接读取磁盘,因此对主键查询有很高的性能。 -- InnoDB 内部做了很多优化,包括从磁盘读取数据时采用的可预测性读,能够自动在内存中创建 hash 索引以加速读操作的自适应哈希索引,以及能够加速插入操作的插入缓冲区等。 -- InnoDB 支持真正的在线热备份,MySQL 其他的存储引擎不支持在线热备份,要获取一致性视图需要停止对所有表的写入,而在读写混合的场景中,停止写入可能也意味着停止读取。 - - - -### MyISAM - -- 设计简单,数据以紧密格式存储。对于只读数据,或者表比较小、可以容忍修复操作,则依然可以使用它。 -- 提供了大量的特性,包括压缩表、空间数据索引等。 -- 不支持事务。 -- 不支持行级锁,只能对整张表加锁,读取时会对需要读到的所有表加共享锁,写入时则对表加排它锁。但在表有读取操作的同时,也可以往表中插入新的记录,这被称为并发插入(CONCURRENT INSERT)。 -- 可以手工或者自动执行检查和修复操作,但是和事务恢复以及崩溃恢复不同,可能导致一些数据丢失,而且修复操作是非常慢的。 -- 如果指定了 DELAY_KEY_WRITE 选项,在每次修改执行完成时,不会立即将修改的索引数据写入磁盘,而是会写到内存中的键缓冲区,只有在清理键缓冲区或者关闭表的时候才会将对应的索引块写入磁盘。这种方式可以极大的提升写入性能,但是在数据库或者主机崩溃时会造成索引损坏,需要执行修复操作。 - - - ## 锁类型 锁是数据库系统区别于文件系统的一个关键特性。锁机制用于管理对共享资源的并发访问。 @@ -2113,7 +2120,54 @@ desc 和asc混用时会导致索引失效 -# 存储引擎 +# MySQL原理 + +## 架构设计 + +![MySQL架构设计](images/Database/MySQL架构设计.jpg) + +从上面的示意图可以看出,MySQL从上到下包含了:**客户端、Server层和存储引擎层**。 + +- **客户端**:可以是我们常用的MySQL命令行窗口,或者是Java的客户端程序等 +- **Server层**:连接器、查询缓存、分析器、优化器和执行器等。大部分MySQL对用户提供的功能都在这一层实现,包括了内置函数的实现,存储过程、触发器、视图等 +- **存储层**:存储引擎层负责数据的存储和提取,存储引擎的实现是插件式的。也就是说用户可以选择自己所需要的存储引擎,如InnoDB、MyISAM等 + + + +### 连接器 + +连接器是MySQL服务端对外的门户,当我们使用命令行黑窗口或者JDBC的Connection.connect(),连接到MySQL Server端时,会校验用户名和密码;然后会查询用户对应的权限列表。当连接建立后,后续的权限范围就在此时确定了,如果连接没有断开的情况下,更改了用户的权限,此时对于该连接也不生效。 + + + +### 查询缓存 + +当连接建立完成后,执行select 语句的时候,就会来到查询缓存。MySQL会将Select 语句为 KEY,将查询结果为VALUE 的形式保存在内存中。如果匹配到对应的 KEY 就会直接从内存中返回结果。 + +但是常我们不会使用MySQL自身的查询缓存,因为当有一条Update 或 Insert 的改表语句时,就会清空对该表的所有查询缓存。缓存的粒度比较大,可以考虑类似 Redis 的分布式缓存做业务数据的缓存。在MySQL 8.0 中,查询缓存直接被移除了。 + + + +### 分析器 + +如果在查询缓存中没有查到数据,就要真正的开始执行SQL语句了。分析器首先会做“词法分析”。词法分析就是识别上面字符串,id、name 是表的字段名,T 是表的名称等等。之后就是语法分析,如果SQL有语法错误,在此时就会报错。 + + + +### 优化器 + +当分析器处理过之后,MySQL就知道SQL 要干什么了,但是此时还需要优化器对待执行的SQL 进行优化。当然MySQL 提供的优化器,相比其他几款商用收费的数据库来说还是比较弱的。当然MySQL 的优化器还是可以对 join 操作,表达式计算等等进行优化,本篇不做过多的介绍。 + + + +### 执行器 + +执行阶段,首先会检查当前用户有没有权限操作该 SQL 语句。如果有,则继续执行后续的操作。 + + + + +## 存储引擎 InnoDB 和 MyISAM 的比较 @@ -2124,7 +2178,9 @@ InnoDB 和 MyISAM 的比较 - 崩溃恢复:MyISAM 崩溃后发生损坏的概率比 InnoDB 高很多,而且恢复的速度也更慢。 - 其它特性:MyISAM 支持压缩表和空间数据索引。 -## InnoDB引擎 + + +### InnoDB引擎 InnoDB 是一个事务安全的存储引擎,它具备提交、回滚以及崩溃恢复的功能以保护用户数据。InnoDB 的行级别锁定保证数据一致性提升了它的多用户并发数以及性能。InnoDB 将用户数据存储在聚集索引中以减少基于主键的普通查询所带来的 I/O 开销。为了保证数据的完整性,InnoDB 还支持外键约束。默认使用B+TREE数据结构存储索引。 @@ -2160,7 +2216,7 @@ InnoDB 是一个事务安全的存储引擎,它具备提交、回滚以及崩 -## MyISAM引擎 +### MyISAM引擎 MyISAM既不支持事务、也不支持外键、其优势是访问速度快,但是表级别的锁定限制了它在读写负载方面的性能,因此它经常应用于只读或者以读为主的数据场景。默认使用B+TREE数据结构存储索引。 @@ -2196,52 +2252,6 @@ MyISAM既不支持事务、也不支持外键、其优势是访问速度快, -# MySQL原理 - -## 架构设计 - -![MySQL架构设计](images/Database/MySQL架构设计.jpg) - -从上面的示意图可以看出,MySQL从上到下包含了:**客户端、Server层和存储引擎层**。 - -- **客户端**:可以是我们常用的MySQL命令行窗口,或者是Java的客户端程序等 -- **Server层**:连接器、查询缓存、分析器、优化器和执行器等。大部分MySQL对用户提供的功能都在这一层实现,包括了内置函数的实现,存储过程、触发器、视图等 -- **存储层**:存储引擎层负责数据的存储和提取,存储引擎的实现是插件式的。也就是说用户可以选择自己所需要的存储引擎,如InnoDB、MyISAM等 - - - -### 连接器 - -连接器是MySQL服务端对外的门户,当我们使用命令行黑窗口或者JDBC的Connection.connect(),连接到MySQL Server端时,会校验用户名和密码;然后会查询用户对应的权限列表。当连接建立后,后续的权限范围就在此时确定了,如果连接没有断开的情况下,更改了用户的权限,此时对于该连接也不生效。 - - - -### 查询缓存 - -当连接建立完成后,执行select 语句的时候,就会来到查询缓存。MySQL会将Select 语句为 KEY,将查询结果为VALUE 的形式保存在内存中。如果匹配到对应的 KEY 就会直接从内存中返回结果。 - -但是常我们不会使用MySQL自身的查询缓存,因为当有一条Update 或 Insert 的改表语句时,就会清空对该表的所有查询缓存。缓存的粒度比较大,可以考虑类似 Redis 的分布式缓存做业务数据的缓存。在MySQL 8.0 中,查询缓存直接被移除了。 - - - -### 分析器 - -如果在查询缓存中没有查到数据,就要真正的开始执行SQL语句了。分析器首先会做“词法分析”。词法分析就是识别上面字符串,id、name 是表的字段名,T 是表的名称等等。之后就是语法分析,如果SQL有语法错误,在此时就会报错。 - - - -### 优化器 - -当分析器处理过之后,MySQL就知道SQL 要干什么了,但是此时还需要优化器对待执行的SQL 进行优化。当然MySQL 提供的优化器,相比其他几款商用收费的数据库来说还是比较弱的。当然MySQL 的优化器还是可以对 join 操作,表达式计算等等进行优化,本篇不做过多的介绍。 - - - -### 执行器 - -执行阶段,首先会检查当前用户有没有权限操作该 SQL 语句。如果有,则继续执行后续的操作。 - - - ## 日志系统 **生产优化** @@ -2266,9 +2276,9 @@ MyISAM既不支持事务、也不支持外键、其优势是访问速度快, -### redo log(重做日志) +### 重做日志(redo log) -在MySQL里,如果我们要执行一条更新语句。执行完成之后,数据不会立马写入磁盘,因为这样对磁盘IO的开销比较大。MySQL里面有一种叫做WAL(Write-Ahead Logging),就是先写日志在写磁盘。就是当有一条记录需要更新的时候,InnoDB 会先写redo log 里面,并更新内存,这个时候更新的操作就算完成了。之后,MySQL会在合适的时候将操作记录 flush 到磁盘上面。当然 flush 的条件可能是系统比较空闲,或者是 redo log 空间不足时。redo log 文件的大小是固定的,比如可以是由4个1GB文件组成的集合。如下图所示: +在MySQL里,如果我们要执行一条更新语句。执行完成之后,数据不会立马写入磁盘,因为这样对磁盘IO的开销比较大。MySQL里面有一种叫做WAL(Write-Ahead Logging),就是先写日志再写磁盘。就是当有一条记录需要更新的时候,InnoDB 会先写redo log 里面,并更新内存,这个时候更新的操作就算完成了。之后,MySQL会在合适的时候将操作记录 flush 到磁盘上面。当然 flush 的条件可能是系统比较空闲,或者是 redo log 空间不足时。redo log 文件的大小是固定的,比如可以是由4个1GB文件组成的集合。如下图所示: ![redolog位置指针](images/Database/redolog位置指针.jpg) @@ -2278,20 +2288,6 @@ write pos 是当前要写入日志的位置,当写到末尾时,会重新到 **redo log写入流程** -前面介绍过了 redo log 的写入首先会写入 redo log cache,其详细的状态如下所示: - -![redolog写入流程](images/Database/redolog写入流程.jpg) - -redo log 对应上面的 3 种状态分别是: - -- **在 MySQL 应用的 redo log buffer 中** -- **write 到文件系统的 page cache 中,但是没有进行实际的写盘操作(fsync)** -- **执行 fsync 之后,写盘结束** - -InnoDB 有一个后台线程,每个 1 秒钟 就会将 redo log buffer 中的日志,调用 write 写入到 文件系统的 page cache 中,然后再调用 fsync 持久化到磁盘中。redo log buffer 是共享的,因此一些正在执行中的事务的 redo log 也有可能被持久化到磁盘中。 - -通常我们说的 MySQL 的 “双1” 操作,指的是 `sync_binlog = 1 AND innodb_flush_log_at_trx_commit = 1` 。`innodb_flush_log_at_trx_commit` 设置成 `1` 表示 redo log 在 prepare 阶段就需要持久化一次,那么 “双1” 配置 每个事务提交的时候都会刷盘 2 次,一次是 `binlog`,一次是 `redo log`。 - 为了控制 redo log 的写入策略,innodb_flush_log_at_trx_commit 会有下面 3 中取值: - **0:每次提交事务只写在 redo log buffer 中** @@ -2306,7 +2302,7 @@ redo log 实际的触发 fsync 操作写盘包含以下几个场景: -### binlog(归档日志) +### 归档日志(binlog) 通过MySQL的架构可以看出,MySQL服务端主要分为2大块:Server层 和 引擎层。redo log 本身是 InnoDB所特有的日志,而Server 层也有自己的日志,那就是binlog。至于为什么会有两种日志,这就是历史原因了。最开始,MySQL原生的存储引擎是MyISAM。它本身不支持事务的特性,而InnoDB 是另外一家公司以插件的形式开发的,为了支持事务等特性,引入了 redo log。两者主要有以下区别: @@ -2338,6 +2334,28 @@ redo log 实际的触发 fsync 操作写盘包含以下几个场景: +### 回滚日志(undo log) + + + +### 错误日志(errorlog) + + + +### 慢查询日志(slow query log) + + + +### 一般查询日志(general log) + + + +### 中继日志(relay log) + + + + + ## 查询过程 ![MySQL查询过程](images/Database/MySQL查询过程.png)