update docs

pull/1/head
Zhang Peng 2019-07-02 12:05:43 +08:00
parent 89a262a79f
commit b78f9d4d13
13 changed files with 1173 additions and 601 deletions

View File

@ -7,10 +7,11 @@
## :memo: 知识点
- :one: [关系型数据库](docs/sql/README.md)
- [关系型数据库面试题](docs/sql/关系型数据库面试题.md)
- [关系型数据库基本原理](docs/sql/关系型数据库基本原理.md)
- [SQL 基本语法](docs/sql/sql.md)
- [关系型数据库面试题](docs/sql/sql-interview.md)
- [关系型数据库基本原理](docs/sql/sql-theory.md)
- [SQL 基本语法](docs/sql/sql-grammar.md)
- [H2 快速指南](docs/sql/h2.md)
- [SqLite 快速指南](docs/sql/sqlite.md)
- [PostgreSQL 快速指南](docs/sql/postgresql.md)
- [数据库中间件 flyway](docs/sql/middleware/flyway.md)
- :two: [Nosql](docs/nosql/README.md)

View File

@ -1,16 +1,15 @@
# 数据库教程
> 数据库技术总结
>
> :dart: 所有配套源码整理归档在 [**db-tutorial**](https://github.com/dunwu/db-tutorial/codes) 中。
## :memo: 知识点
## 知识点
- :one: [关系型数据库](sql/README.md)
- [关系型数据库面试题](sql/关系型数据库面试题.md)
- [关系型数据库基本原理](sql/关系型数据库基本原理.md)
- [SQL 基本语法](sql/sql.md)
- [关系型数据库面试题](sql/sql-interview.md)
- [关系型数据库基本原理](sql/sql-theory.md)
- [SQL 基本语法](sql/sql-grammar.md)
- [H2 快速指南](sql/h2.md)
- [SqLite 快速指南](sql/sqlite.md)
- [PostgreSQL 快速指南](sql/postgresql.md)
- [数据库中间件 flyway](sql/middleware/flyway.md)
- :two: [Nosql](nosql/README.md)
@ -21,6 +20,6 @@
- :four: [Redis](nosql/redis/README.md)
- [Redis 快速入门](nosql/redis/redis-quickstart.md)
## :door: 传送门
## 传送门
| [技术文档归档](https://github.com/dunwu/blog) | [数据库教程系列](https://github.com/dunwu/db-tutorial/codes) |

View File

@ -0,0 +1,7 @@
<div align="center"><img width="100px" src="http://dunwu.test.upcdn.net/images/others/zp.png"/></div>
# DB Tutorial
> 数据库教程
[开始阅读](README.md)

View File

@ -4,18 +4,209 @@
<meta charset="UTF-8" />
<title>db-tutorial</title>
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
<meta name="description" content="数据库教程" />
<meta name="description" content="DB Tutorial" />
<meta
name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"
/>
<link rel="icon" href="http://dunwu.test.upcdn.net/images/others/zp_50_50.png" type="image/x-icon" />
<link rel="stylesheet" href="//unpkg.com/docsify/lib/themes/vue.css" title="vue" />
<link rel="stylesheet" href="//unpkg.com/docsify/lib/themes/dark.css" title="dark" disabled />
<link rel="stylesheet" href="//unpkg.com/docsify/lib/themes/buble.css" title="buble" disabled />
<link rel="stylesheet" href="//unpkg.com/docsify/lib/themes/pure.css" title="pure" disabled />
<style>
nav.app-nav li ul {
min-width: 100px;
h1 + ul {
display: block !important;
}
.content img,
.sidebar img {
border: none;
border-radius: 8px;
box-shadow: 0 0 8px grey;
}
.content,
.sidebar,
.sidebar-toggle,
body,
.search input {
color: #6b615f !important;
background-color: #fff4e6 !important;
}
.content strong,
.sidebar strong,
body strong {
color: #5c5869 !important;
}
</style>
<style>
.cover-main .anchor span {
text-align: center;
background-image: -webkit-linear-gradient(left, #ffdcb4, #b96972 25%, #e88a57 50%, #804170 75%, #a596cd);
-webkit-text-fill-color: transparent;
-webkit-background-clip: text;
-webkit-background-size: 200% 100%;
-webkit-animation: masked-animation 1.5s infinite linear;
font-family: "Brush Script MT", 隶书, serif;
font-weight: 600;
}
.cover-main blockquote p {
color: #5c5869;
font-family: "Arial", 隶书, serif;
}
.cover-main ul a:hover {
color: #fe4165 !important;
}
.cover-main p a:hover {
text-align: center;
background-image: -webkit-linear-gradient(left, #ffdcb4, #b96972 25%, #e88a57 50%, #804170 75%, #a596cd);
-webkit-text-fill-color: transparent;
-webkit-background-clip: text;
-webkit-background-size: 200% 100%;
-webkit-animation: masked-animation 1.5s infinite linear;
}
/* content 样式内容 */
.sidebar a,
.content a {
color: #399ab2 !important;
text-decoration: none !important;
}
.sidebar a:hover,
.content a:hover {
color: #fe4165 !important;
text-decoration: underline !important;
}
.content h1 :hover,
.content h2 :hover,
.content h3 :hover,
.content h4 :hover {
text-align: center;
background-image: -webkit-linear-gradient(left, #ffdcb4, #b96972 25%, #e88a57 50%, #804170 75%, #a596cd);
-webkit-text-fill-color: transparent;
-webkit-background-clip: text;
-webkit-background-size: 200% 100%;
-webkit-animation: masked-animation 1.5s infinite linear;
font-family: "微软雅黑", serif;
font-weight: bold;
}
@-webkit-keyframes masked-animation {
0% {
background-position: 0 0;
}
100% {
background-position: -100% 0;
}
}
.content h1 a,
.content h1 span {
color: #399ab2 !important;
font-size: 30px;
text-shadow: 2px 2px 5px grey;
}
.content h2 a,
.content h2 span {
color: #60497c !important;
font-size: 26px;
text-shadow: 2px 2px 5px grey;
}
.content h3 a,
.content h3 span {
color: #346093 !important;
font-size: 22px;
text-shadow: 2px 2px 5px grey;
}
.content h4 a,
.content h4 span {
font-size: 18px;
color: #78943a;
text-shadow: 2px 2px 5px grey;
}
img.emoji {
border: none;
border-radius: 0;
box-shadow: none;
}
</style>
<style>
.content > p {
font-size: 16px !important;
line-height: 24px;
}
.content blockquote {
display: block;
padding: 0 16px;
border-left: 8px solid #dddfe4;
background: #fff2c9;
overflow: auto;
}
.content pre {
padding-left: 0 !important;
padding-right: 0 !important;
border-radius: 8px;
box-shadow: 1px 1px 20px 3px #dddddd !important;
}
.content code {
background-color: white;
border-radius: 6px;
box-shadow: 1px 1px 1px whitesmoke;
}
.content table {
display: table;
padding-left: 0 !important;
padding-right: 0 !important;
box-shadow: 2px 2px 20px 6px #dddddd !important;
}
.content th {
font-weight: bold;
font-size: 16px;
background-color: #cce6b6;
}
</style>
<style>
@media (min-width: 600px) {
.markdown-section pre > code {
font-size: 0.9rem !important;
}
}
@media (max-width: 600px) {
.markdown-section pre > code {
padding-top: 5px;
padding-bottom: 5px;
}
pre:after {
content: "" !important;
}
}
@media (min-width: 600px) {
pre code {
padding-left: 20px !important;
}
}
@media (max-width: 600px) {
pre {
padding-left: 0px !important;
padding-right: 0px !important;
}
}
</style>
</head>
@ -23,31 +214,34 @@
<div id="app">正在加载...</div>
<script>
window.$docsify = {
auto2top: true,
loadNavbar: "navbar.md",
name: "数据库教程",
name: "DB Tutorial",
repo: "https://github.com/dunwu/db-tutorial",
logo: "http://dunwu.test.upcdn.net/images/others/zp_100_100.png",
auto2top: true,
coverpage: "coverpage.md",
maxLevel: 4,
subMaxLevel: 4,
formatUpdated: "{MM}/{DD} {HH}:{mm}",
pagination: {
previousText: "上一章节",
nextText: "下一章节",
crossChapter: true
search: {
maxAge: 86400000,
paths: ["/"],
placeholder: "🔍 搜索",
noData: "没有结果!",
depth: 4
}
// coverpage: true,
// 完整配置参数
};
</script>
<script src="//unpkg.com/docsify/lib/docsify.min.js"></script>
<script src="//unpkg.com/docsify/lib/plugins/emoji.js"></script>
<script src="//unpkg.com/docsify/lib/plugins/zoom-image.js"></script>
<script src="//unpkg.com/docsify-pagination/dist/docsify-pagination.min.js"></script>
<script src="//unpkg.com/docsify/lib/plugins/search.js"></script>
<!--代码高亮-->
<!--@see https://github.com/PrismJS/prism -->
<script src="//unpkg.com/prismjs/components/prism-basic.min.js"></script>
<script src="//unpkg.com/prismjs/components/prism-bash.min.js"></script>
<script src="//unpkg.com/prismjs/components/prism-java.min.js"></script>
<script src="//unpkg.com/prismjs/components/prism-ini.min.js"></script>
<script src="//unpkg.com/prismjs/components/prism-sql.min.js"></script>
<script src="//unpkg.com/prismjs/components/prism-markdown.min.js"></script>
<script src="//unpkg.com/prismjs/components/prism-nginx.min.js"></script>
</body>
</html>

View File

@ -1,14 +0,0 @@
- :one: [关系型数据库](sql/README.md)
- [关系型数据库面试题](sql/关系型数据库面试题.md)
- [关系型数据库基本原理](sql/关系型数据库基本原理.md)
- [SQL 基本语法](sql/sql.md)
- [H2 快速指南](sql/h2.md)
- [PostgreSQL 快速指南](sql/postgresql.md)
- [数据库中间件 flyway](sql/middleware/flyway.md)
- :two: [Nosql](nosql/README.md)
- :three: [Mysql](sql/mysql/README.md)
- [Mysql 命令](sql/mysql/mysql-cli.md)
- [Mysql 维护](sql/mysql/mysql-maintain.md)
- [Mysql 原理](sql/mysql/mysql-theory.md)
- :four: [Redis](nosql/redis/README.md)
- [Redis 快速入门](nosql/redis/redis-quickstart.md)

View File

@ -0,0 +1,9 @@
{
"name": "linux-tutorial",
"version": "1.0.0",
"scripts": {
"start": "docsify serve ./"
},
"dependencies": {},
"devDependencies": {}
}

View File

@ -2,30 +2,9 @@
> 关系数据库,是建立在关系模型基础上的数据库,借助于集合代数等数学概念和方法来处理数据库中的数据。现实世界中的各种实体以及实体之间的各种联系均用关系模型来表示。关系模型由关系数据结构、关系操作集合、关系完整性约束三部分组成。
## :memo: 知识点
### 通用知识点
- [关系型数据库 SQL 基本语法](sql.md)
- [关系型数据库基本原理](关系型数据库基本原理.md)
- [关系型数据库面试题](关系型数据库面试题.md)
### 关系型数据库管理系统RDBMS
- [Mysql](mysql)
- Oracle
- SQL Server
- [PostgreSQL](postgresql.md)
- SQLite
- DB2
- [H2](h2.md)
### 流行数据库中间件
- [flyway](middleware/flyway.md)
## :books: 学习资源
## :door: 传送门
| [技术文档归档](https://github.com/dunwu/blog) | [数据库教程系列](https://github.com/dunwu/db-tutorial/codes) |
- [关系型数据库面试题](sql-interview.md)
- [关系型数据库基本原理](sql-theory.md)
- [SQL 基本语法](sql-grammar.md)
- [H2 快速指南](h2.md)
- [PostgreSQL 快速指南](postgresql.md)
- [数据库中间件 flyway](middleware/flyway.md)

View File

@ -1,26 +1,5 @@
# Mysql 维护
<!-- TOC depthFrom:2 depthTo:3 -->
- [安装配置](#安装配置)
- [安装 mysql yum 源](#安装-mysql-yum-源)
- [安装 mysql 服务器](#安装-mysql-服务器)
- [启动 mysql 服务](#启动-mysql-服务)
- [初始化数据库密码](#初始化数据库密码)
- [配置远程访问](#配置远程访问)
- [跳过登录认证](#跳过登录认证)
- [运维](#运维)
- [备份与恢复](#备份与恢复)
- [备份](#备份)
- [恢复](#恢复)
- [卸载](#卸载)
- [问题](#问题)
- [JDBC 与 Mysql 因 CST 时区协商无解导致偏差了 14 或 13 小时](#jdbc-与-mysql-因-cst-时区协商无解导致偏差了-14-或-13-小时)
- [参考资料](#参考资料)
- [:door: 传送门](#door-传送门)
<!-- /TOC -->
## 安装配置
通过 rpm 包安装
@ -96,14 +75,14 @@ $ yum install mysql-community-server
```bash
# 启动 mysql 服务
$ systemctl start mysqld.service
systemctl start mysqld.service
# 查看运行状态
$ systemctl status mysqld.service
systemctl status mysqld.service
# 开机启动
$ systemctl enable mysqld
$ systemctl daemon-reload
systemctl enable mysqld
systemctl daemon-reload
```
### 初始化数据库密码
@ -118,22 +97,24 @@ $ grep "password" /var/log/mysqld.log
执行命令:
```bash
mysql -uroot -p
mysql -uroot -p<临时密码>
```
输入临时密码,进入 mysql
输入临时密码,进入 mysql,如果要修改密码,执行以下指令:
```bash
ALTER user 'root'@'localhost' IDENTIFIED BY 'Tw#123456';
ALTER user 'root'@'localhost' IDENTIFIED BY '你的密码';
```
注:密码强度默认为中等,大小写字母、数字、特殊符号,只有修改成功后才能修改配置再设置更简单的密码
### 配置远程访问
```
GRANT ALL ON *.* TO 'root'@'localhost';
FLUSH PRIVILEGES;
```sql
mysql> CREATE USER 'root'@'%' IDENTIFIED BY '你的密码';
mysql> GRANT ALL ON *.* TO 'root'@'%';
mysql> ALTER USER 'root'@'%' IDENTIFIED WITH mysql_native_password BY '你的密码';
mysql> FLUSH PRIVILEGES;
```
### 跳过登录认证
@ -148,15 +129,257 @@ vim /etc/my.cnf
执行 `service mysqld restart`,重启 mysql
## 部署
### 主从节点部署
假设需要配置一个主从 Mysql 服务器环境
- master 节点192.168.8.10
- slave 节点192.168.8.11
#### 配置主从同步
1主节点配置
执行 `vi /etc/my.cnf` ,添加如下配置:
```ini
[mysqld]
server-id=1
log-bin=mysql-bin
```
- `server-id` - 服务器 ID 号;
- `log-bin` - 同步的日志路径及文件名一定注意这个目录要是mysql有权限写入的
2从节点配置
执行 `vi /etc/my.cnf` ,添加如下配置:
```ini
[mysqld]
server-id=2
log-bin=mysql-bin
```
3创建用于复制操作的用户
```sql
mysql> CREATE USER 'sync'@'192.168.8.11' IDENTIFIED WITH mysql_native_password BY '密码'; -- 创建用户
mysql> GRANT REPLICATION SLAVE ON *.* TO 'sync'@'192.168.8.11'; -- 授权
mysql> FLUSH PRIVILEGES; -- 刷新授权表信息
```
4查看主节点状态
```sql
mysql> show master status;
+------------------+----------+--------------+---------------------------------------------+-------------------+
| File | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set |
+------------------+----------+--------------+---------------------------------------------+-------------------+
| mysql-bin.000001 | 4202 | | mysql,information_schema,performance_schema | |
+------------------+----------+--------------+---------------------------------------------+-------------------+
1 row in set (0.00 sec)
```
5在Slave节点上设置主节点参数
`MASTER_LOG_FILE``MASTER_LOG_POS` 参数要分别与 `show master status` 指令获得的 `File``Position` 属性值对应。
```sql
mysql> CHANGE MASTER TO
MASTER_HOST='192.168.199.149',
MASTER_USER='sync',
MASTER_PASSWORD='密码',
MASTER_LOG_FILE='binlog.000001',
MASTER_LOG_POS=4202;
```
6查看主从同步状态
```
mysql> show slave status\G;
```
说明:如果以下两项参数均为 YES说明配置正确。
- `Slave_IO_Running`
- `Slave_SQL_Running`
7启动 slave 进程
```
mysql> start slave;
```
#### 同步主节点已有数据到从节点
主库操作:
1停止主库的数据更新操作
```sql
mysql> flush tables with read lock;
```
2新开终端生成主数据库的备份导出数据库
```bash
$ mysqldump -uroot -p<密码> test > test.sql
```
3将备份文件传到从库
```bash
$ scp test.sql root@192.168.8.11:/root/
```
4主库解锁
```mysql
mysql> unlock tables;
```
从库操作:
1停止从库slave
```mysql
mysql> stop slave;
```
2新建数据库test
```mysql
mysql> create database test default charset utf8;
```
3导入数据
```bash
$ mysql -uroot -ptest123 cmdb<cmdb.sql
```
4查看从库已有该数据库和数据
```mysql
mysql> show databases;
+--------------------+
| Database |
+--------------------+
| information_schema |
| cmdb |
| mysql |
| performance_schema |
| test |
+--------------------+
```
## 运维
## 备份与恢复
### 创建用户
```
CREATE USER 'username'@'host' IDENTIFIED BY 'password';
```
说明:
- username你将创建的用户名
- host指定该用户在哪个主机上可以登陆如果是本地用户可用 localhost如果想让该用户可以**从任意远程主机登陆**,可以使用通配符`%`
- password该用户的登陆密码密码可以为空如果为空则该用户可以不需要密码登陆服务器
示例:
```sql
CREATE USER 'dog'@'localhost' IDENTIFIED BY '123456';
CREATE USER 'pig'@'192.168.1.101_' IDENDIFIED BY '123456';
CREATE USER 'pig'@'%' IDENTIFIED BY '123456';
CREATE USER 'pig'@'%' IDENTIFIED BY '';
CREATE USER 'pig'@'%';
```
### 授权
命令:
```sql
GRANT privileges ON databasename.tablename TO 'username'@'host'
```
说明:
- privileges用户的操作权限如`SELECT``INSERT``UPDATE`等,如果要授予所的权限则使用`ALL`
- databasename数据库名
- tablename表名如果要授予该用户对所有数据库和表的相应操作权限则可用`*`表示,如`*.*`
示例:
```sql
GRANT SELECT, INSERT ON test.user TO 'pig'@'%';
GRANT ALL ON *.* TO 'pig'@'%';
GRANT ALL ON maindataplus.* TO 'pig'@'%';
```
注意:
用以上命令授权的用户不能给其它用户授权,如果想让该用户可以授权,用以下命令:
```sql
GRANT privileges ON databasename.tablename TO 'username'@'host' WITH GRANT OPTION;
```
### 撤销授权
命令:
```
REVOKE privilege ON databasename.tablename FROM 'username'@'host';
```
说明:
privilege, databasename, tablename同授权部分
例子:
```
REVOKE SELECT ON *.* FROM 'pig'@'%';
```
注意:
假如你在给用户`'pig'@'%'`授权的时候是这样的(或类似的):`GRANT SELECT ON test.user TO 'pig'@'%'`,则在使用`REVOKE SELECT ON *.* FROM 'pig'@'%';`命令并不能撤销该用户对 test 数据库中 user 表的`SELECT` 操作。相反,如果授权使用的是`GRANT SELECT ON *.* TO 'pig'@'%';`则`REVOKE SELECT ON test.user FROM 'pig'@'%';`命令也不能撤销该用户对 test 数据库中 user 表的`Select`权限。
具体信息可以用命令`SHOW GRANTS FOR 'pig'@'%';` 查看。
### 更改用户密码
```sql
SET PASSWORD FOR 'username'@'host' = PASSWORD('newpassword');
```
如果是当前登陆用户用:
```sql
SET PASSWORD = PASSWORD("newpassword");
```
示例:
```sql
SET PASSWORD FOR 'pig'@'%' = PASSWORD("123456");
```
### 备份与恢复
Mysql 备份数据使用 mysqldump 命令。
mysqldump 将数据库中的数据备份成一个文本文件,表的结构和表中的数据将存储在生成的文本文件中。
### 备份
备份
1备份一个数据库
@ -183,7 +406,7 @@ mysqldump -u <username> -p --databases <database1> <database2> ... > backup.sql
mysqldump -u <username> -p -all-databases > backup.sql
```
### 恢复
恢复
Mysql 恢复数据使用 mysqldump 命令。
@ -193,7 +416,7 @@ Mysql 恢复数据使用 mysqldump 命令。
mysql -u <username> -p <database> < backup.sql
```
## 卸载
### 卸载
1查看已安装的 mysql
@ -258,11 +481,12 @@ Query OK, 0 rows affected (0.00 sec)
## 参考资料
https://www.cnblogs.com/xiaopotian/p/8196464.html
https://www.cnblogs.com/bigbrotherer/p/7241845.html
https://blog.csdn.net/managementandjava/article/details/80039650
http://www.manongjc.com/article/6996.html
https://www.cnblogs.com/xyabk/p/8967990.html
- https://www.cnblogs.com/xiaopotian/p/8196464.html
- https://www.cnblogs.com/bigbrotherer/p/7241845.html
- https://blog.csdn.net/managementandjava/article/details/80039650
- http://www.manongjc.com/article/6996.html
- https://www.cnblogs.com/xyabk/p/8967990.html
- [MySQL 8.0主从Master-Slave配置](https://blog.csdn.net/zyhlwzy/article/details/80569422)
## :door: 传送门

View File

@ -2,74 +2,34 @@
> 关键词:存储引擎,数据类型,事务,MVCC,索引,执行计划,主从复制
<!-- TOC depthFrom:2 depthTo:3 -->
- [1. 存储引擎](#1-存储引擎)
- [1.1. InnoDB](#11-innodb)
- [1.2. MyISAM](#12-myisam)
- [1.3. 选择存储引擎](#13-选择存储引擎)
- [2. 数据类型](#2-数据类型)
- [2.1. 整型](#21-整型)
- [2.2. 浮点数](#22-浮点数)
- [2.3. 字符串](#23-字符串)
- [2.4. 时间和日期](#24-时间和日期)
- [3. 事务](#3-事务)
- [3.1. 事务隔离级别](#31-事务隔离级别)
- [3.2. 死锁](#32-死锁)
- [4. MVCC](#4-mvcc)
- [5. 索引](#5-索引)
- [5.1. 索引的优点和缺点](#51-索引的优点和缺点)
- [5.2. 索引类型](#52-索引类型)
- [5.3. 索引数据结构](#53-索引数据结构)
- [5.4. 索引原则](#54-索引原则)
- [6. 查询性能优化](#6-查询性能优化)
- [6.1. 使用 Explain 进行分析](#61-使用-explain-进行分析)
- [6.2. 优化数据访问](#62-优化数据访问)
- [6.3. 重构查询方式](#63-重构查询方式)
- [7. 复制](#7-复制)
- [7.1. 主从复制](#71-主从复制)
- [7.2. 读写分离](#72-读写分离)
- [8. 参考资料](#8-参考资料)
- [:door: 传送门](#door-传送门)
<!-- /TOC -->
## 1. 存储引擎
在文件系统中Mysql 将每个数据库(也可以成为 schema保存为数据目录下的一个子目录。创建表示Mysql 会在数据库子目录下创建一个和表同名的 .frm 文件保存表的定义。因为 Mysql 使用文件系统的目录和文件来保存数据库和表的定义大小写敏感性和具体平台密切相关。Windows 中大小写不敏感;类 Unix 中大小写敏感。**不同的存储引擎保存数据和索引的方式是不同的,但表的定义则是在 Mysql 服务层统一处理的。**
### 1.1. InnoDB
InnoDB 是 MySQL 默认的事务型存储引擎,只有在需要 InnoDB 不支持的特性时,才考虑使用其它存储引擎。
InnoDB 实现了四个标准的隔离级别默认级别是可重复读REPEATABLE READ。在可重复读隔离级别下通过多版本并发控制MVCC+ 间隙锁next-key locking防止幻影读。
主索引是聚簇索引,在索引中保存了数据,从而避免直接读取磁盘,因此对查询性能有很大的提升。
内部做了很多优化,包括从磁盘读取数据时采用的可预测性读、能够加快读操作并且自动创建的自适应哈希索引、能够加速插入操作的插入缓冲区等。
支持真正的在线热备份。其它存储引擎不支持在线热备份,要获取一致性视图需要停止对所有表的写入,而在读写混合场景中,停止写入可能也意味着停止读取。
### 1.2. MyISAM
MyISAM 设计简单,数据以紧密格式存储。对于只读数据,或者表比较小、可以容忍修复操作,则依然可以使用 MyISAM。
MyISAM 提供了大量的特性,包括压缩表、空间数据索引等。
不支持事务。
不支持行级锁只能对整张表加锁读取时会对需要读到的所有表加共享锁写入时则对表加排它锁。但在表有读取操作的同时也可以往表中插入新的记录这被称为并发插入CONCURRENT INSERT
可以手工或者自动执行检查和修复操作,但是和事务恢复以及崩溃恢复不同,可能导致一些数据丢失,而且修复操作是非常慢的。
如果指定了 DELAY_KEY_WRITE 选项,在每次修改执行完成时,不会立即将修改的索引数据写入磁盘,而是会写到内存中的键缓冲区,只有在清理键缓冲区或者关闭表的时候才会将对应的索引块写入磁盘。这种方式可以极大的提升写入性能,但是在数据库或者主机崩溃时会造成索引损坏,需要执行修复操作。
### 1.3. 选择存储引擎
### 选择存储引擎
#### Mysql 内置的存储引擎
- **InnoDB** - Mysql 的默认事务型存储引擎。性能不错且支持自动崩溃恢复。
- **MyISAM** - Mysql 5.1 版本前的默认存储引擎。特性丰富但不支持事务,也没有崩溃恢复功能。
```
mysql> SHOW ENGINES;
+--------------------+---------+----------------------------------------------------------------+--------------+------+------------+
| Engine | Support | Comment | Transactions | XA | Savepoints |
+--------------------+---------+----------------------------------------------------------------+--------------+------+------------+
| FEDERATED | NO | Federated MySQL storage engine | NULL | NULL | NULL |
| MEMORY | YES | Hash based, stored in memory, useful for temporary tables | NO | NO | NO |
| InnoDB | DEFAULT | Supports transactions, row-level locking, and foreign keys | YES | YES | YES |
| PERFORMANCE_SCHEMA | YES | Performance Schema | NO | NO | NO |
| MyISAM | YES | MyISAM storage engine | NO | NO | NO |
| MRG_MYISAM | YES | Collection of identical MyISAM tables | NO | NO | NO |
| BLACKHOLE | YES | /dev/null storage engine (anything you write to it disappears) | NO | NO | NO |
| CSV | YES | CSV storage engine | NO | NO | NO |
| ARCHIVE | YES | Archive storage engine | NO | NO | NO |
+--------------------+---------+----------------------------------------------------------------+--------------+------+------------+
9 rows in set (0.00 sec)
```
- **InnoDB** - Mysql 的默认事务型存储引擎,并且提供了行级锁和外键的约束。性能不错且支持自动崩溃恢复。
- **MyISAM** - Mysql 5.1 版本前的默认存储引擎。特性丰富但不支持事务,也不支持行级锁和外键,也没有崩溃恢复功能。
- **CSV** - 可以将 CSV 文件作为 Mysql 的表来处理,但这种表不支持索引。
- **Memory** - 适合快速访问数据,且数据不会被修改,重启丢失也没有关系。
- **NDB** - 用于 Mysql 集群场景。
@ -95,6 +55,34 @@ MyISAM 提供了大量的特性,包括压缩表、空间数据索引等。
ALTER TABLE mytable ENGINE = InnoDB
```
### 1.1. InnoDB
InnoDB 是 MySQL 默认的事务型存储引擎,只有在需要 InnoDB 不支持的特性时,才考虑使用其它存储引擎。
InnoDB 实现了四个标准的隔离级别默认级别是可重复读REPEATABLE READ。在可重复读隔离级别下通过多版本并发控制MVCC+ 间隙锁next-key locking防止幻影读。
主索引是聚簇索引,在索引中保存了数据,从而避免直接读取磁盘,因此对查询性能有很大的提升。
内部做了很多优化,包括从磁盘读取数据时采用的可预测性读、能够加快读操作并且自动创建的自适应哈希索引、能够加速插入操作的插入缓冲区等。
支持真正的在线热备份。其它存储引擎不支持在线热备份,要获取一致性视图需要停止对所有表的写入,而在读写混合场景中,停止写入可能也意味着停止读取。
### 1.2. MyISAM
MyISAM 设计简单,数据以紧密格式存储。对于只读数据,或者表比较小、可以容忍修复操作,则依然可以使用 MyISAM。
MyISAM引擎使用B+Tree作为索引结构**叶节点的data域存放的是数据记录的地址**。
MyISAM 提供了大量的特性,包括压缩表、空间数据索引等。
不支持事务。
不支持行级锁只能对整张表加锁读取时会对需要读到的所有表加共享锁写入时则对表加排它锁。但在表有读取操作的同时也可以往表中插入新的记录这被称为并发插入CONCURRENT INSERT
可以手工或者自动执行检查和修复操作,但是和事务恢复以及崩溃恢复不同,可能导致一些数据丢失,而且修复操作是非常慢的。
如果指定了 DELAY_KEY_WRITE 选项,在每次修改执行完成时,不会立即将修改的索引数据写入磁盘,而是会写到内存中的键缓冲区,只有在清理键缓冲区或者关闭表的时候才会将对应的索引块写入磁盘。这种方式可以极大的提升写入性能,但是在数据库或者主机崩溃时会造成索引损坏,需要执行修复操作。
## 2. 数据类型
### 2.1. 整型
@ -530,6 +518,10 @@ SELECT * FROM post WHERE post.id IN (123,456,567,9098,8904);
### 7.1. 主从复制
Mysql 支持两种复制:基于行的复制和基于语句的复制。
这两种方式都是在主库上记录二进制日志,然后在从库重放日志的方式来实现异步的数据复制。这意味着:复制过程存在时延,这段时间内,主从数据可能不一致。
主要涉及三个线程binlog 线程、I/O 线程和 SQL 线程。
- **binlog 线程** 负责将主服务器上的数据更改写入二进制文件binlog中。

View File

@ -39,7 +39,6 @@
- [9.3. 视图VIEW](#93-视图view)
- [9.4. 索引INDEX](#94-索引index)
- [10. 约束](#10-约束)
- [10.1. 创建表时使用约束条件](#101-创建表时使用约束条件)
- [11. 事务处理](#11-事务处理)
- [12. 权限控制](#12-权限控制)
- [12.1. 创建账户](#121-创建账户)
@ -57,8 +56,8 @@
- [15.1. 创建触发器](#151-创建触发器)
- [15.2. 查看触发器](#152-查看触发器)
- [15.3. 删除触发器](#153-删除触发器)
- [16. 更多内容](#16-更多内容)
- [:door: 传送门](#door-传送门)
- [16. 参考资料](#16-参考资料)
- [17. 传送门](#17-传送门)
<!-- /TOC -->
@ -119,7 +118,7 @@ WHERE username = 'root';
### 2.3. SQL 分类
#### 数据定义语言DDL
#### 1.2.3.1. 数据定义语言DDL
数据定义语言Data Definition LanguageDDL是 SQL 语言集中负责数据结构定义与数据库对象定义的语言。
@ -127,7 +126,7 @@ DDL 的主要功能是定义数据库对象。
DDL 的核心指令是 `CREATE`、`ALTER`、`DROP`。
#### 数据操纵语言DML
#### 1.2.3.2. 数据操纵语言DML
数据操纵语言Data Manipulation Language, DML是用于数据库操作对数据库其中的对象和数据运行访问工作的编程语句。
@ -135,7 +134,7 @@ DML 的主要功能是访问数据,因此其语法都是以读写数据库为
DML 的核心指令是 `INSERT`、`UPDATE`、`DELETE`、`SELECT`。这四个指令合称 CRUD(Create, Read, Update, Delete),即增删改查。
#### 数据控制语言DCL
#### 1.2.3.3. 数据控制语言DCL
数据控制语言 (Data Control Language, DCL) 是一种可对数据访问权进行控制的指令,它可以控制特定用户账户对数据表、查看表、预存程序、用户自定义函数等数据库对象的控制权。
@ -154,7 +153,7 @@ DCL 以控制用户的访问权限为主,因此其指令作法并不复杂,
根据不同的 DBMS 以及不同的安全性实体,其支持的权限控制也有所不同。
#### 事务控制语言TCL
#### 1.2.3.4. 事务控制语言TCL
事务控制语言 (Transaction Control Language, TCL) 用于管理数据库中的事务。这些用于管理由 DML 语句所做的更改。它还允许将语句分组为逻辑事务。
@ -577,7 +576,7 @@ WHERE cust_id IN (SELECT cust_id
> <img src="https://raw.githubusercontent.com/dunwu/images/master/images/database/mysql/sql-join.png" alt="sql-join">
> </div>
#### 内连接INNER JOIN
#### 1.8.1.1. 内连接INNER JOIN
```sql
SELECT vend_name, prod_name, prod_price
@ -585,7 +584,7 @@ FROM vendors INNER JOIN products
ON vendors.vend_id = products.vend_id;
```
#### 自连接
#### 1.8.1.2. 自连接
```sql
SELECT c1.cust_id, c1.cust_name, c1.cust_contact
@ -594,7 +593,7 @@ WHERE c1.cust_name = c2.cust_name
AND c2.cust_contact = 'Jim Jones';
```
#### 自然连接NATURAL JOIN
#### 1.8.1.3. 自然连接NATURAL JOIN
```sql
SELECT *
@ -602,7 +601,7 @@ FROM Products
NATURAL JOIN Customers;
```
#### 左连接LEFT JOIN
#### 1.8.1.4. 左连接LEFT JOIN
```sql
SELECT customers.cust_id, orders.order_num
@ -610,7 +609,7 @@ FROM customers LEFT JOIN orders
ON customers.cust_id = orders.cust_id;
```
#### 右连接RIGHT JOIN
#### 1.8.1.5. 右连接RIGHT JOIN
```sql
SELECT customers.cust_id, orders.order_num
@ -655,19 +654,19 @@ WHERE cust_name = 'Fun4All';
### 9.1. 数据库DATABASE
#### 创建数据库
#### 1.9.1.1. 创建数据库
```sql
CREATE DATABASE test;
```
#### 删除数据库
#### 1.9.1.2. 删除数据库
```sql
DROP DATABASE test;
```
#### 选择数据库
#### 1.9.1.3. 选择数据库
```sql
USE test;
@ -675,7 +674,7 @@ USE test;
### 9.2. 数据表TABLE
#### 创建数据表
#### 1.9.2.1. 创建数据表
**普通创建**
@ -695,13 +694,13 @@ CREATE TABLE vip_user AS
SELECT * FROM user;
```
#### 删除数据表
#### 1.9.2.2. 删除数据表
```sql
DROP TABLE user;
```
#### 修改数据表
#### 1.9.2.3. 修改数据表
**添加列**
@ -749,7 +748,7 @@ DROP PRIMARY KEY;
> - 通过只给用户访问视图的权限,保证数据的安全性;
> - 更改数据格式和表示。
#### 创建视图
#### 1.9.3.1. 创建视图
```sql
CREATE VIEW top_10_user_view AS
@ -758,7 +757,7 @@ FROM user
WHERE id < 10;
```
#### 删除视图
#### 1.9.3.2. 删除视图
```sql
DROP VIEW top_10_user_view;
@ -774,21 +773,21 @@ DROP VIEW top_10_user_view;
> - 唯一索引
> - 唯一索引表明此索引的每一个索引值只对应唯一的数据记录。
#### 创建索引
#### 1.9.4.1. 创建索引
```sql
CREATE INDEX user_index
ON user (id);
```
#### 创建唯一索引
#### 1.9.4.2. 创建唯一索引
```sql
CREATE UNIQUE INDEX user_index
ON user (id);
```
#### 删除索引
#### 1.9.4.3. 删除索引
```sql
ALTER TABLE user
@ -808,7 +807,7 @@ DROP INDEX user_index;
> - `CHECK` - 保证列中的值符合指定的条件。
> - `DEFAULT` - 规定没有给列赋值时的默认值。
### 10.1. 创建表时使用约束条件
创建表时使用约束条件
```sql
CREATE TABLE Users (
@ -1007,6 +1006,7 @@ call getTotal();
> - `BEGIN``END`
>
> - 当触发器的触发条件满足时,将会执行 BEGIN 和 END 之间的触发器执行动作。
>
> > 注意:在 MySQL 中,分号 `;` 是语句结束的标识符遇到分号表示该段语句已经结束MySQL 可以开始执行了。因此,解释器遇到触发器执行动作中的分号后就开始执行,然后会报错,因为没有找到和 BEGIN 匹配的 END。
> >
> > 这时就会用到 `DELIMITER` 命令DELIMITER 是定界符,分隔符的意思)。它是一条命令,不需要语句结束标识,语法为:`DELIMITER new_delemiter`。`new_delemiter` 可以设为 1 个或多个长度的符号,默认的是分号 `;`,我们可以把它修改为其他符号,如 `$` - `DELIMITER $` 。在这之后的语句,以分号结束,解释器不会有什么反应,只有遇到了 `$`,才认为是语句结束。注意,使用完之后,我们还应该记得把它给修改回来。
@ -1072,9 +1072,12 @@ SHOW TRIGGERS;
DROP TRIGGER IF EXISTS trigger_insert_user;
```
## 16. 更多内容
(完)
> :notebook: 本文已归档到:「[blog](https://github.com/dunwu/blog)」
------
## 16. 参考资料
- BenForta. SQL 必知必会 [M]. 人民邮电出版社, 2013.
- [『浅入深出』MySQL 中事务的实现](https://draveness.me/mysql-transaction)
@ -1087,6 +1090,6 @@ DROP TRIGGER IF EXISTS trigger_insert_user;
- [SQL database security](https://www.w3resource.com/sql/database-security/create-users.php)
- [Mysql 中的存储过程](https://www.cnblogs.com/chenpi/p/5136483.html)
## :door: 传送门
## 17. 传送门
| [技术文档归档](https://github.com/dunwu/blog) | [数据库教程系列](https://github.com/dunwu/db-tutorial/codes) |

View File

@ -0,0 +1,582 @@
# 关系型数据库面试题
<!-- TOC depthFrom:2 depthTo:2 -->
- [1. 概念](#1-概念)
- [2. SQL](#2-sql)
- [3. 索引和约束](#3-索引和约束)
- [4. 事务](#4-事务)
- [5. 锁](#5-锁)
- [6. 分库分表](#6-分库分表)
- [7. 数据库架构设计](#7-数据库架构设计)
- [8. 参考资料](#8-参考资料)
- [9. :door: 传送门](#9-door-传送门)
<!-- /TOC -->
## 1. 概念
### 1.1.1. 什么是存储过程?有哪些优缺点?
**存储过程就像我们编程语言中的函数一样,封装了我们的代码(PLSQL、T-SQL)**。
优点:
- **能够将代码封装起来**
- **保存在数据库之中**
- **让编程语言进行调用**
- **存储过程是一个预编译的代码块,执行效率比较高**
- **一个存储过程替代大量 T_SQL 语句 ,可以降低网络通信量,提高通信速率**
缺点:
- **每个数据库的存储过程语法几乎都不一样,十分难以维护(不通用)**
- **业务逻辑放在数据库上,难以迭代**
示例:
```sql
DELIMITER //
CREATE PROCEDURE phelloword()
BEGIN
SELECT * FROM admin;
END//
CALL phelloword()
```
### 1.1.2. 什么是视图?以及视图的使用场景有哪些?
视图是一种基于数据表的一种**虚表**
- 视图是一种虚表
- 视图建立在已有表的基础上, 视图赖以建立的这些表称为基表
- **向视图提供数据内容的语句为 SELECT 语句,可以将视图理解为存储起来的 SELECT 语句**
- 视图向用户提供基表数据的另一种表现形式
- 视图没有存储真正的数据,真正的数据还是存储在基表中
- 程序员虽然操作的是视图,但最终视图还会转成操作基表
- 一个基表可以有 0 个或多个视图
有的时候,我们可能只关系一张数据表中的某些字段,而另外的一些人只关系同一张数据表的某些字段...
那么把全部的字段都都显示给他们看,这是不合理的。
我们应该做到:**他们想看到什么样的数据,我们就给他们什么样的数据...一方面就能够让他们只关注自己的数据,另一方面,我们也保证数据表一些保密的数据不会泄露出来...**
![img](https://user-gold-cdn.xitu.io/2018/3/5/161f3de9b3092439?imageView2/0/w/1280/h/960/format/webp/ignore-error/1)
我们在查询数据的时候,常常需要编写非常长的 SQL 语句,几乎每次都要写很长很长....上面已经说了,**视图就是基于查询的一种虚表,也就是说,视图可以将查询出来的数据进行封装。。。那么我们在使用的时候就会变得非常方便**...
值得注意的是:**使用视图可以让我们专注与逻辑,但不提高查询效率**
## 2. SQL
### 1.2.1. drop、delete 与 truncate 分别在什么场景之下使用?
- drop table
- 属于 DDL
- 不可回滚
- 不可带 where
- 表内容和结构删除
- 删除速度快
- truncate table
- 属于 DDL
- 不可回滚
- 不可带 where
- 表内容删除
- 删除速度快
- delete from
- 属于 DML
- 可回滚
- 可带 where
- 表结构在,表内容要看 where 执行的情况
- 删除速度慢,需要逐行删除
- **不再需要一张表的时候,用 drop**
- **想删除部分数据行时候,用 delete并且带上 where 子句**
- **保留表而删除所有数据的时候用 truncate**
## 3. 索引和约束
### SQL 约束有哪几种?
- NOT NULL: 用于控制字段的内容一定不能为空NULL
- UNIQUE: 控件字段内容不能重复,一个表允许有多个 Unique 约束。
- PRIMARY KEY: 也是用于控件字段内容不能重复,但它在一个表只允许出现一个。
- FOREIGN KEY: 用于预防破坏表之间连接的动作,也能防止非法数据插入外键列,因为它必须是它指向的那个表中的值之一。
- CHECK: 用于控制字段的值范围。
### 超键、候选键、主键、外键分别是什么?
- 超键:**在关系中能唯一标识元组的属性集称为关系模式的超键**。一个属性可以为作为一个超键,多个属性组合在一起也可以作为一个超键。**超键包含候选键和主键**。
- **候选键(候选码):是最小超键,即没有冗余元素的超键**。
- **主键(主码):数据库表中对储存数据对象予以唯一和完整标识的数据列或属性的组合**。一个数据列只能有一个主键且主键的取值不能缺失即不能为空值Null
- **外键:在一个表中存在的另一个表的主键称此表的外键**。
### 1.3.1. 数据库索引有哪些数据结构?
- B-Tree
- B+Tree
- Hash
#### 1.3.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/images/master/images/database/RDB/B-TREE.png" />
</div>
对于每个结点,主要包含一个关键字数组 Key[]一个指针数组指向儿子Son[]。
在 B-Tree 内,查找的流程是:
1. 使用顺序查找(数组长度较短时)或折半查找方法查找 Key[]数组,若找到关键字 K则返回该结点的地址及 K 在 Key[]中的位置;
2. 否则,可确定 K 在某个 Key[i]和 Key[i+1]之间,则从 Son[i]所指的子结点继续查找,直到在某结点中查找成功;
3. 或直至找到叶结点且叶结点中的查找仍不成功时,查找过程失败。
#### 1.3.1.2. B+Tree
B+Tree 是 B-Tree 的变种:
- 每个节点的指针上限为 2d 而不是 2d+1d 为节点的出度)。
- 非叶子节点不存储 data只存储 key叶子节点不存储指针。
<div align="center">
<img src="https://raw.githubusercontent.com/dunwu/images/master/images/database/RDB/B+TREE.png" />
</div>
由于并不是所有节点都具有相同的域,因此 B+Tree 中叶节点和内节点一般大小不同。这点与 B-Tree 不同,虽然 B-Tree 中不同节点存放的 key 和指针可能数量不一致,但是每个节点的域和上限是一致的,所以在实现中 B-Tree 往往对每个节点申请同等大小的空间。
##### 1.3.1.2.1. 带有顺序访问指针的 B+Tree
一般在数据库系统或文件系统中使用的 B+Tree 结构都在经典 B+Tree 的基础上进行了优化,增加了顺序访问指针。
<div align="center">
<img src="https://raw.githubusercontent.com/dunwu/images/master/images/database/RDB/带有顺序访问指针的B+Tree.png" />
</div>
在 B+Tree 的每个叶子节点增加一个指向相邻叶子节点的指针,就形成了带有顺序访问指针的 B+Tree。
这个优化的目的是为了提高区间访问的性能,例如上图中如果要查询 key 为从 18 到 49 的所有数据记录,当找到 18 后,只需顺着节点和指针顺序遍历就可以一次性访问到所有数据节点,极大提到了区间查询效率。
#### 1.3.1.3. Hash
Hash 索引只有精确匹配索引所有列的查询才有效。
对于每一行数据,对所有的索引列计算一个 hashcode。哈希索引将所有的 hashcode 存储在索引中,同时在 Hash 表中保存指向每个数据行的指针。
哈希索引的优点:
- 因为索引数据结构紧凑,所以查询速度非常快。
哈希索引的缺点:
- 哈希索引数据不是按照索引值顺序存储的,所以无法用于排序。
- 哈希索引不支持部分索引匹配查找。如,在数据列 (A,B) 上建立哈希索引,如果查询只有数据列 A无法使用该索引。
- 哈希索引只支持等值比较查询,不支持任何范围查询,如 WHERE price > 100。
- 哈希索引有可能出现哈希冲突,出现哈希冲突时,必须遍历链表中所有的行指针,逐行比较,直到找到符合条件的行。
### 1.3.2. B-Tree 和 B+Tree 有什么区别?
- B+Tree 更适合外部存储(一般指磁盘存储),由于内节点(非叶子节点)不存储 data所以一个节点可以存储更多的内节点每个节点能索引的范围更大更精确。也就是说使用 B+Tree 单次磁盘 IO 的信息量相比较 B-Tree 更大IO 效率更高。
- mysql 是关系型数据库经常会按照区间来访问某个索引列B+Tree 的叶子节点间按顺序建立了链指针,加强了区间访问性,所以 B+Tree 对索引列上的区间范围查询很友好。而 B-Tree 每个节点的 key 和 data 在一起,无法进行区间查找。
### 1.3.3. 索引原则有哪些?
#### 1.3.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;
```
#### 1.3.3.2. 前缀索引和索引选择性
有时候需要索引很长的字符列,这会让索引变得大且慢。
解决方法是:可以索引开始的部分字符,这样可以大大节约索引空间,从而提高索引效率。但这样也会降低索引的选择性。
索引的选择性是指:不重复的索引值和数据表记录总数的比值。最大值为 1此时每个记录都有唯一的索引与其对应。选择性越高查询效率也越高。
对于 BLOB/TEXT/VARCHAR 这种文本类型的列,必须使用前缀索引,因为数据库往往不允许索引这些列的完整长度。
要选择足够长的前缀以保证较高的选择性,同时又不能太长(节约空间)。
#### 1.3.3.3. 多列索引
不要为每个列创建独立的索引。
#### 1.3.3.4. 选择合适的索引列顺序
经验法则:将选择性高的列或基数大的列优先排在多列索引最前列。
但有时,也需要考虑 WHERE 子句中的排序、分组和范围条件等因素,这些因素也会对查询性能造成较大影响。
#### 1.3.3.5. 聚簇索引
聚簇索引不是一种单独的索引类型,而是一种数据存储方式。
聚簇表示数据行和相邻的键值紧凑地存储在一起。因为无法同时把数据行存放在两个不同的地方,所以一个表只能有一个聚簇索引。
#### 1.3.3.6. 覆盖索引
索引包含所有需要查询的字段的值。
具有以下优点:
- 因为索引条目通常远小于数据行的大小,所以若只读取索引,能大大减少数据访问量。
- 一些存储引擎(例如 MyISAM在内存中只缓存索引而数据依赖于操作系统来缓存。因此只访问索引可以不使用系统调用通常比较费时
- 对于 InnoDB 引擎,若辅助索引能够覆盖查询,则无需访问主索引。
#### 1.3.3.7. 使用索引扫描来做排序
索引最好既满足排序,又用于查找行。这样,就可以使用索引来对结果排序。
#### 1.3.3.8. = 和 in 可以乱序
比如 a = 1 and b = 2 and c = 3 建立a,b,c索引可以任意顺序mysql 的查询优化器会帮你优化成索引可以识别的形式。
#### 1.3.3.9. 尽量的扩展索引,不要新建索引
比如表中已经有 a 的索引,现在要加(a,b)的索引,那么只需要修改原来的索引即可。
## 4. 事务
### 1.4.1. 什么是事务?
事务简单来说:**一个 Session 中所进行所有的操作,要么同时成功,要么同时失败**
**ACID — 数据库事务正确执行的四个基本要素**
- 原子性Atomicity
- 一致性Consistency
- 隔离性Isolation
- 持久性Durability
**一个支持事务Transaction中的数据库系统必需要具有这四种特性否则在事务过程Transaction processing当中无法保证数据的正确性交易过程极可能达不到交易。**
### 1.4.2. 数据库事务隔离级别?事务隔离级别分别解决什么问题?
- `未提交读READ UNCOMMITTED` - 事务中的修改,即使没有提交,对其它事务也是可见的。
- `提交读READ COMMITTED` - 一个事务只能读取已经提交的事务所做的修改。换句话说,一个事务所做的修改在提交之前对其它事务是不可见的。
- `可重复读REPEATABLE READ` - 保证在同一个事务中多次读取同样数据的结果是一样的。
- `可串行化SERIALIXABLE` - 强制事务串行执行。
| 隔离级别 | 脏读 | 不可重复读 | 幻影读 |
| :------: | :--: | :--------: | :----: |
| 未提交读 | YES | YES | YES |
| 提交读 | NO | YES | YES |
| 可重复读 | NO | NO | YES |
| 可串行化 | NO | NO | NO |
- `脏读` - **一个事务读取到另外一个事务未提交的数据**
- `不可重复读` - **一个事务读取到另外一个事务已经提交的数据,也就是说一个事务可以看到其他事务所做的修改**
- `虚读(幻读)` - **是指在一个事务内读取到了别的事务插入的数据,导致前后读取不一致。**
### 1.4.3. 如何解决分布式事务?若出现网络问题或宕机问题,如何解决?
## 5. 锁
### 1.5.1. 数据库的乐观锁和悲观锁是什么?
确保在多个事务同时存取数据库中同一数据时不破坏事务的隔离性和统一性以及数据库的统一性,**乐观锁和悲观锁是并发控制主要采用的技术手段。**
- **`悲观锁`** - 假定会发生并发冲突,屏蔽一切可能违反数据完整性的操作
- **在查询完数据的时候就把事务锁起来,直到提交事务**
- 实现方式:使用数据库中的锁机制
- **`乐观锁`** - 假设不会发生并发冲突,只在提交操作时检查是否违反数据完整性。
- **在修改数据的时候把事务锁起来,通过 version 的方式来进行锁定**
- 实现方式:使用 version 版本或者时间戳
### 1.5.2. 数据库锁有哪些类型?如何实现?
#### 1.5.2.1. 锁粒度
- **表级锁table lock** - 锁定整张表。用户对表进行写操作前,需要先获得写锁,这会阻塞其他用户对该表的所有读写操作。只有没有写锁时,其他用户才能获得读锁,读锁之间不会相互阻塞。
- **行级锁row lock** - 仅对指定的行记录进行加锁,这样其它进程还是可以对同一个表中的其它记录进行操作。
InnoDB 行锁是通过给索引上的索引项加锁来实现的。只有通过索引条件检索数据InnoDB 才使用行级锁否则InnoDB 将使用表锁!
索引分为主键索引和非主键索引两种,如果一条 sql 语句操作了主键索引MySQL 就会锁定这条主键索引如果一条语句操作了非主键索引MySQL 会先锁定该非主键索引,再锁定相关的主键索引。在 UPDATE、DELETE 操作时MySQL 不仅锁定 WHERE 条件扫描过的所有索引记录,而且会锁定相邻的键值,即所谓的 next-key locking。
当两个事务同时执行一个锁住了主键索引在等待其他相关索引。另一个锁定了非主键索引在等待主键索引。这样就会发生死锁。发生死锁后InnoDB 一般都可以检测到,并使一个事务释放锁回退,另一个获取锁完成事务。
#### 1.5.2.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 也会对这个间隙进行加锁。另外,使用相等的检索条件时,若指定了本身不存在的记录作为检索条件的值的话,则此值对应的索引项也会加锁。
#### 1.5.2.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 自动加的,不需要用户干预。
## 6. 分库分表
### 1.6.1. 为什么要分库分表?
分库分表的基本思想就要把一个数据库切分成多个部分放到不同的数据库(server)上,从而缓解单一数据库的性能问题。
分库分表一定是为了**支撑高并发、数据量大**两个问题的。
#### 1.6.1.1. 分表
比如你单表都几千万数据了,你确定你能扛住么?绝对不行,**单表数据量太大**,会极大影响你的 sql **执行的性能**,到了后面你的 sql 可能就跑的很慢了。一般来说,就以我的经验来看,单表到几百万的时候,性能就会相对差一些了,你就得分表了。
分表是啥意思?就是把一个表的数据放到多个表中,然后查询的时候你就查一个表。比如按照用户 id 来分表,将一个用户的数据就放在一个表中。然后操作的时候你对一个用户就操作那个表就好了。这样可以控制每个表的数据量在可控的范围内,比如每个表就固定在 200 万以内。
#### 1.6.1.2. 分库
分库是啥意思?就是你一个库一般我们经验而言,最多支撑到并发 2000一定要扩容了而且一个健康的单库并发值你最好保持在每秒 1000 左右,不要太大。那么你可以将一个库的数据拆分到多个库中,访问的时候就访问一个库好了。
这就是所谓的**分库分表**,为啥要分库分表?你明白了吧。
| # | 分库分表前 | 分库分表后 |
| ------------ | ---------------------------- | -------------------------------------------- |
| 并发支撑情况 | MySQL 单机部署,扛不住高并发 | MySQL 从单机到多机,能承受的并发增加了多倍 |
| 磁盘使用情况 | MySQL 单机磁盘容量几乎撑满 | 拆分为多个库,数据库服务器磁盘使用率大大降低 |
| SQL 执行性能 | 单表数据量太大SQL 越跑越慢 | 单表数据量减少SQL 执行效率明显提升 |
### 1.6.2. 用过哪些分库分表中间件?不同的分库分表中间件都有什么优点和缺点?
这个其实就是看看你了解哪些分库分表的中间件,各个中间件的优缺点是啥?然后你用过哪些分库分表的中间件。
比较常见的包括:
- cobar
- TDDL
- atlas
- sharding-jdbc
- mycat
#### 1.6.2.1. cobar
阿里 b2b 团队开发和开源的,属于 proxy 层方案,就是介于应用服务器和数据库服务器之间。应用程序通过 JDBC 驱动访问 cobar 集群cobar 根据 SQL 和分库规则对 SQL 做分解,然后分发到 MySQL 集群不同的数据库实例上执行。早些年还可以用,但是最近几年都没更新了,基本没啥人用,差不多算是被抛弃的状态吧。而且不支持读写分离、存储过程、跨库 join 和分页等操作。
#### 1.6.2.2. TDDL
淘宝团队开发的,属于 client 层方案。支持基本的 crud 语法和读写分离,但不支持 join、多表查询等语法。目前使用的也不多因为还依赖淘宝的 diamond 配置管理系统。
#### 1.6.2.3. atlas
360 开源的,属于 proxy 层方案,以前是有一些公司在用的,但是确实有一个很大的问题就是社区最新的维护都在 5 年前了。所以,现在用的公司基本也很少了。
#### 1.6.2.4. sharding-jdbc
当当开源的,属于 client 层方案。确实之前用的还比较多一些,因为 SQL 语法支持也比较多,没有太多限制,而且目前推出到了 2.0 版本,支持分库分表、读写分离、分布式 id 生成、柔性事务最大努力送达型事务、TCC 事务)。而且确实之前使用的公司会比较多一些(这个在官网有登记使用的公司,可以看到从 2017 年一直到现在,是有不少公司在用的),目前社区也还一直在开发和维护,还算是比较活跃,个人认为算是一个现在也**可以选择的方案**。
#### 1.6.2.5. mycat
基于 cobar 改造的,属于 proxy 层方案,支持的功能非常完善,而且目前应该是非常火的而且不断流行的数据库中间件,社区很活跃,也有一些公司开始在用了。但是确实相比于 sharding jdbc 来说,年轻一些,经历的锤炼少一些。
#### 1.6.2.6. 总结
综上,现在其实建议考量的,就是 sharding-jdbc 和 mycat这两个都可以去考虑使用。
sharding-jdbc 这种 client 层方案的**优点在于不用部署,运维成本低,不需要代理层的二次转发请求,性能很高**,但是如果遇到升级啥的需要各个系统都重新升级版本再发布,各个系统都需要**耦合** sharding-jdbc 的依赖;
mycat 这种 proxy 层方案的**缺点在于需要部署**,自己运维一套中间件,运维成本高,但是**好处在于对于各个项目是透明的**,如果遇到升级之类的都是自己中间件那里搞就行了。
通常来说,这两个方案其实都可以选用,但是我个人建议中小型公司选用 sharding-jdbcclient 层方案轻便,而且维护成本低,不需要额外增派人手,而且中小型公司系统复杂度会低一些,项目也没那么多;但是中大型公司最好还是选用 mycat 这类 proxy 层方案,因为可能大公司系统和项目非常多,团队很大,人员充足,那么最好是专门弄个人来研究和维护 mycat然后大量项目直接透明使用即可。
### 1.6.3. 你们具体是如何对数据库如何进行垂直拆分或水平拆分的?
**水平拆分**的意思,就是把一个表的数据给弄到多个库的多个表里去,但是每个库的表结构都一样,只不过每个库表放的数据是不同的,所有库表的数据加起来就是全部数据。水平拆分的意义,就是将数据均匀放更多的库里,然后用多个库来扛更高的并发,还有就是用多个库的存储容量来进行扩容。
[![database-split-horizon](https://github.com/doocs/advanced-java/raw/master/images/database-split-horizon.png)](https://github.com/doocs/advanced-java/blob/master/images/database-split-horizon.png)
**垂直拆分**的意思,就是**把一个有很多字段的表给拆分成多个表****或者是多个库上去**。每个库表的结构都不一样,每个库表都包含部分字段。一般来说,会**将较少的访问频率很高的字段放到一个表里去**,然后**将较多的访问频率很低的字段放到另外一个表里去**。因为数据库是有缓存的,你访问频率高的行字段越少,就可以在缓存里缓存更多的行,性能就越好。这个一般在表层面做的较多一些。
[![database-split-vertically](https://github.com/doocs/advanced-java/raw/master/images/database-split-vertically.png)](https://github.com/doocs/advanced-java/blob/master/images/database-split-vertically.png)
这个其实挺常见的,不一定我说,大家很多同学可能自己都做过,把一个大表拆开,订单表、订单支付表、订单商品表。
还有**表层面的拆分**,就是分表,将一个表变成 N 个表,就是**让每个表的数据量控制在一定范围内**,保证 SQL 的性能。否则单表数据量越大SQL 性能就越差。一般是 200 万行左右,不要太多,但是也得看具体你怎么操作,也可能是 500 万,或者是 100 万。你的 SQL 越复杂,就最好让单表行数越少。
好了,无论分库还是分表,上面说的那些数据库中间件都是可以支持的。就是基本上那些中间件可以做到你分库分表之后,**中间件可以根据你指定的某个字段值**,比如说 userid**自动路由到对应的库上去,然后再自动路由到对应的表里去**。
你就得考虑一下,你的项目里该如何分库分表?一般来说,垂直拆分,你可以在表层面来做,对一些字段特别多的表做一下拆分;水平拆分,你可以说是并发承载不了,或者是数据量太大,容量承载不了,你给拆了,按什么字段来拆,你自己想好;分表,你考虑一下,你如果哪怕是拆到每个库里去,并发和容量都 ok 了,但是每个库的表还是太大了,那么你就分表,将这个表分开,保证每个表的数据量并不是很大。
而且这儿还有两种**分库分表的方式**
- 一种是按照 range 来分,就是每个库一段连续的数据,这个一般是按比如**时间范围**来的,但是这种一般较少用,因为很容易产生热点问题,大量的流量都打在最新的数据上了。
- 或者是按照某个字段 hash 一下均匀分散,这个较为常用。
range 来分,好处在于说,扩容的时候很简单,因为你只要预备好,给每个月都准备一个库就可以了,到了一个新的月份的时候,自然而然,就会写新的库了;缺点,但是大部分的请求,都是访问最新的数据。实际生产用 range要看场景。
hash 分发,好处在于说,可以平均分配每个库的数据量和请求压力;坏处在于说扩容起来比较麻烦,会有一个数据迁移的过程,之前的数据需要重新计算 hash 值重新分配到不同的库或表。
### 1.6.4. 分库分表的常见问题以及解决方案?
#### 1.6.4.1. 事务问题
方案一:使用分布式事务
- 优点:交由数据库管理,简单有效
- 缺点:性能代价高,特别是 shard 越来越多时
方案二:由应用程序和数据库共同控制
- 原理:将一个跨多个数据库的分布式事务分拆成多个仅处于单个数据库上面的小事务,并通过应用程序来总控各个小事务。
- 优点:性能上有优势
- 缺点:需要应用程序在事务控制上做灵活设计。如果使用了 spring 的事务管理,改动起来会面临一定的困难。
#### 1.6.4.2. 跨节点 Join 的问题
只要是进行切分,跨节点 Join 的问题是不可避免的。但是良好的设计和切分却可以减少此类情况的发生。解决这一问题的普遍做法是分两次查询实现。在第一次查询的结果集中找出关联数据的 id根据这些 id 发起第二次请求得到关联数据。
#### 1.6.4.3. 跨节点的 count,order by,group by 以及聚合函数问题
这些是一类问题,因为它们都需要基于全部数据集合进行计算。多数的代理都不会自动处理合并工作。
解决方案:与解决跨节点 join 问题的类似,分别在各个节点上得到结果后在应用程序端进行合并。和 join 不同的是每个节点的查询可以并行执行,因此很多时候它的速度要比单一大表快很多。但如果结果集很大,对应用程序内存的消耗是一个问题。
#### 1.6.4.4. ID 唯一性
一旦数据库被切分到多个物理节点上,我们将不能再依赖数据库自身的主键生成机制。一方面,某个分区数据库自生成的 ID 无法保证在全局上是唯一的;另一方面,应用程序在插入数据之前需要先获得 ID以便进行 SQL 路由。
一些常见的主键生成策略:
- 使用全局唯一 IDGUID。
- 为每个分片指定一个 ID 范围。
- 分布式 ID 生成器 (如 Twitter 的 Snowflake 算法)。
#### 1.6.4.5. 数据迁移,容量规划,扩容等问题
来自淘宝综合业务平台团队,它利用对 2 的倍数取余具有向前兼容的特性(如对 4 取余得 1 的数对 2 取余也是 1来分配数据避免了行级别的数据迁移但是依然需要进行表级别的迁移同时对扩容规模和分表数量都有限制。总得来说这些方案都不是十分的理想多多少少都存在一些缺点这也从一个侧面反映出了 Sharding 扩容的难度。
#### 1.6.4.6. 分库数量
分库数量首先和单库能处理的记录数有关一般来说Mysql 单库超过 5000 万条记录Oracle 单库超过 1 亿条记录DB 压力就很大(当然处理能力和字段数量/访问模式/记录长度有进一步关系)。
#### 1.6.4.7. 跨分片的排序分页
- 如果是在前台应用提供分页,则限定用户只能看前面 n 页,这个限制在业务上也是合理的,一般看后面的分页意义不大(如果一定要看,可以要求用户缩小范围重新查询)。
- 如果是后台批处理任务要求分批获取数据,则可以加大 page size比如每次获取 5000 条记录,有效减少分页数(当然离线访问一般走备库,避免冲击主库)。
- 分库设计时,一般还有配套大数据平台汇总所有分库的记录,有些分页查询可以考虑走大数据平台。
### 1.6.5. 如何设计可以动态扩容缩容的分库分表方案?
### 1.6.6. 有哪些分库分表中间件?各自有什么优缺点?底层实现原理?
#### 1.6.6.1. 简单易用的组件:
- [当当 sharding-jdbc](https://github.com/dangdangdotcom/sharding-jdbc)
- [蘑菇街 TSharding](https://github.com/baihui212/tsharding)
#### 1.6.6.2. 强悍重量级的中间件:
- [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)
## 7. 数据库架构设计
### 1.7.1. 高并发系统数据层面如何设计?
#### 1.7.1.1. 读写分离的原理
主服务器用来处理写操作以及实时性要求比较高的读操作,而从服务器用来处理读操作。
读写分离常用代理方式来实现,代理服务器接收应用层传来的读写请求,然后决定转发到哪个服务器。
MySQL 读写分离能提高性能的原因在于:
- 主从服务器负责各自的读和写,极大程度缓解了锁的争用;
- 从服务器可以配置 MyISAM 引擎,提升查询性能以及节约系统开销;
- 增加冗余,提高可用性。
<div align="center">
<img src="https://raw.githubusercontent.com/dunwu/images/master/images/database/mysql/master-slave-proxy.png" />
</div>
#### 1.7.1.2. 垂直切分
按照业务线或功能模块拆分为不同数据库。
更进一步是服务化改造,将强耦合的系统拆分为多个服务。
#### 1.7.1.3. 水平切分
- 哈希取模hash(key) % NUM_DB
- 范围:可以是 ID 范围也可以是时间范围
- 映射表:使用单独的一个数据库来存储映射关系
## 8. 参考资料
[数据库面试题(开发者必看)](https://juejin.im/post/5a9ca0d6518825555c1d1acd)
## 9. :door: 传送门
| [技术文档归档](https://github.com/dunwu/blog) | [数据库教程系列](https://github.com/dunwu/db-tutorial/codes) |

View File

@ -1,404 +0,0 @@
# 关系型数据库面试题
<!-- TOC depthFrom:2 depthTo:3 -->
- [1. 存储引擎](#1-存储引擎)
- [1.1. mysql 有哪些存储引擎?有什么区别?](#11-mysql-有哪些存储引擎有什么区别)
- [2. 索引](#2-索引)
- [2.1. 数据库索引有哪些数据结构?](#21-数据库索引有哪些数据结构)
- [2.2. B-Tree 和 B+Tree 有什么区别?](#22-b-tree-和-btree-有什么区别)
- [2.3. 索引原则有哪些?](#23-索引原则有哪些)
- [3. 事务](#3-事务)
- [3.1. 数据库事务隔离级别?事务隔离级别分别解决什么问题?](#31-数据库事务隔离级别事务隔离级别分别解决什么问题)
- [3.2. 如何解决分布式事务?若出现网络问题或宕机问题,如何解决?](#32-如何解决分布式事务若出现网络问题或宕机问题如何解决)
- [4. 锁](#4-锁)
- [4.1. 数据库锁有哪些类型?如何实现?](#41-数据库锁有哪些类型如何实现)
- [5. 分库分表](#5-分库分表)
- [5.1. 为什么要分库分表?](#51-为什么要分库分表)
- [5.2. 分库分表的常见问题以及解决方案?](#52-分库分表的常见问题以及解决方案)
- [5.3. 如何设计可以动态扩容缩容的分库分表方案?](#53-如何设计可以动态扩容缩容的分库分表方案)
- [5.4. 有哪些分库分表中间件?各自有什么优缺点?底层实现原理?](#54-有哪些分库分表中间件各自有什么优缺点底层实现原理)
- [6. 数据库优化](#6-数据库优化)
- [6.1. 什么是执行计划?](#61-什么是执行计划)
- [7. 数据库架构设计](#7-数据库架构设计)
- [7.1. 高并发系统数据层面如何设计?](#71-高并发系统数据层面如何设计)
- [:door: 传送门](#door-传送门)
<!-- /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/images/master/images/database/RDB/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/images/master/images/database/RDB/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/images/master/images/database/RDB/带有顺序访问指针的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/images/master/images/database/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/images/master/images/database/mysql/master-slave.png" />
</div>
#### 垂直切分
按照业务线或功能模块拆分为不同数据库。
更进一步是服务化改造,将强耦合的系统拆分为多个服务。
#### 水平切分
- 哈希取模hash(key) % NUM_DB
- 范围:可以是 ID 范围也可以是时间范围
- 映射表:使用单独的一个数据库来存储映射关系
## :door: 传送门
| [技术文档归档](https://github.com/dunwu/blog) | [数据库教程系列](https://github.com/dunwu/db-tutorial/codes) |