diff --git a/docs/mysql.md b/docs/mysql.md index ede72b1..86aff8a 100644 --- a/docs/mysql.md +++ b/docs/mysql.md @@ -22,23 +22,18 @@ tags: - [2.3. 字符串](#23-字符串) - [2.4. 时间和日期](#24-时间和日期) - [3. 索引](#3-索引) - - [3.1. B Tree 原理](#31-b-tree-原理) - - [3.2. 索引分类](#32-索引分类) - - [3.3. 索引的优点](#33-索引的优点) - - [3.4. 索引优化](#34-索引优化) + - [3.1. 索引的优点和缺点](#31-索引的优点和缺点) + - [3.2. 索引类型](#32-索引类型) + - [3.3. 索引数据结构](#33-索引数据结构) + - [3.4. 索引原则](#34-索引原则) - [4. 查询性能优化](#4-查询性能优化) - [4.1. 使用 Explain 进行分析](#41-使用-explain-进行分析) - [4.2. 优化数据访问](#42-优化数据访问) - [4.3. 重构查询方式](#43-重构查询方式) -- [5. 切分](#5-切分) - - [5.1. 水平切分](#51-水平切分) - - [5.2. 垂直切分](#52-垂直切分) - - [5.3. Sharding 策略](#53-sharding-策略) - - [5.4. Sharding 存在的问题及解决方案](#54-sharding-存在的问题及解决方案) -- [6. 复制](#6-复制) - - [6.1. 主从复制](#61-主从复制) - - [6.2. 读写分离](#62-读写分离) -- [7. 参考资料](#7-参考资料) +- [5. 复制](#5-复制) + - [5.1. 主从复制](#51-主从复制) + - [5.2. 读写分离](#52-读写分离) +- [6. 参考资料](#6-参考资料) @@ -133,11 +128,116 @@ MySQL 提供了 FROM_UNIXTIME() 函数把 UNIX 时间戳转换为日期,并提 索引是在存储引擎层实现的,而不是在服务器层实现的,所以不同存储引擎具有不同的索引类型和实现。 -### 3.1. B Tree 原理 +### 3.1. 索引的优点和缺点 -#### B-Tree +优点: -[![img](https://github.com/CyC2018/Interview-Notebook/raw/master/pics/06976908-98ab-46e9-a632-f0c2760ec46c.png)](https://github.com/CyC2018/Interview-Notebook/blob/master/pics/06976908-98ab-46e9-a632-f0c2760ec46c.png) +- 大大减少了服务器需要扫描的数据行数。 +- 帮助服务器避免进行排序和创建临时表(B+Tree 索引是有序的,可以用来做 ORDER BY 和 GROUP BY 操作); +- 将随机 I/O 变为顺序 I/O(B+Tree 索引是有序的,也就将相邻的数据都存储在一起)。 + +缺点: + +1. 创建索引和维护索引要耗费时间,这种时间随着数据量的增加而增加。 +2. 索引需要占物理空间,除了数据表占数据空间之外,每一个索引还要占一定的物理空间,如果要建立组合索引那么需要的空间就会更大。 +3. 当对表中的数据进行增加、删除和修改的时候,索引也要动态的维护,这样就降低了数据的维护速度。 + +### 3.2. 索引类型 + +MySQL 目前主要有以下几种索引类型: + +#### 普通索引 + +普通索引:最基本的索引,没有任何限制。 + +```sql +CREATE TABLE `table` ( + ... + INDEX index_name (title(length)) +) +``` + +#### 唯一索引 + +唯一索引:索引列的值必须唯一,但允许有空值。如果是组合索引,则列值的组合必须唯一。 + +```sql +CREATE TABLE `table` ( + ... + UNIQUE indexName (title(length)) +) +``` + +#### 主键索引 + +主键索引:一种特殊的唯一索引,一个表只能有一个主键,不允许有空值。一般是在建表的时候同时创建主键索引。 + +```sql +CREATE TABLE `table` ( + `id` int(11) NOT NULL AUTO_INCREMENT, + ... + PRIMARY KEY (`id`) +) +``` + +#### 组合索引 + +组合索引:多个字段上创建的索引,只有在查询条件中使用了创建索引时的第一个字段,索引才会被使用。使用组合索引时遵循最左前缀集合。 + +```sql +CREATE TABLE `table` ( + ... + INDEX index_name (title(length), title(length), ...) +) +``` + +#### 全文索引 + +全文索引:主要用来查找文本中的关键字,而不是直接与索引中的值相比较。fulltext 索引跟其它索引大不相同,它更像是一个搜索引擎,而不是简单的 WHERE 语句的参数匹配。fulltext 索引配合 match against 操作使用,而不是一般的 WHERE 语句加 LIKE。它可以在 CREATE TABLE,ALTER TABLE ,CREATE INDEX 使用,不过目前只有 char、varchar,text 列上可以创建全文索引。值得一提的是,在数据量较大时候,现将数据放入一个没有全局索引的表中,然后再用 CREATE INDEX 创建 fulltext 索引,要比先为一张表建立 fulltext 然后再将数据写入的速度快很多。 + +```sql +CREATE TABLE `table` ( + `content` text CHARACTER NULL, + ... + FULLTEXT (content) +) +``` + +### 3.3. 索引数据结构 + +#### B+Tree 索引 + +B+Tree 索引是大多数 MySQL 存储引擎的默认索引类型。 + +因为不再需要进行全表扫描,只需要对树进行搜索即可,因此查找速度快很多。除了用于查找,还可以用于排序和分组。 + +可以指定多个列作为索引列,多个索引列共同组成键。 + +B+Tree 索引适用于全键值、键值范围和键前缀查找,其中键前缀查找只适用于最左前缀查找。 + +如果不是按照索引列的顺序进行查找,则无法使用索引。 + +InnoDB 的 B+Tree 索引分为主索引和辅助索引。 + +主索引的叶子节点 data 域记录着完整的数据记录,这种索引方式被称为聚簇索引。因为无法把数据行存放在两个不同的地方,所以一个表只能有一个聚簇索引。 + +
+ +
+ +辅助索引的叶子节点的 data 域记录着主键的值,因此在使用辅助索引进行查找时,需要先查找到主键值,然后再到主索引中进行查找。 + +
+ +
+ +##### B Tree 原理 + +###### B-Tree + +
+ +
定义一条数据记录为一个二元组 [key, data],B-Tree 是满足下列条件的数据结构: @@ -149,9 +249,11 @@ MySQL 提供了 FROM_UNIXTIME() 函数把 UNIX 时间戳转换为日期,并提 由于插入删除新的数据记录会破坏 B-Tree 的性质,因此在插入删除时,需要对树进行一个分裂、合并、旋转等操作以保持 B-Tree 性质。 -#### B+Tree +###### B+Tree -[![img](https://github.com/CyC2018/Interview-Notebook/raw/master/pics/7299afd2-9114-44e6-9d5e-4025d0b2a541.png)](https://github.com/CyC2018/Interview-Notebook/blob/master/pics/7299afd2-9114-44e6-9d5e-4025d0b2a541.png) +
+ +
与 B-Tree 相比,B+Tree 有以下不同点: @@ -159,13 +261,15 @@ MySQL 提供了 FROM_UNIXTIME() 函数把 UNIX 时间戳转换为日期,并提 - 内节点不存储 data,只存储 key; - 叶子节点不存储指针。 -#### 顺序访问指针 +###### 顺序访问指针的 B+Tree -[![img](https://github.com/CyC2018/Interview-Notebook/raw/master/pics/061c88c1-572f-424f-b580-9cbce903a3fe.png)](https://github.com/CyC2018/Interview-Notebook/blob/master/pics/061c88c1-572f-424f-b580-9cbce903a3fe.png) +
+ +
一般在数据库系统或文件系统中使用的 B+Tree 结构都在经典 B+Tree 基础上进行了优化,在叶子节点增加了顺序访问指针,做这个优化的目的是为了提高区间访问的性能。 -#### 优势 +###### 优势 红黑树等平衡树也可以用来实现索引,但是文件系统及数据库系统普遍采用 B Tree 作为索引结构,主要有以下两个原因: @@ -185,30 +289,6 @@ B+Tree 相比于 B-Tree 更适合外存索引,因为 B+Tree 内节点去掉了 更多内容请参考:[MySQL 索引背后的数据结构及算法原理](http://blog.codinglabs.org/articles/theory-of-mysql-index.html) -### 3.2. 索引分类 - -#### B+Tree 索引 - -B+Tree 索引是大多数 MySQL 存储引擎的默认索引类型。 - -因为不再需要进行全表扫描,只需要对树进行搜索即可,因此查找速度快很多。除了用于查找,还可以用于排序和分组。 - -可以指定多个列作为索引列,多个索引列共同组成键。 - -B+Tree 索引适用于全键值、键值范围和键前缀查找,其中键前缀查找只适用于最左前缀查找。 - -如果不是按照索引列的顺序进行查找,则无法使用索引。 - -InnoDB 的 B+Tree 索引分为主索引和辅助索引。 - -主索引的叶子节点 data 域记录着完整的数据记录,这种索引方式被称为聚簇索引。因为无法把数据行存放在两个不同的地方,所以一个表只能有一个聚簇索引。 - -[![img](https://github.com/CyC2018/Interview-Notebook/raw/master/pics/c28c6fbc-2bc1-47d9-9b2e-cf3d4034f877.jpg)](https://github.com/CyC2018/Interview-Notebook/blob/master/pics/c28c6fbc-2bc1-47d9-9b2e-cf3d4034f877.jpg) - -辅助索引的叶子节点的 data 域记录着主键的值,因此在使用辅助索引进行查找时,需要先查找到主键值,然后再到主索引中进行查找。 - -[![img](https://github.com/CyC2018/Interview-Notebook/raw/master/pics/7ab8ca28-2a41-4adf-9502-cc0a21e63b51.jpg)](https://github.com/CyC2018/Interview-Notebook/blob/master/pics/7ab8ca28-2a41-4adf-9502-cc0a21e63b51.jpg) - #### 哈希索引 InnoDB 引擎有一个特殊的功能叫“自适应哈希索引”,当某个索引值被使用的非常频繁时,会在 B+Tree 索引之上再创建一个哈希索引,这样就让 B+Tree 索引具有哈希索引的一些优点,比如快速的哈希查找。 @@ -232,40 +312,19 @@ MyISAM 存储引擎支持空间数据索引,可以用于地理数据存储。 必须使用 GIS 相关的函数来维护数据。 -### 3.3. 索引的优点 +### 3.4. 索引原则 -- 大大减少了服务器需要扫描的数据行数。 -- 帮助服务器避免进行排序和创建临时表(B+Tree 索引是有序的,可以用来做 ORDER BY 和 GROUP BY 操作); -- 将随机 I/O 变为顺序 I/O(B+Tree 索引是有序的,也就将相邻的数据都存储在一起)。 +#### 最左前缀匹配原则 -### 3.4. 索引优化 +mysql 会一直向右匹配直到遇到范围查询(>、<、between、like)就停止匹配。 -#### 独立的列 - -在进行查询时,索引列不能是表达式的一部分,也不能是函数的参数,否则无法使用索引。 - -例如下面的查询不能使用 actor_id 列的索引: - -``` -SELECT actor_id FROM sakila.actor WHERE actor_id + 1 = 5; -``` - -#### 多列索引 - -在需要使用多个列作为条件进行查询时,使用多列索引比使用多个单列索引性能更好。例如下面的语句中,最好把 actor_id 和 film_id 设置为多列索引。 - -``` -SELECT film_id, actor_ id FROM sakila.film_actor -WhERE actor_id = 1 AND film_id = 1; -``` - -#### 索引列的顺序 +例如:`a = 1 and b = 2 and c > 3 and d = 4`,如果建立(a,b,c,d)顺序的索引,d 是用不到索引的,如果建立(a,b,d,c)的索引则都可以用到,a,b,d 的顺序可以任意调整。 让选择性最强的索引列放在前面,索引的选择性是指:不重复的索引值和记录总数的比值。最大值为 1,此时每个记录都有唯一的索引与其对应。选择性越高,查询效率也越高。 例如下面显示的结果中 customer_id 的选择性比 staff_id 更高,因此最好把 customer_id 列放在多列索引的前面。 -``` +```sql SELECT COUNT(DISTINCT staff_id)/COUNT(*) AS staff_id_selectivity, COUNT(DISTINCT customer_id)/COUNT(*) AS customer_id_selectivity, COUNT(*) @@ -278,6 +337,33 @@ customer_id_selectivity: 0.0373 COUNT(*): 16049 ``` +#### = 和 in 可以乱序 + +比如 a = 1 and b = 2 and c = 3 建立(a,b,c)索引可以任意顺序,mysql 的查询优化器会帮你优化成索引可以识别的形式。 + +#### 索引列不能参与计算 + +在进行查询时,索引列不能是表达式的一部分,也不能是函数的参数,否则无法使用索引。 + +例如下面的查询不能使用 actor_id 列的索引: + +``` +SELECT actor_id FROM sakila.actor WHERE actor_id + 1 = 5; +``` + +#### 尽量的扩展索引,不要新建索引 + +比如表中已经有 a 的索引,现在要加(a,b)的索引,那么只需要修改原来的索引即可。 + +#### 多列索引 + +在需要使用多个列作为条件进行查询时,使用多列索引比使用多个单列索引性能更好。例如下面的语句中,最好把 actor_id 和 film_id 设置为多列索引。 + +``` +SELECT film_id, actor_ id FROM sakila.film_actor +WhERE actor_id = 1 AND film_id = 1; +``` + #### 前缀索引 对于 BLOB、TEXT 和 VARCHAR 类型的列,必须使用前缀索引,只索引开始的部分字符。 @@ -369,54 +455,9 @@ SELECT * FROM tag_post WHERE tag_id=1234; SELECT * FROM post WHERE post.id IN (123,456,567,9098,8904); ``` -## 5. 切分 +## 5. 复制 -### 5.1. 水平切分 - -[![img](https://github.com/CyC2018/Interview-Notebook/raw/master/pics/63c2909f-0c5f-496f-9fe5-ee9176b31aba.jpg)](https://github.com/CyC2018/Interview-Notebook/blob/master/pics/63c2909f-0c5f-496f-9fe5-ee9176b31aba.jpg) - -水平切分又称为 Sharding,它是将同一个表中的记录拆分到多个结构相同的表中。 - -当一个表的数据不断增多时,Sharding 是必然的选择,它可以将数据分布到集群的不同节点上,从而缓存单个数据库的压力。 - -### 5.2. 垂直切分 - -[![img](https://github.com/CyC2018/Interview-Notebook/raw/master/pics/e130e5b8-b19a-4f1e-b860-223040525cf6.jpg)](https://github.com/CyC2018/Interview-Notebook/blob/master/pics/e130e5b8-b19a-4f1e-b860-223040525cf6.jpg) - -垂直切分是将一张表按列切分成多个表,通常是按照列的关系密集程度进行切分,也可以利用垂直切分将经常被使用的列和不经常被使用的列切分到不同的表中。 - -在数据库的层面使用垂直切分将按数据库中表的密集程度部署到不同的库中,例如将原来的电商数据库垂直切分成商品数据库 payDB、用户数据库 userBD 等。 - -### 5.3. Sharding 策略 - -- 哈希取模:hash(key) % NUM_DB -- 范围:可以是 ID 范围也可以是时间范围 -- 映射表:使用单独的一个数据库来存储映射关系 - -### 5.4. Sharding 存在的问题及解决方案 - -#### 事务问题 - -使用分布式事务来解决,比如 XA 接口。 - -#### JOIN - -可以将原来的 JOIN 查询分解成多个单表查询,然后在用户程序中进行 JOIN。 - -#### ID 唯一性 - -- 使用全局唯一 ID:GUID。 -- 为每个分片指定一个 ID 范围。 -- 分布式 ID 生成器 (如 Twitter 的 Snowflake 算法)。 - -更多内容请参考: - -- [How Sharding Works](https://medium.com/@jeeyoungk/how-sharding-works-b4dec46b3f6) -- [大众点评订单系统分库分表实践](https://tech.meituan.com/dianping_order_db_sharding.html) - -## 6. 复制 - -### 6.1. 主从复制 +### 5.1. 主从复制 主要涉及三个线程:binlog 线程、I/O 线程和 SQL 线程。 @@ -424,9 +465,11 @@ SELECT * FROM post WHERE post.id IN (123,456,567,9098,8904); - **I/O 线程** :负责从主服务器上读取二进制日志文件,并写入从服务器的中继日志中。 - **SQL 线程** :负责读取中继日志并重放其中的 SQL 语句。 -[![img](https://github.com/CyC2018/Interview-Notebook/raw/master/pics/master-slave.png)](https://github.com/CyC2018/Interview-Notebook/blob/master/pics/master-slave.png) +
+ +
-### 6.2. 读写分离 +### 5.2. 读写分离 主服务器用来处理写操作以及实时性要求比较高的读操作,而从服务器用来处理读操作。 @@ -438,9 +481,11 @@ MySQL 读写分离能提高性能的原因在于: - 从服务器可以配置 MyISAM 引擎,提升查询性能以及节约系统开销; - 增加冗余,提高可用性。 -[![img](https://github.com/CyC2018/Interview-Notebook/raw/master/pics/master-slave-proxy.png)](https://github.com/CyC2018/Interview-Notebook/blob/master/pics/master-slave-proxy.png) +
+ +
-## 7. 参考资料 +## 6. 参考资料 - BaronScbwartz, PeterZaitsev, VadimTkacbenko, 等. 高性能 MySQL[M]. 电子工业出版社, 2013. - 姜承尧. MySQL 技术内幕: InnoDB 存储引擎 [M]. 机械工业出版社, 2011. @@ -448,3 +493,4 @@ MySQL 读写分离能提高性能的原因在于: - [服务端指南 数据存储篇 | MySQL(09) 分库与分表带来的分布式困境与应对之策](http://blog.720ui.com/2017/mysql_core_09_multi_db_table2/) - [How to create unique row ID in sharded databases?](https://stackoverflow.com/questions/788829/how-to-create-unique-row-id-in-sharded-databases) - [SQL Azure Federation – Introduction](http://geekswithblogs.net/shaunxu/archive/2012/01/07/sql-azure-federation-ndash-introduction.aspx) +- [分库分表需要考虑的问题及方案](https://www.jianshu.com/p/32b3e91aa22c) diff --git a/docs/数据库系统概论.md b/docs/数据库系统概论.md index d142f8f..226bd44 100644 --- a/docs/数据库系统概论.md +++ b/docs/数据库系统概论.md @@ -9,52 +9,192 @@ tags: # 数据库系统概论 +> 本文所述内容主要针对的是关系型数据库,nosql 数据库并不适用。 + -- [事务](#事务) - - [概念](#概念) - - [ACID](#acid) - - [AUTOCOMMIT](#autocommit) -- [并发一致性问题](#并发一致性问题) - - [丢失修改](#丢失修改) - - [脏数据](#脏数据) - - [不可重复读](#不可重复读) - - [幻影读](#幻影读) -- [封锁](#封锁) - - [封锁粒度](#封锁粒度) - - [封锁类型](#封锁类型) - - [封锁协议](#封锁协议) - - [MySQL 隐式与显示锁定](#mysql-隐式与显示锁定) -- [隔离级别](#隔离级别) - - [未提交读(READ UNCOMMITTED)](#未提交读read-uncommitted) - - [提交读(READ COMMITTED)](#提交读read-committed) - - [可重复读(REPEATABLE READ)](#可重复读repeatable-read) - - [可串行化(SERIALIXABLE)](#可串行化serialixable) -- [多版本并发控制](#多版本并发控制) - - [版本号](#版本号) - - [Undo 日志](#undo-日志) - - [实现过程](#实现过程) - - [快照读与当前读](#快照读与当前读) -- [Next-Key Locks](#next-key-locks) - - [Record Locks](#record-locks) - - [Grap Locks](#grap-locks) - - [Next-Key Locks](#next-key-locks-1) -- [关系数据库设计理论](#关系数据库设计理论) - - [函数依赖](#函数依赖) - - [异常](#异常) - - [范式](#范式) -- [ER 图](#er-图) - - [实体的三种联系](#实体的三种联系) - - [表示出现多次的关系](#表示出现多次的关系) - - [联系的多向性](#联系的多向性) - - [表示子类](#表示子类) -- [资料](#资料) +- [1. 索引](#1-索引) + - [1.1. 索引的优点和缺点](#11-索引的优点和缺点) + - [1.2. 索引类型](#12-索引类型) + - [1.3. 索引数据结构](#13-索引数据结构) + - [1.4. 索引原则](#14-索引原则) +- [2. 事务](#2-事务) + - [2.1. ACID](#21-acid) + - [2.2. 并发一致性问题](#22-并发一致性问题) + - [2.3. 事务隔离级别](#23-事务隔离级别) +- [3. 数据库锁](#3-数据库锁) + - [3.1. 数据库锁的粒度](#31-数据库锁的粒度) + - [3.2. 数据库锁的类型](#32-数据库锁的类型) + - [3.3. 数据库锁的协议](#33-数据库锁的协议) +- [4. 多版本并发控制](#4-多版本并发控制) + - [4.1. 版本号](#41-版本号) + - [4.2. Undo 日志](#42-undo-日志) + - [4.3. 实现过程](#43-实现过程) + - [4.4. 快照读与当前读](#44-快照读与当前读) +- [5. 分库分表](#5-分库分表) + - [5.1. 水平拆分](#51-水平拆分) + - [5.2. 垂直拆分](#52-垂直拆分) + - [5.3. Sharding 策略](#53-sharding-策略) + - [5.4. Sharding 存在的问题及解决方案](#54-sharding-存在的问题及解决方案) +- [6. 关系数据库设计理论](#6-关系数据库设计理论) + - [6.1. 函数依赖](#61-函数依赖) + - [6.2. 异常](#62-异常) + - [6.3. 范式](#63-范式) +- [7. ER 图](#7-er-图) + - [7.1. 实体的三种联系](#71-实体的三种联系) + - [7.2. 表示出现多次的关系](#72-表示出现多次的关系) + - [7.3. 联系的多向性](#73-联系的多向性) + - [7.4. 表示子类](#74-表示子类) +- [8. 资料](#8-资料) -## 事务 +## 1. 索引 -### 概念 +索引能够轻易将查询性能提升几个数量级。 + +- 数据量小的表,使用全表扫描比建立索引更高效。 +- 数据量大的表,使用索引更高效。 +- 数据量特大的表,建立和维护索引的代价将会随之增长,可以使用分区技术。 + +### 1.1. 索引的优点和缺点 + +优点: + +- 通过创建唯一性索引,可以保证数据库表中每一行数据的唯一性。 +- 大大减少了服务器需要扫描的数据行数,从而提高检索速度。 +- 可以加速表和表之间的连接,特别是在实现数据的参考完整性方面特别有意义。 +- 在使用分组和排序子句进行数据检索时,同样可以显著减少查询中分组和排序的时间。 +- 通过使用索引,可以在查询的过程中,使用优化隐藏器,提高系统的性能。 + +缺点: + +- 创建索引和维护索引要耗费时间,这种时间随着数据量的增加而增加。 +- 索引需要占物理空间,除了数据表占数据空间之外,每一个索引还要占一定的物理空间,如果要建立组合索引那么需要的空间就会更大。 +- 当对表中的数据进行增加、删除和修改的时候,索引也要动态的维护,这样就降低了数据的维护速度。 + +### 1.2. 索引类型 + +主流的关系型数据库一般都支持以下索引类型: + +- 普通索引:最基本的索引,没有任何限制。 +- 唯一索引:索引列的值必须唯一,但允许有空值。如果是组合索引,则列值的组合必须唯一。 +- 主键索引:一种特殊的唯一索引,一个表只能有一个主键,不允许有空值。一般是在建表的时候同时创建主键索引。 +- 组合索引:多个字段上创建的索引,只有在查询条件中使用了创建索引时的第一个字段,索引才会被使用。使用组合索引时遵循最左前缀集合。 + +### 1.3. 索引数据结构 + +**主流数据库的索引一般使用的数据结构为:B-Tree 或 B+Tree。** + +#### B-Tree + +B-Tree 不同于 Binary Tree(二叉树,最多有两个子树),它是平衡搜索树。 + +一棵 M 阶的 B-Tree 满足以下条件: + +- 每个结点至多有 M 个孩子; +- 除根结点和叶结点外,其它每个结点至少有 M/2 个孩子; +- 根结点至少有两个孩子(除非该树仅包含一个结点); +- 所有叶结点在同一层,叶结点不包含任何关键字信息; +- 有 K 个关键字的非叶结点恰好包含 K+1 个孩子; + +对于任意结点,其内部的关键字 Key 是升序排列的。每个节点中都包含了 data。 + +
+ +
+ +对于每个结点,主要包含一个关键字数组 Key[],一个指针数组(指向儿子)Son[]。 + +在 B-Tree 内,查找的流程是: + +1. 使用顺序查找(数组长度较短时)或折半查找方法查找 Key[]数组,若找到关键字 K,则返回该结点的地址及 K 在 Key[]中的位置; +2. 否则,可确定 K 在某个 Key[i]和 Key[i+1]之间,则从 Son[i]所指的子结点继续查找,直到在某结点中查找成功; +3. 或直至找到叶结点且叶结点中的查找仍不成功时,查找过程失败。 + +#### B+Tree + +B+Tree 是 B-Tree 的变种: + +- 每个节点的指针上限为 2d 而不是 2d+1(d 为节点的出度)。 +- 非叶子节点不存储 data,只存储 key;叶子节点不存储指针。 + +
+ +
+ +由于并不是所有节点都具有相同的域,因此 B+Tree 中叶节点和内节点一般大小不同。这点与 B-Tree 不同,虽然 B-Tree 中不同节点存放的 key 和指针可能数量不一致,但是每个节点的域和上限是一致的,所以在实现中 B-Tree 往往对每个节点申请同等大小的空间。 + +##### 带有顺序访问指针的 B+Tree + +一般在数据库系统或文件系统中使用的 B+Tree 结构都在经典 B+Tree 的基础上进行了优化,增加了顺序访问指针。 + +
+ +
+ +在 B+Tree 的每个叶子节点增加一个指向相邻叶子节点的指针,就形成了带有顺序访问指针的 B+Tree。 + +这个优化的目的是为了提高区间访问的性能,例如上图中如果要查询 key 为从 18 到 49 的所有数据记录,当找到 18 后,只需顺着节点和指针顺序遍历就可以一次性访问到所有数据节点,极大提到了区间查询效率。 + +### 1.4. 索引原则 + +#### 最左前缀匹配原则 + +mysql 会一直向右匹配直到遇到范围查询(>、<、between、like)就停止匹配。 + +例如:`a = 1 and b = 2 and c > 3 and d = 4`,如果建立(a,b,c,d)顺序的索引,d 是用不到索引的,如果建立(a,b,d,c)的索引则都可以用到,a,b,d 的顺序可以任意调整。 + +#### = 和 in 可以乱序 + +比如 a = 1 and b = 2 and c = 3 建立(a,b,c)索引可以任意顺序,mysql 的查询优化器会帮你优化成索引可以识别的形式。 + +#### 尽量选择区分度高的列作为索引 + +让选择性最强的索引列放在前面,索引的选择性是指:不重复的索引值和记录总数的比值。最大值为 1,此时每个记录都有唯一的索引与其对应。选择性越高,查询效率也越高。 + +示字段不重复比例公式: `count(distinct col) / count(*)` + +#### 索引列不能参与计算 + +在进行查询时,索引列不能是表达式的一部分,也不能是函数的参数,否则无法使用索引。 + +例如下面的查询不能使用 actor_id 列的索引: + +```sql +SELECT actor_id FROM sakila.actor WHERE actor_id + 1 = 5; +``` + +#### 尽量的扩展索引,不要新建索引 + +比如表中已经有 a 的索引,现在要加(a,b)的索引,那么只需要修改原来的索引即可。 + +#### 多列索引 + +在需要使用多个列作为条件进行查询时,使用多列索引比使用多个单列索引性能更好。例如下面的语句中,最好把 actor_id 和 film_id 设置为多列索引。 + +``` +SELECT film_id, actor_ id FROM sakila.film_actor +WhERE actor_id = 1 AND film_id = 1; +``` + +#### 前缀索引 + +对于文本类型的列,必须使用前缀索引,只索引开始的部分字符。 + +对于前缀长度的选取需要根据索引选择性来确定。 + +#### 覆盖索引 + +索引包含所有需要查询的字段的值。 + +具有以下优点: + +- 因为索引条目通常远小于数据行的大小,所以若只读取索引,能大大减少数据访问量。 +- 一些存储引擎(例如 MyISAM)在内存中只缓存索引,而数据依赖于操作系统来缓存。因此,只访问索引可以不使用系统调用(通常比较费时)。 +- 对于 InnoDB 引擎,若辅助索引能够覆盖查询,则无需访问主索引。 + +## 2. 事务
@@ -62,25 +202,28 @@ tags: 事务指的是满足 ACID 特性的一组操作,可以通过 Commit 提交一个事务,也可以使用 Rollback 进行回滚。 -### ACID +- MySQL 默认采用自动提交模式。也就是说,如果不显式使用`START TRANSACTION`语句来开始一个事务,那么每个查询都会被当做一个事务自动提交。 +- Oracle 默认采用非自动提交模式。也就是说,必须通过 COMMIT 来提交事务。 -#### 1. 原子性(Automicity) +### 2.1. ACID + +#### 原子性(Automicity) 事务被视为不可分割的最小单元,事务的所有操作要么全部提交成功,要么全部失败回滚。 回滚可以用日志来实现,日志记录着事务所执行的修改操作,在回滚时反向执行这些修改操作即可。 -#### 2. 一致性(Consistency) +#### 一致性(Consistency) 数据库在事务执行前后都保持一致性状态。 在一致性状态下,所有事务对一个数据的读取结果都是相同的。 -#### 3. 隔离性(Isolation) +#### 隔离性(Isolation) 一个事务所做的修改在最终提交以前,对其它事务是不可见的。 -#### 4. 持久性(Durability) +#### 持久性(Durability) 一旦事务提交,则其所做的修改将会永远保存到数据库中。即使系统发生崩溃,事务执行的结果也不能丢失。 @@ -99,15 +242,11 @@ tags:
-### AUTOCOMMIT - -MySQL 默认采用自动提交模式。也就是说,如果不显式使用`START TRANSACTION`语句来开始一个事务,那么每个查询都会被当做一个事务自动提交。 - -## 并发一致性问题 +### 2.2. 并发一致性问题 在并发环境下,事务的隔离性很难保证,因此会出现很多并发一致性问题。 -### 丢失修改 +#### 丢失修改 T1 和 T2 两个事务都对一个数据进行修改,T1 先修改,T2 随后修改,T2 的修改覆盖了 T1 的修改。 @@ -115,7 +254,7 @@ T1 和 T2 两个事务都对一个数据进行修改,T -### 脏数据 +#### 脏数据 T1 修改一个数据,T2 随后读取这个数据。如果 T1 撤销了这次修改,那么 T2 读取的数据是脏数据。 @@ -123,7 +262,7 @@ T1 修改一个数据,T2 随后读取这个数据。如 -### 不可重复读 +#### 不可重复读 T2 读取一个数据,T1 对该数据做了修改。如果 T2 再次读取这个数据,此时读取的结果和第一次读取的结果不同。 @@ -131,7 +270,7 @@ T2 读取一个数据,T1 对该数据做了修改。如 -### 幻影读 +#### 幻影读 T1 读取某个范围的数据,T2 在这个范围内插入新的数据,T1 再次读取这个范围的数据,此时读取的结果和和第一次读取的结果不同。 @@ -139,15 +278,32 @@ T1 读取某个范围的数据,T2 在这个范围内插 ---- +#### 并发一致性解决方案 -产生并发不一致性问题主要原因是破坏了事务的隔离性,解决方法是通过并发控制来保证隔离性。并发控制可以通过封锁来实现,但是封锁操作需要用户自己控制,相当复杂。数据库管理系统提供了事务的隔离级别,让用户以一种更轻松的方式处理并发一致性问题。 +产生并发不一致性问题主要原因是破坏了事务的隔离性,解决方法是通过并发控制来保证隔离性。 -## 封锁 +并发控制可以通过封锁来实现,但是封锁操作需要用户自己控制,相当复杂。数据库管理系统提供了事务的隔离级别,让用户以一种更轻松的方式处理并发一致性问题。 -### 封锁粒度 +### 2.3. 事务隔离级别 -MySQL 中提供了两种封锁粒度:行级锁以及表级锁。 +未提交读(READ UNCOMMITTED) - 事务中的修改,即使没有提交,对其它事务也是可见的。 +提交读(READ COMMITTED) - 一个事务只能读取已经提交的事务所做的修改。换句话说,一个事务所做的修改在提交之前对其它事务是不可见的。 +可重复读(REPEATABLE READ) - 保证在同一个事务中多次读取同样数据的结果是一样的。 +可串行化(SERIALIXABLE) - 强制事务串行执行。 + +| 隔离级别 | 脏读 | 不可重复读 | 幻影读 | +| :------: | :--: | :--------: | :----: | +| 未提交读 | YES | YES | YES | +| 提交读 | NO | YES | YES | +| 可重复读 | NO | NO | YES | +| 可串行化 | NO | NO | NO | + +## 3. 数据库锁 + +### 3.1. 数据库锁的粒度 + +- **表级锁** - 直接锁定整张表,在锁定期间,其它进程无法对该表进行写操作。如果是写锁,则其它进程则读也不允许。 +- **行级锁** - 仅对指定的行记录进行加锁,这样其它进程还是可以对同一个表中的其它记录进行操作。 应该尽量只锁定需要修改的那部分数据,而不是所有的资源。锁定的数据量越少,发生锁争用的可能就越小,系统的并发程度就越高。 @@ -155,9 +311,14 @@ MySQL 中提供了两种封锁粒度:行级锁以及表级锁。 在选择封锁粒度时,需要在锁开销和并发程度之间做一个权衡。 -### 封锁类型 +主流数据库对锁粒度的支持: -#### 1. 读写锁 +- Mysql 的 InnoDB 存储引擎支持表级锁和行级锁;MyISAM 存储引擎只支持表级锁。 +- Oracle 支持表级锁和行级锁。 + +### 3.2. 数据库锁的类型 + +#### 读写锁 - 排它锁(Exclusive),简写为 X 锁,又称写锁。 - 共享锁(Shared),简写为 S 锁,又称读锁。 @@ -174,7 +335,7 @@ MySQL 中提供了两种封锁粒度:行级锁以及表级锁。 | X | NO | NO | | S | NO | YES | -#### 2. 意向锁 +#### 意向锁 使用意向锁(Intention Locks)可以更容易地支持多粒度封锁。 @@ -201,11 +362,11 @@ MySQL 中提供了两种封锁粒度:行级锁以及表级锁。 - 任意 IS/IX 锁之间都是兼容的,因为它们只是表示想要对表加锁,而不是真正加锁; - S 锁只与 S 锁和 IS 锁兼容,也就是说事务 T 想要对数据行加 S 锁,其它事务可以已经获得对表或者表中的行的 S 锁。 -### 封锁协议 +### 3.3. 数据库锁的协议 -#### 1. 三级封锁协议 +#### 三级锁协议 -**一级封锁协议** +**一级锁协议** 事务 T 要修改数据 A 时必须加 X 锁,直到 T 结束才释放锁。 @@ -226,7 +387,7 @@ MySQL 中提供了两种封锁粒度:行级锁以及表级锁。 | | commit | | | unlock-x(A) | -**二级封锁协议** +**二级锁协议** 在一级的基础上,要求读取数据 A 时必须加 S 锁,读取完马上释放 S 锁。 @@ -247,7 +408,7 @@ MySQL 中提供了两种封锁粒度:行级锁以及表级锁。 | | commit | | | unlock-s(A) | -**三级封锁协议** +**三级锁协议** 在二级的基础上,要求读取数据 A 时必须加 S 锁,直到事务结束了才能释放 S 锁。 @@ -268,11 +429,11 @@ MySQL 中提供了两种封锁粒度:行级锁以及表级锁。 | | commit | | | unlock-X(A) | -#### 2. 两段锁协议 +#### 两段锁协议 加锁和解锁分为两个阶段进行。 -可串行化调度是指,通过并发控制,使得并发执行的事务结果与某个串行执行的事务结果相同。 +可串行化调度是指:通过并发控制,使得并发执行的事务结果与某个串行执行的事务结果相同。 事务遵循两段锁协议是保证可串行化调度的充分条件。例如以下操作满足两段锁协议,它是可串行化调度。 @@ -286,67 +447,29 @@ lock-x(A)...lock-s(B)...lock-s(C)...unlock(A)...unlock(C)...unlock(B) lock-x(A)...unlock(A)...lock-s(B)...unlock(B)...lock-s(C)...unlock(C) ``` -### MySQL 隐式与显示锁定 +## 4. 多版本并发控制 -MySQL 的 InnoDB 存储引擎采用两段锁协议,会根据隔离级别在需要的时候自动加锁,并且所有的锁都是在同一时刻被释放,这被称为隐式锁定。 +多版本并发控制(Multi-Version Concurrency Control, MVCC)是实现隔离级别的一种具体方式,用于实现提交读和可重复读这两种隔离级别。而未提交读隔离级别总是读取最新的数据行,无需使用 MVCC;可串行化隔离级别需要对所有读取的行都加锁,单纯使用 MVCC 无法实现。 -InnoDB 也可以使用特定的语句进行显示锁定: - -```sql -SELECT ... LOCK In SHARE MODE; -SELECT ... FOR UPDATE; -``` - -## 隔离级别 - -### 未提交读(READ UNCOMMITTED) - -事务中的修改,即使没有提交,对其它事务也是可见的。 - -### 提交读(READ COMMITTED) - -一个事务只能读取已经提交的事务所做的修改。换句话说,一个事务所做的修改在提交之前对其它事务是不可见的。 - -### 可重复读(REPEATABLE READ) - -保证在同一个事务中多次读取同样数据的结果是一样的。 - -### 可串行化(SERIALIXABLE) - -强制事务串行执行。 - ---- - -| 隔离级别 | 脏读 | 不可重复读 | 幻影读 | -| :------: | :--: | :--------: | :----: | -| 未提交读 | YES | YES | YES | -| 提交读 | NO | YES | YES | -| 可重复读 | NO | NO | YES | -| 可串行化 | NO | NO | NO | - -## 多版本并发控制 - -多版本并发控制(Multi-Version Concurrency Control, MVCC)是 MySQL 的 InnoDB 存储引擎实现隔离级别的一种具体方式,用于实现提交读和可重复读这两种隔离级别。而未提交读隔离级别总是读取最新的数据行,无需使用 MVCC;可串行化隔离级别需要对所有读取的行都加锁,单纯使用 MVCC 无法实现。 - -### 版本号 +### 4.1. 版本号 - 系统版本号:是一个递增的数字,每开始一个新的事务,系统版本号就会自动递增。 - 事务版本号:事务开始时的系统版本号。 -InooDB 的 MVCC 在每行记录后面都保存着两个隐藏的列,用来存储两个版本号: +MVCC 在每行记录后面都保存着两个隐藏的列,用来存储两个版本号: - 创建版本号:指示创建一个数据行的快照时的系统版本号; - 删除版本号:如果该快照的删除版本号大于当前事务版本号表示该快照有效,否则表示该快照已经被删除了。 -### Undo 日志 +### 4.2. Undo 日志 -InnoDB 的 MVCC 使用到的快照存储在 Undo 日志中,该日志通过回滚指针把一个数据行(Record)的所有快照连接起来。 +MVCC 使用到的快照存储在 Undo 日志中,该日志通过回滚指针把一个数据行(Record)的所有快照连接起来。 -### 实现过程 +### 4.3. 实现过程 以下实现过程针对可重复读隔离级别。 -#### 1. SELECT +#### SELECT 当开始新一个事务时,该事务的版本号肯定会大于当前所有数据行快照的创建版本号,理解这一点很关键。 @@ -356,21 +479,33 @@ InnoDB 的 MVCC 使用到的快照存储在 Undo 日志中,该日志通过回 除了上面的要求,T 所要读取的数据行快照的删除版本号必须大于 T 的版本号,因为如果小于等于 T 的版本号,那么表示该数据行快照是已经被删除的,不应该去读取它。 -#### 2. INSERT +#### INSERT 将当前系统版本号作为数据行快照的创建版本号。 -#### 3. DELETE +
+ +
+ +#### DELETE 将当前系统版本号作为数据行快照的删除版本号。 -#### 4. UPDATE +
+ +
+ +#### UPDATE 将当前系统版本号作为更新后的数据行快照的创建版本号,同时将当前系统版本号作为更新前的数据行快照的删除版本号。可以理解为先执行 DELETE 后执行 INSERT。 -### 快照读与当前读 +
+ +
-#### 1. 快照读 +### 4.4. 快照读与当前读 + +#### 快照读 使用 MVCC 读取的是快照中的数据,这样可以减少加锁所带来的开销。 @@ -378,7 +513,7 @@ InnoDB 的 MVCC 使用到的快照存储在 Undo 日志中,该日志通过回 select * from table ...; ``` -#### 2. 当前读 +#### 当前读 读取的是最新的数据,需要加锁。以下第一个语句需要加 S 锁,其它都需要加 X 锁。 @@ -390,51 +525,78 @@ update; delete; ``` -## Next-Key Locks +## 5. 分库分表 -Next-Key Locks 也是 MySQL 的 InnoDB 存储引擎的一种锁实现。MVCC 不能解决幻读的问题,Next-Key Locks 就是为了解决这个问题而存在的。在可重复读(REPEATABLE READ)隔离级别下,使用 MVCC + Next-Key Locks 可以解决幻读问题。 +### 5.1. 水平拆分 -### Record Locks +
+ +
-锁定的对象是索引,而不是数据。如果表没有设置索引,InnoDB 会自动在主键上创建隐藏的聚集索引,因此 Record Locks 依然可以使用。 +水平切分又称为 Sharding,它是将同一个表中的记录拆分到多个结构相同的表中。 -### Grap Locks +当一个表的数据不断增多时,Sharding 是必然的选择,它可以将数据分布到集群的不同节点上,从而缓存单个数据库的压力。 -锁定一个范围内的索引,例如当一个事务执行以下语句,其它事务就不能在 t.c 中插入 15。 +### 5.2. 垂直拆分 -```sql -SELECT c FROM t WHERE c BETWEEN 10 and 20 FOR UPDATE; -``` +
+ +
-### Next-Key Locks +垂直切分是将一张表按列切分成多个表,通常是按照列的关系密集程度进行切分,也可以利用垂直切分将经常被使用的列和不经常被使用的列切分到不同的表中。 -它是 Record Locks 和 Gap Locks 的结合。在 user 中有以下记录: +在数据库的层面使用垂直切分将按数据库中表的密集程度部署到不同的库中,例如将原来的电商数据库垂直切分成商品数据库 payDB、用户数据库 userBD 等。 -```sql -| id | last_name | first_name | age | -|------|-------------|--------------|-------| -| 4 | stark | tony | 21 | -| 1 | tom | hiddleston | 30 | -| 3 | morgan | freeman | 40 | -| 5 | jeff | dean | 50 | -| 2 | donald | trump | 80 | -+------|-------------|--------------|-------+ -``` +### 5.3. Sharding 策略 -那么就需要锁定以下范围: +- 哈希取模:hash(key) % NUM_DB +- 范围:可以是 ID 范围也可以是时间范围 +- 映射表:使用单独的一个数据库来存储映射关系 -```sql -(-∞, 21] -(21, 30] -(30, 40] -(40, 50] -(50, 80] -(80, ∞) -``` +### 5.4. Sharding 存在的问题及解决方案 -## 关系数据库设计理论 +#### 事务问题 -### 函数依赖 +使用分布式事务来解决,比如 XA 接口。 + +#### 跨节点 Join 的问题 + +可以将原来的 JOIN 查询分解成多个单表查询,然后在用户程序中进行 JOIN。 + +解决这一问题的普遍做法是分两次查询实现。在第一次查询的结果集中找出关联数据的 id,根据这些 id 发起第二次请求得到关联数据。 + +#### 跨节点的 count,order by,group by 以及聚合函数问题 + +这些是一类问题,因为它们都需要基于全部数据集合进行计算。多数的代理都不会自动处理合并工作。 + +解决方案:与解决跨节点 join 问题的类似,分别在各个节点上得到结果后在应用程序端进行合并。和 join 不同的是每个节点的查询可以并行执行,因此很多时候它的速度要比单一大表快很多。但如果结果集很大,对应用程序内存的消耗是一个问题。 + +#### ID 唯一性 + +一旦数据库被切分到多个物理节点上,我们将不能再依赖数据库自身的主键生成机制。一方面,某个分区数据库自生成的 ID 无法保证在全局上是唯一的;另一方面,应用程序在插入数据之前需要先获得 ID,以便进行 SQL 路由。 + +一些常见的主键生成策略: + +- 使用全局唯一 ID:GUID。 +- 为每个分片指定一个 ID 范围。 +- 分布式 ID 生成器 (如 Twitter 的 Snowflake 算法)。 + +#### 数据迁移,容量规划,扩容等问题 + +来自淘宝综合业务平台团队,它利用对 2 的倍数取余具有向前兼容的特性(如对 4 取余得 1 的数对 2 取余也是 1)来分配数据,避免了行级别的数据迁移,但是依然需要进行表级别的迁移,同时对扩容规模和分表数量都有限制。总得来说,这些方案都不是十分的理想,多多少少都存在一些缺点,这也从一个侧面反映出了 Sharding 扩容的难度。 + +更多内容请参考: + +- [How Sharding Works](https://medium.com/@jeeyoungk/how-sharding-works-b4dec46b3f6) +- [大众点评订单系统分库分表实践](https://tech.meituan.com/dianping_order_db_sharding.html) + +#### 分库数量 + +分库数量首先和单库能处理的记录数有关,一般来说,Mysql 单库超过 5000 万条记录,Oracle 单库超过 1 亿条记录,DB 压力就很大(当然处理能力和字段数量/访问模式/记录长度有进一步关系)。 + +## 6. 关系数据库设计理论 + +### 6.1. 函数依赖 记 A->B 表示 A 函数决定 B,也可以说 B 函数依赖于 A。 @@ -444,7 +606,7 @@ SELECT c FROM t WHERE c BETWEEN 10 and 20 FOR UPDATE; 对于 A->B,B->C,则 A->C 是一个传递依赖。 -### 异常 +### 6.2. 异常 以下的学生课程关系的函数依赖为 Sno, Cname -> Sname, Sdept, Mname, Grade,键码为 {Sno, Cname}。也就是说,确定学生和课程之后,就能确定其它信息。 @@ -462,7 +624,7 @@ SELECT c FROM t WHERE c BETWEEN 10 and 20 FOR UPDATE; - 删除异常:删除一个信息,那么也会丢失其它信息。例如如果删除了 课程-1,需要删除第一行和第三行,那么 学生-1 的信息就会丢失。 - 插入异常,例如想要插入一个学生的信息,如果这个学生还没选课,那么就无法插入。 -### 范式 +### 6.3. 范式 范式理论是为了解决以上提到四种异常。 @@ -472,11 +634,11 @@ SELECT c FROM t WHERE c BETWEEN 10 and 20 FOR UPDATE; -#### 1. 第一范式 (1NF) +#### 第一范式 (1NF) 属性不可分; -#### 2. 第二范式 (2NF) +#### 第二范式 (2NF) 每个非主属性完全函数依赖于键码。 @@ -529,7 +691,7 @@ Sname, Sdept 和 Mname 都部分依赖于键码,当一个学生选修了多门 - Sno, Cname -> Grade -#### 3. 第三范式 (3NF) +#### 第三范式 (3NF) 非主属性不传递依赖于键码。 @@ -550,32 +712,32 @@ Sname, Sdept 和 Mname 都部分依赖于键码,当一个学生选修了多门 | 学院-1 | 院长-1 | | 学院-2 | 院长-2 | -## ER 图 +## 7. ER 图 Entity-Relationship,有三个组成部分:实体、属性、联系。 用来进行关系型数据库系统的概念设计。 -### 实体的三种联系 +### 7.1. 实体的三种联系 包含一对一,一对多,多对多三种。 如果 A 到 B 是一对多关系,那么画个带箭头的线段指向 B;如果是一对一,画两个带箭头的线段;如果是多对多,画两个不带箭头的线段。下图的 Course 和 Student 是一对多的关系。 -### 表示出现多次的关系 +### 7.2. 表示出现多次的关系 一个实体在联系出现几次,就要用几条线连接。下图表示一个课程的先修关系,先修关系出现两个 Course 实体,第一个是先修课程,后一个是后修课程,因此需要用两条线来表示这种关系。 -### 联系的多向性 +### 7.3. 联系的多向性 虽然老师可以开设多门课,并且可以教授多名学生,但是对于特定的学生和课程,只有一个老师教授,这就构成了一个三元联系。 一般只使用二元联系,可以把多元关系转换为二元关系。 -### 表示子类 +### 7.4. 表示子类 用一个三角形和两条线来连接类和子类,与子类有关的属性和联系都连到子类上,而与父类和子类都有关的连到父类上。 -## 资料 +## 8. 资料 - [数据库系统原理](https://github.com/CyC2018/Interview-Notebook/blob/master/notes/数据库系统原理.md)