📝 Writing docs.

pull/1/head
Zhang Peng 2018-07-08 19:37:33 +08:00
parent ab2238b762
commit c6b200ac89
4 changed files with 649 additions and 126 deletions

View File

@ -2,6 +2,7 @@
- [x] [数据库系统概论](docs/数据库系统概论.md) - [x] [数据库系统概论](docs/数据库系统概论.md)
- [x] [数据库优化](docs/数据库优化.md) - [x] [数据库优化](docs/数据库优化.md)
- [x] [数据库面试题](docs/数据库面试题.md)
- [x] [Sql](docs/sql.md) - [x] [Sql](docs/sql.md)
- [x] [Mysql](docs/mysql.md) - [x] [Mysql](docs/mysql.md)
- [x] [H2](docs/h2.md) - [x] [H2](docs/h2.md)

View File

@ -15,39 +15,41 @@ tags:
- [1. 存储引擎](#1-存储引擎) - [1. 存储引擎](#1-存储引擎)
- [1.1. InnoDB](#11-innodb) - [1.1. InnoDB](#11-innodb)
- [1.2. MyISAM](#12-myisam) - [1.2. MyISAM](#12-myisam)
- [1.3. 比较](#13-比较) - [1.3. 选择存储引擎](#13-选择存储引擎)
- [2. 数据类型](#2-数据类型) - [2. 数据类型](#2-数据类型)
- [2.1. 整型](#21-整型) - [2.1. 整型](#21-整型)
- [2.2. 浮点数](#22-浮点数) - [2.2. 浮点数](#22-浮点数)
- [2.3. 字符串](#23-字符串) - [2.3. 字符串](#23-字符串)
- [2.4. 时间和日期](#24-时间和日期) - [2.4. 时间和日期](#24-时间和日期)
- [3. 事务](#3-事务) - [3. 事务](#3-事务)
- [事务隔离级别](#事务隔离级别) - [3.1. 事务隔离级别](#31-事务隔离级别)
- [死锁](#死锁) - [3.2. 死锁](#32-死锁)
- [MVCC](#mvcc) - [4. MVCC](#4-mvcc)
- [4. 索引](#4-索引) - [5. 索引](#5-索引)
- [4.1. 索引的优点和缺点](#41-索引的优点和缺点) - [5.1. 索引的优点和缺点](#51-索引的优点和缺点)
- [4.2. 索引类型](#42-索引类型) - [5.2. 索引类型](#52-索引类型)
- [4.3. 索引数据结构](#43-索引数据结构) - [5.3. 索引数据结构](#53-索引数据结构)
- [4.4. 索引原则](#44-索引原则) - [5.4. 索引原则](#54-索引原则)
- [5. 查询性能优化](#5-查询性能优化) - [6. 查询性能优化](#6-查询性能优化)
- [5.1. 使用 Explain 进行分析](#51-使用-explain-进行分析) - [6.1. 使用 Explain 进行分析](#61-使用-explain-进行分析)
- [5.2. 优化数据访问](#52-优化数据访问) - [6.2. 优化数据访问](#62-优化数据访问)
- [5.3. 重构查询方式](#53-重构查询方式) - [6.3. 重构查询方式](#63-重构查询方式)
- [6. 复制](#6-复制) - [7. 复制](#7-复制)
- [6.1. 主从复制](#61-主从复制) - [7.1. 主从复制](#71-主从复制)
- [6.2. 读写分离](#62-读写分离) - [7.2. 读写分离](#72-读写分离)
- [7. 参考资料](#7-参考资料) - [8. 参考资料](#8-参考资料)
<!-- /TOC --> <!-- /TOC -->
## 1. 存储引擎 ## 1. 存储引擎
在文件系统中Mysql 将每个数据库(也可以成为 schema保存为数据目录下的一个子目录。创建表示Mysql 会在数据库子目录下创建一个和表同名的 .frm 文件保存表的定义。因为 Mysql 使用文件系统的目录和文件来保存数据库和表的定义大小写敏感性和具体平台密切相关。Windows 中大小写不敏感;类 Unix 中大小写敏感。**不同的存储引擎保存数据和索引的方式是不同的,但表的定义则是在 Mysql 服务层统一处理的。**
### 1.1. InnoDB ### 1.1. InnoDB
InnoDB 是 MySQL 默认的事务型存储引擎,只有在需要 InnoDB 不支持的特性时,才考虑使用其它存储引擎。 InnoDB 是 MySQL 默认的事务型存储引擎,只有在需要 InnoDB 不支持的特性时,才考虑使用其它存储引擎。
实现了四个标准的隔离级别默认级别是可重复读REPEATABLE READ。在可重复读隔离级别下通过多版本并发控制MVCC+ 间隙锁next-key locking防止幻影读。 InnoDB 实现了四个标准的隔离级别默认级别是可重复读REPEATABLE READ。在可重复读隔离级别下通过多版本并发控制MVCC+ 间隙锁next-key locking防止幻影读。
主索引是聚簇索引,在索引中保存了数据,从而避免直接读取磁盘,因此对查询性能有很大的提升。 主索引是聚簇索引,在索引中保存了数据,从而避免直接读取磁盘,因此对查询性能有很大的提升。
@ -69,15 +71,37 @@ MyISAM 提供了大量的特性,包括压缩表、空间数据索引等。
如果指定了 DELAY_KEY_WRITE 选项,在每次修改执行完成时,不会立即将修改的索引数据写入磁盘,而是会写到内存中的键缓冲区,只有在清理键缓冲区或者关闭表的时候才会将对应的索引块写入磁盘。这种方式可以极大的提升写入性能,但是在数据库或者主机崩溃时会造成索引损坏,需要执行修复操作。 如果指定了 DELAY_KEY_WRITE 选项,在每次修改执行完成时,不会立即将修改的索引数据写入磁盘,而是会写到内存中的键缓冲区,只有在清理键缓冲区或者关闭表的时候才会将对应的索引块写入磁盘。这种方式可以极大的提升写入性能,但是在数据库或者主机崩溃时会造成索引损坏,需要执行修复操作。
### 1.3. 比较 ### 1.3. 选择存储引擎
- 事务InnoDB 是事务型的,可以使用 Commit 和 Rollback 语句。 #### Mysql 内置的存储引擎
- 并发MyISAM 只支持表级锁,而 InnoDB 还支持行级锁。
- **InnoDB** - Mysql 的默认事务型存储引擎。性能不错且支持自动崩溃恢复。
- **MyISAM** - Mysql 5.1 版本前的默认存储引擎。特性丰富但不支持事务,也没有崩溃恢复功能。
- **CSV** - 可以将 CSV 文件作为 Mysql 的表来处理,但这种表不支持索引。
- **Memory** - 适合快速访问数据,且数据不会被修改,重启丢失也没有关系。
- **NDB** - 用于 Mysql 集群场景。
#### 如何选择合适的存储引擎?
大多数情况下InnoDB 都是正确的选择,除非需要用到 InnoDB 不具备的特性。
如果应用需要选择 InnoDB 以外的存储引擎,可以考虑以下因素:
- 事务如果需要支持事务InnoDB 是首选。如果不需要支持事务,且主要是 SELECT 和 INSERT 操作MyISAM 是不错的选择。
- 并发MyISAM 只支持表级锁,而 InnoDB 还支持行级锁。所以InnoDB 并发性能更高。
- 外键InnoDB 支持外键。 - 外键InnoDB 支持外键。
- 备份InnoDB 支持在线热备份。 - 备份InnoDB 支持在线热备份。
- 崩溃恢复MyISAM 崩溃后发生损坏的概率比 InnoDB 高很多,而且恢复的速度也更慢。 - 崩溃恢复MyISAM 崩溃后发生损坏的概率比 InnoDB 高很多,而且恢复的速度也更慢。
- 其它特性MyISAM 支持压缩表和空间数据索引。 - 其它特性MyISAM 支持压缩表和空间数据索引。
#### 转换表的存储引擎
下面的语句可以将 mytable 表的引擎修改为 InnoDB
```sql
ALTER TABLE mytable ENGINE = InnoDB;
```
## 2. 数据类型 ## 2. 数据类型
### 2.1. 整型 ### 2.1. 整型
@ -136,17 +160,17 @@ Mysql 不是所有的存储引擎都实现了事务处理。支持事务的存
Mysql 默认采用自动提交AUTOCOMMIT模式。 Mysql 默认采用自动提交AUTOCOMMIT模式。
### 事务隔离级别 ### 3.1. 事务隔离级别
InnoDB 支持 SQL 标准的四种隔离级别默认的级别是可重复读。并且通过间隙锁next-key locking策略防止幻读的出现。 InnoDB 支持 SQL 标准的四种隔离级别默认的级别是可重复读。并且通过间隙锁next-key locking策略防止幻读的出现。
### 死锁 ### 3.2. 死锁
在 Mysql 中,锁的行为和顺序与存储引擎相关。 在 Mysql 中,锁的行为和顺序与存储引擎相关。
InnoDB 中解决死锁问题的方法是:将持有最少行级排他锁的事务进行回滚。 InnoDB 中解决死锁问题的方法是:将持有最少行级排他锁的事务进行回滚。
## MVCC ## 4. MVCC
InnoDB 的 MVCC是通过在每行记录后面保存两个隐藏的列来实现的。这两个列一个保存了行的创建时间一个保存行的过期时间。当然存储的并不是实际的时间值而是系统版本号。每开始一个新事务系统版本号就会自动递增。事务开始时的系统版本号会作为事务的版本号用来和查询到的每行记录的版本号进行比较。 InnoDB 的 MVCC是通过在每行记录后面保存两个隐藏的列来实现的。这两个列一个保存了行的创建时间一个保存行的过期时间。当然存储的并不是实际的时间值而是系统版本号。每开始一个新事务系统版本号就会自动递增。事务开始时的系统版本号会作为事务的版本号用来和查询到的每行记录的版本号进行比较。
@ -174,7 +198,7 @@ InnoDB 的 MVCC是通过在每行记录后面保存两个隐藏的列来实
将当前系统版本号作为更新后的数据行快照的创建版本号,同时将当前系统版本号作为更新前的数据行快照的删除版本号。可以理解为先执行 DELETE 后执行 INSERT。 将当前系统版本号作为更新后的数据行快照的创建版本号,同时将当前系统版本号作为更新前的数据行快照的删除版本号。可以理解为先执行 DELETE 后执行 INSERT。
## 4. 索引 ## 5. 索引
索引能够轻易将查询性能提升几个数量级。 索引能够轻易将查询性能提升几个数量级。
@ -182,7 +206,7 @@ InnoDB 的 MVCC是通过在每行记录后面保存两个隐藏的列来实
索引是在存储引擎层实现的,而不是在服务器层实现的,所以不同存储引擎具有不同的索引类型和实现。 索引是在存储引擎层实现的,而不是在服务器层实现的,所以不同存储引擎具有不同的索引类型和实现。
### 4.1. 索引的优点和缺点 ### 5.1. 索引的优点和缺点
优点: 优点:
@ -196,7 +220,7 @@ InnoDB 的 MVCC是通过在每行记录后面保存两个隐藏的列来实
2. 索引需要占物理空间,除了数据表占数据空间之外,每一个索引还要占一定的物理空间,如果要建立组合索引那么需要的空间就会更大。 2. 索引需要占物理空间,除了数据表占数据空间之外,每一个索引还要占一定的物理空间,如果要建立组合索引那么需要的空间就会更大。
3. 当对表中的数据进行增加、删除和修改的时候,索引也要动态的维护,这样就降低了数据的维护速度。 3. 当对表中的数据进行增加、删除和修改的时候,索引也要动态的维护,这样就降低了数据的维护速度。
### 4.2. 索引类型 ### 5.2. 索引类型
MySQL 目前主要有以下几种索引类型: MySQL 目前主要有以下几种索引类型:
@ -257,7 +281,7 @@ CREATE TABLE `table` (
) )
``` ```
### 4.3. 索引数据结构 ### 5.3. 索引数据结构
#### B+Tree 索引 #### B+Tree 索引
@ -366,7 +390,7 @@ MyISAM 存储引擎支持空间数据索引,可以用于地理数据存储。
必须使用 GIS 相关的函数来维护数据。 必须使用 GIS 相关的函数来维护数据。
### 4.4. 索引原则 ### 5.4. 索引原则
#### 最左前缀匹配原则 #### 最左前缀匹配原则
@ -434,9 +458,9 @@ WhERE actor_id = 1 AND film_id = 1;
- 一些存储引擎(例如 MyISAM在内存中只缓存索引而数据依赖于操作系统来缓存。因此只访问索引可以不使用系统调用通常比较费时 - 一些存储引擎(例如 MyISAM在内存中只缓存索引而数据依赖于操作系统来缓存。因此只访问索引可以不使用系统调用通常比较费时
- 对于 InnoDB 引擎,若辅助索引能够覆盖查询,则无需访问主索引。 - 对于 InnoDB 引擎,若辅助索引能够覆盖查询,则无需访问主索引。
## 5. 查询性能优化 ## 6. 查询性能优化
### 5.1. 使用 Explain 进行分析 ### 6.1. 使用 Explain 进行分析
Explain 用来分析 SELECT 查询语句,开发人员可以通过分析 Explain 结果来优化查询语句。 Explain 用来分析 SELECT 查询语句,开发人员可以通过分析 Explain 结果来优化查询语句。
@ -448,7 +472,7 @@ Explain 用来分析 SELECT 查询语句,开发人员可以通过分析 Explai
更多内容请参考:[MySQL 性能优化神器 Explain 使用分析](https://segmentfault.com/a/1190000008131735) 更多内容请参考:[MySQL 性能优化神器 Explain 使用分析](https://segmentfault.com/a/1190000008131735)
### 5.2. 优化数据访问 ### 6.2. 优化数据访问
#### 减少请求的数据量 #### 减少请求的数据量
@ -468,7 +492,7 @@ Explain 用来分析 SELECT 查询语句,开发人员可以通过分析 Explai
最有效的方式是使用索引来覆盖查询。 最有效的方式是使用索引来覆盖查询。
### 5.3. 重构查询方式 ### 6.3. 重构查询方式
#### 切分大查询 #### 切分大查询
@ -509,9 +533,9 @@ SELECT * FROM tag_post WHERE tag_id=1234;
SELECT * FROM post WHERE post.id IN (123,456,567,9098,8904); SELECT * FROM post WHERE post.id IN (123,456,567,9098,8904);
``` ```
## 6. 复制 ## 7. 复制
### 6.1. 主从复制 ### 7.1. 主从复制
主要涉及三个线程binlog 线程、I/O 线程和 SQL 线程。 主要涉及三个线程binlog 线程、I/O 线程和 SQL 线程。
@ -523,7 +547,7 @@ SELECT * FROM post WHERE post.id IN (123,456,567,9098,8904);
<img src="https://raw.githubusercontent.com/dunwu/Database/master/images/mysql/master-slave.png" /> <img src="https://raw.githubusercontent.com/dunwu/Database/master/images/mysql/master-slave.png" />
</div> </div>
### 6.2. 读写分离 ### 7.2. 读写分离
主服务器用来处理写操作以及实时性要求比较高的读操作,而从服务器用来处理读操作。 主服务器用来处理写操作以及实时性要求比较高的读操作,而从服务器用来处理读操作。
@ -539,7 +563,7 @@ MySQL 读写分离能提高性能的原因在于:
<img src="https://raw.githubusercontent.com/dunwu/Database/master/images/mysql/master-slave-proxy.png" /> <img src="https://raw.githubusercontent.com/dunwu/Database/master/images/mysql/master-slave-proxy.png" />
</div> </div>
## 7. 参考资料 ## 8. 参考资料
- BaronScbwartz, PeterZaitsev, VadimTkacbenko 等. 高性能 MySQL[M]. 电子工业出版社, 2013. - BaronScbwartz, PeterZaitsev, VadimTkacbenko 等. 高性能 MySQL[M]. 电子工业出版社, 2013.
- 姜承尧. MySQL 技术内幕: InnoDB 存储引擎 [M]. 机械工业出版社, 2011. - 姜承尧. MySQL 技术内幕: InnoDB 存储引擎 [M]. 机械工业出版社, 2011.

View File

@ -24,8 +24,8 @@ tags:
- [2.3. 事务隔离级别](#23-事务隔离级别) - [2.3. 事务隔离级别](#23-事务隔离级别)
- [2.4. 死锁](#24-死锁) - [2.4. 死锁](#24-死锁)
- [3. 并发控制](#3-并发控制) - [3. 并发控制](#3-并发控制)
- [3.1. 数据库锁的类型](#31-数据库锁的类型) - [3.1. 锁粒度](#31-锁粒度)
- [3.2. 锁粒度](#32-锁粒度) - [3.2. 数据库锁的类型](#32-数据库锁的类型)
- [3.3. 数据库锁的协议](#33-数据库锁的协议) - [3.3. 数据库锁的协议](#33-数据库锁的协议)
- [4. 多版本并发控制](#4-多版本并发控制) - [4. 多版本并发控制](#4-多版本并发控制)
- [4.1. 版本号](#41-版本号) - [4.1. 版本号](#41-版本号)
@ -36,18 +36,22 @@ tags:
- [5.1. 水平拆分](#51-水平拆分) - [5.1. 水平拆分](#51-水平拆分)
- [5.2. 垂直拆分](#52-垂直拆分) - [5.2. 垂直拆分](#52-垂直拆分)
- [5.3. Sharding 策略](#53-sharding-策略) - [5.3. Sharding 策略](#53-sharding-策略)
- [5.4. Sharding 存在的问题及解决方案](#54-sharding-存在的问题及解决方案) - [5.4. 分库分表的问题及解决方案](#54-分库分表的问题及解决方案)
- [5.5. 常用的分库分表中间件](#55-常用的分库分表中间件) - [5.5. 常用的分库分表中间件](#55-常用的分库分表中间件)
- [6. 关系数据库设计理论](#6-关系数据库设计理论) - [6. sql 优化](#6-sql-优化)
- [6.1. 函数依赖](#61-函数依赖) - [6.1. 使用执行计划进行分析](#61-使用执行计划进行分析)
- [6.2. 异常](#62-异常) - [6.2. 优化数据访问](#62-优化数据访问)
- [6.3. 范式](#63-范式) - [6.3. 重构查询方式](#63-重构查询方式)
- [7. ER 图](#7-er-图) - [7. 关系数据库设计理论](#7-关系数据库设计理论)
- [7.1. 实体的三种联系](#71-实体的三种联系) - [7.1. 函数依赖](#71-函数依赖)
- [7.2. 表示出现多次的关系](#72-表示出现多次的关系) - [7.2. 异常](#72-异常)
- [7.3. 联系的多向性](#73-联系的多向性) - [7.3. 范式](#73-范式)
- [7.4. 表示子类](#74-表示子类) - [8. ER 图](#8-er-图)
- [8. 资料](#8-资料) - [8.1. 实体的三种联系](#81-实体的三种联系)
- [8.2. 表示出现多次的关系](#82-表示出现多次的关系)
- [8.3. 联系的多向性](#83-联系的多向性)
- [8.4. 表示子类](#84-表示子类)
- [9. 资料](#9-资料)
<!-- /TOC --> <!-- /TOC -->
@ -137,6 +141,23 @@ B+Tree 是 B-Tree 的变种:
这个优化的目的是为了提高区间访问的性能,例如上图中如果要查询 key 为从 18 到 49 的所有数据记录,当找到 18 后,只需顺着节点和指针顺序遍历就可以一次性访问到所有数据节点,极大提到了区间查询效率。 这个优化的目的是为了提高区间访问的性能,例如上图中如果要查询 key 为从 18 到 49 的所有数据记录,当找到 18 后,只需顺着节点和指针顺序遍历就可以一次性访问到所有数据节点,极大提到了区间查询效率。
#### Hash
Hash 索引只有精确匹配索引所有列的查询才有效。
对于每一行数据,对所有的索引列计算一个 hashcode。哈希索引将所有的 hashcode 存储在索引中,同时在 Hash 表中保存指向每个数据行的指针。
哈希索引的优点:
- 因为索引数据结构紧凑,所以查询速度非常快。
哈希索引的缺点:
- 哈希索引数据不是按照索引值顺序存储的,所以无法用于排序。
- 哈希索引不支持部分索引匹配查找。如,在数据列 (A,B) 上建立哈希索引,如果查询只有数据列 A无法使用该索引。
- 哈希索引只支持等值比较查询,不支持任何范围查询,如 WHERE price > 100。
- 哈希索引有可能出现哈希冲突,出现哈希冲突时,必须遍历链表中所有的行指针,逐行比较,直到找到符合条件的行。
### 1.4. 索引原则 ### 1.4. 索引原则
#### 独立的列 #### 独立的列
@ -166,9 +187,13 @@ SELECT ... WHERE TO_DAYS(CURRENT_DAT) - TO_DAYS(date_col) <= 10;
#### 多列索引 #### 多列索引
为每个列创建独立的索引往往不如多列索引。 不要为每个列创建独立的索引。
经验法则:将选择性高的列或基数大的列优先排在多列索引最前列。但有时,也需要考虑 WHERE 子句中的排序、分组和范围条件等因素,这些因素也会对查询性能造成较大影响。 #### 选择合适的索引列顺序
经验法则:将选择性高的列或基数大的列优先排在多列索引最前列。
但有时,也需要考虑 WHERE 子句中的排序、分组和范围条件等因素,这些因素也会对查询性能造成较大影响。
#### 聚簇索引 #### 聚簇索引
@ -190,51 +215,14 @@ SELECT ... WHERE TO_DAYS(CURRENT_DAT) - TO_DAYS(date_col) <= 10;
索引最好既满足排序,又用于查找行。这样,就可以使用索引来对结果排序。 索引最好既满足排序,又用于查找行。这样,就可以使用索引来对结果排序。
#### 最左前缀匹配原则
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 可以乱序 #### = 和 in 可以乱序
比如 a = 1 and b = 2 and c = 3 建立a,b,c索引可以任意顺序mysql 的查询优化器会帮你优化成索引可以识别的形式。 比如 a = 1 and b = 2 and c = 3 建立a,b,c索引可以任意顺序mysql 的查询优化器会帮你优化成索引可以识别的形式。
#### 索引列不能参与计算
在进行查询时,索引列不能是表达式的一部分,也不能是函数的参数,否则无法使用索引。
例如下面的查询不能使用 actor_id 列的索引:
#### 尽量的扩展索引,不要新建索引 #### 尽量的扩展索引,不要新建索引
比如表中已经有 a 的索引,现在要加(a,b)的索引,那么只需要修改原来的索引即可。 比如表中已经有 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. 事务 ## 2. 事务
<div align="center"> <div align="center">
@ -344,7 +332,20 @@ T<sub>1</sub> 读取某个范围的数据T<sub>2</sub> 在这个范围内插
无论何时,只要有多个查询需要在同一时刻修改数据,就会产生并发控制的问题。 无论何时,只要有多个查询需要在同一时刻修改数据,就会产生并发控制的问题。
### 3.1. 数据库锁的类型 ### 3.1. 锁粒度
应该尽量只锁定需要修改的那部分数据,而不是所有的资源。在给定的资源上,锁定的数据量越少,则系统的并发程度越高,只要相互之间不发生冲突即可。
但是加锁需要消耗资源,锁的各种操作,包括获取锁、释放锁、以及检查锁状态等,都会增加系统开销。因此锁粒度越小,系统开销就越大。
所谓锁策略,就是在锁的开销和并发程度之间寻求平衡,这种平衡自然也会影响到性能。
很多数据库都提供了表级锁和行级锁。
- **表级锁table lock** - 锁定整张表。用户对表进行写操作前,需要先获得写锁,这会阻塞其他用户对该表的所有读写操作。只有没有写锁时,其他用户才能获得读锁,读锁之间不会相互阻塞。
- **行级锁row lock** - 仅对指定的行记录进行加锁,这样其它进程还是可以对同一个表中的其它记录进行操作。
### 3.2. 数据库锁的类型
#### 读写锁 #### 读写锁
@ -390,19 +391,6 @@ T<sub>1</sub> 读取某个范围的数据T<sub>2</sub> 在这个范围内插
- 任意 IS/IX 锁之间都是兼容的,因为它们只是表示想要对表加锁,而不是真正加锁; - 任意 IS/IX 锁之间都是兼容的,因为它们只是表示想要对表加锁,而不是真正加锁;
- S 锁只与 S 锁和 IS 锁兼容,也就是说事务 T 想要对数据行加 S 锁,其它事务可以已经获得对表或者表中的行的 S 锁。 - S 锁只与 S 锁和 IS 锁兼容,也就是说事务 T 想要对数据行加 S 锁,其它事务可以已经获得对表或者表中的行的 S 锁。
### 3.2. 锁粒度
应该尽量只锁定需要修改的那部分数据,而不是所有的资源。在给定的资源上,锁定的数据量越少,则系统的并发程度越高,只要相互之间不发生冲突即可。
但是加锁需要消耗资源,锁的各种操作,包括获取锁、释放锁、以及检查锁状态等,都会增加系统开销。因此锁粒度越小,系统开销就越大。
所谓锁策略,就是在锁的开销和并发程度之间寻求平衡,这种平衡自然也会影响到性能。
很多数据库都提供了表级锁和行级锁。
- **表级锁table lock** - 锁定整张表。用户对表进行写操作前,需要先获得写锁,这会阻塞其他用户对该表的所有读写操作。只有没有写锁时,其他用户才能获得读锁,读锁之间不会相互阻塞。
- **行级锁row lock** - 仅对指定的行记录进行加锁,这样其它进程还是可以对同一个表中的其它记录进行操作。
### 3.3. 数据库锁的协议 ### 3.3. 数据库锁的协议
#### 三级锁协议 #### 三级锁协议
@ -578,15 +566,17 @@ delete;
分库分表的基本思想就要把一个数据库切分成多个部分放到不同的数据库(server)上,从而缓解单一数据库的性能问题。 分库分表的基本思想就要把一个数据库切分成多个部分放到不同的数据库(server)上,从而缓解单一数据库的性能问题。
当然,现实中更多是这两种情况混杂在一起,这时候需要根据实际情况做出选择,也可能会综合使用垂直与水平切分,从而将原有数据库切分成类似矩阵一样可以无限扩充的数据库(server)阵列。
### 5.1. 水平拆分 ### 5.1. 水平拆分
<div align="center"> <div align="center">
<img src="https://raw.githubusercontent.com/dunwu/Database/master/images/database/数据库水平拆分.jpg" width="500" /> <img src="https://raw.githubusercontent.com/dunwu/Database/master/images/database/数据库水平拆分.jpg" width="500" />
</div> </div>
水平切分又称为 Sharding它是将同一个表中的记录拆分到多个结构相同的表中 对于海量数据的数据库,如果表并不多,但每张表的数据非常多,这时候适合水平切分,即把表的数据按某种规则(比如按 ID 散列)切分到多个数据库(server)上
当一个表的数据不断增多时Sharding 是必然的选择,它可以将数据分布到集群的不同节点上,从而缓存单个数据库的压力 水平切分又称为 Sharding它是将同一个表中的记录拆分到多个结构相同的表中
### 5.2. 垂直拆分 ### 5.2. 垂直拆分
@ -596,7 +586,7 @@ delete;
垂直切分是将一张表按列切分成多个表,通常是按照列的关系密集程度进行切分,也可以利用垂直切分将经常被使用的列和不经常被使用的列切分到不同的表中。 垂直切分是将一张表按列切分成多个表,通常是按照列的关系密集程度进行切分,也可以利用垂直切分将经常被使用的列和不经常被使用的列切分到不同的表中。
在数据库的层面使用垂直切分将按数据库中表的密集程度部署到不同的库中,例如将原来的电商数据库垂直切分成商品数据库 payDB、用户数据库 userBD 等 如果是因为表多而数据多,这时候适合使用垂直切分,即把关系紧密(比如同一模块)的表切分出来放在一个 server 上
### 5.3. Sharding 策略 ### 5.3. Sharding 策略
@ -604,17 +594,24 @@ delete;
- 范围:可以是 ID 范围也可以是时间范围 - 范围:可以是 ID 范围也可以是时间范围
- 映射表:使用单独的一个数据库来存储映射关系 - 映射表:使用单独的一个数据库来存储映射关系
### 5.4. Sharding 存在的问题及解决方案 ### 5.4. 分库分表的问题及解决方案
#### 事务问题 #### 事务问题
使用分布式事务来解决,比如 XA 接口。 方案一:使用分布式事务
- 优点:交由数据库管理,简单有效
- 缺点:性能代价高,特别是 shard 越来越多时
方案二:由应用程序和数据库共同控制
- 原理:将一个跨多个数据库的分布式事务分拆成多个仅处于单个数据库上面的小事务,并通过应用程序来总控各个小事务。
- 优点:性能上有优势
- 缺点:需要应用程序在事务控制上做灵活设计。如果使用了 spring 的事务管理,改动起来会面临一定的困难。
#### 跨节点 Join 的问题 #### 跨节点 Join 的问题
可以将原来的 JOIN 查询分解成多个单表查询,然后在用户程序中进行 JOIN。 只要是进行切分,跨节点 Join 的问题是不可避免的。但是良好的设计和切分却可以减少此类情况的发生。解决这一问题的普遍做法是分两次查询实现。在第一次查询的结果集中找出关联数据的 id根据这些 id 发起第二次请求得到关联数据。
解决这一问题的普遍做法是分两次查询实现。在第一次查询的结果集中找出关联数据的 id根据这些 id 发起第二次请求得到关联数据。
#### 跨节点的 count,order by,group by 以及聚合函数问题 #### 跨节点的 count,order by,group by 以及聚合函数问题
@ -636,15 +633,16 @@ delete;
来自淘宝综合业务平台团队,它利用对 2 的倍数取余具有向前兼容的特性(如对 4 取余得 1 的数对 2 取余也是 1来分配数据避免了行级别的数据迁移但是依然需要进行表级别的迁移同时对扩容规模和分表数量都有限制。总得来说这些方案都不是十分的理想多多少少都存在一些缺点这也从一个侧面反映出了 Sharding 扩容的难度。 来自淘宝综合业务平台团队,它利用对 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 压力就很大(当然处理能力和字段数量/访问模式/记录长度有进一步关系)。 分库数量首先和单库能处理的记录数有关一般来说Mysql 单库超过 5000 万条记录Oracle 单库超过 1 亿条记录DB 压力就很大(当然处理能力和字段数量/访问模式/记录长度有进一步关系)。
#### 跨分片的排序分页
- 如果是在前台应用提供分页,则限定用户只能看前面 n 页,这个限制在业务上也是合理的,一般看后面的分页意义不大(如果一定要看,可以要求用户缩小范围重新查询)。
- 如果是后台批处理任务要求分批获取数据,则可以加大 page size比如每次获取 5000 条记录,有效减少分页数(当然离线访问一般走备库,避免冲击主库)。
- 分库设计时,一般还有配套大数据平台汇总所有分库的记录,有些分页查询可以考虑走大数据平台。
### 5.5. 常用的分库分表中间件 ### 5.5. 常用的分库分表中间件
#### 简单易用的组件: #### 简单易用的组件:
@ -663,9 +661,81 @@ delete;
- [OneProxy(支付宝首席架构师楼方鑫开发)](http://www.cnblogs.com/youge-OneSQL/articles/4208583.html) - [OneProxy(支付宝首席架构师楼方鑫开发)](http://www.cnblogs.com/youge-OneSQL/articles/4208583.html)
- [vitess谷歌开发的数据库中间件](https://github.com/youtube/vitess) - [vitess谷歌开发的数据库中间件](https://github.com/youtube/vitess)
## 6. 关系数据库设计理论 ## 6. sql 优化
### 6.1. 函数依赖 ### 6.1. 使用执行计划进行分析
执行计划 Explain 用来分析 SELECT 查询语句,开发人员可以通过分析 Explain 结果来优化查询语句。
比较重要的字段有:
- select_type : 查询类型,有简单查询、联合查询、子查询等
- key : 使用的索引
- rows : 扫描的行数
更多内容请参考:[MySQL 性能优化神器 Explain 使用分析](https://segmentfault.com/a/1190000008131735)
### 6.2. 优化数据访问
#### 减少请求的数据量
(一)只返回必要的列
最好不要使用 SELECT \* 语句。
(二)只返回必要的行
使用 WHERE 语句进行查询过滤,有时候也需要使用 LIMIT 语句来限制返回的数据。
(三)缓存重复查询的数据
使用缓存可以避免在数据库中进行查询,特别要查询的数据经常被重复查询,缓存可以带来的查询性能提升将会是非常明显的。
#### 减少服务器端扫描的行数
最有效的方式是使用索引来覆盖查询。
### 6.3. 重构查询方式
#### 切分大查询
一个大查询如果一次性执行的话,可能一次锁住很多数据、占满整个事务日志、耗尽系统资源、阻塞很多小的但重要的查询。
```
DELEFT FROM messages WHERE create < DATE_SUB(NOW(), INTERVAL 3 MONTH);
```
```
rows_affected = 0
do {
rows_affected = do_query(
"DELETE FROM messages WHERE create < DATE_SUB(NOW(), INTERVAL 3 MONTH) LIMIT 10000")
} while rows_affected > 0
```
#### 分解大连接查询
将一个大连接查询JOIN分解成对每一个表进行一次单表查询然后将结果在应用程序中进行关联这样做的好处有
- 让缓存更高效。对于连接查询,如果其中一个表发生变化,那么整个查询缓存就无法使用。而分解后的多个查询,即使其中一个表发生变化,对其它表的查询缓存依然可以使用。
- 分解成多个单表查询,这些单表查询的缓存结果更可能被其它查询使用到,从而减少冗余记录的查询。
- 减少锁竞争;
- 在应用层进行连接,可以更容易对数据库进行拆分,从而更容易做到高性能和可扩展。
- 查询本身效率也可能会有所提升。例如下面的例子中,使用 IN() 代替连接查询,可以让 MySQL 按照 ID 顺序进行查询,这可能比随机的连接要更高效。
```
SELECT * FROM tag
JOIN tag_post ON tag_post.tag_id=tag.id
JOIN post ON tag_post.post_id=post.id
WHERE tag.tag='mysql';
SELECT * FROM tag WHERE tag='mysql';
SELECT * FROM tag_post WHERE tag_id=1234;
SELECT * FROM post WHERE post.id IN (123,456,567,9098,8904);
```
## 7. 关系数据库设计理论
### 7.1. 函数依赖
记 A->B 表示 A 函数决定 B也可以说 B 函数依赖于 A。 记 A->B 表示 A 函数决定 B也可以说 B 函数依赖于 A。
@ -675,7 +745,7 @@ delete;
对于 A->BB->C则 A->C 是一个传递依赖。 对于 A->BB->C则 A->C 是一个传递依赖。
### 6.2. 异常 ### 7.2. 异常
以下的学生课程关系的函数依赖为 Sno, Cname -> Sname, Sdept, Mname, Grade键码为 {Sno, Cname}。也就是说,确定学生和课程之后,就能确定其它信息。 以下的学生课程关系的函数依赖为 Sno, Cname -> Sname, Sdept, Mname, Grade键码为 {Sno, Cname}。也就是说,确定学生和课程之后,就能确定其它信息。
@ -693,7 +763,7 @@ delete;
- 删除异常:删除一个信息,那么也会丢失其它信息。例如如果删除了 课程-1需要删除第一行和第三行那么 学生-1 的信息就会丢失。 - 删除异常:删除一个信息,那么也会丢失其它信息。例如如果删除了 课程-1需要删除第一行和第三行那么 学生-1 的信息就会丢失。
- 插入异常,例如想要插入一个学生的信息,如果这个学生还没选课,那么就无法插入。 - 插入异常,例如想要插入一个学生的信息,如果这个学生还没选课,那么就无法插入。
### 6.3. 范式 ### 7.3. 范式
范式理论是为了解决以上提到四种异常。 范式理论是为了解决以上提到四种异常。
@ -781,33 +851,35 @@ Sname, Sdept 和 Mname 都部分依赖于键码,当一个学生选修了多门
| 学院-1 | 院长-1 | | 学院-1 | 院长-1 |
| 学院-2 | 院长-2 | | 学院-2 | 院长-2 |
## 7. ER 图 ## 8. ER 图
Entity-Relationship有三个组成部分实体、属性、联系。 Entity-Relationship有三个组成部分实体、属性、联系。
用来进行关系型数据库系统的概念设计。 用来进行关系型数据库系统的概念设计。
### 7.1. 实体的三种联系 ### 8.1. 实体的三种联系
包含一对一,一对多,多对多三种。 包含一对一,一对多,多对多三种。
如果 A 到 B 是一对多关系,那么画个带箭头的线段指向 B如果是一对一画两个带箭头的线段如果是多对多画两个不带箭头的线段。下图的 Course 和 Student 是一对多的关系。 如果 A 到 B 是一对多关系,那么画个带箭头的线段指向 B如果是一对一画两个带箭头的线段如果是多对多画两个不带箭头的线段。下图的 Course 和 Student 是一对多的关系。
### 7.2. 表示出现多次的关系 ### 8.2. 表示出现多次的关系
一个实体在联系出现几次,就要用几条线连接。下图表示一个课程的先修关系,先修关系出现两个 Course 实体,第一个是先修课程,后一个是后修课程,因此需要用两条线来表示这种关系。 一个实体在联系出现几次,就要用几条线连接。下图表示一个课程的先修关系,先修关系出现两个 Course 实体,第一个是先修课程,后一个是后修课程,因此需要用两条线来表示这种关系。
### 7.3. 联系的多向性 ### 8.3. 联系的多向性
虽然老师可以开设多门课,并且可以教授多名学生,但是对于特定的学生和课程,只有一个老师教授,这就构成了一个三元联系。 虽然老师可以开设多门课,并且可以教授多名学生,但是对于特定的学生和课程,只有一个老师教授,这就构成了一个三元联系。
一般只使用二元联系,可以把多元关系转换为二元关系。 一般只使用二元联系,可以把多元关系转换为二元关系。
### 7.4. 表示子类 ### 8.4. 表示子类
用一个三角形和两条线来连接类和子类,与子类有关的属性和联系都连到子类上,而与父类和子类都有关的连到父类上。 用一个三角形和两条线来连接类和子类,与子类有关的属性和联系都连到子类上,而与父类和子类都有关的连到父类上。
## 8. 资料 ## 9. 资料
- [数据库系统原理](https://github.com/CyC2018/Interview-Notebook/blob/master/notes/数据库系统原理.md) - [数据库系统原理](https://github.com/CyC2018/Interview-Notebook/blob/master/notes/数据库系统原理.md)
- [分库分表需要考虑的问题及方案](https://www.jianshu.com/p/32b3e91aa22c) - [分库分表需要考虑的问题及方案](https://www.jianshu.com/p/32b3e91aa22c)
- [数据库分库分表(sharding)系列(二) 全局主键生成策略](https://blog.csdn.net/bluishglc/article/details/7710738)
- [一种支持自由规划无须数据迁移和修改路由代码的 Sharding 扩容方案](https://blog.csdn.net/bluishglc/article/details/7970268)

View File

@ -0,0 +1,426 @@
# 数据库面试题
<!-- TOC depthFrom:2 depthTo:4 -->
- [1. 存储引擎](#1-存储引擎)
- [1.1. mysql 有哪些存储引擎?有什么区别?](#11-mysql-有哪些存储引擎有什么区别)
- [2. 索引](#2-索引)
- [2.1. 数据库索引有哪些数据结构?](#21-数据库索引有哪些数据结构)
- [2.1.1. B-Tree](#211-b-tree)
- [2.1.2. B+Tree](#212-btree)
- [2.1.3. Hash](#213-hash)
- [2.2. B-Tree 和 B+Tree 有什么区别?](#22-b-tree-和-btree-有什么区别)
- [2.3. 索引原则有哪些?](#23-索引原则有哪些)
- [2.3.1. 独立的列](#231-独立的列)
- [2.3.2. 前缀索引和索引选择性](#232-前缀索引和索引选择性)
- [2.3.3. 多列索引](#233-多列索引)
- [2.3.4. 选择合适的索引列顺序](#234-选择合适的索引列顺序)
- [2.3.5. 聚簇索引](#235-聚簇索引)
- [2.3.6. 覆盖索引](#236-覆盖索引)
- [2.3.7. 使用索引扫描来做排序](#237-使用索引扫描来做排序)
- [2.3.8. = 和 in 可以乱序](#238--和-in-可以乱序)
- [2.3.9. 尽量的扩展索引,不要新建索引](#239-尽量的扩展索引不要新建索引)
- [3. 事务](#3-事务)
- [3.1. 数据库事务隔离级别?事务隔离级别分别解决什么问题?](#31-数据库事务隔离级别事务隔离级别分别解决什么问题)
- [3.2. 如何解决分布式事务?若出现网络问题或宕机问题,如何解决?](#32-如何解决分布式事务若出现网络问题或宕机问题如何解决)
- [4. 锁](#4-锁)
- [4.1. 数据库锁有哪些类型?如何实现?](#41-数据库锁有哪些类型如何实现)
- [4.1.1. 锁粒度](#411-锁粒度)
- [4.1.2. 读写锁](#412-读写锁)
- [4.1.3. 意向锁](#413-意向锁)
- [5. 分库分表](#5-分库分表)
- [5.1. 为什么要分库分表?](#51-为什么要分库分表)
- [5.2. 分库分表的常见问题以及解决方案?](#52-分库分表的常见问题以及解决方案)
- [5.2.1. 事务问题](#521-事务问题)
- [5.2.2. 跨节点 Join 的问题](#522-跨节点-join-的问题)
- [5.2.3. 跨节点的 count,order by,group by 以及聚合函数问题](#523-跨节点的-countorder-bygroup-by-以及聚合函数问题)
- [5.2.4. ID 唯一性](#524-id-唯一性)
- [5.2.5. 数据迁移,容量规划,扩容等问题](#525-数据迁移容量规划扩容等问题)
- [5.2.6. 分库数量](#526-分库数量)
- [5.2.7. 跨分片的排序分页](#527-跨分片的排序分页)
- [5.3. 如何设计可以动态扩容缩容的分库分表方案?](#53-如何设计可以动态扩容缩容的分库分表方案)
- [5.4. 有哪些分库分表中间件?各自有什么优缺点?底层实现原理?](#54-有哪些分库分表中间件各自有什么优缺点底层实现原理)
- [简单易用的组件:](#简单易用的组件)
- [强悍重量级的中间件:](#强悍重量级的中间件)
- [6. 数据库优化](#6-数据库优化)
- [6.1. 什么是执行计划?](#61-什么是执行计划)
- [7. 数据库架构设计](#7-数据库架构设计)
- [7.1. 高并发系统数据层面如何设计?](#71-高并发系统数据层面如何设计)
- [读写分离的原理](#读写分离的原理)
- [垂直切分](#垂直切分)
- [水平切分](#水平切分)
<!-- /TOC -->
## 1. 存储引擎
### 1.1. mysql 有哪些存储引擎?有什么区别?
- **InnoDB** - Mysql 的默认事务型存储引擎。性能不错且支持自动崩溃恢复。
- **MyISAM** - Mysql 5.1 版本前的默认存储引擎。特性丰富但不支持事务,也没有崩溃恢复功能。
- **CSV** - 可以将 CSV 文件作为 Mysql 的表来处理,但这种表不支持索引。
- **Memory** - 适合快速访问数据,且数据不会被修改,重启丢失也没有关系。
- **NDB** - 用于 Mysql 集群场景。
## 2. 索引
### 2.1. 数据库索引有哪些数据结构?
- B-Tree
- B+Tree
- Hash
#### 2.1.1. B-Tree
一棵 M 阶的 B-Tree 满足以下条件:
- 每个结点至多有 M 个孩子;
- 除根结点和叶结点外,其它每个结点至少有 M/2 个孩子;
- 根结点至少有两个孩子(除非该树仅包含一个结点);
- 所有叶结点在同一层,叶结点不包含任何关键字信息;
- 有 K 个关键字的非叶结点恰好包含 K+1 个孩子;
对于任意结点,其内部的关键字 Key 是升序排列的。每个节点中都包含了 data。
<div align="center">
<img src="https://raw.githubusercontent.com/dunwu/Database/master/images/database/B-TREE.png" />
</div>
对于每个结点,主要包含一个关键字数组 Key[]一个指针数组指向儿子Son[]。
在 B-Tree 内,查找的流程是:
1. 使用顺序查找(数组长度较短时)或折半查找方法查找 Key[]数组,若找到关键字 K则返回该结点的地址及 K 在 Key[]中的位置;
2. 否则,可确定 K 在某个 Key[i]和 Key[i+1]之间,则从 Son[i]所指的子结点继续查找,直到在某结点中查找成功;
3. 或直至找到叶结点且叶结点中的查找仍不成功时,查找过程失败。
#### 2.1.2. B+Tree
B+Tree 是 B-Tree 的变种:
- 每个节点的指针上限为 2d 而不是 2d+1d 为节点的出度)。
- 非叶子节点不存储 data只存储 key叶子节点不存储指针。
<div align="center">
<img src="https://raw.githubusercontent.com/dunwu/Database/master/images/database/B+TREE.png" />
</div>
由于并不是所有节点都具有相同的域,因此 B+Tree 中叶节点和内节点一般大小不同。这点与 B-Tree 不同,虽然 B-Tree 中不同节点存放的 key 和指针可能数量不一致,但是每个节点的域和上限是一致的,所以在实现中 B-Tree 往往对每个节点申请同等大小的空间。
##### 带有顺序访问指针的 B+Tree
一般在数据库系统或文件系统中使用的 B+Tree 结构都在经典 B+Tree 的基础上进行了优化,增加了顺序访问指针。
<div align="center">
<img src="https://raw.githubusercontent.com/dunwu/Database/master/images/database/带有顺序访问指针的B+Tree.png" />
</div>
在 B+Tree 的每个叶子节点增加一个指向相邻叶子节点的指针,就形成了带有顺序访问指针的 B+Tree。
这个优化的目的是为了提高区间访问的性能,例如上图中如果要查询 key 为从 18 到 49 的所有数据记录,当找到 18 后,只需顺着节点和指针顺序遍历就可以一次性访问到所有数据节点,极大提到了区间查询效率。
#### 2.1.3. Hash
Hash 索引只有精确匹配索引所有列的查询才有效。
对于每一行数据,对所有的索引列计算一个 hashcode。哈希索引将所有的 hashcode 存储在索引中,同时在 Hash 表中保存指向每个数据行的指针。
哈希索引的优点:
- 因为索引数据结构紧凑,所以查询速度非常快。
哈希索引的缺点:
- 哈希索引数据不是按照索引值顺序存储的,所以无法用于排序。
- 哈希索引不支持部分索引匹配查找。如,在数据列 (A,B) 上建立哈希索引,如果查询只有数据列 A无法使用该索引。
- 哈希索引只支持等值比较查询,不支持任何范围查询,如 WHERE price > 100。
- 哈希索引有可能出现哈希冲突,出现哈希冲突时,必须遍历链表中所有的行指针,逐行比较,直到找到符合条件的行。
### 2.2. B-Tree 和 B+Tree 有什么区别?
- B+Tree 更适合外部存储(一般指磁盘存储),由于内节点(非叶子节点)不存储 data所以一个节点可以存储更多的内节点每个节点能索引的范围更大更精确。也就是说使用 B+Tree 单次磁盘 IO 的信息量相比较 B-Tree 更大IO 效率更高。
- mysql 是关系型数据库经常会按照区间来访问某个索引列B+Tree 的叶子节点间按顺序建立了链指针,加强了区间访问性,所以 B+Tree 对索引列上的区间范围查询很友好。而 B-Tree 每个节点的 key 和 data 在一起,无法进行区间查找。
### 2.3. 索引原则有哪些?
#### 2.3.1. 独立的列
如果查询中的列不是独立的列,则数据库不会使用索引。
“独立的列” 是指索引列不能是表达式的一部分,也不能是函数的参数。
:x: 错误示例:
```sql
SELECT actor_id FROM sakila.actor WHERE actor_id + 1 = 5;
SELECT ... WHERE TO_DAYS(CURRENT_DAT) - TO_DAYS(date_col) <= 10;
```
#### 2.3.2. 前缀索引和索引选择性
有时候需要索引很长的字符列,这会让索引变得大且慢。
解决方法是:可以索引开始的部分字符,这样可以大大节约索引空间,从而提高索引效率。但这样也会降低索引的选择性。
索引的选择性是指:不重复的索引值和数据表记录总数的比值。最大值为 1此时每个记录都有唯一的索引与其对应。选择性越高查询效率也越高。
对于 BLOB/TEXT/VARCHAR 这种文本类型的列,必须使用前缀索引,因为数据库往往不允许索引这些列的完整长度。
要选择足够长的前缀以保证较高的选择性,同时又不能太长(节约空间)。
#### 2.3.3. 多列索引
不要为每个列创建独立的索引。
#### 2.3.4. 选择合适的索引列顺序
经验法则:将选择性高的列或基数大的列优先排在多列索引最前列。
但有时,也需要考虑 WHERE 子句中的排序、分组和范围条件等因素,这些因素也会对查询性能造成较大影响。
#### 2.3.5. 聚簇索引
聚簇索引不是一种单独的索引类型,而是一种数据存储方式。
聚簇表示数据行和相邻的键值紧凑地存储在一起。因为无法同时把数据行存放在两个不同的地方,所以一个表只能有一个聚簇索引。
#### 2.3.6. 覆盖索引
索引包含所有需要查询的字段的值。
具有以下优点:
- 因为索引条目通常远小于数据行的大小,所以若只读取索引,能大大减少数据访问量。
- 一些存储引擎(例如 MyISAM在内存中只缓存索引而数据依赖于操作系统来缓存。因此只访问索引可以不使用系统调用通常比较费时
- 对于 InnoDB 引擎,若辅助索引能够覆盖查询,则无需访问主索引。
#### 2.3.7. 使用索引扫描来做排序
索引最好既满足排序,又用于查找行。这样,就可以使用索引来对结果排序。
#### 2.3.8. = 和 in 可以乱序
比如 a = 1 and b = 2 and c = 3 建立a,b,c索引可以任意顺序mysql 的查询优化器会帮你优化成索引可以识别的形式。
#### 2.3.9. 尽量的扩展索引,不要新建索引
比如表中已经有 a 的索引,现在要加(a,b)的索引,那么只需要修改原来的索引即可。
## 3. 事务
### 3.1. 数据库事务隔离级别?事务隔离级别分别解决什么问题?
- 未提交读READ UNCOMMITTED - 事务中的修改,即使没有提交,对其它事务也是可见的。
- 提交读READ COMMITTED - 一个事务只能读取已经提交的事务所做的修改。换句话说,一个事务所做的修改在提交之前对其它事务是不可见的。
- 可重复读REPEATABLE READ - 保证在同一个事务中多次读取同样数据的结果是一样的。
- 可串行化SERIALIXABLE - 强制事务串行执行。
| 隔离级别 | 脏读 | 不可重复读 | 幻影读 |
| :------: | :--: | :--------: | :----: |
| 未提交读 | YES | YES | YES |
| 提交读 | NO | YES | YES |
| 可重复读 | NO | NO | YES |
| 可串行化 | NO | NO | NO |
### 3.2. 如何解决分布式事务?若出现网络问题或宕机问题,如何解决?
## 4. 锁
### 4.1. 数据库锁有哪些类型?如何实现?
#### 4.1.1. 锁粒度
- **表级锁table lock** - 锁定整张表。用户对表进行写操作前,需要先获得写锁,这会阻塞其他用户对该表的所有读写操作。只有没有写锁时,其他用户才能获得读锁,读锁之间不会相互阻塞。
- **行级锁row lock** - 仅对指定的行记录进行加锁,这样其它进程还是可以对同一个表中的其它记录进行操作。
InnoDB 行锁是通过给索引上的索引项加锁来实现的。只有通过索引条件检索数据InnoDB 才使用行级锁否则InnoDB 将使用表锁!
索引分为主键索引和非主键索引两种,如果一条 sql 语句操作了主键索引MySQL 就会锁定这条主键索引如果一条语句操作了非主键索引MySQL 会先锁定该非主键索引,再锁定相关的主键索引。在 UPDATE、DELETE 操作时MySQL 不仅锁定 WHERE 条件扫描过的所有索引记录,而且会锁定相邻的键值,即所谓的 next-key locking。
当两个事务同时执行一个锁住了主键索引在等待其他相关索引。另一个锁定了非主键索引在等待主键索引。这样就会发生死锁。发生死锁后InnoDB 一般都可以检测到,并使一个事务释放锁回退,另一个获取锁完成事务。
#### 4.1.2. 读写锁
- 排它锁Exclusive简写为 X 锁,又称写锁。
- 共享锁Shared简写为 S 锁,又称读锁。
有以下两个规定:
- 一个事务对数据对象 A 加了 X 锁,就可以对 A 进行读取和更新。加锁期间其它事务不能对 A 加任何锁。
- 一个事务对数据对象 A 加了 S 锁,可以对 A 进行读取操作,但是不能进行更新操作。加锁期间其它事务能对 A 加 S 锁,但是不能加 X 锁。
锁的兼容关系如下:
| - | X | S |
| :-: | :-: | :-: |
| X | NO | NO |
| S | NO | YES |
使用:
- 排他锁:`SELECT ... FOR UPDATE;`
- 共享锁:`SELECT ... LOCK IN SHARE MODE;`
innodb 下的记录锁也叫行锁间隙锁next-key 锁统统属于排他锁。
在 InnoDB 中行锁是通过给索引上的索引项加锁来实现的。如果没有索引InnoDB 将会通过隐藏的聚簇索引来对记录加锁。另外,根据针对 sql 语句检索条件的不同,加锁又有以下三种情形需要我们掌握。
1. Record lock对索引项加锁。若没有索引项则使用表锁。
2. Gap lock对索引项之间的间隙加锁。
3. Next-key lock1+2锁定一个范围并且锁定记录本身。对于行的查询都是采用该方法主要目的是解决幻读的问题。当利用范围条件而不是相等条件获取排他锁时innoDB 会给符合条件的所有数据加锁。对于在条件范围内但是不存在的记录叫做间隙。innoDB 也会对这个间隙进行加锁。另外,使用相等的检索条件时,若指定了本身不存在的记录作为检索条件的值的话,则此值对应的索引项也会加锁。
#### 4.1.3. 意向锁
使用意向锁Intention Locks可以更容易地支持多粒度封锁。
在存在行级锁和表级锁的情况下,事务 T 想要对表 A 加 X 锁,就需要先检测是否有其它事务对表 A 或者表 A 中的任意一行加了锁,那么就需要对表 A 的每一行都检测一次,这是非常耗时的。
意向锁在原来的 X/S 锁之上引入了 IX/ISIX/IS 都是表锁,用来表示一个事务想要在表中的某个数据行上加 X 锁或 S 锁。有以下两个规定:
- 一个事务在获得某个数据行对象的 S 锁之前,必须先获得表的 IS 锁或者更强的锁;
- 一个事务在获得某个数据行对象的 X 锁之前,必须先获得表的 IX 锁。
通过引入意向锁,事务 T 想要对表 A 加 X 锁,只需要先检测是否有其它事务对表 A 加了 X/IX/S/IS 锁,如果加了就表示有其它事务正在使用这个表或者表中某一行的锁,因此事务 T 加 X 锁失败。
各种锁的兼容关系如下:
| - | X | IX | S | IS |
| :-: | :-: | :-: | :-: | :-: |
| X | NO | NO | NO | NO |
| IX | NO | YES | NO | YES |
| S | NO | NO | YES | YES |
| IS | NO | YES | YES | YES |
解释如下:
- 任意 IS/IX 锁之间都是兼容的,因为它们只是表示想要对表加锁,而不是真正加锁;
- S 锁只与 S 锁和 IS 锁兼容,也就是说事务 T 想要对数据行加 S 锁,其它事务可以已经获得对表或者表中的行的 S 锁。
意向锁是 InnoDB 自动加的,不需要用户干预。
## 5. 分库分表
### 5.1. 为什么要分库分表?
分库分表的基本思想就要把一个数据库切分成多个部分放到不同的数据库(server)上,从而缓解单一数据库的性能问题。
### 5.2. 分库分表的常见问题以及解决方案?
#### 5.2.1. 事务问题
方案一:使用分布式事务
- 优点:交由数据库管理,简单有效
- 缺点:性能代价高,特别是 shard 越来越多时
方案二:由应用程序和数据库共同控制
- 原理:将一个跨多个数据库的分布式事务分拆成多个仅处于单个数据库上面的小事务,并通过应用程序来总控各个小事务。
- 优点:性能上有优势
- 缺点:需要应用程序在事务控制上做灵活设计。如果使用了 spring 的事务管理,改动起来会面临一定的困难。
#### 5.2.2. 跨节点 Join 的问题
只要是进行切分,跨节点 Join 的问题是不可避免的。但是良好的设计和切分却可以减少此类情况的发生。解决这一问题的普遍做法是分两次查询实现。在第一次查询的结果集中找出关联数据的 id根据这些 id 发起第二次请求得到关联数据。
#### 5.2.3. 跨节点的 count,order by,group by 以及聚合函数问题
这些是一类问题,因为它们都需要基于全部数据集合进行计算。多数的代理都不会自动处理合并工作。
解决方案:与解决跨节点 join 问题的类似,分别在各个节点上得到结果后在应用程序端进行合并。和 join 不同的是每个节点的查询可以并行执行,因此很多时候它的速度要比单一大表快很多。但如果结果集很大,对应用程序内存的消耗是一个问题。
#### 5.2.4. ID 唯一性
一旦数据库被切分到多个物理节点上,我们将不能再依赖数据库自身的主键生成机制。一方面,某个分区数据库自生成的 ID 无法保证在全局上是唯一的;另一方面,应用程序在插入数据之前需要先获得 ID以便进行 SQL 路由。
一些常见的主键生成策略:
- 使用全局唯一 IDGUID。
- 为每个分片指定一个 ID 范围。
- 分布式 ID 生成器 (如 Twitter 的 Snowflake 算法)。
#### 5.2.5. 数据迁移,容量规划,扩容等问题
来自淘宝综合业务平台团队,它利用对 2 的倍数取余具有向前兼容的特性(如对 4 取余得 1 的数对 2 取余也是 1来分配数据避免了行级别的数据迁移但是依然需要进行表级别的迁移同时对扩容规模和分表数量都有限制。总得来说这些方案都不是十分的理想多多少少都存在一些缺点这也从一个侧面反映出了 Sharding 扩容的难度。
#### 5.2.6. 分库数量
分库数量首先和单库能处理的记录数有关一般来说Mysql 单库超过 5000 万条记录Oracle 单库超过 1 亿条记录DB 压力就很大(当然处理能力和字段数量/访问模式/记录长度有进一步关系)。
#### 5.2.7. 跨分片的排序分页
- 如果是在前台应用提供分页,则限定用户只能看前面 n 页,这个限制在业务上也是合理的,一般看后面的分页意义不大(如果一定要看,可以要求用户缩小范围重新查询)。
- 如果是后台批处理任务要求分批获取数据,则可以加大 page size比如每次获取 5000 条记录,有效减少分页数(当然离线访问一般走备库,避免冲击主库)。
- 分库设计时,一般还有配套大数据平台汇总所有分库的记录,有些分页查询可以考虑走大数据平台。
### 5.3. 如何设计可以动态扩容缩容的分库分表方案?
### 5.4. 有哪些分库分表中间件?各自有什么优缺点?底层实现原理?
#### 简单易用的组件:
- [当当 sharding-jdbc](https://github.com/dangdangdotcom/sharding-jdbc)
- [蘑菇街 TSharding](https://github.com/baihui212/tsharding)
#### 强悍重量级的中间件:
- [sharding ](https://github.com/go-pg/sharding)
- [TDDL Smart Client 的方式(淘宝)](https://github.com/alibaba/tb_tddl)
- [Atlas(Qihoo 360)](https://github.com/Qihoo360/Atlas)
- [alibaba.cobar(是阿里巴巴B2B部门开发)](https://github.com/alibaba/cobar)
- [MyCAT基于阿里开源的 Cobar 产品而研发)](http://www.mycat.org.cn/)
- [Oceanus(58 同城数据库中间件)](https://github.com/58code/Oceanus)
- [OneProxy(支付宝首席架构师楼方鑫开发)](http://www.cnblogs.com/youge-OneSQL/articles/4208583.html)
- [vitess谷歌开发的数据库中间件](https://github.com/youtube/vitess)
## 6. 数据库优化
### 6.1. 什么是执行计划?
## 7. 数据库架构设计
### 7.1. 高并发系统数据层面如何设计?
#### 读写分离的原理
主服务器用来处理写操作以及实时性要求比较高的读操作,而从服务器用来处理读操作。
读写分离常用代理方式来实现,代理服务器接收应用层传来的读写请求,然后决定转发到哪个服务器。
MySQL 读写分离能提高性能的原因在于:
- 主从服务器负责各自的读和写,极大程度缓解了锁的争用;
- 从服务器可以配置 MyISAM 引擎,提升查询性能以及节约系统开销;
- 增加冗余,提高可用性。
<div align="center">
<img src="https://raw.githubusercontent.com/dunwu/Database/master/images/mysql/master-slave-proxy.png" />
</div>
##### Mysql 的复制原理
Mysql 支持两种复制:基于行的复制和基于语句的复制。
这两种方式都是在主库上记录二进制日志,然后在从库重放日志的方式来实现异步的数据复制。这意味着:复制过程存在时延,这段时间内,主从数据可能不一致。
主要涉及三个线程binlog 线程、I/O 线程和 SQL 线程。
- **binlog 线程** 负责将主服务器上的数据更改写入二进制文件binlog中。
- **I/O 线程** :负责从主服务器上读取二进制日志文件,并写入从服务器的中继日志中。
- **SQL 线程** :负责读取中继日志并重放其中的 SQL 语句。
<div align="center">
<img src="https://raw.githubusercontent.com/dunwu/Database/master/images/mysql/master-slave.png" />
</div>
#### 垂直切分
按照业务线或功能模块拆分为不同数据库。
更进一步是服务化改造,将强耦合的系统拆分为多个服务。
#### 水平切分
- 哈希取模hash(key) % NUM_DB
- 范围:可以是 ID 范围也可以是时间范围
- 映射表:使用单独的一个数据库来存储映射关系