添加一个非常优秀的代码库。redis和Mysql放到了外边。

This commit is contained in:
法然
2022-12-05 11:53:08 +08:00
parent 012fc7a16d
commit ea02f1c503
342 changed files with 18929 additions and 570 deletions

102
MySQL/1 创建数据库.md Normal file
View File

@@ -0,0 +1,102 @@
# 系列一 数据库数据表的创建等基本操作
```sql
#python很像
-- 也为注释内容
-- 创建一个数据库
CREATE DATABASE IF NOT EXISTS maizi DEFAULT CHARACTER SET 'utf8';
USE maizi;
SET NAMES GBK;
--创建数据表user
--编号id
--用户名usename
--性别sex
--邮箱email
--地址addr
--生日birth
--薪水salary
--电话tel
--是否结婚married
--当需要中文的时候,需要临时转换客户端的编码方式
--SET NAMES GBK;
--字段注释通过comment注释内容给字段添加注释。
CREATE TABLE IF NOT EXISTS first_table(
id SMALLINT,
usename TINYINT,
age TINYINT,
email VARCHAR(50),
addr VARCHAR(200),
birth YEAR,
salary FLOAT(8,2),
tel INT,
married TINYINT(1) COMMENT '0代表未结婚非零代表结婚'
)ENGINE=innoDB CHARSET=UTF8;
SHOW TABLES;
--创建一个课程表course
--编号cid
--课程名称 courseName
--课程描述 courseDesc
CREATE TABLE IF NOT EXISTS course(
cid TINYINT,
courseName VARCHAR(50),
courseDesc VARCHAR(200)
);
SHOW TABLES;
--作业
--创建新闻类表
--创建新闻表
#
--查看表的表结构
DESC tbl_name
DESCRIBE tbl_name
SHOW COLUMNS FROM tbl_name
--测试数据的越界处理
CREATE TABLE test1(
num1 TINYINT,
num2 SMALLINT,
num3 MEDIUMINT,
NUM4 INT,
NUM5 BIGINT
);
--向表中插入记录INSERT tbl_name VALUE|VALUES(值,...);
INSERT test1 VALUES(-128,-32768,0,-2147483648,0);
--查询表中所有记录SELECT * FORM tbl_name;
SELECT *FROM test1;
--测试数据填充
CREATE TABLE test3(
num1 TINYINT ZEROFILL,
num2 SMALLINT ZEROFILL,
num3 MEDIUMINT ZEROFILL,
num4 INT ZEROFILL,
num5 BIGINT ZEROFILL
);
INSERT test3 values(1,1,1,1,1);
--测试浮点类型
--定点数内部以字符串形式保存
CREATE TABLE test4(
num1 FLOAT(6,2),
num2 DOUBLE(6,2),
num3 DECIMAL(6,2)
);
INSERT test4 VALUES(3.1415,3.1415,3.1415);
SELECT * FROM test4;
SELECT * FROM test4 WHERE num2 = 3.14;
```

View File

@@ -0,0 +1,95 @@
# 系列十 子查询 &正则表达式查询 &运算符的使用
> * 子查询
> * 正则表达式查询
> * 运算符的使用
```sql
#
--子查询的使用
--就是用查询的语句代替条件查询的一部分,查询嵌套
--由[not] in 引发的子集合查询
SELECT id FROM department;
SELECT id ,username FROM employee WHERE depId IN(1,2,3,4)
SELECT id ,username FROM employee WHERE depId IN(SELECT id FROM department);
--由比较运算符引发的子查询
SELECT level FROM scholarship WHERE id = 1;
SELECT id,username FROM student WHERE score >= 90;
SELECT id,username FROM student WHERE score >=(SELECT level FROM scholarship WHERE id = 1);
--由[NOT]EXISTS 引发的子查询
SELECT id FROM department WHERE id = 5;
SELECT id ,username FROM employee WHERE EXISTS(SELECT id FROM department WHERE id = 5);
#
--子查询的其他形式
--使用ANY/SOME/ALL 的子查询
--ANY/SOME表示存在性问题
--ALL 表示任意性问题
--查询所有获得奖学金的人
SELECT id ,username,score FROM student WHERE score >= ANY(SLECT level FROM scholarship);
--将结果集出入到另一张表中
SELECT id ,score FROM student;
INSERT test1(id,num)
--创建表的同时将查询结果插入
CREATE TABEL test2(
id TINYINT UNSIGNED AUTO_INCREMENT KEY,
score TINYINT UNSIGNED
)SELECT id ,score FROM student;
#
--正则表达式查询
--REGEXP'匹配方式'
--常用的匹配方式
--^匹配字符开始的部分
--&匹配字符串结尾的部分
--.代表字符串中热议一个字符
--[字符集合]匹配字符集合中的任意一个字符
--[^字符集合]匹配除了字符集合意外的的任意一个字符,只要含有者之外的字符的字符串都能匹配到
--s1,s2,s3匹配s1,s2,s3张任意一个字符串
--*代表零个一个或着多个其前的字符
--+代表1个或者多个其前的字符
--STRING{N}字符出现N次
--字符串{M,N }字符串至少出现M次最多N次
--查询用户名以t开始的用户
SELECT * FROM cms_user WHERE username REGEXP '^t';
--查询以g结尾的用户
SELECT * FROM cms_user WHERE username REGEXP 'g$';
--
SELECT *FROM cms_user WHERE username REGEXP 'R..G';
--可以用模糊查询实现相同的效果
SELECT * FROM cms_user WHERE username LIKE 'R__G';
--
SELECT * FROM cms_user WHERE username REGEXP '[lto]';
--除了字符集合内容中的内容都会出现
SELECT * FROM cms_user WHERE username REGEXP '[^l]';
--匹配集合中的任何一个字符串
SELECT * FROM cms_user WHERE username REGEXP 'nf|qu|tf';
--匹配前边的字符
SELECT * FROM cms_user WHERE username REGEXP 'que*';
SELECT * FROM cms_user WHERE username REGEXP 't+';
SELECT * FROM cms_user WHERE username REGEXP 'que{2}';
SELECT * FROM cms_user WHERE username REGEXP 'que*{1,3}';
#
--MySQL中运算符的使用
--算数运算符+ - * / div %
--比较运算符> < = != <=>(检测是否为null) IS NULL NOT BETWEEN AND OR IN
--逻辑运算符&& AND || OR ! NOT XOR
--运算符的优先级
```

108
MySQL/11 数学函数.md Normal file
View File

@@ -0,0 +1,108 @@
系列十一函数与表达式的应用
> * 数学函数
> * 字符串函数
> * 日期时间函数
> * 条件判断函数和系统函数
```sql
#
--数学函数库中的函数
CEIL()--进一取整
FLOOR()--舍一取整
MOD()--取余数
POWER()--幂运算
ROUND()--四舍五入
TRUNCATE()--数字截取
ABS()--取绝对值
PI()--圆周率
RAND()--返回0~1之间的随机数
SIGN()--返回x的符号
EXP()--计算e的几次方
#
--字符串函数库
--CHAR_LENGTH(S)返回字符串的字符数
--length()返回字符串的长度
--concat(s1,s2...)将字符串合并成为一个字符串
--CONCAT_WS(X,S1,S2...)指定分隔符连接字符串
--UPPER(S)/UCASE(S)将字符串转换为大写
--LOWER(S)LCASE(S)将字符串转换为小写
--LEFT(S,N)/RIGHT(S,N)返回字符串前或后n个字符
--LPAD(S1LEN,S2)/RPAD(S1LEN,S2)将字符串S1用S2填充到制定的LEN.
--LTRIM(S)/RTRIM(S)/TRIM(S)去掉字符串中的空格
--TRIM(S1 FROM S)去掉字符串s中开始处和结尾处的字符串
--REPEAT(S,N)重复字符串指定次数
--SPACE(N)返回N个空格
--REPLACE(S,S1S2)将字符串s中搜索s1替换成s2
--STRCMP(S1,S2)比较字符串,>=<分别返回1,0-1不区分大小写
--SUBSTRING(S,N,LEN)截取字符串
--REVERSE(S)反转字符串
--ELT(N,S1,S2...)返回指定位置的字符串
#
--日期时间函数
--CURDATE(),CURRENT_DATE()返回当前的日期
--CURTIME(),CURRENT_TIME()当前时间
--NOW()当前的日期和时间
--MONTH(D)返回日期中月份的值
--MONTHNAME(D)返回日期中月份的名称
--DAYNAME(D)返回是星期几
--DAYOFWEEK(D)返回一周内的第几天
--WEEKDAY(D)返回星期
--WEEK(D)一年中的低多少个星期
--YEAR(D)返回年份值
--HOUR(T)返回小时值
--MINUTE(T)返回分钟值
--SECOND(T)返回秒数
--DATEDIFF(D1D2)返回两个日期之间相隔的天数
#
--条件判断函数和系统函数
--IF (EXPR,V1V2)如果表达式成立返回结果v1否则返回V2
--IFNULL(V1V2)如果v1不为空就显示v1的值否则v2
CASE WHEN exp1
THEN V1
[WHEN EXP2 THEN V2]
[ELSE VN]
END
--case表示函数的开始end表示函数结束。如果表达式exp1成立时返回v1
--否则exp2成立时返回v2一次类推知道else成立
--系统信息函数
VERSION()--返回数据可的版本号
CONNECTION_ID()--返回服务器的连接数
DATABASE(),SCHEMA()--返回当前数据库
USER(),SYSTEM_USER()--返回当前用户
CURRENT_USER()--返回当前用户
CURRENT_USER--返回当前用户
CHARSET(STR)--返回字符串str的字符集
COLLATION(STR)--返回字符串str的校验字符集
LAST_INSERT_ID()--返回最近生成的AUTO_INCREMET自增长值
#
--其它常用的函数
--常用到的加密函数
MD5(STR)--信息摘要算法
PASSWORD(STR)--密码算法
ENCODE(str.pwd_str)--加密结果为二进制
DECODE(crypt_str,pwd_str)--对encode加密的的结果反向解密
FROMAT(x,n)--将数字x进行格式化将x保留到小数点
ASCII(S)--返回字符串s的第一个字符的ascii码值
BIN(X)--返回x的二进制编码
HEX(X)--返回x的十六进制编码
OCT(X)--返回x的八进制编码
CONV(X,F1,F2)--将x从f1进制数编程f2进制数
INET_ATON(IP)--将IP地址转换为数字
INET_NTOA(n)--将数字转换成ip地址
GET_LOCT(name,time)--定义锁
IS_FREE_LOCK('KING')--判断锁是否存在
RELEASE_LOCK(name)--解锁
```

91
MySQL/12 索引.md Normal file
View File

@@ -0,0 +1,91 @@
系列十二索引的使用和数据库的管理
> * 索引的使用
> * 管理数据库
```sql
#
--索引的使用
--索引有一列或多了组合而成,起作用是提高对表中数据的查询速度
--缺点是创建和维护索引需要耗费时间
--索引可以提高查询速度,减慢写入速度
--索引的分类bitree索引和hash索引
--普通索引,类似书签 index = 索引名称[索引字段]
--唯一索引unique key或者主键unique key = 索引名称{索引字段}
--全文索引只支持字符串字段只能建立在全英文的内容上FULLTEXT KEY = 索引名称 索引字段
--单列索引一个字段上的索引INDEX in_test1(test1)
--多列索引多个字段上的索引INDEX 多列索引的名称(字段1字段2字段3...)
--空间索引SPACIAL INDEX spa_test(test1);
--如何创建索引
--索引和索引名称不同,索引名称呢就像是某个目录的名字,叫小明索引,索引是指字段
--创建表的时候创建索引
CREATE TABLE tbl_name(
[],
...,
[UNIQUE|FULLTEXT|SPATIAL]INDEX|KEY[]()
[()][asc|desc]
);
--在已经存在的表上创建索引
CREATE[UNIQUE|FULLTEXT|SPATIAL]INDEX
ON {
[()][asc|desc]}
--以 id为普通索引
CREATE INDEX in_id ON test4(id);
ALTER TABLE tbl_name ADD [UNIQUI|FULLTEXT|SPECIAL]
()[()][ASC|DESC];
ALTER TABLE test4 ADD INDEX in_username(username);
--删除索引
DROP INDEX ON tbl_name
ALTER tbl_name DROP INDEX
#
--管理数据库
--workbench
--通过web方式控制和操作MySQL数据库
--PHPmyadmin
--通过客户端管理MySQL
```
MySQL中常用的索引结构有B+树索引和哈希索引两种。目前建表用的B+树索引就是BTREE索引。
在MySQL中MyISAM和InnoDB两种存储引擎都不支持哈希索引。只有HEAP/MEMORY引擎才能显示支持哈希索引。
创建索引:
```sql
CREATE TABLE userInfo(
`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键',
`username` varchar(3) NOT NULL COMMENT '用户名',
`age` int(10) NOT NULL COMMENT '年龄',
`addr` varchar(40) NOT NULL COMMENT '地址',
PRIMARY KEY (`id`),
```
```sql
KEY `ind_user_info_username` (`username`) USING BTREE, --此处普通索引
key 'ind_user_info_username_addr' ('username_addr') USING BTREE, --此处联合索引
unique key(uid) USINGBTREE, --此处唯一索引
key 'ind_user_info_addr' (addr(12)) USINGBTREE - addr列只创建了最左12个字符长度的部分索引
)ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='用户表';
```

View File

@@ -0,0 +1,120 @@
## 1 数据库对象操作
### 创建数据库
```sql
CREATE {DATABASE | SCHEMA} [IF NOT EXISTS]db_name
[DEFAULT CHARACTER SET ='GBK'];
```
* 创建数据库
* —— 类型、名称
* —— 可选参数,表示如果不存在时创建
* —— 默认编码方式
### 查看数据库
```sql
SHOW WARNINGS;
SHOW DATABASES;
SHOW CREATE DATABASE;
```
### 修改指定数据库的编码方式
```sql
ALTER {DATABASE\|SCHMA}db\_name
[DEFAULT CHARACTER SET = 'GBK'];
```
### 打开指定数据库
```
USE db_name; 打开的数据库名称
SELECT DATABASE\|SCHEMA();查看当前打开的而数据库名称
```
### 删除指定的数据库
```
DROP {DATABASE\|SCHEMA}[IF NOT EXISTS] db_name
```
## 2 数据表对象操作
### 定义
* 数据表用来存储数据的结构
* 由行和列构成的二维网络
### 创建数据表
```
CREATE TABLE [IF NOT EXISTS] tbl_name
(字段名称 字段类型[完整约束条件])
ENGINE =引擎名称
CHARSET = 编码方式;
```
### 数据类型补充
> 字节数分别是1-2-3-4-8-1
```
- TINYINT
- SMALLINT
- MEDIUMAINT
- INT
- BIGINT
- BOOL
- BOOLEAN
```
### 浮点类型补充
```
FLOAT[(m,d)]
double[(m,d)]
decemal[m,d]
```
### 字符串类型
```
CHAR(M) M带变得存放的字符数定长字符串
VARCHAR(M) 变长字符串
TEXT
SET("value","value")集合类型
ENUM("value","value")枚举类型
```
### 日期时间类型
```
TIME存储时间
DATE存储
DATETIME存储时间日期
TIMESTAMP存储时间戳
YEAR存储年份
```
### 二进制类型
> 图片或者视频的二进制码
## 3 存储引擎简介
### 存储引擎
> 指表的类型,表在计算机中怎样存储
```
SHOW ENGINES;
SHOW VARIABLES LIKE'storage_engin';
InnoDB 支持外接,存储.fim中读写效率地占用空间大
MyISAM 索引 结构 数据在三个文件中,占用空间小,快,但不支持事务
MEMORY 存储在内存中的内容创建表,对应磁盘文件,对磁盘快速处理。
```

View File

@@ -0,0 +1,21 @@
## 数据库注册表修改心得
> 几个月前跟随导员用xampp做的集成开发环境进行Apache服务器上的php网站开发项目
>
> 近几日大作业老师要求使用深喉咙的php继承开发环境结果现在的mysql不听话各种出错
### 端口冲突:
> 由于xampp深喉咙加上我自己的电脑总共装了三个版本的mysql导致端口占用等问题时有发生所以吧xampp和深喉咙的mysql直接删掉反而不能启动了
### 注册表修改:
> 后来发现深喉咙自动修改了注册表原来一直启动的并不是电脑上的mysql而是深喉咙自带的mysql修改注册表。
http://jingyan.baidu.com/article/91f5db1bd298ed1c7f05e315.html
### 但是登录的时候报了一个奇怪的错误,
> mysqlnd cannot connect to MySQL 4.1+ using the old insecure authentication后来查证是因为前不久不小心更新了xampp导致数据库中原来密码出现问题密码格式不能被新版的phpmyadmin解析于是看教程经过PASSWORD()函数修改原来的16位密码为41位更加安全的密码。
>
> 最终运行成功!

104
MySQL/2 创建数据表.md Normal file
View File

@@ -0,0 +1,104 @@
# 系列二 数据类型的测试
```sql
#
--查看表的表结构
DESC tbl_name;
DESCRIBE tbl_name;
SHOW COLUMNS FROM tbl_name;
SHOW CREATE TABLE tbl_name;
--测试数据的越界处理
CREATE TABLE test1(
num1 TINYINT,
num2 SMALLINT,
num3 MEDIUMINT,
NUM4 INT,
NUM5 BIGINT
);
--向表中插入记录INSERT tbl_name VALUE|VALUES(值,...);
INSERT test1 VALUES(-128,-32768,0,-2147483648,0);
--查询表中所有记录SELECT * FORM tbl_name;
SELECT *FROM test1;
--测试数据填充
CREATE TABLE test3(
num1 TINYINT ZEROFILL,
num2 SMALLINT ZEROFILL,
num3 MEDIUMINT ZEROFILL,
num4 INT ZEROFILL,
num5 BIGINT ZEROFILL
);
INSERT test3 values(1,1,1,1,1);
--测试浮点类型
--定点数内部以字符串形式保存
CREATE TABLE test4(
num1 FLOAT(6,2),
num2 DOUBLE(6,2),
num3 DECIMAL(6,2)
);
INSERT test4 VALUES(3.1415,3.1415,3.1415);
SELECT * FROM test4;
SELECT * FROM test4 WHERE num2 = 3.14;
#
--定长字符串占用空间一定,空间大,速度快
--变长字符串,空间小,速度慢
--char不保存末尾的空格varchar保存末尾的空格
--CONCAT(,);字符串连接函数
--测试char varchar
CREATE TABLE IF NOT EXISTS test5(
str1 CHAR(5),
str2 VARCHAR(5)
);
INSERT test5 VALUES('1','1');
INSERT test5 VALUES('12345','12345');
INSERT test5 VALUES('123456','123456');
SELECT CONCAT('-',str1),CONCAT('-',str2) FROM test5;
SELECT LENGTH('A')--字符串长度检测函数
--text储存超长字符串。不能有默认值
#
CREATE TABLE IF NOT EXISTS test7(
sex ENUM('nan','nv','baomi')
);
INSERT test7 VALUES('nan')
INSERT test7 VALUES('nv')
INSERT test7 VALUES('baomi')
--测试集合类型
CREATE TABLE IF NOT EXISTS test8(
fav SET('A','B','C','D')
);
INSERT test8 VALUES('A,C,D');
INSERT test8 VALUES('A,D,E');
INSERT test8 VALUES(15);
SELECT * FROM test8;
#
---年份的测试
CREATE TABLE IF NOT EXISTS test9(
birth YEAR
);
INSERT test9 VALUES(1901);
INSERT test9 values('2000');
INSERT test9 VALUES(0);
INSERT test9 VALUES('0');
SELECT * FROM test9;
--time的测试
--DATE
--TIME
--DATETIME
--TIMESTTAMP
--YREAR
```

View File

@@ -0,0 +1,95 @@
# 3 系列三 完整性约束条件
> * 主键约束
> * 自增长
> * 默认值和非空约束
> * 唯一约束
```sql
#
--完整性约束条件
--PRIMARY KEY 主键,不能重复,非空,无意义字段,
--AUTO_IINCREMENT
--FOREIGN KEY
--NOT NULL
--UNIQUE KEY
--DEFAULT
--的是主键
CREATE TABLE IF NOT EXISTS USER1(
id INT PRIMARY KEY,
username VARCHAR(20)
);
--查看创建表的定义
SHOW CREATE TABLE user1;
INSERT user1 VALUES(1,'king')
INSERT user1 VALUES(2,'quee')
CREATE TABLE IF NOT EXISTS user2(
id INT,
username VARCHAR(20),
card CHAR(18),
PRIMARY KEY(id, card)
);
INSERT user2 values(1,'king','111');
INSERT user2 values(1,'queue','112');
#
--AUTO_INCREMENT,对象一定是主键,在已有的最大主键上+1
--测试自增长
CREATE TABLE IF NOT EXISITS user5(
id SMALLINT KEY AUTO_INCREMENT,
username VARCHAR(20)
);
--指定位置插入值
INSERT user5(username) VALUES('queue');
INSERT user5 VALUES(DEFUALT,'HAHA')
INSERT user5 VALUES(null,'HAHA')
--修改自增长的起始值
ALTER TABLE user6 AUTO_INCREMENT = 500;
#
--NOT NULL 非空
--测试非空
CREATE TABLE IF NOT EXISTS user7(
id INT UNSIGNED KEY AUTO_INCREMENT,
username VARCHAR(20) NOT NULL,
password CHAR(32)NOT NULL,
age TINYINT UNSIGNED
);
INSERT user7(username,password) VALUES('KING1','KINGJ',12)
#
--DEFAULT配合NOT NULL使用
CREATE TABLE IF NOT EXISTS user8(
id INT UNSIGNED KEY AUTO_INCREMENT,
username VARCHAR(20) NOT NULL,
password CHAR(32)NOT NULL,
age TINYINT UNSIGNED DEFAULT 18,
addr VARCHAR(50) NOT NULL DEFAULT 'BEIJING'
);
#
--UNIQUE KEY唯一
CREATE TABLE IF NOT EXISTS user9(
id TINYINT UNDIGNED KEY AUTO_INCREMENT,
username VARCHAR(20) NOT NULL UNIQUE,
card CHAR(17) UNIQUE
);
--NULL值不算重复
CREATE TABEL [IF NOT EXISTS] tbl_name(
[unsigned|zerofill] [default ][not null] [[primary]KEY|UNIQUE[KEY]]
)ENGINE = INNODB CHARSET = UTF8 AUTO_INCREMENT = 100;
```

145
MySQL/4 修改表结构.md Normal file
View File

@@ -0,0 +1,145 @@
# 系类四 数据表结构的相关操作
> * 修改表名称
> * 添加删除字段
> * 修改字段和完整性约束条件
```sql
#
CREATE TABLE IF NOT EXISTS user10(
id SMALLINT UNSIGNED KEY AUTO_INCREMENT,
username VARCHAR(20) NOT NULL UNIQUE,
password CHAR(32) NOT NULL ,
email VARCHAR(50) NOT NULL DEFAULT '4646546@qq.com',
age TINYINT UNSIGNED DEFAULT 18,
sex ENUM('man','woman','secret') DEFAULT 'secret',
addr VARCHAR(200) NOT NULL DEFAULT 'beijing',
salary float(6,2),
regTime INT UNSIGNED,
face CHAR(100) NOT NULL DEFAULT 'default.jpg'
);
--修改表名
--ALTER TABEL tbl_name RENAME [TO|AS] new_name;
--RENAME TABLE tba_name TO new_name;
ALTER TABLE user10 RENAME TO user11;
ALTER TABLE user11 RENAME AS user10;
ALTER TABLE user10 RENAME user11;
RENAME TABLE user11 TO user10;
RENAME TABLE user10 TO user11;
--挺有意思的感觉SQL语言有点规则动作+类型+名称+附属操作
#
--添加或删除字段
ALTER TABEL tbl_name ADD [][FIRST|AFTER ]
ALTER TABLE tbl_name DROP
--添加card字段char类型
ALTER TABLE user10 ADD card CHAR(18);
ALTER TABLE user10 ADD test1 VARCHAR(100) NOT NULL UNIQUE;
ALTER TABLE user10 ADD test2 VARCHAR(100) NOT NULL UNIQUE FIRST;
ALTER TABLE user10 ADD test3 VARCHAR(100) NOT NULL DEFAULT 100 AFTER password;
--选中一次表完成多个操作
ALTER TABLE user10
ADD test4 INT NOT NULL DEFAULT 123 AFTER password,
ADD test5 FLOAT(6,2)FIRST,
ADD test6 SET('A','V','C');
--删除某些字段
ALTER TABLE user10 DROP test1;
--删除多个字段
ALTER TABLE user10
DROP test3,
DROP test4;
--批量处理添加删除操作
ALTER TABLE user10
ADD test INT UNSIGNED NOT NULL DEFAULT 10 AFTER sex,
DROP addr;
#
--modify修改完整性约束条件时不能对默认值和主键进行操作。
ALTER TABLE tbl_name MODIFY [][FIRST|AFTER ];
ALTER TABLE tbl_name CHANGE [][FIRST|AFTER ];
--修改email的类型
ALTER TABLE user10 MODIFY email VARCHAR(200);
--修改email的完整性约束条件
ALTER TABLE user10 MODIFY email VARCHAR(20) NOT NULL DEFAULT '1651656@qq.com';
--修改email的位置
ALTER TABLE user10 MODIFY email VARCHAR(20) AFTER test;
--使用change更换字段的相关属性
ALTER TABLE user10 CHANGE test test1 CHAR(32) NOT NULL DEFAULT '123' FIRST;
#
--添加和删除默认值
ALTER TABLE tbl_name ALTER SET DEFAULT ;
ALTER TABLE tbl_name ALTER DROP DEFAULT;
#
--添加主键删除主键
ALTER TABLE tbl_name ADD [CONSTRAINT[symbol]]PRIMARY KEY[index_type]()
ALTER TABLE tbl_name DROP PRIMARY KEY;
--添加单主键
ALTER TABLE test10 ADD PRIMARY KEY (id);
--添加复合主键
ALTER TABLE test13 ADD PRIMARY KEY(id, card);
--完整表达形式
ALTER TABLE test12 ADD CONSTRAINT symbol PRIMARY KEY index_type(id);
#
--添加删除唯一索引
ALTER TABLE tbl_name ADD [CONSTRAINT [SYMPOL]] UNIQUE [INDEX|KEY][]();
ALTER TABLE tbl_name DROP [INDEX|KEY] index_name;
--示例
ALTER TABLE user12 ADD UNIQUE(usename);
ALTER TABLE user12 ADD CONSTRAINT symple UNIQUE KEY uni_card(card);
LTER TABLE user12 ADD CONSTRAINT symple UNIQUE INDEX uni_test1_test2(test1,test2)
ALTER TABLE tbl_name DROP INDEX username;
ALTER TABLE tbl_name DROP KEY mulUni_test1_test2;
--修改表的存储引擎
ALTER TABLE tbl_name ENGINE = ;
--修改表的引擎为MYISAM
ALTER TABLE test12 ENGINE = MYISAM;
--设置自增长的值
ALTER TABLE tbl_name AUTO_INCREMEN = ;
--修改主键的自增长值为
ALTER TABLE user12 AUTO_INCREMENT = 100;
#
--删除数据表
DROP TABLE user12;
DROP TABLE IF EXISTS user11,user12,user14;
--在打开MySQL的同时直接打开数据表
mysql -uroot -p -D maizi --promot = \d~\D~\u~\h
--查看打开的数据库
SELECT DATABASE();
```

View File

@@ -0,0 +1,59 @@
# 系列五 对表中数据的操作
> * 插入记录
> * 更新和删除记录
```sql
#
--插入记录的操作
--不指定具体字段名的插入
INSERT [INTO]tbl_name VALUES|VALUE(...);
--列出制定字段
INSERT [INTO] tbl_name(,...) VALUES|VALUE(...);
--同时插入多条记录
INSERT [INTO] tbl_name [(,...)] VALUES|VALUE(...)()(...)...;
--通过SET形式插入记录
INSERT [INTO] tbl_name =
--将查询结果插入到表中
INSERT [INTO]tbl_name[(,...)]SELECT tbl_name[WHERE ]
--插入所有的数据的一条记录
INSERT [INTO]user1 VALUES|VALUE(1,'king',20);
--插入具体字段的值的一条记录
INSERT [INTO] user1(username,password) VALUES|VALUE('a','aaa');
--插入多条记录
INSERT user VALUES(6,'D',45),
(233,'FASF',45),
(54,'AF',84);
--通过set的形式插入记录
INSERT INTO user SET id = 98 ,username ='test',password = 'fjie';
--将查询结果插入到表中,字段数目必须匹配,格式也必须相同。
INSERT test1 SELECT id ,username FROM user1;
#
--更新和删除记录
--有限制条件的更新记录
UPDATE tbl_name SET = ,...[WHERE ][ORDER BY ][LIMIT ]
--有限值条件的删除记录
DELETE FROM tbl_name [WHERE ][ORDER BY ][LIMIT ]
--清空列鬼所有的记录
TRUNCATE [TABLE]tbl_name
--示例
--表单的年龄更新
UPDATE user1 SET age = 5;
--表单的多个记录更新
UPDATE user1 SET age = 20,email ='123@qq.com';
--表单的多个有限制条件的记录更新
UPDATE user1 SET password = 'king123',email = '123@qq.com',age = 99 WHERE id = 1;
--用表达式表示记录的限制条件和更新的内容
UPDATE user1 SET age = age - 5 WHERE id >= 3;
--删除多个记录,但是不会清空AUTO_increment的值
DELETE FROM user;
--有条件的删除记录
DELETE FROM test1 WHERE id = 1;
--truncate彻底清空数据表,包括所有的配置
TRUNCATE TABLE test1;
```

100
MySQL/6 查询表数据.md Normal file
View File

@@ -0,0 +1,100 @@
# 系列六 表达式与查询
> * 查询表达式
> * 带条件的查询
> * 范围查询
> * 模糊查询
> * 逻辑运算符查询
```sql
--SELECT 之后表示要查询的字段
--FROM 之后表示要查询的数据库和表的名称
--WHERE 之后表示要查询的记录的描述比较运算符<>=NULL、范围查询的两个关键字BETWEEN/IN、模糊查询LIKE、多条件查询AND /OR
#
--查询表达式完整形式
SELECT select_expr [,selext_expr...]
[
FROM table_refereces
[WHERE ]
[GROUP BY {col_name|position}[ASC|DESC],...]
[HAVING ]
[ORDER BY {col_name|position}[ASC|DESC],...]
[LIMIT ]
]
--查询表达式完整形式
--每一个表达式表示想要的一列,必须至少有一列,多个列之间用逗号分隔
--*通配符表示所有列tbl_name.*可以表示某个表的所有列
--从特定表中选取特定的不同的字段值进行显示
SELECT * FROM cms_admin;
SELECT cms_admin.* FROM cms_admin;
SELECT id,username FROM cms_admin;
--表来自以哪个数据库中的表。
--从特定的数据库中选取特定的表的特定的字段db_name.tbl_name
SELECT id ,username,role FROM cms.cms_admin;
--字段来源于那张表
SELECT cms_admin.id,cms_admin.username,cms_admin.role FROM cms.cms_admin;
--给表别名来访问会更简单一些 AS可以省略
SELECT a.username,a.id FROM cms_admin AS a;
--给字段起别名
SELECT id AS '编号',username AS '用户名' role '角色' FROM cms_admin;
--当时别名是,显示的名称是别名
SELECT a.id AS id1,a.username AS u FROM cms_admin;
#
--带条件的查询where条件的使用,比较运算符的使用
SELECT id ,uername,email FROM cms_user WHERE id = 1;
--使用<=>检测值是否为NULL除此之外与=用法相同
SELECT *FROM cms_admin WHERE age <=> NULL;
--使用is null或者IS NOT NULL
SELECT *FROM cms_admin WHERE age IS NULL;
#
--范围查询 BETWEEN NOT BETWEEN IN NOT IN
--查询编号在3-10之间的用户
SELECT *FROM cms_user WHERE id BETWEEN 3 AND 10;
--查询编号为1,3,5,7,9的数
SELECT * FROM cms_user WHERE id IN(1,3,5,7,9)
--查询ProID为1和3的用户
SELECT * FROM cms_user WHERE proID IN(1,3)
--查询用户名为kingqueen张三的记录忽略英文的大小写
SELECT * FROM cms_user WHERE id IN('king','queen','张三')
#
--模糊查询LINKE/NOT LINKE
--%:代表另个一个或多个任意字符
--_:代表一个人任意字符
--查询姓张的用户
SELECT *FROM cms_user WHERE username LIKE '%张%';
--查询用户命中包含in的用户
SELECT * FROM cms_user WHERE username LIKE '%in%';
--查询用户名为三位的用户
SELECT * FROM cms_user WHERE username LIKE '____';
--_and%同时使用
SELECT * FROM cms_user WHERE username LIKE '_I%';
#
--逻辑运算符与查询AND OR
--查询用户名为king密码也为king
SELECT * FROM cms_user WHERE username = 'king' AND password = 'king';
--查询编号不大于三的变量年龄部位null的用户
SELECT * FROM cms_user WHERE id >= 3 AND age IS NOT NULL;
--查询编号在5~10之间的用户或用户名为4位的
SELECT * FROME cms_user WHERE id BETWEEN 5 AND 10 OR username LIKE '____';
```

104
MySQL/7 高级表查询.md Normal file
View File

@@ -0,0 +1,104 @@
# 系列七 高级的查询功能
> * 分组查询group by
> * 聚合函数avg
> * 分组筛选having
> * 结果排序order by
> * 限制数量limit
```sql
#
--分组查询GROUP BY
--按照用户所属省分进行分组
SELECT * FROM cms_user GROUP BY proId;
--按照字段位置进行分组
SELECT * FROM cms_user GROUP BY 7;
--按照多个字段进行分组
SELECT F FROM cms_user GROUP BY sex,proId;
--先写条件,后对满足条件的记录进行分组
SELECT * FROM cms_user WHERE id > 5 GROUP BY sex;
#
--分组查询配合聚合函数
--配合GROUP_CONCAT()函数进行使用
--查询idsexusername的详情 按照性别分组
--通过性别分组分组后得到username的分组详情
SELECT id, sex, GROUP_CONCAT(username) FROM cms_user GROUP BY sex;
--查询ProID性别详情注册时间详情用户名详情 按照ProID进行分组
SELECT proId ,GROUP_CONCAT(username),GROUP_CONCAT(sex),GROUP_CONCAT(regTime) FROM cms_user GROUP BY proId;
--常见的聚合函数
COUNT()
MAX()
MIN()
AVG()
SUM()
--查询编号sex用户名详情以及组中总人数按照sex分组
SELECT id,sex,GROUP_CONCAT(username)AS user ,COUNT(*)AS totalUsers FROM cms_user GROUP BY sex;
--COUNT(字段)不统计NULL值
SELECT COUNT(id) AS totalUsers FROM cms_user;
--查询编号sex用户名详情以及组中总人数组中最大年龄最小年龄平均年龄年龄总和按照sex分组
SELECT id,sex,GROUP_CONCAT(username),
COUNT(*) AS totalUsers,
MAX(age) AS max_age,
MIN(age) AS min_age,
AVG(age) AS avg_age,
SUM(age) AS sum_age
FROM cms_user GROUP BY sex;
--配合WITH ROLLUP记录上面所有记录的总和
--在末尾加上WITH ROLLUP 属于聚合统计次字段的总的内容
SELECT id,sex,
COUNT(*) AS totalUsers,
MAX(age) AS max_age,
MIN(age) AS min_age,
AVG(age) AS avg_age,
SUM(age) AS sum_age
FROM cms_user GROUP BY sex WITH ROLLUP;
#
--having语句对分组结果进行二次筛选
SELECT id,sex,GROUP_CONCAT(username),
COUNT(*) AS totalUsers,
MAX(age) AS max_age,
MIN(age) AS min_age,
AVG(age) AS avg_age,
SUM(age) AS sum_age
FROM cms_user GROUP BY sex
HAVING COUNT(*)>2 AND MAX(age)>60;
#
--ORDER BY 对查询结果进行排序;
--查询按照id降序进行排列DESC /ASC
SELECT * FROM cms_user ORDER BY id ASC;
--按照多个字段进行排序
SELECT *FROM cms_user ORDER BY age ASC ,id DESC;
--实现随机提取记录
ORDER BY RAND();
#
--通过limit限制显示条数
--LIMIT 显示条数
--LIMIT偏移量显示条数
--查询表中前三条记录
SELECT * FROM cms_user LIMIT 3;
SELECT * FROM cms_user ORDER BY id DESC LIMIT 5;
SELECT * FROM cms_user LIMTI 10, 5;
```

View File

@@ -0,0 +1,76 @@
# 系列八 条件语句在各个部分的应用
> 更新,插入,删除记录的中的数据
> * 条件更新、条件删除
> * 连接查询
```sql
#
--条件更新
UPDATE cms_user SET age = age - 3 WHERE username LIKE '____';
--更新前三条记录,让已有年龄+10
UPDATE cms_user SET age = age +10 LIMIT 3;
--按照id降序排列
UPDATE cms_user SET age = age +10 ORDER BY id DESC LIMIT 3;
--条件删除
DELETE FROM cms_user WHERE sex = 'man' ORDER BY age DESC LIMIT 3;
#
--连接查询
--将两个或两个以上的表按某个条件链接起来,从中个选取出需要的数据
--链接插叙是同时查询两个或两个以上的表时使用的。
--当不同的表中存在相同意义的字段是,可以通过该字段链接这几个表
--内连接查询
--显示连个表中符合链接条件的记录
--JOIN / CROSS JOIN / INNER JOIN
--通过on连接条件
--查询cms_user表中idusername
--查询provinces表中proName
SELECT cms_user.id ,username ,proName FROM cms_user,provinces;
--并不能实现想要的目的
--cms_user的ProId对应省份表中的id,使用where第一次筛选与内连接语句是等价的
SELECT cms_user.id ,username ,proName FROM cms_user,provinces
WHERE cms_user.proId = provinces.id;
--用内连接的方式把各个表中的相关信息统一为同一个表中的信息
--查询cms_user表中的id,username,email,sex;
--查询procinces表中的proName
SELECT u.id,u.username,u.email,u.sex,p.proName
FROM cms_user AS u
INNER JOIN provinces AS p
ON u.proId = p.id;
--通过on连接条件
SELECT u.id,u.username,u.email,u.sex,p.proName
FROM cms_user AS u
CROSS JOIN provinces AS p
ON u.proId = p.id;
--查询cms_user表中idusername,sex
--查询procinces表中的proName
--条件是cms_user 的性别是男的用户
--根据p.proName进行分组GROUP
--对分组结果进行二次筛选,跳出组中人数大于等于一的人。
SELECT u.id,u.username,u.email,u.sex,p.proName,COUNT(*)AS totalUsers
FROM cms_user AS u
JOIN provinces AS p
ON u.proId = p.id
WHERE u.sex = 'man'
GROUP BY p.proName
HAVING COUNT(*) >= 1
ORDER BY u.id ASC
LIMIT 0,2;
#
--外联结查询
--左外连接LEFT[OUTER]JOIN显示左全部记录及右表符合链接条件的记录
--右外连接RIGHT[OUTER]JOIN显示右表全部记录及左表符合链接条件的记录
SELECT u.id,u.username,u.email,u.sex,p.proName,COUNT(*)AS totalUsers
FROM cms_user AS u
LEFT JOIN provinces AS p
ON u.proId = p.id;
--左外连接就是一左边的表为查询主表,从左边寻找满足条件要求的项,并在右表中找到对应的数据
```

135
MySQL/9 外键与查询.md Normal file
View File

@@ -0,0 +1,135 @@
# 系列九 外键的相关操作
> * 外键操作
> * 外键的创建、删除
> * 联合查询
```sql
#
--外键操作
--作用:保证数据的一致性和完整性
--外键是表的一个特殊字段。被参照的表是主表,外键所在的字段的表为字表,是指外键的原则需要记住
--就是依赖于数据库中已存在的表的主键。外间的作用时间里该表与其附表的关联关系,
--附表中对记录做操作时,子表中与之对应的信息也应该有相应的改变。
--可以实现一对一或一对多的关系
--父表和子表必须使用相同的存储引擎,而且禁止使用临时表
--数据表的存储引擎智能为innodb
--外键列和参照列必须具有相似的数据类型。其中数字的长度或是否为有符号位必须相同;而字符长度可以不同
--外键列和参照列必须创建索引。如果外键列不存在索引的话MySQL自动创建索引
--实践操作,创建部门表(主表)
CREATE TABLE IF NOT EXISTS department (
id TINYINT UNSIGNED AUTO_INCREMENT KEY,
depName VARCHAR(20) NOT NULL UNIQUE
)ENGINE = INNODB;
INSERT department(depName)VALUES('teach'),
('market'),
('undergo'),
('watch');
--创建员工表(字表)
CREATE TABLE IF NOT EXISTS employee(
id SMALLINT UNSIGNED AUTO_INCREMENT KEY,
username VARCHAR(20) NOT NULL UNIQUE,
depId TINYINT UNSIGNED
)ENGINE = INNODB;
INSERT employee(username ,depId)VALUES('kin',1),
('joe',2 ),
('est',3),
('bob',4),
('tom',5);
--内连接实现
SELECT e.id,e.username,d.depName FROM
employee AS e
JOIN
department AS d
ON e.depId = d.id;
--删除watch部门,虽然部门没有了,但是子表中部门下的人仍然存在。
DELETE FROM department WHERE depName = 'watch';
--所以以上内容都是不符合要求的
DROP TABLE department , employee;
--下面是通过外键常见的表
CREATE TABLE IF NOT EXISTS department (
id TINYINT UNSIGNED AUTO_INCREMENT KEY,
depName VARCHAR(20) NOT NULL UNIQUE
)ENGINE = INNODB;
INSERT department(depName)VALUES('teach'),
('market'),
('undergo'),
('watch');
--创建员工表(字表)
CREATE TABLE IF NOT EXISTS employee(
id SMALLINT UNSIGNED AUTO_INCREMENT KEY,
username VARCHAR(20) NOT NULL UNIQUE,
depId TINYINT UNSIGNED
FOREINGN KYE(depId)REFERENCES department(id)
)ENGINE = INNODB;
INSERT employee(username ,depId)VALUES('kin',1),
('joe',2 ),
('est',3),
('bob',4),
('tom',5);
--如果子表中有某个父表记录下的内容,则父表中不能执行删除操作
--如果父表中没有相应的外键,子表在插入时所使用的外键超出父表所使用的范围,会报错
#
--添加和删除外键的操作
--添加外键
ALTER TABLE employee DROP FOREIGN KEY em_fk_dep; --em_fk_dep是外键的索引
--删除外键
ALTER TABLE employee ADD CONSTRANINT emp_fk_dep FOREIGN KEY(depId) REFERENCES department(id);
--CASCADE:从附表中珊瑚或更新且自动删除或更新子表中匹配行
--SET NULL:从附表中删除或更新行并设置子表中的外键列为NULL。如果使用该选项必须保证子表列没有指定NOT NULL
--RESTRICT:拒绝对父表的删除和更新操作
--NO ACTION:在MySQL中与restrict关键字相同
--创建员工表(字表)
--ON DELETE CASCADE在删除的时候是及连的
--ON UPDATE CASCADE在更新的时候是及连的
CREATE TABLE IF NOT EXISTS employee(
id SMALLINT UNSIGNED AUTO_INCREMENT KEY,
username VARCHAR(20) NOT NULL UNIQUE,
depId TINYINT UNSIGNED
FOREINGN KYE(depId)REFERENCES department(id) ON DELETE CASCADE ;
)ENGINE = INNODB;
INSERT employee(username ,depId)VALUES('kin',1),
('joe',2 ),
('est',3),
('bob',4),
('tom',5);
--删除部门表中的一个部门
DELETE FROM department WHERE id = 1;
--更新部门表中的数据
UPDATE department SET id = id = 10;
#
--联合查询
--union 和union all
--NNION 合并相同信息
SELECT username FORM employee UNION SELECT username from cms_user;
--UNION ALL 不合并相同信息
SELECT username FORM employee UNION ALL SELECT username from cms_user;
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 131 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 162 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 140 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 396 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 109 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 390 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 437 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 133 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 128 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 204 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 126 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 93 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 105 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 117 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB

View File

@@ -0,0 +1,118 @@
# Mysql 架构原理
> 参考文献
> * [Mysql 架构原理](https://blog.csdn.net/hguisu/article/details/7106342)
## 1 概述
### 分层结构概述
<!-- ![](image/2021-09-01-22-02-49.png) -->
![](image/2021-09-01-22-05-53.png)
1. 客户端各种语言都提供了连接mysql数据库的方法比如jdbc、php、go等可根据选择 的后端开发语言选择相应的方法或框架连接mysql
2. server层包括连接器、查询缓存、分析器、优化器、执行器等涵盖mysql的大多数核心服务功能以及所有的内置函数例如日期、世家、数 学和加密函数等),所有跨存储引擎的功能都在这一层实现,比如存储过程、触发器、视图等。
3. 存储引擎层:负责数据的存储和提取,是真正与底层物理文件打交道的组件。 数据本质是存储在磁盘上的通过特定的存储引擎对数据进行有组织的存放并根据业务需要对数据进行提取。存储引擎的架构模式是插件式的支持InnodbMyIASM、Memory等多个存储引擎。现在最常用的存储引擎是Innodb它从mysql5.5.5版本开始成为了默认存储引擎。
4. 物理文件层存储数据库真正的表数据、日志等。物理文件包括redolog、undolog、binlog、errorlog、querylog、slowlog、data、index等
### 连接器
连接器负责来自客户端的连接、获取用户权限、维持和管理连接。
一个用户成功建立连接后,即使你用管理员账号对这个用户的权限做了修改,也不会影响已经存在连接的权限。修改完成后,只有再新建连接才会使用新的权限设置。
### 查询缓存
mysql拿到一个查询请求后会先到查询缓存查看之前是否执行过这条语句。前执行过的语句及其结果可能会以key-value对的形式被直接缓存在内存中。key是查询的语句value是查询的结果。如果当前sql查询能够直接在查询缓存中找到key那么这个value就会被直接返回给客户端。
### 分析器
词法分析(识别关键字,操作,表名,列名)
语法分析 (判断是否符合语法)
### 优化器
优化器是在表里面有多个索引的时候决定使用哪个索引或者在一个语句有多表关联join的时候决定各个表的连接顺序。优化器阶段完成后这个语句的执行方案就确定下来了然后进入执行器阶段。
### 执行器
开始执行的时候,要先判断一下用户对这个表 T 有没有执行查询的权限。如果没有,就会返回没有权限的错误。如果命中查询缓存,会在查询缓存返回结果的时候,做权限验证。查询也会在优化器之前调用 precheck 验证权限。如果有权限就打开表继续执行。打开表的时候执行器就会根据表的引擎定义去调用这个引擎提供的接口。在数据库的慢查询日志中看到一个rows_examined 的字段表示这个语句执行过程中扫描了多少行这个值就是在执行器每次调用引擎获取数据航的时候累加的。在有些场景下执行器调用一次在引擎内部则扫描了多行因此引擎扫描行数跟rows_examined并不是完全相同的。
## 2 SQL查询语句
![](image/2021-09-01-22-06-49.png)
### 执行过程
SQL执行步骤请求、缓存、SQL解析、优化SQL查询、调用引擎执行返回结果:
1. 连接:客户端向 MySQL 服务器发送一条查询请求与connectors交互连接池认证相关处理。
2. 缓存:服务器首先检查查询缓存,如果命中缓存,则立刻返回存储在缓存中的结果,否则进入下一阶段
3. 解析服务器进行SQL解析(词法语法)、预处理。
4. 优化:再由优化器生成对应的执行计划。
5. 执行MySQL 根据执行计划,调用存储引擎的 API来执行查询。
6. 结果:将结果返回给客户端,同时缓存查询结果。
## 3 SQL更新语句
![](image/2021-09-01-22-14-07.png)
### 执行过程
1. 连接:客户端向 MySQL 服务器发送一条更新请求
2. 缓存:清除表查询缓存,跟这个有关的查询缓存会失效。这就是一般不建议使用查询缓存的原因。
3. 解析:分析器进行 SQL解析词法和语法分析分析这是一条更新语句和。
4. 优化优化器生成对应的执行计划优化器决定使用ID这个索引
5. 执行:执行器负责更新,找到这一行,然后进行更新:
1. 取数据行: 执行器先找引擎取 ID=2 这一行: ID 是主键,引擎直接用树搜索找到这一行。如果 ID=2 这一行所在的数据页本来就在内存中,就直接返回给执行器;否则,需要先从磁盘读入内存,然后再返回。)
2. 更新数据: 执行器拿到引擎给的行数据,把这个值加上 1比如原来是 N现在就是 N+1得到新的一行数据再调用引擎接口写入这行新数据。
3. 更新内存: 引擎将这行新数据更新到内存中,
4. 更新 redo log :同时将这个更新操作记录到 redo log 里面,此时 redo log 处于 prepare 状态。然后告知执行器执行完成了,随时可以提交事务。
5. 写入binlog:执行器生成这个操作的 binlog并把 binlog 写入磁盘。
6. 提交事务: 执行器调用引擎的提交事务接口,引擎把刚刚写入的 redo log 改成提交commit状态更新完成。
![](image/2021-09-01-22-16-48.png)
## 4 日志类型
![](image/2021-09-01-22-17-34.png)
### redo log
* redo log记录的是page的变化。数据内容的变化是物理层的。。对于面向disk的数据库需要把数据先写入到page中然后成批的flush到磁盘所以在Page没有flush前需要有log能记录下page的state。这就是Redo所以Redo是物理的因为记录的是Page的具体变更用Redo的目的是恢复出之前的page。
* Redo本身也是要写disk将多次写操作存入到buffer中在事务commit的时候才把Redo顺序写入disk。redo log prepare状态切换到commit状态完成提交。
使用场景:
* redo log在数据库重启恢复的时候被使用因为其属于物理日志的特性恢复速度远快于逻辑日志。
* redo log作为异常宕机或者介质故障后的数据恢复使用
### bin log
* MySQL Server层面的又称为归档日志属于逻辑日志是以二进制的形式记录的是这个语句的原始逻辑。
使用场景:
* 在主从复制的时候使用。主库向从库写入变化。
* binlog可以作为恢复数据使用
### undo log
* Undolog主要分为两种
* insert undo log。代表事务在insert新记录时产生的undo log, 只在事务回滚时需要,并且在事务提交后可以被立即丢弃
* update undo log。事务在进行update或delete时产生的undo log; 不仅在事务回滚时需要在快照读时也需要所以不能随便删除只有在快速读或事务回滚不涉及该日志时对应的日志才会被purge线程统一清除
### 日志对比
- 层次不同。redo/undo 是 innodb 引擎层维护的,而 binlog 是 mysql server 层维护的,跟采用何种引擎没有关系,记录的是所有引擎的更新操作的日志记录。
- 记录内容不同。redo/undo 记录的是 每个页/每个数据 的修改情况,属于物理日志+逻辑日志结合的方式redo log 是物理日志undo log 是逻辑日志。binlog 记录的都是事务操作内容binlog 有三种模式Statement基于 SQL 语句的复制、Row基于行的复制 以及 Mixed混合模式。不管采用的是什么模式当然格式是二进制的
- 记录时机不同。redo/undo 在 事务执行过程中 会不断的写入,而 binlog 是在 事务最终提交前 写入的。binlog 什么时候刷新到磁盘跟参数 sync_binlog 相关

View File

@@ -0,0 +1,193 @@
# MySQL存储引擎
## 1 概述
### MySQL架构
### 存储引擎
1. MySQL中的数据用各种不同的技术存储在文件(或者内存)中。这些技术中的每一种技术都使用不同的存储机制、索引技巧、锁定水平并且最终提供广泛的不同的功能和能力。通过选择不同的技术,你能够获得额外的速度或者功能,从而改善你的应用的整体功能。
2. 例如如果你在研究大量的临时数据你也许需要使用内存MySQL存储引擎。内存存储引擎能够在内存中存储所有的表格数据。又或者你也许需要一个支持事务处理的数据库(以确保事务处理不成功时数据的回退能力)。
3. 这些不同的技术以及配套的相关功能在 MySQL中被称作存储引擎(也称作表类型)。 MySQL默认配置了许多不同的存储引擎可以预先设置或者在MySQL服务器中启用。你可以选择适用于服务器、数据库和表格的存储引擎以便在选择如何存储你的信息、如何检索这些信息以及你需要你的数据结合什么性能和功能的时候为你提供最大的灵活性。
### MySQL支持的存储引擎
![](image/2021-04-23-12-41-46.png)
### 存储引擎对比
| 功  能 | MYISAM | Memory | InnoDB | Archive |
|--------|--------|--------|--------|---------|
| 存储限制 | 256TB | RAM | 64TB | None |
| 支持事物 | No | No | Yes | No |
| 支持全文索引 | Yes | No | No | No |
| 支持数索引 | Yes | Yes | Yes | No |
| 支持哈希索引 | No | Yes | No | No |
| 支持数据缓存 | No | N/A | Yes | No |
| 支持外键 | No | No | Yes | No |
## 2.1 MyISAM
### 存储引擎的实现
使用这个存储引擎每个MyISAM在磁盘上存储成三个文件。
1. frm文件存储表的定义数据
2. MYD文件存放表具体记录的数据
3. MYI文件存储索引
frm和MYI可以存放在不同的目录下。MYI文件用来存储索引但仅保存记录所在页的指针索引的结构是B+树结构。下面这张图就是MYI文件保存的机制
![](image/2021-04-23-12-46-47.png)
从这张图可以发现这个存储引擎通过MYI的B+树结构来查找记录页再根据记录页查找记录。并且支持全文索引、B树索引和数据压缩。
### 支持数据的类型
1. 静态固定长度表。这种方式的优点在于存储速度非常快,容易发生缓存,而且表发生损坏后也容易修复。缺点是占空间。这也是默认的存储格式。
2. 动态可变长表。优点是节省空间,但是一旦出错恢复起来比较麻烦。
3. 压缩表。上面说到支持数据压缩说明肯定也支持这个格式。在数据文件发生错误时候可以使用check table工具来检查而且还可以使用repair table工具来恢复。
### 存储引擎的特点
有一个重要的特点那就是**不支持事务**,但是这也意味着他的存储速度更快,如果你的读写操作允许有错误数据的话,只是追求速度,可以选择这个存储引擎。
MyISAM基于ISAM存储引擎并对其进行扩展。它是在Web、数据仓储和其他应用环境下最常使用的存储引擎之一。MyISAM拥有较高的插入、查询速度但不支持事物。MyISAM主要特性有
1. 大文件达到63位文件长度在支持大文件的文件系统和操作系统上被支持
2. 当把删除和更新及插入操作混合使用的时候,动态尺寸的行产生更少碎片。这要通过合并相邻被删除的块,以及若下一个块被删除,就扩展到下一块自动完成
3. 每个MyISAM表最大索引数是64这可以通过重新编译来改变。每个索引最大的列数是16
4. 最大的键长度是1000字节这也可以通过编译来改变对于键长度超过250字节的情况一个超过1024字节的键将被用上
5. **BLOB和TEXT列**可以被索引
6. NULL被允许在索引的列中这个值占每个键的0~1个字节
7. 所有数字键值以高字节优先被存储以允许一个更高的索引压缩
8. 每个MyISAM类型的表都有一个AUTO_INCREMENT的内部列当INSERT和UPDATE操作的时候该列被更新同时AUTO_INCREMENT列将被刷新。所以说MyISAM类型表的AUTO_INCREMENT列更新比InnoDB类型的AUTO_INCREMENT更快
9. 可以把数据文件和索引文件放在不同目录
10. 每个字符列可以有不同的字符集
11. 有VARCHAR的表可以固定或动态记录长度
12. VARCHAR和CHAR列可以多达64KB
### 存储引擎的特点2
设计简单,数据以紧密格式存储。对于只读数据,或者表比较小、可以容忍修复操作,则依然可以使用它。
提供了大量的特性,包括压缩表、空间数据索引等。
不支持事务。
不支持行级锁只能对整张表加锁读取时会对需要读到的所有表加共享锁写入时则对表加排它锁。但在表有读取操作的同时也可以往表中插入新的记录这被称为并发插入CONCURRENT INSERT
可以手工或者自动执行检查和修复操作,但是和事务恢复以及崩溃恢复不同,可能导致一些数据丢失,而且修复操作是非常慢的。
如果指定了 DELAY_KEY_WRITE 选项,在每次修改执行完成时,不会立即将修改的索引数据写入磁盘,而是会写到内存中的键缓冲区,只有在清理键缓冲区或者关闭表的时候才会将对应的索引块写入磁盘。这种方式可以极大的提升写入性能,但是在数据库或者主机崩溃时会造成索引损坏,需要执行修复操作。
## 2.2 InnoDB
### 存储引擎的特点
InnoDB是默认的数据库存储引擎他的主要特点有
> 对于InnoDB来说最大的特点在于**支持事务**。但是这是以损失效率来换取的。
1. 可以通过自动增长列方法是auto_increment。
2. 支持事务。默认的事务隔离级别为可重复度通过MVCC并发版本控制来实现的。
3. 使用的锁粒度为行级锁,可以支持更高的并发;
4. 支持外键约束;外键约束其实降低了表的查询速度,但是增加了表之间的耦合度。
5. 配合一些热备工具可以支持在线热备份;
6. 在InnoDB中存在着缓冲管理通过**缓冲池**,将索引和数据全部缓存起来,加快查询的速度;
7. 对于InnoDB类型的表其数据的物理组织形式是**聚簇表**。所有的数据按照主键来组织。数据和索引放在一块都位于B+数的叶子节点上;
### 存储引擎的实现
当然InnoDB的存储表和索引也有下面两种形式
1. 使用共享表空间存储:所有的表和索引存放在同一个表空间中。
2. 使用多表空间存储表结构放在frm文件数据和索引放在IBD文件中。分区表的话每个分区对应单独的IBD文件分区表的定义可以查看我的其他文章。使用分区表的好处在于提升查询效率。
### InnoDB索引和MyISAM索引的区别
一是主索引的区别InnoDB的数据文件本身就是索引文件。而MyISAM的索引和数据是分开的。
二是辅助索引的区别InnoDB的辅助索引data域存储相应记录主键的值而不是地址。而MyISAM的辅助索引和主索引没有多大区别。
### 存储引擎的特点2
> InnoDB是事务型数据库的首选引擎支持事务安全表ACID支持行锁定和外键上图也看到了InnoDB是默认的MySQL引擎。InnoDB主要特性有
1. InnoDB给MySQL提供了具有提交、回滚和崩溃恢复能力的事物安全ACID兼容存储引擎。InnoDB锁定在行级并且也在SELECT语句中提供一个类似Oracle的非锁定读。这些功能增加了多用户部署和性能。在SQL查询中可以自由地将InnoDB类型的表和其他MySQL的表类型混合起来甚至在同一个查询中也可以混合
2. InnoDB是为处理巨大数据量的最大性能设计。它的CPU效率可能是任何其他基于磁盘的关系型数据库引擎锁不能匹敌的
3. InnoDB存储引擎完全与MySQL服务器整合InnoDB存储引擎为在主内存中缓存数据和索引而维持它自己的缓冲池。InnoDB将它的表和索引在一个逻辑表空间中表空间可以包含数个文件或原始磁盘文件。这与MyISAM表不同比如在MyISAM表中每个表被存放在分离的文件中。InnoDB表可以是任何尺寸即使在文件尺寸被限制为2GB的操作系统上
4. InnoDB支持外键完整性约束存储表中的数据时每张表的存储都按主键顺序存放如果没有显示在表定义时指定主键InnoDB会为每一行生成一个6字节的ROWID并以此作为主键
5. InnoDB被用在众多需要高性能的大型数据库站点上。InnoDB不创建目录使用InnoDB时MySQL将在MySQL数据目录下创建一个名为ibdata1的10MB大小的自动扩展数据文件以及两个名为ib_logfile0和ib_logfile1的5MB大小的日志文件
### 存储引擎的特点3
1. 是 MySQL 默认的事务型存储引擎,只有在需要它不支持的特性时,才考虑使用其它存储引擎。
2. 实现了四个标准的隔离级别默认级别是可重复读REPEATABLE READ。在可重复读隔离级别下通过多版本并发控制MVCC+ Next-Key Locking 防止幻影读。
3. 主索引是聚簇索引,在索引中保存了数据,从而避免直接读取磁盘,因此对查询性能有很大的提升。
4. 内部做了很多优化,包括从磁盘读取数据时采用的可预测性读、能够加快读操作并且自动创建的自适应哈希索引、能够加速插入操作的插入缓冲区等。
5. 支持真正的在线热备份。其它存储引擎不支持在线热备份,要获取一致性视图需要停止对所有表的写入,而在读写混合场景中,停止写入可能也意味着停止读取。
## 2.3 Memory
### 存储引擎的实现
将数据存在内存为了提高数据的访问速度每一个表实际上和一个磁盘文件关联。文件是frm。
1. 支持的数据类型有限制比如不支持TEXT和BLOB类型对于字符串类型的数据只支持固定长度的行VARCHAR会被自动存储为CHAR类型
2. 支持的锁粒度为表级锁。所以在访问量比较大时表级锁会成为MEMORY存储引擎的瓶颈
3. 由于数据是存放在内存中,一旦服务器出现故障,数据都会丢失;
4. 查询的时候如果有用到临时表而且临时表中有BLOBTEXT类型的字段那么这个临时表就会转化为MyISAM类型的表性能会急剧降低
5. 默认使用hash索引。
6. 如果一个内部表很大,会转化为磁盘表。
### 存储引擎的特点
MEMORY存储引擎将表中的数据存储到内存中未查询和引用其他表数据提供快速访问。MEMORY主要特性有
1. MEMORY表的每个表可以有多达32个索引每个索引16列以及500字节的最大键长度
2. MEMORY存储引擎执行HASH和BTREE缩影
3. 可以在一个MEMORY表中有非唯一键值
4. MEMORY表使用一个固定的记录长度格式
5. MEMORY不支持BLOB或TEXT列
6. MEMORY支持AUTO_INCREMENT列和对可包含NULL值的列的索引
7. MEMORY表在所由客户端之间共享就像其他任何非TEMPORARY表
8. MEMORY表内存被存储在内存中内存是MEMORY表和服务器在查询处理时的空闲中创建的内部表共享
9. 当不再需要MEMORY表的内容时要释放被MEMORY表使用的内存应该执行DELETE FROM或TRUNCATE TABLE或者删除整个表使用DROP TABLE
## 2.4 MERGE
1. MERGE存储引擎是一组MyISAM表的组合这些MyISAM表结构必须完全相同尽管其使用不如其它引擎突出但是在某些情况下非常有用。说白了Merge表就是几个相同MyISAM表的聚合器Merge表中并没有数据对Merge类型的表可以进行查询、更新、删除操作这些操作实际上是对内部的MyISAM表进行操作。Merge存储引擎的使用场景。
2. 对于服务器日志这种信息一般常用的存储策略是将数据分成很多表每个名称与特定的时间端相关。例如可以用12个相同的表来存储服务器日志数据每个表用对应各个月份的名字来命名。当有必要基于所有12个日志表的数据来生成报表这意味着需要编写并更新多表查询以反映这些表中的信息。与其编写这些可能出现错误的查询不如将这些表合并起来使用一条查询之后再删除Merge表而不影响原来的数据删除Merge表只是删除Merge表的定义对内部的表没有任何影响。
## 2.5 ARCHIVE
1. Archive是归档的意思在归档之后很多的高级功能就不再支持了仅仅支持最基本的插入和查询两种功能。在MySQL 5.5版以前Archive是不支持索引但是在MySQL 5.5以后的版本中就开始支持索引了。Archive拥有很好的压缩机制它使用zlib压缩库在记录被请求时会实时压缩所以它经常被用来当做仓库使用。

View File

@@ -0,0 +1,218 @@
# 性能调优
> 参考文献
> * [https://www.jb51.net/article/70111.htm](https://www.jb51.net/article/70111.htm)
> * [https://www.cnblogs.com/jiekzou/p/5371085.html](https://www.cnblogs.com/jiekzou/p/5371085.html)
## 0 基础方法
1. 数据库设计优化:
1. 选择合适的存储引擎
2. 设计合理的表结构(符合3NF)
3. 添加适当索引(index) 普通索引、主键索引、唯一索引、全文索引、组合索引、覆盖索引。
2. 查询语句优化:
1. 遵守查询规范。
2. 分析日志通过show status命令了解各种SQL的执行频率。定位执行效率较低的SQL语句重点select记录慢查询
3. explain分析低效率的SQL语句
3. 查询过程优化
1. 从内存中读取数据
2. 减少磁盘写入操作(更大的写缓存)
3. 提高磁盘读取速度(硬件设备改善)
## 1.1 存储引擎
* 支持全文索引MyISAM
* 支持外键Innodb
* 支持缓存Innodb
* 支持事务Innodb
* 支持并发MyISAM 只支持表级锁,而 InnoDB 还支持行级锁。
* 支持备份InnoDB 支持在线热备份。
* 崩溃恢复MyISAM 崩溃后发生损坏的概率比 InnoDB 高很多,而且恢复的速度也更慢。
* 其它特性MyISAM 支持压缩表和空间数据索引。
## 1.2 表结构
符合3BNF/EBNF范式
* 属性不可分
* 非主属性依赖完全的主属性
* 非主属性不依赖其他的非主属性
* 消除多值依赖(多对多关系)
## 1.3 添加索引
索引类型选择:
1. **前缀索引**。对于 BLOB、TEXT 和 VARCHAR 类型的列必须使用前缀索引只索引开始的部分字符。前缀长度的选取需要根据索引选择性来确定。字符串列加索引最好加短索引即对前该列的前xx个字符例如key 'ind_user_info_addr' (addr(10)) USINGBTREE代表对addr列的前10个字符家索引。
2. **复合索引**。在需要使用多个列作为条件进行查询时,使用多列索引比使用多个单列索引性能更好。如果创建了一个(username,age,addr)的复合索引,那么相当于创建了(username,age,addr)(username,age)(username)三个索引,所以在创建复合索引的时候应该将最常用的限制条件的列放在最左边,依次递减。
索引使用规范:
1. 不能再索引字段上计算。会导致索引无效,成为全表扫描。
2. 将非”索引”数据分离,比如将大篇文章分离存储,不影响其他自动查询。
3. **覆盖索引**。能够直接通过索引查询到所需要的字段,不需要通过回表查询,找到数据。
4. **关联查询**。保证关联的字段都建立索引并且字段类型一致这样才能两个表都使用索引。如果字段类型不一样至少一个表不能使用索引。保证连接的索引是相同类型。即a.age = b.agea表的和b表的age字段类型保证一样并且都建立了索引。
5. **最左匹配原则**索引列如果使用like条件进行查询那么 like 'xxx%' 可以使用索引like '%xxx' 不能使用索引。
6. 索引列的值最好不要有null值。列中只要包含null则不会被包含到索引中复合索引中只要有一列为null值则这个复合索引失效的。所以创建索引列不要设置默认值null
7. 如果where条件中使用了一个列的索引那么后面order by 在用这个列进行排序时便不会再使用索引。where条件用到复合索引中的字段时最好把该字段放在复合索引的左端这样才能使用索引提高查询。
8. 排序索引。尽量不要使用包含多个列的排序,如果需要则给这些列加上复合索引。
## 2.1 查询规范
1. **减少请求的数据量**。单条查询最后增加 LIMIT停止全表扫描。
- 只返回必要的列:最好不要使用 SELECT * 语句。
- 只返回必要的行:使用 LIMIT 语句来限制返回的数据。
3. 不用 MYSQL 内置的函数,因为内置函数不会建立查询缓存。
4. inner join 内连接也叫做等值连接left/right join 是外连接。能用inner join连接的尽量用inner join连接
5. 尽量使用外连接来替换子查询。在使用on和where的时候先用on在用where。使用join时用小的结果驱动大的结果left join左表结果尽量小如果有条件先放左边处理right join同理反向。多表联合查询尽量拆分多个简单的sql语句进行查询。
6. 尽量不要使用BY RAND()命令。减少排序order by。少用OR。尽量不要使用not in 和<> 操作
7. 避免类型转换,也就是转入的参数类型要和字段类型一致。
8. 不要在列上进行运算。
9. 使用批量插入操作代替一个一个插入
10. 对于多表查询可以建立视图。
## 2.2 分析日志
修改mysql的慢查询.
```
show variables like 'long_query_time' ; //可以显示当前慢查询时间
set long_query_time=1 ;//可以修改慢查询时间
```
记录所有查询,这在用 ORM 系统或者生成查询语句的系统很有用。
```
log=/var/log/mysql.log
```
注意不要在生产环境用,否则会占满你的磁盘空间。
记录执行时间超过 1 秒的查询:
```
long_query_time=1
log-slow-queries=/var/log/mysql/log-slow-queries.log
```
## 2.3 explain分析查询
### 分析方法
```
Explain select * from emp where ename=“wsrcla”
```
会产生如下信息:
* select_type:表示查询的类型。
* table:输出结果集的表
* type:表示表的连接类型
* possible_keys:表示查询时,可能使用的索引
* key:表示实际使用的索引
* key_len:索引字段的长度
* rows:扫描出的行数(估算的行数)
* Extra:执行情况的描述和说明
### 重构查询方式
#### 1. 切分大查询
一个大查询如果一次性执行的话,可能一次锁住很多数据、占满整个事务日志、耗尽系统资源、阻塞很多小的但重要的查询。
```sql
DELETE FROM messages WHERE create < DATE_SUB(NOW(), INTERVAL 3 MONTH);
```
```sql
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
```
#### 2. 分解大连接查询
将一个大连接查询分解成对每一个表进行一次单表查询,然后在应用程序中进行关联,这样做的好处有:
- 让缓存更高效。对于连接查询,如果其中一个表发生变化,那么整个查询缓存就无法使用。而分解后的多个查询,即使其中一个表发生变化,对其它表的查询缓存依然可以使用。
- 分解成多个单表查询,这些单表查询的缓存结果更可能被其它查询使用到,从而减少冗余记录的查询。
- 减少锁竞争;
- 在应用层进行连接,可以更容易对数据库进行拆分,从而更容易做到高性能和可伸缩。
- 查询本身效率也可能会有所提升。例如下面的例子中,使用 IN() 代替连接查询,可以让 MySQL 按照 ID 顺序进行查询,这可能比随机的连接要更高效。
```sql
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';
```
```sql
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);
```
## 3.1 内存读取
### 增大缓存
* 将更新操作先记录在change buffer减少读磁盘语句的执行速度会得到明显的提升。而且数据读入内存是需要占用buffer pool的所以这种方式还能够避免占用内存提高内存利用率
* innodb_buffer_pool_size。将数据完全保存在 innodb_buffer_pool中。可以完全从内存中读取数据最大限度减少磁盘操作。
### 数据预热
默认情况,只有某条数据被读取一次,才会缓存在 innodb_buffer_pool。数据库启动后需要进行数据预热将磁盘上的所有数据缓存到内存中。数据预热可以提高读取速度。
```sql
SELECT DISTINCT
CONCAT('SELECT ',ndxcollist,' FROM ',db,'.',tb,
' ORDER BY ',ndxcollist,';') SelectQueryToLoadCache
FROM
(
SELECT
engine,table_schema db,table_name tb,
index_name,GROUP_CONCAT(column_name ORDER BY seq_in_index) ndxcollist
FROM
(
SELECT
B.engine,A.table_schema,A.table_name,
A.index_name,A.column_name,A.seq_in_index
FROM
information_schema.statistics A INNER JOIN
(
SELECT engine,table_schema,table_name
FROM information_schema.tables WHERE
engine='InnoDB'
) B USING (table_schema,table_name)
WHERE B.table_schema NOT IN ('information_schema','mysql')
ORDER BY table_schema,table_name,index_name,seq_in_index
) A
GROUP BY table_schema,table_name,index_name
) AA
ORDER BY db,tb;
```
## 3.2 减少磁盘读写
### 使用足够大的写入缓存innodb_log_file_size
* 如果用 1G 的 innodb_log_file_size ,假如服务器当机,需要 10 分钟来恢复。
* 推荐 innodb_log_file_size 设置为 0.25 * innodb_buffer_pool_size
### innodb_flush_log_at_trx_commit
这个选项和写磁盘操作密切相关:
```sql
innodb_flush_log_at_trx_commit = 1
innodb_flush_log_at_trx_commit = 0/2
```
如果你的应用不涉及很高的安全性 (金融系统),或者基础架构足够安全,或者事务都很小,都可以用 0 或者 2 来降低磁盘操作。
## 3.3 提高磁盘读取速度
## 4 其他原则
1. 不在数据库做运算cpu计算务必移至业务层
1. 控制单表数据量单表记录控制在1000w
1. 控制列数量字段数控制在20以内
1. 平衡范式与冗余:为提高效率牺牲范式设计,冗余数据
1. 拒绝3B拒绝大sql大事物大批量

View File

@@ -0,0 +1,228 @@
# Mysql索引
> 参考文献
> * [MySQL索引类型](cnblogs.com/luyucheng/p/6289714.html)
## 1 索引类型——应用
### 索引定义
* 普通索引
* 唯一索引
* 主键索引
* 组合索引
* 全文索引
```
CREATE TABLE table_name[col_name data type]
[unique|fulltext][index|key][index_name](col_name[length])[asc|desc]
```
1. unique|fulltext为可选参数分别表示唯一索引、全文索引
2. index和key为同义词两者作用相同用来指定创建索引
3. col_name为需要创建索引的字段列该列必须从数据表中该定义的多个列中选择
4. index_name指定索引的名称为可选参数如果不指定默认col_name为索引值
5. length为可选参数表示索引的长度只有字符串类型的字段才能指定索引长度
6. asc或desc指定升序或降序的索引值存储
### 普通索引
1.普通索引
是最基本的索引,它没有任何限制。它有以下几种创建方式:
1直接创建索引
```sql
CREATE INDEX index_name ON table(column(length))
```
2修改表结构的方式添加索引
```sql
ALTER TABLE table_name ADD INDEX index_name ON (column(length))
```
3创建表的时候同时创建索引
```sql
CREATE TABLE `table` (
`id` int(11) NOT NULL AUTO_INCREMENT ,
`title` char(255) CHARACTER NOT NULL ,
`content` text CHARACTER NULL ,
`time` int(10) NULL DEFAULT NULL ,
PRIMARY KEY (`id`),
INDEX index_name (title(length))
)
```
4删除索引
```sql
DROP INDEX index_name ON table
```
### 唯一索引
与前面的普通索引类似,不同的就是:索引列的值必须唯一,但允许有空值。如果是组合索引,则列值的组合必须唯一。它有以下几种创建方式:
1创建唯一索引
```sql
CREATE UNIQUE INDEX indexName ON table(column(length))
```
2修改表结构
```sql
ALTER TABLE table_name ADD UNIQUE indexName ON (column(length))
```
3创建表的时候直接指定
```sql
CREATE TABLE `table` (
`id` int(11) NOT NULL AUTO_INCREMENT ,
`title` char(255) CHARACTER NOT NULL ,
`content` text CHARACTER NULL ,
`time` int(10) NULL DEFAULT NULL ,
UNIQUE indexName (title(length))
);
```
### 主键索引
是一种特殊的唯一索引,一个表只能有一个主键,不允许有空值。一般是在建表的时候同时创建主键索引:
```sql
CREATE TABLE `table` (
`id` int(11) NOT NULL AUTO_INCREMENT ,
`title` char(255) NOT NULL ,
PRIMARY KEY (`id`)
);
```
主键设计的原则:
1. 一定要显式定义主键
2. 采用与业务无关的单独列
3. 采用自增列
4. 数据类型采用int并尽可能小能用tinyint就不用int能用int就不用bigint
5. 将主键放在表的第一列
这样设计的原因:
1. 在innodb引擎中只能有一个聚集索引我们知道聚集索引的叶子节点上直接存有行数据所以聚集索引列尽量不要更改而innodb表在有主键时会自动将主键设为聚集索引如果不显式定义主键会选第一个没有null值的唯一索引作为聚集索引唯一索引涉及到的列内容难免被修改引发存储碎片且可能不是递增关系存取效率低所以最好显式定义主键且采用与业务无关的列以避免修改如果这个条件也不符合就会自动添加一个不可见不可引用的6byte大小的rowid作为聚集索引
2. 需采用自增列来使数据顺序插入,新增数据顺序插入到当前索引的后面,符合叶子节点的分裂顺序,性能较高;若不用自增列,数据的插入近似于随机,插入时需要插入到现在索引页的某个中间位置,需要移动数据,造成大量的数据碎片,索引结构松散,性能很差
3. 在主键插入时会判断是否有重复值所以尽量采用较小的数据类型以减小比对长度提高性能且可以减小存储需求磁盘占用小进而减少磁盘IO和内存占用而且主键存储占用小普通索引的占用也相应较小减少占用减少IO且存储索引的页中能包含较多的数据减少页的分裂提高效率
4. 将主键放在表的第一列好像是习惯,原因我也不知道,试了下是可以放在其他列的......
### 组合索引
指多个字段上创建的索引,只有在查询条件中使用了创建索引时的第一个字段,索引才会被使用。使用组合索引时遵循最左前缀集合
```
ALTER TABLE `table` ADD INDEX name_city_age (name,city,age);
```
### 全文索引
* FULLTEXT即为全文索引目前只有MyISAM引擎支持。其可以在CREATE TABLE ALTER TABLE CREATE INDEX 使用,不过目前只有 CHAR、VARCHAR TEXT 列上可以创建全文索引。
* 主要用来查找文本中的关键字而不是直接与索引中的值相比较。fulltext索引跟其它索引大不相同它更像是一个搜索引擎而不是简单的where语句的参数匹配。
* fulltext索引配合match against操作使用而不是一般的where语句加like。它可以在create tablealter table create index使用不过目前只有char、varchartext 列上可以创建全文索引。值得一提的是在数据量较大时候现将数据放入一个没有全局索引的表中然后再用CREATE index创建fulltext索引要比先为一张表建立fulltext然后再将数据写入的速度快很多。
1创建表的适合添加全文索引
```sql
CREATE TABLE `table` (
`id` int(11) NOT NULL AUTO_INCREMENT ,
`title` char(255) CHARACTER NOT NULL ,
`content` text CHARACTER NULL ,
`time` int(10) NULL DEFAULT NULL ,
PRIMARY KEY (`id`),
FULLTEXT (content)
);
```
2修改表结构添加全文索引
```
ALTER TABLE article ADD FULLTEXT index_content(content)
```
3直接创建索引
```
CREATE FULLTEXT INDEX index_content ON article(content)
```
### 覆盖索引
索引包含所有需要查询的字段的值。
* 覆盖索引select的数据列只用从索引中就能够取得不必读取数据行换句话说查询列要被所建的索引覆盖。
* 这个时候,可以不用访问指定的数据。也就不需要回表查询具体的数据,通过索引即可得到想要的数据。
具有以下优点:
- 索引通常远小于数据行的大小,只读取索引能大大减少数据访问量。
- 一些存储引擎(例如 MyISAM在内存中只缓存索引而数据依赖于操作系统来缓存。因此只访问索引可以不使用系统调用通常比较费时
- 对于 InnoDB 引擎,若辅助索引能够覆盖查询,则无需访问主索引。
### 索引优点
- 大大减少了服务器需要扫描的数据行数。
- 帮助服务器避免进行排序和分组以及避免创建临时表B+Tree 索引是有序的,可以用于 ORDER BY 和 GROUP BY 操作。临时表主要是在排序和分组过程中创建,不需要排序和分组,也就不需要创建临时表)。
- 将随机 I/O 变为顺序 I/OB+Tree 索引是有序的,会将相邻的数据都存储在一起)。
### 索引缺点
1. 虽然索引大大提高了查询速度同时却会降低更新表的速度如对表进行insert、update和delete。因为更新表时不仅要保存数据还要保存一下索引文件。
2. 建立索引会占用磁盘空间的索引文件。一般情况这个问题不太严重,但如果你在一个大表上创建了多种组合索引,索引文件的会增长很快。
索引只是提高效率的一个因素,如果有大数据量的表,就需要花时间研究建立最优秀的索引,或优化查询语句。
### 注意事项
1. 索引不会包含有null值的列。只要列中包含有null值都将不会被包含在索引中复合索引中只要有一列含有null值那么这一列对于此复合索引就是无效的。所以我们在数据库设计时不要让字段的默认值为null。
2. 使用短索引。对串列进行索引如果可能应该指定一个前缀长度。例如如果有一个char(255)的列如果在前10个或20个字符内多数值是惟一的那么就不要对整个列进行索引。短索引不仅可以提高查询速度而且可以节省磁盘空间和I/O操作。
3. 索引列排序。查询只使用一个索引因此如果where子句中已经使用了索引的话那么order by中的列是不会使用索引的。因此数据库默认排序可以符合要求的情况下不要使用排序操作尽量不要包含多个列的排序如果需要最好给这些列创建复合索引。
4. like语句操作。一般情况下不推荐使用like操作如果非使用不可如何使用也是一个问题。like “%aaa%” 不会使用索引而like “aaa%”可以使用索引。
5. 不要在列上进行运算。这将导致索引失效而进行全表扫描,例如
```sql
SELECT * FROM table_name WHERE YEAR(column_name)<2017;
```
6. 不使用not in和<>操作
## 2 索引类型——聚集
* **聚集索引**(聚簇索引):数据行的物理顺序与列值(一般是主键的那一列)的逻辑顺序相同,一个表中只能拥有一个聚集索引。以 InnoDB 作为存储引擎的表,表中的数据都会有一个主键,即使你不创建主键,系统也会帮你创建一个隐式的主键。这是因为 InnoDB 是把数据存放在 B+ 树中的,而 B+ 树的键值就是主键,在 B+ 树的叶子节点中,存储了表中所有的数据。这种以主键作为 B+ 树索引的键值而构建的 B+ 树索引,我们称之为聚集索引。
* **非聚集索引**(非聚簇索引):该索引中索引的逻辑顺序与磁盘上行的物理存储顺序不同,一个表中可以拥有多个非聚集索引。以主键以外的列值作为键值构建的 B+ 树索引,我们称之为非聚集索引。
* **回表**:非聚集索引与聚集索引的区别在于非聚集索引的叶子节点不存储表中的数据,而是存储该列对应的主键,想要查找数据我们还需要根据主键再去聚集索引中进行查找,这个再根据聚集索引查找数据的过程,我们称为**回表**。
使用以下语句进行查询,不需要进行二次查询,直接就可以从非聚集索引的节点里面就可以获取到查询列的数据。
```
select id, username from t1 where username = '小明'
select username from t1 where username = '小明'
```
但是使用以下语句进行查询就需要二次的查询去获取原数据行的score
```
select username, score from t1 where username = '小明'
```
## 3 索引类型——算法
### 顺序索引
* 顺序文件上的索引:按指定属性升序或降序存储的关系。在属性上建立一个顺序索引文件,索引文件有属性值和响应的元组指针组成。
### B+树索引
* B+树上的索引B+树的叶节点为属性值和相应的元组指针。具有动态平衡的特点。
### hash索引
* 散列索引(哈希索引):建立若干个桶,将索引属性按照其散列函数值映射到相应的桶中,同种存放索引属性值和响应的元组指针。散列索引具有查找速度快的特点。
* HASH索引可以一次定位不需要像树形索引那样逐层查找,因此具有极高的效率。但是,这种高效是有条件的,即只在“=”和“in”条件下高效对于范围查询、排序及组合索引仍然效率不高。这是MySQL里默认和最常用的索引类型。
### 位图索引
* 位图索引:用为向量记录索引属性中可能出现的值,每个为向量对应一个可能的值。
### 前缀索引
* tire索引/前缀索引:用来索引字符串。
> 特点:索引能够加快数据库查询,需要占用一定的存储空间。基本表更新后,索引也需要更新。用户不必显示地选择索引,关系数据库管理系统在执行查询时,会自动选择合适的索引作为存储路径。
> MySQL中常用的索引结构有B+树索引和哈希索引两种。目前建表用的B+树索引就是BTREE索引。
> 在MySQL中MyISAM和InnoDB两种存储引擎都不支持哈希索引。只有HEAP/MEMORY引擎才能显示支持哈希索引。

View File

@@ -0,0 +1,568 @@
# MySQL事务管理
> 参考文献
> * [Mysql并发控制](https://zhuanlan.zhihu.com/p/133823461)
> 本章主要讲了MySQL的事务管理即MySQL的并发控制MySQL在并发过程中如何进行同步即MySQL保证事物的原子性、隔离性、一致性、持久性的方法。也属于并发机制的一部分。
## 4 多级锁协议
### 封锁粒度
MySQL 各存储引擎使用了三种类型(级别)的锁定机制:表级锁定,行级锁定和页级锁定。
1. 表级锁定table-level
表级别的锁定是 MySQL 各存储引擎中最大颗粒度的锁定机制。该锁定机制最大的特点是实现逻辑非常简单,带来的系统负面影响最小。所以获取锁和释放锁的速度很快。由于表级锁一次会将整个表锁定,所以可以很好的避免困扰我们的死锁问题。
当然,锁定颗粒度大所带来最大的负面影响就是出现锁定资源争用的概率也会最高,致使并发度大打折扣。
使用**表级锁定的主要是 MyISAMMEMORYCSV**等一些非事务性存储引擎。
2. 行级锁定row-level
行级锁定最大的特点就是锁定对象的颗粒度很小,也是目前各大数据库管理软件所实现的锁定颗粒度最小的。由于锁定颗粒度很小,所以发生锁定资源争用的概率也最小,能够给予应用程序尽可能大的并发处理能力而提高一些需要高并发应用系统的整体性能。
虽然能够在并发处理能力上面有较大的优势,但是行级锁定也因此带来了不少弊端。由于锁定资源的颗粒度很小,所以每次获取锁和释放锁需要做的事情也更多,带来的消耗自然也就更大了。此外,行级锁定也最容易发生死锁。
使用**行级锁定的主要是 InnoDB 存储引擎**。
3. 页级锁定page-level
页级锁定是 MySQL 中比较独特的一种锁定级别,在其他数据库管理软件中也并不是太常见。页级锁定的特点是锁定颗粒度介于行级锁定与表级锁之间,所以获取锁定所需要的资源开销,以及所能提供的并发处理能力也同样是介于上面二者之间。另外,页级锁定和行级锁定一样,会发生死锁。
使用**页级锁定的主要是 BerkeleyDB 存储引擎**。
优缺点:
- 表级锁:开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低;
- 行级锁:开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高;
- 页面锁:开销和加锁时间界于表锁和行锁之间;会出现死锁;锁定粒度界于表锁和行锁之间,并发度一般。
适用场景:
- 从锁的角度来说,表级锁更适合于以查询为主,只有少量按索引条件更新数据的应用,如 Web 应用而行级锁则更适合于有大量按索引条件并发更新少量不同数据同时又有并发查询的应用如一些在线事务处理OLTP系统。
注意事项:
- InnoDB 行锁是通过给索引上的索引项加锁来实现的只有通过索引条件检索数据InnoDB 才使用行级锁否则InnoDB 将使用表锁
- 由于 MySQL 的行锁是针对索引加的锁,不是针对记录加的锁,所以虽然是访问不同行的记录,但是如果是使用相同的索引键,是会出现锁冲突的。
### 封锁类型
可以说 InnoDB 的锁定模式实际上可以分为四种共享锁S排他锁X意向共享锁IS和意向排他锁IX。意向锁是 InnoDB 自动加的,不需用户干预。
- 读写锁
- 共享锁Shared简写为 S 锁,又称读锁。当一个事务需要给自己需要的某个资源加锁的时候,如果遇到一个共享锁正锁定着自己需要的资源的时候,自己可以再加一个共享锁,不过不能加排他锁。
- 互斥锁Exclusive简写为 X 锁,又称写锁、独占锁。如果遇到自己需要锁定的资源已经被一个排他锁占有之后,则只能等待该锁定释放资源之后自己才能获取锁定资源并添加自己的锁定。
- 意向锁的作用就是当一个事务在需要获取资源锁定的时候,如果遇到自己需要的资源已经被排他锁占用的时候,该事务可以需要锁定行的表上面添加一个合适的意向锁。
- 如果自己需要一个共享锁,那么就在表上面添加一个意向共享锁。
- 而如果自己需要的是某行(或者某些行)上面添加一个排他锁的话,则先在表上面添加一个意向排他锁。意向共享锁可以同时并存多个,但是意向排他锁同时只能有一个存在。
### 封锁规则
读写锁有如下规定
- 一个事务对数据对象 A 加了 X 锁,就可以对 A 进行读取和更新。加锁期间其它事务不能对 A 加任何锁。
- 一个事务对数据对象 A 加了 S 锁,可以对 A 进行读取操作,但是不能进行更新操作。加锁期间其它事务能对 A 加 S 锁,但是不能加 X 锁。
意向锁在原来的 X/S 锁之上引入了 IX/IS**IX/IS 都是表锁**,用来表示一个事务想要在表中的某个数据行上加 X 锁或 S 锁。主要是为了防止遍历整个行,判断是否有行级锁。有以下两个规定:
- 一个事务在获得某个数据行对象的 S 锁之前,必须先获得表的 IS 锁或者更强的锁;
- 一个事务在获得某个数据行对象的 X 锁之前,必须先获得表的 IX 锁。
![](image/2021-09-01-17-37-28.png)
- 任意 IS/IX 锁之间都是兼容的,因为它们只表示想要对表加锁,而不是真正加锁;
- 这里兼容关系针对的是表级锁,而表级的 IX 锁和行级的 X 锁兼容,两个事务可以对两个数据行加 X 锁。(事务 T<sub>1</sub> 想要对数据行 R<sub>1</sub> 加 X 锁,事务 T<sub>2</sub> 想要对同一个表的数据行 R<sub>2</sub> 加 X 锁,两个事务都需要对该表加 IX 锁,但是 IX 锁是兼容的,并且 IX 锁与行级的 X 锁也是兼容的,因此两个事务都能加锁成功,对同一个表中的两个数据行做修改。)
### 封锁协议
**一级封锁协议**
- 事务 T 要修改数据 A 时必须加 X 锁,直到 T 结束才释放锁。
- 可以解决丢失修改的问题,因为不能同时有两个事务对同一个数据进行修改,那么事务的修改就不会被覆盖。
![](image/2021-09-01-17-54-00.png)
**二级封锁协议**
- 在一级的基础上,要求读取数据 A 时必须加 S 锁,读取完马上释放 S 锁。
- 可以解决读脏数据问题,因为如果一个事务在对数据 A 进行修改,根据 1 级封锁协议,会加 X 锁,那么就不能再加 S 锁了,也就是不会读入数据。
![](image/2021-09-01-17-54-50.png)
**三级封锁协议**
- 在二级的基础上,要求读取数据 A 时必须加 S 锁,直到事务结束了才能释放 S 锁。
- 可以解决不可重复读的问题,因为读 A 时,其它事务不能对 A 加 X 锁,从而避免了在读的期间数据发生改变。
![](image/2021-09-01-17-55-26.png)
**两段锁协议**
- 加锁和解锁分为两个阶段进行。
- 可串行化调度是指,通过并发控制,使得并发执行的事务结果与某个串行执行的事务结果相同。串行执行的事务互不干扰,不会出现并发一致性问题。
![](image/2021-09-01-18-26-00.png)
事务遵循两段锁协议是保证可串行化调度的充分条件。例如以下操作满足两段锁协议,它是可串行化调度。
```html
lock-x(A)...lock-s(B)...lock-s(C)...unlock(A)...unlock(C)...unlock(B)
```
但不是必要条件,例如以下操作不满足两段锁协议,但它还是可串行化调度。
```html
lock-x(A)...unlock(A)...lock-s(B)...unlock(B)...lock-s(C)...unlock(C)
```
**隐式与显示锁定**
- MySQL 的 InnoDB 存储引擎采用两段锁协议,会根据隔离级别在需要的时候自动加锁,并且所有的锁都是在同一时刻被释放,这被称为隐式锁定。
- InnoDB 也可以使用特定的语句进行显示锁定:
```sql
SELECT ... LOCK In SHARE MODE;
SELECT ... FOR UPDATE;
```
## 5 多版本并发控制 MVCC
### 原理
**数据库并发的三种场景**
* 读读之间不存在冲突,无须加锁。
* 写写之间的冲突需要严格隔离,必须加锁。有线程安全问题。
* 读写之间的冲突导致了脏读、不可重复读、幻影读等问题。有线程安全问题。
**MVCC带来的好处**
* 在并发读写数据库时,可以做到在读操作时不用阻塞写操作,写操作也不用阻塞读操作,提高了数据库并发读写的性能
* 同时还可以解决脏读,幻读,不可重复读等事务隔离问题,但不能解决更新丢失问题
### 定义
**多版本并发控制**Multi-Version Concurrency Control, MVCC是 是一种并发控制的方法,一般在数据库管理系统中,实现对数据库的并发访问,在编程语言中实现事务内存。
- MySQL 的 InnoDB 存储引擎实现隔离级别的一种具体方式,用于实现提交读和可重复读这两种隔离级别。
- 未提交读隔离级别总是读取最新的数据行,要求很低,无需使用 MVCC。
- 可串行化隔离级别需要对所有读取的行都加锁,单纯使用 MVCC 无法实现。
**当前读**:它读取的是记录的最新版本读取时还要保证其他并发事务不能修改当前记录会对读取的记录进行加锁。MVCC 会对数据库进行修改的操作INSERT、UPDATE、DELETE需要进行加锁操作从而读取最新的数据。可以看到 MVCC 并不是完全不用加锁,而只是避免了 SELECT 的读加锁操作。
**快照读**不加锁的select操作就是快照读即不加锁的非阻塞读MVCC 的 SELECT 操作是快照中的数据,不需要进行加锁操作。
**版本号**MVCC 用来实现版本控制的序号
- 系统版本号 SYS_ID是一个递增的数字每开始一个新的事务系统版本号就会自动递增。
- 事务版本号 TRX_ID :事务开始时的系统版本号。
**实现手段**
* 隐式字段
* Undo日志
* ReadView
### 隐式字段
InnoDB 的 MVCC,是通过在每行记录后面保存**两个隐藏的列**来实现的,这两个列,分别保存了这个行的创建时间,一个保存的是行的删除时间。
* 创建时间(事务版本)
* 删除时间(事务版本)
* 回滚指针(指向上一个事务版本号)
这里存储的并不是实际的时间值,而是系统版本号(可以理解为事务的 ID),每开始一个新的事务,系统版本号就会自动递增,事务开始时刻的系统版本号会作为事务的 ID.
### Undo日志
Undolog主要分为两种
* insert undo log。代表事务在insert新记录时产生的undo log, 只在事务回滚时需要,并且在事务提交后可以被立即丢弃
* update undo log。事务在进行update或delete时产生的undo log; 不仅在事务回滚时需要在快照读时也需要所以不能随便删除只有在快速读或事务回滚不涉及该日志时对应的日志才会被purge线程统一清除
MVCC 的多版本指的是多个版本的快照,快照存储在 Undo 日志中,该日志通过回滚指针 ROLL_PTR 把一个数据行的所有快照连接起来。
例如在 MySQL 创建一个表 t包含主键 id 和一个字段 x。我们先插入一个数据行然后对该数据行执行两次更新操作。
```sql
INSERT INTO t(id, x) VALUES(1, "a");
UPDATE t SET x="b" WHERE id=1;
UPDATE t SET x="c" WHERE id=1;
```
因为没有使用 `START TRANSACTION` 将上面的操作当成一个事务来执行,根据 MySQL 的 AUTOCOMMIT 机制,每个操作都会被当成一个事务来执行,所以上面的操作总共涉及到三个事务。快照中除了记录事务版本号 TRX_ID 和操作之外,还记录了一个 bit 的 DEL 字段,用于标记是否被删除。
![](image/2021-09-01-20-40-19.png)
INSERT、UPDATE、DELETE 操作会创建一个日志,并将事务版本号 TRX_ID 写入。DELETE 可以看成是一个特殊的 UPDATE还会额外将 DEL 字段设置为 1。
### ReadView
在该事务执行的快照读的那一刻会生成数据库系统当前的一个快照记录并维护系统当前活跃事务的ID
Read View主要是用来做可见性判断的, 即当我们某个事务执行快照读的时候对该记录创建一个Read View读视图把它比作条件用来判断当前事务能够看到哪个版本的数据。
MVCC 维护了一个 ReadView 结构,主要包含了当前系统未提交的事务列表 TRX_IDs {TRX_ID_1, TRX_ID_2, ...},还有该列表的最小值 TRX_ID_MIN 和 TRX_ID_MAX。
![](image/2021-09-01-20-40-30.png)
在进行 SELECT 操作时,根据数据行快照的 TRX_ID 与 TRX_ID_MIN 和 TRX_ID_MAX 之间的关系,从而判断数据行快照是否可以使用:
- TRX_ID \< TRX_ID_MIN表示该数据行快照时在当前所有未提交事务之前进行更改的因此可以使用。
- TRX_ID \> TRX_ID_MAX表示该数据行快照是在事务启动之后被更改的因此不可使用。
- TRX_ID_MIN \<= TRX_ID \<= TRX_ID_MAX需要根据隔离级别再进行判断
- 提交读:如果 TRX_ID 在 TRX_IDs 列表中,表示该数据行快照对应的事务还未提交,则该快照不可使用。否则表示已经提交,可以使用。
- 可重复读:都不可以使用。因为如果可以使用的话,那么其它事务也可以读到这个数据行快照并进行修改,那么当前事务再去读这个数据行得到的值就会发生改变,也就是出现了不可重复读问题。
在数据行快照不可使用的情况下,需要沿着 Undo Log 的回滚指针 ROLL_PTR 找到下一个快照,再进行上面的判断。
**purge**
* 从前面的分析可以看出为了实现InnoDB的MVCC机制更新或者删除操作都只是设置一下老记录的deleted_bit并不真正将过时的记录删除。
* 为了节省磁盘空间InnoDB有专门的purge线程来清理deleted_bit为true的记录。为了不影响MVCC的正常工作purge线程自己也维护了一个read view这个read view相当于系统中最老活跃事务的read view;如果某个记录的deleted_bit为true并且DB_TRX_ID相对于purge线程的read view可见那么这条记录一定是可以被安全清除的。
## 6 间隙锁 Next-Key Locks
Next-Key Locks 是 MySQL 的 InnoDB 存储引擎的一种锁实现。
MVCC 不能解决幻影读问题Next-Key Locks 就是为了解决这个问题而存在的。在可重复读REPEATABLE READ隔离级别下使用 MVCC + Next-Key Locks 可以解决幻读问题。
### Record Locks
锁定一个记录上的索引,而不是记录本身。
如果表没有设置索引InnoDB 会自动在主键上创建隐藏的聚簇索引,因此 Record Locks 依然可以使用。
### Gap Locks
锁定索引之间的间隙,但是不包含索引本身。例如当一个事务执行以下语句,其它事务就不能在 t.c 中插入 15。
```sql
SELECT c FROM t WHERE c BETWEEN 10 and 20 FOR UPDATE;
```
### Next-Key Locks
它是 Record Locks 和 Gap Locks 的结合不仅锁定一个记录上的索引也锁定索引之间的间隙。它锁定一个前开后闭区间例如一个索引包含以下值10, 11, 13, and 20那么就需要锁定以下区间
```sql
(-, 10]
(10, 11]
(11, 13]
(13, 20]
(20, +)
```
## 7 MVCC实例
下面看一下在 REPEATABLE READ 隔离级别下,MVCC 具体是如何操作的。
### 实现方法——Insert(1)
InnoDB 为新插入的每一行保存当前系统版本号作为版本号.
**第一个事务ID为1**
```sql
start transaction;
insert into yang values(NULL,'yang') ;
insert into yang values(NULL,'long');
insert into yang values(NULL,'fei');
commit;
```
<table>
<thead>
<tr><th>id</th><th>name</th><th>创建时间(事务ID)</th><th>删除时间(事务ID)</th></tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>yang</td>
<td>1</td>
<td>undefined</td>
</tr>
<tr>
<td>2</td>
<td>long</td>
<td>1</td>
<td>undefined</td>
</tr>
<tr>
<td>3</td>
<td>fei</td>
<td>1</td>
<td>undefined</td>
</tr>
</tbody>
</table>
### 实现方法——Select(2)
InnoDB 会根据以下两个条件检查每行记录,只有满足一下两个条件,才能作为结果返回:
- InnoDB 只会查找版本早于当前事务版本的数据行(也就是,行的系统版本号小于或等于事务的系统版本号),这样可以确保事务读取的行,要么是在事务开始前已经存在的,要么是事务自身插入或者修改过的.
- 行的删除版本要么未定义,要么大于当前事务版本号,这可以确保事务读取到的行,在事务开始之前未被删除.
**第二个事务,ID 为 2**
```
start transaction;
select * from yang; //(1)
select * from yang; //(2)
commit;
```
此时执行(1)中的内容,得到如下的表内容:
<table>
<thead>
<tr><th>id</th><th>name</th><th>创建时间(事务ID)</th><th>删除时间(事务ID)</th></tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>yang</td>
<td>1</td>
<td>undefined</td>
</tr>
<tr>
<td>2</td>
<td>long</td>
<td>1</td>
<td>undefined</td>
</tr>
<tr>
<td>3</td>
<td>fei</td>
<td>1</td>
<td>undefined</td>
</tr>
</tbody>
</table>
### 实现方法——Insert(3)
- InnoDB 会为删除的每一行保存当前系统的版本号(事务的 ID)作为删除标识.
- 假设在执行这个事务 ID 为 2 的过程中,刚执行到(1),这时,有另一个事务 ID 为 3 往这个表里插入了一条数据;
**第三个事务 ID 为 3;**
```sql
start transaction;
insert into yang values(NULL,'tian');
commit;
```
这时表中的数据如下
<table>
<thead>
<tr><th>id</th><th>name</th><th>创建时间(事务ID)</th><th>删除时间(事务ID)</th></tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>yang</td>
<td>1</td>
<td>undefined</td>
</tr>
<tr>
<td>2</td>
<td>long</td>
<td>1</td>
<td>undefined</td>
</tr>
<tr>
<td>3</td>
<td>fei</td>
<td>1</td>
<td>undefined</td>
</tr>
<tr>
<td>4</td>
<td>tian</td>
<td>3</td>
<td>undefined</td>
</tr>
</tbody>
</table>
然后接着执行事务 2 中的(2),由于 id=4 的数据的创建时间(事务 ID 为 3),执行当前事务的 ID 为 2,而 InnoDB 只会查找事务 ID 小于等于当前事务 ID 的数据行,所以 id=4 的数据行并不会在执行事务 2 中的(2)被检索出来,在事务 2 中的两条 select 语句检索出来的数据都只会下表:
<table>
<thead>
<tr><th>id</th><th>name</th><th>创建时间(事务ID)</th><th>删除时间(事务ID)</th></tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>yang</td>
<td>1</td>
<td>undefined</td>
</tr>
<tr>
<td>2</td>
<td>long</td>
<td>1</td>
<td>undefined</td>
</tr>
<tr>
<td>3</td>
<td>fei</td>
<td>1</td>
<td>undefined</td>
</tr>
</tbody>
</table>
### 实现方法——DELETE(4)
假设在执行这个事务 ID 为 2 的过程中,刚执行到(1),假设事务执行完事务 3 后,接着又执行了事务 4;
**第四个事务ID为4**
```sql
start transaction;
delete from yang where id=1;
commit;
```
此时数据表如下:
<table>
<thead>
<tr><th>id</th><th>name</th><th>创建时间(事务ID)</th><th>删除时间(事务ID)</th></tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>yang</td>
<td>1</td>
<td>4</td>
</tr>
<tr>
<td>2</td>
<td>long</td>
<td>1</td>
<td>undefined</td>
</tr>
<tr>
<td>3</td>
<td>fei</td>
<td>1</td>
<td>undefined</td>
</tr>
<tr>
<td>4</td>
<td>tian</td>
<td>3</td>
<td>undefined</td>
</tr>
</tbody>
</table>
接着执行事务 ID 为 2 的事务(2),根据 SELECT 检索条件可以知道,它会检索创建时间(创建事务的 ID)小于当前事务 ID 的行和删除时间(删除事务的 ID)大于当前事务的行,而 id=4 的行上面已经说过,而 id=1 的行由于删除时间(删除事务的 ID)大于当前事务的 ID,所以事务 2 的(2)select \* from yang 也会把 id=1 的数据检索出来.所以,事务 2 中的两条 select 语句检索出来的数据都如下:
<table>
<thead>
<tr><th>id</th><th>name</th><th>创建时间(事务ID)</th><th>删除时间(事务ID)</th></tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>yang</td>
<td>1</td>
<td>4</td>
</tr>
<tr>
<td>2</td>
<td>long</td>
<td>1</td>
<td>undefined</td>
</tr>
<tr>
<td>3</td>
<td>fei</td>
<td>1</td>
<td>undefined</td>
</tr>
</tbody>
</table>
### 实现方法——Update(5)
InnoDB 执行 UPDATE实际上是新插入了一行记录并保存其创建时间为当前事务的 ID同时保存当前事务 ID 到要 UPDATE 的行的删除时间.
假设在执行完事务 2 的(1)后又执行,其它用户执行了事务 3,4,这时,又有一个用户对这张表执行了 UPDATE 操作:
**第 5 个事务ID为5**
```
start transaction;
update yang set name='Long' where id=2;
commit;
```
根据 update 的更新原则:会生成新的一行,并在原来要修改的列的删除时间列上添加本事务 ID,得到表如下:
<table>
<thead>
<tr><th>id</th><th>name</th><th>创建时间(事务ID)</th><th>删除时间(事务ID)</th></tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>yang</td>
<td>1</td>
<td>4</td>
</tr>
<tr>
<td>2</td>
<td>long</td>
<td>1</td>
<td>5</td>
</tr>
<tr>
<td>3</td>
<td>fei</td>
<td>1</td>
<td>undefined</td>
</tr>
<tr>
<td>4</td>
<td>tian</td>
<td>3</td>
<td>undefined</td>
</tr>
<tr>
<td>2</td>
<td>Long</td>
<td>5</td>
<td>undefined</td>
</tr>
</tbody>
</table>
继续执行事务 2 的(2),根据 select 语句的检索条件,得到下表:
<table>
<thead>
<tr><th>id</th><th>name</th><th>创建时间(事务ID)</th><th>删除时间(事务ID)</th></tr>
</thead>
<tbody>
<tr>
<td>1</td>
<td>yang</td>
<td>1</td>
<td>4</td>
</tr>
<tr>
<td>2</td>
<td>long</td>
<td>1</td>
<td>5</td>
</tr>
<tr>
<td>3</td>
<td>fei</td>
<td>1</td>
<td>undefined</td>
</tr>
</tbody>
</table>
还是和事务 2 中(1)select 得到相同的结果.

View File

@@ -0,0 +1,226 @@
# 5 JOIN连接查询
> 参考文献
> * [MySQL的JOIN用法](https://www.cnblogs.com/hcfinal/p/10231694.html)
![](../image/2021-09-01-15-38-13.png)
* 数据库实例
```sql
1 CREATE TABLE t_blog(
2 id INT PRIMARY KEY AUTO_INCREMENT,
3 title VARCHAR(50),
4 typeId INT
5 );
6 SELECT * FROM t_blog;
7 +----+-------+--------+
8 | id | title | typeId |
9 +----+-------+--------+
10 | 1 | aaa | 1 |
11 | 2 | bbb | 2 |
12 | 3 | ccc | 3 |
13 | 4 | ddd | 4 |
14 | 5 | eee | 4 |
15 | 6 | fff | 3 |
16 | 7 | ggg | 2 |
17 | 8 | hhh | NULL |
18 | 9 | iii | NULL |
19 | 10 | jjj | NULL |
20 +----+-------+--------+
21 -- 博客的类别
22 CREATE TABLE t_type(
23 id INT PRIMARY KEY AUTO_INCREMENT,
24 name VARCHAR(20)
25 );
26 SELECT * FROM t_type;
27 +----+------------+
28 | id | name |
29 +----+------------+
30 | 1 | C++ |
31 | 2 | C |
32 | 3 | Java |
33 | 4 | C# |
34 | 5 | Javascript |
35 +----+------------+
```
## 1. 笛卡尔积
要理解各种JOIN首先要理解笛卡尔积。笛卡尔积就是将A表的每一条记录与B表的每一条记录强行拼在一起。所以如果A表有n条记录B表有m条记录笛卡尔积产生的结果就会产生n*m条记录。
## 2. 内连接INNER JOIN。
内连接INNER JOIN是最常用的连接操作。从数学的角度讲就是求两个表的交集从笛卡尔积的角度讲就是从笛卡尔积中挑出ON子句条件成立的记录。有INNER JOINWHERE等值连接STRAIGHT_JOIN,JOIN(省略INNER)四种写法。
```sql
SELECT * FROM t_blog INNER JOIN t_type ON t_blog.typeId=t_type.id;
SELECT * FROM t_blog,t_type WHERE t_blog.typeId=t_type.id;
SELECT * FROM t_blog STRAIGHT_JOIN t_type ON t_blog.typeId=t_type.id; --注意STRIGHT_JOIN有个下划线
SELECT * FROM t_blog JOIN t_type ON t_blog.typeId=t_type.id;
+----+-------+--------+----+------+
| id | title | typeId | id | name |
+----+-------+--------+----+------+
| 1 | aaa | 1 | 1 | C++ |
| 2 | bbb | 2 | 2 | C |
| 7 | ggg | 2 | 2 | C |
| 3 | ccc | 3 | 3 | Java |
| 6 | fff | 3 | 3 | Java |
| 4 | ddd | 4 | 4 | C# |
| 5 | eee | 4 | 4 | C# |
+----+-------+--------+----+------+
```
## 3. 左连接LEFT JOIN
左连接LEFT JOIN的含义就是求两个表的交集外加左表剩下的数据。依旧从笛卡尔积的角度讲就是先从笛卡尔积中挑出ON子句条件成立的记录然后加上左表中剩余的记录见最后三条
```
SELECT * FROM t_blog LEFT JOIN t_type ON t_blog.typeId=t_type.id;
+----+-------+--------+------+------+
| id | title | typeId | id | name |
+----+-------+--------+------+------+
| 1 | aaa | 1 | 1 | C++ |
| 2 | bbb | 2 | 2 | C |
| 7 | ggg | 2 | 2 | C |
| 3 | ccc | 3 | 3 | Java |
| 6 | fff | 3 | 3 | Java |
| 4 | ddd | 4 | 4 | C# |
| 5 | eee | 4 | 4 | C# |
| 8 | hhh | NULL | NULL | NULL |
| 9 | iii | NULL | NULL | NULL |
| 10 | jjj | NULL | NULL | NULL |
+----+-------+--------+------+------+
```
## 4. 右连接RIGHT JOIN
同理右连接RIGHT JOIN就是求两个表的交集外加右表剩下的数据。再次从笛卡尔积的角度描述右连接就是从笛卡尔积中挑出ON子句条件成立的记录然后加上右表中剩余的记录见最后一条
```
SELECT * FROM t_blog RIGHT JOIN t_type ON t_blog.typeId=t_type.id;
+------+-------+--------+----+------------+
| id | title | typeId | id | name |
+------+-------+--------+----+------------+
| 1 | aaa | 1 | 1 | C++ |
| 2 | bbb | 2 | 2 | C |
| 3 | ccc | 3 | 3 | Java |
| 4 | ddd | 4 | 4 | C# |
| 5 | eee | 4 | 4 | C# |
| 6 | fff | 3 | 3 | Java |
| 7 | ggg | 2 | 2 | C |
| NULL | NULL | NULL | 5 | Javascript |
+------+-------+--------+----+------------+
```
## 5. 外连接OUTER JOIN
外连接就是求两个集合的并集。从笛卡尔积的角度讲就是从笛卡尔积中挑出ON子句条件成立的记录然后加上左表中剩余的记录最后加上右表中剩余的记录。另外MySQL不支持OUTER JOIN但是我们可以对左连接和右连接的结果做UNION操作来实现。
```
SELECT * FROM t_blog LEFT JOIN t_type ON t_blog.typeId=t_type.id
UNION
SELECT * FROM t_blog RIGHT JOIN t_type ON t_blog.typeId=t_type.id;
+------+-------+--------+------+------------+
| id | title | typeId | id | name |
+------+-------+--------+------+------------+
| 1 | aaa | 1 | 1 | C++ |
| 2 | bbb | 2 | 2 | C |
| 7 | ggg | 2 | 2 | C |
| 3 | ccc | 3 | 3 | Java |
| 6 | fff | 3 | 3 | Java |
| 4 | ddd | 4 | 4 | C# |
| 5 | eee | 4 | 4 | C# |
| 8 | hhh | NULL | NULL | NULL |
| 9 | iii | NULL | NULL | NULL |
| 10 | jjj | NULL | NULL | NULL |
| NULL | NULL | NULL | 5 | Javascript |
+------+-------+--------+------+------------+
```
## 6. USING子句
MySQL中连接SQL语句中ON子句的语法格式为table1.column_name = table2.column_name。当模式设计对联接表的列采用了相同的命名样式时就可以使用 USING 语法来简化 ON 语法格式为USING(column_name)。
所以USING的功能相当于ON区别在于USING指定一个属性名用于连接两个表而ON指定一个条件。另外SELECT *时USING会去除USING指定的列而ON不会。实例如下。
```sql
SELECT * FROM t_blog INNER JOIN t_type ON t_blog.typeId =t_type.id;
+----+-------+--------+----+------+
| id | title | typeId | id | name |
+----+-------+--------+----+------+
| 1 | aaa | 1 | 1 | C++ |
| 2 | bbb | 2 | 2 | C |
| 7 | ggg | 2 | 2 | C |
| 3 | ccc | 3 | 3 | Java |
| 6 | fff | 3 | 3 | Java |
| 4 | ddd | 4 | 4 | C# |
| 5 | eee | 4 | 4 | C# |
+----+-------+--------+----+------+
SELECT * FROM t_blog INNER JOIN t_type USING(typeId);
ERROR 1054 (42S22): Unknown column 'typeId' in 'from clause'
SELECT * FROM t_blog INNER JOIN t_type USING(id); -- 应为t_blog的typeId与t_type的id不同名无法用Using这里用id代替下。
+----+-------+--------+------------+
| id | title | typeId | name |
+----+-------+--------+------------+
| 1 | aaa | 1 | C++ |
| 2 | bbb | 2 | C |
| 3 | ccc | 3 | Java |
| 4 | ddd | 4 | C# |
| 5 | eee | 4 | Javascript |
+----+-------+--------+------------+
```
## 7. 自然连接NATURE JOIN
自然连接就是USING子句的简化版它找出两个表中相同的列作为连接条件进行连接。有左自然连接右自然连接和普通自然连接之分。在t_blog和t_type示例中两个表相同的列是id所以会拿id作为连接条件。 另外千万分清下面三条语句的区别 。
1. 自然连接:SELECT * FROM t_blog NATURAL JOIN t_type;
2. 笛卡尔积:SELECT * FROM t_blog NATURA JOIN t_type;
3. 笛卡尔积:SELECT * FROM t_blog NATURE JOIN t_type;
```sql
SELECT * FROM t_blog NATURAL JOIN t_type;
SELECT t_blog.id,title,typeId,t_type.name FROM t_blog,t_type WHERE t_blog.id=t_type.id;
SELECT t_blog.id,title,typeId,t_type.name FROM t_blog INNER JOIN t_type ON t_blog.id=t_type.id;
SELECT t_blog.id,title,typeId,t_type.name FROM t_blog INNER JOIN t_type USING(id);
+----+-------+--------+------------+
| id | title | typeId | name |
+----+-------+--------+------------+
| 1 | aaa | 1 | C++ |
| 2 | bbb | 2 | C |
| 3 | ccc | 3 | Java |
| 4 | ddd | 4 | C# |
| 5 | eee | 4 | Javascript |
+----+-------+--------+------------+
SELECT * FROM t_blog NATURAL LEFT JOIN t_type;
SELECT t_blog.id,title,typeId,t_type.name FROM t_blog LEFT JOIN t_type ON t_blog.id=t_type.id;
SELECT t_blog.id,title,typeId,t_type.name FROM t_blog LEFT JOIN t_type USING(id);
+----+-------+--------+------------+
| id | title | typeId | name |
+----+-------+--------+------------+
| 1 | aaa | 1 | C++ |
| 2 | bbb | 2 | C |
| 3 | ccc | 3 | Java |
| 4 | ddd | 4 | C# |
| 5 | eee | 4 | Javascript |
| 6 | fff | 3 | NULL |
| 7 | ggg | 2 | NULL |
| 8 | hhh | NULL | NULL |
| 9 | iii | NULL | NULL |
| 10 | jjj | NULL | NULL |
+----+-------+--------+------------+
SELECT * FROM t_blog NATURAL RIGHT JOIN t_type;
SELECT t_blog.id,title,typeId,t_type.name FROM t_blog RIGHT JOIN t_type ON t_blog.id=t_type.id;
SELECT t_blog.id,title,typeId,t_type.name FROM t_blog RIGHT JOIN t_type USING(id);
+----+------------+-------+--------+
| id | name | title | typeId |
+----+------------+-------+--------+
| 1 | C++ | aaa | 1 |
| 2 | C | bbb | 2 |
| 3 | Java | ccc | 3 |
| 4 | C# | ddd | 4 |
| 5 | Javascript | eee | 4 |
+----+------------+-------+--------+
```

View File

@@ -0,0 +1,146 @@
# MySQL分库分表和主从分离
## 1 概述
### 分库分表的原因
当一张表的数据达到几千万时,查询一次所花的时间会变多,如果有联合查询的话,有可能会死在那儿了。分表的目的就在于此,减小数据库的负担,缩短查询时间。
* 用户请求量太大。因为单服务器TPS内存IO都是有限的。解决方法分散请求到多个服务器上 其实用户请求和执行一个sql查询是本质是一样的都是请求一个资源只是用户请求还会经过网关路由http服务器等。
* 单库太大。单个数据库处理能力有限单库所在服务器上磁盘空间不足单库上操作的IO瓶颈 解决方法:切分成更多更小的库
* 单表太大。CRUD都成问题索引膨胀查询超时。解决方法切分成多个数据集更小的表。
### 分库分表的形式
* 单库单表。单库单表是最常见的数据库设计,例如,有一张用户(user)表放在数据库db中所有的用户都可以在db库中的user表中查到。
* 单库多表。随着用户数量的增加user表的数据量会越来越大当数据量达到一定程度的时候对user表的查询会渐渐的变慢从而影响整个DB的性能。如果使用mysql, 还有一个更严重的问题是当需要添加一列的时候mysql会锁表期间所有的读写操作只能等待。可以通过某种方式将user进行水平的切分产生两个表结构完全一样的user_0000,user_0001等表user_0000 + user_0001 + …的数据刚好是一份完整的数据。
* 多库多表。随着数据量增加也许单台DB的存储空间不够随着查询量的增加单台数据库服务器已经没办法支撑。这个时候可以再对数据库进行水平区分。
## 2 分库分表
### 水平切分
水平切分又称为 Sharding它是将同一个表中的记录拆分到多个结构相同的表中。
* 水平分库:系统绝对并发量上来了,分表难以根本上解决问题,并且还没有明显的业务归属来垂直分库。
* 水平分表系统绝对并发量并没有上来只是单表的数据量太多影响了SQL效率加重了CPU负担以至于成为瓶颈。
当一个表的数据不断增多时Sharding 是必然的选择,它可以将数据分布到集群的不同节点上,从而缓存单个数据库的压力。
![](image/2021-09-02-00-50-45.png)
* 主要特点
* 每个库的结构都一样;
* 每个库的数据都不一样,没有交集;
* 所有库的并集是全量数据;
### 垂直切分
垂直切分是将一张表按列切分成多个表,通常是按照列的关系密集程度进行切分,也可以利用垂直切分将经常被使用的列和不经常被使用的列切分到不同的表中。
在数据库的层面使用垂直切分将按数据库中表的密集程度部署到不同的库中,例如将原来的电商数据库垂直切分成商品数据库、用户数据库等。
如果把业务切割得足够独立,那把不同业务的数据放到不同的数据库服务器将是一个不错的方案,而且万一其中一个业务崩溃了也不会影响其他业务的正常进行,并且也起到了负载分流的作用,大大提升了数据库的吞吐能力。
* 垂直分库:系统绝对并发量上来了,并且可以抽象出单独的业务模块。
* 垂直分表系统绝对并发量并没有上来表的记录并不多但是字段多并且热点数据和非热点数据在一起单行数据所需的存储空间较大。以至于数据库缓存的数据行减少查询时会去读磁盘数据产生大量的随机读IO产生IO瓶颈。
![](image/2021-09-02-00-51-00.png)
* 主要特点
* 每个库的结构都不一样;
* 每个库的数据也不一样,没有交集;
* 所有库的并集是全量数据;
### Sharding 策略
- 映射法哈希取模hash(key) % N。Snowflake算法。
- HASH取模方法优点 能保证数据较均匀的分散落在不同的库、表中,减轻了数据库压力
- HASH取模方法缺点 扩容麻烦、迁移数据时每次都需要重新计算hash值分配到不同的库和表
- 范围法:可以是 ID 范围、时间范围、地理范围。
- RANGE方法优点 扩容简单,提前建好库、表就好
- RANGE方法缺点 大部分读和写都访会问新的数据有IO瓶颈这样子造成新库压力过大不建议采用。
- 映射表:使用单独的一个数据库来存储映射关系。
### Sharding 工具
* sharding-spherejar前身是sharding-jdbc
* TDDLjarTaobao Distribute Data Layer
* Mycat中间件。
### Sharding 存在的问题
1. 事务问题。使用分布式事务来解决,比如 XA 接口。
2. 连接。可以将原来的连接分解成多个单表查询,然后在用户程序中进行连接。
3. ID 唯一性
- 使用全局唯一 IDGUID
- 为每个分片指定一个 ID 范围。
- 分布式 ID 生成器能够计算出sharding ID
4. 维度的问题。假如用户购买了商品,需要将交易记录保存取来,如果按照用户的纬度分表,则每个用户的交易记录都保存在同一表中,所以很快很方便的查找到某用户的 购买情况,但是某商品被购买的情况则很有可能分布在多张表中,查找起来比较麻烦。反之,按照商品维度分表,可以很方便的查找到此商品的购买情况,但要查找 到买人的交易记录比较麻烦。所以常见的解决方式有:
* 通过扫表的方式解决,此方法基本不可能,效率太低了。
* 记录两份数据,一份按照用户纬度分表,一份按照商品维度分表。
* 通过搜索引擎解决,但如果实时性要求很高,又得关系到实时搜索。
### 分库分表时机
单表1000万条记录一下,写入读取性能是比较好的. 这样在留点buffer,那么单表全是数据字型的保持在800万条记录以下, 有字符型的单表保持在500万以下。
不管是IO瓶颈还是CPU瓶颈最终都会导致数据库的活跃连接数增加进而逼近甚至达到数据库可承载活跃连接数的阈值。在业务Service来看就是可用数据库连接少甚至无连接可用。
* IO 瓶颈
* 第一种磁盘读IO瓶颈热点数据太多数据库缓存放不下每次查询时会产生大量的IO降低查询速度 -> 分库和垂直分表。
* 第二种网络IO瓶颈请求的数据太多网络带宽不够 -> 分库。
* CPU瓶颈
* 第一种SQL问题如SQL中包含joingroup byorder by非索引字段条件查询等增加CPU运算的操作 -> SQL优化建立合适的索引在业务Service层进行业务计算。
* 第二种单表数据量太大查询时扫描的行太多SQL效率低CPU率先出现瓶颈 -> 水平分表。
## 3 主从同步
### 主从分离
将数据库的写操作和读操作进行分离, 使用多个从库副本Slaver Replication负责读使用主库Master负责写 从库从主库同步更新数据,保持数据一致。架构上就是数据库主从同步。 从库可以水平扩展所以更多的读请求不成问题。通过此方式可以有效的提高DB集群的 QPS.
1. **读写分离**:在从服务器可以执行查询工作,降低主服务器压力;(主库写,从库读,降压)读写分离
2. **备份容灾**:在从主服务器进行备份,避免备份期间影响主服务器服务;容灾。可以作为一种备份机制,相当于热备份(在从备份,避免备份期间影响主服务器服务)
3. **提高可用性**:当主服务器出现问题时,可以切换到从服务器。提高可用性
### 主从结构
1. 一主多从
2. 一主一从
3. 主主结构(互为主从,相互可写)
### 主从同步过程
Mysql服务器之间的主从同步是基于二进制日志BINLOG机制主服务器使用二进制日志来记录数据库的变动情况从服务器通过读取和执行该日志文件来保持和主服务器的数据一致。
![](image/2021-09-02-12-05-03.png)
![](image/2021-09-02-10-11-38.png)
1. SQL语句操作变化存入BinLog日志中
2. slave上线连接到master服务器进行数据同步
3. dump thread线程把Binlog数据发送到slave中
4. slave启动之后创建一个I/O线程读取master传过来的Binlog内容并写入到Relay log.
5. slave还会创建一个SQL线程从Relay log里面读取内容从Exec_Master_Log_Pos位置开始执行读取到的更新事件将更新内容写入到slave的db.
### 主从同步机制
![](image/2021-09-02-10-14-13.png)
1. 异步复制。MySQL 默认的复制策略Master处理事务过程中将其写入Binlog就会通知Dump thread线程处理然后完成事务的提交不会关心是否成功发送到任意一个slave中
![](image/2021-09-02-10-15-31.png)
2. 半同步复制。Master处理事务过程中提交完事务后必须等至少一个Slave将收到的binlog写入relay log返回ack才能继续执行处理用户的事务。
![](image/2021-09-02-10-16-47.png)
3. 增强半同步复制。半同步的问题是因为等待ACK的点是Commit之后此时Master已经完成数据变更用户已经可以看到最新数据当Binlog还未同步到Slave时发生主从切换那么此时从库是没有这个最新数据的用户又看到老数据。增强半同步将等待ACK的点放在提交Commit之前此时数据还未被提交外界看不到数据变更此时如果发送主从切换新库依然还是老数据不存在数据不一致的问题。
![](image/2021-09-02-10-17-53.png)
4. 组复制。MySQL在引擎层完成Prepare操作写Redo日志之后会被MySQL的预设Hook拦截进入MGR层。MGR层将事务信息打包通过Paxos协议发送到全部节点上只要集群中过半节点回复ACK那么将告诉所有节点数据包同步成功然后每个节点开始自己认证certify通过就开始写Binlog提交事务或者写relay log数据同步如果认证不通过则rollback。总结MGR内部实现了分布式数据一致性协议paxos通过其来保证数据一致性。
![](image/2021-09-02-10-19-31.png)

View File

@@ -0,0 +1,369 @@
## 1 并发机制
1. 什么是并发,并发与多线程有什么关系?
1. 先从广义上来说,或者从实际场景上来说.
1. 高并发通常是海量用户同时访问(比如12306买票、淘宝的双十一抢购)如果把一个用户看做一个线程的话那么并发可以理解成多线程同时访问高并发即海量线程同时访问。ps我们在这里模拟高并发可以for循环多个线程即可
2. 从代码或数据的层次上来说。多个线程同时在一条相同的数据上执行多个数据库操作。
## 2 并发分类
> 参考文献
> * [锁与并发](https://www.cnblogs.com/yaopengfei/p/8399358.html)
### 积极并发(乐观锁)
积极并发(乐观并发、乐观锁):无论何时从数据库请求数据,数据都会被读取并保存到应用内存中。数据库级别没有放置任何显式锁。数据操作会按照数据层接收到的先后顺序来执行。
积极并发本质就是允许冲突发生,然后在代码本身采取一种合理的方式去解决这个并发冲突,常见的方式有:
1. 忽略冲突强制更新:数据库会保存最后一次更新操作(以更新为例),会损失很多用户的更新操作。
2. 部分更新允许所有的更改但是不允许更新完整的行只有特定用户拥有的列更新了。这就意味着如果两个用户更新相同的记录但却不同的列那么这两个更新都会成功而且来自这两个用户的更改都是可见的。EF默认实现不了这种情况
3. 询问用户:当一个用户尝试更新一个记录时,但是该记录自从他读取之后已经被别人修改了,这时应用程序就会警告该用户该数据已经被某人更改了,然后询问他是否仍然要重写该数据还是首先检查已经更新的数据。(EF可以实现这种情况在后面详细介绍)
4. 拒绝修改:当一个用户尝试更新一个记录时,但是该记录自从他读取之后已经被别人修改了,此时告诉该用户不允许更新该数据,因为数据已经被某人更新了。
### 消极并发(悲观锁)
消极并发(悲观并发、悲观锁):无论何时从数据库请求数据,数据都会被读取,然后该数据上就会加锁,因此没有人能访问该数据。这会降低并发出现问题的机会,缺点是加锁是一个昂贵的操作,会降低整个应用程序的性能。
消极并发的本质就是永远不让冲突发生,通常的处理凡是是只读锁和更新锁。
1. 当把只读锁放到记录上时应用程序只能读取该记录。如果应用程序要更新该记录它必须获取到该记录上的更新锁。如果记录上加了只读锁那么该记录仍然能够被想要只读锁的请求使用。然而如果需要更新锁该请求必须等到所有的只读锁释放。同样如果记录上加了更新锁那么其他的请求不能再在这个记录上加锁该请求必须等到已存在的更新锁释放才能加锁。总结这里我们可以简单理解把并发业务部分用一个锁lock,实质是数据库锁,后面章节单独介绍)锁住,使其同时只允许一个线程访问即可。
2. 加锁会带来很多弊端:
1. 应用程序必须管理每个操作正在获取的所有锁;
2. 加锁机制的内存需求会降低应用性能
3. 多个请求互相等待需要的锁,会增加死锁的可能性。
## 3 并发问题的解决方案(提高并发的方法)
并发机制的解决方案
1. 从架构的角度去解决(大层次 如12306买票
  nginx负载均衡、数据库读写分离、多个业务服务器、多个数据库服务器、NoSQL 使用队列来处理业务,将高并发的业务依次放到队列中,然后按照先进先出的原则, 逐个处理(队列的处理可以采用 Redis、RabbitMq等等
  (PS在后面的框架篇章里详细介绍该方案)
2. 从代码的角度去解决(在服务器能承载压力的情况下,并发访问同一条数据)
  实际的业务场景:如进销存类的项目,涉及到同一个物品的出库、入库、库存,我们都知道库存在数据库里对应了一条记录,入库要查出现在库存的数量,然后加上入库的数量,假设两个线程同时入库,假设查询出来的库存数量相同,但是更新库存数量在数据库层次上是有先后,最终就保留了后更新的数据,显然是不正确的,应该保留的是两次入库的数量和。
(该案例的实质:多个线程同时在一条相同的数据上执行多个数据库操作)
事先准备一张数据库表:
解决方案一:(最常用的方式)
  给入库和出库操作加一个锁,使其同时只允许一个线程访问,这样即使两个线程同时访问,但在代码层次上,由于锁的原因,还是有先有后的,这样就保证了入库操作的线程唯一性,当然库存量就不会出错了.
总结该方案可以说是适合处理小范围的并发且锁内的业务执行不是很复杂。假设一万线程同时入库每次入库要等2s那么这一万个线程执行完成需要的总时间非常多显然不适合。
(这种方式的实质就是给核心业务加了个lock锁这里就不做测试了)
解决方案二EF处理积极并发带来的冲突
1. 配置准备
  (1). 针对DBFirst模式可以给相应的表额外加一列RowVersion数据库中为timestamp类型对应的类中为byte[]类型并且在Edmx模型上给该字段的并发模式设置为fixed(默认为None),这样该表中所有字段都监控并发。
如果不想监视所有列在不添加RowVersion的情况下只需在Edmx模型是给特定的字段的并发模式设置为fixed这样只有被设置的字段被监测并发。
  测试结果: (DBFirst模式下的并发测试)
  事先在UserInfor1表中插入一条id、userName、userSex、userAge均为1的数据(清空数据)。
测试情况1
  在不设置RowVersion并发模式为Fixed的情况下两个线程修改不同字段(修改同一个字段一个道理),后执行的线程的结果覆盖前面的线程结果.
  发现测试结果为1,1,男,1 ; 显然db1线程修改的结果被db2线程给覆盖了. (修改同一个字段一个道理)
```
1 {
2 //1.创建两个EF上下文模拟代表两个线程
3 var db1 = new ConcurrentTestDBEntities();
4 var db2 = new ConcurrentTestDBEntities();
5
6 UserInfor1 user1 = db1.UserInfor1.Find("1");
7 UserInfor1 user2 = db2.UserInfor1.Find("1");
8
9 //2. 执行修改操作
10 //db1的线程先执行完修改操作并保存
11 user1.userName = "ypf";
12 db1.Entry(user1).State = EntityState.Modified;
13 db1.SaveChanges();
14
15 //db2的线程在db1线程修改完成后执行修改操作
16 try
17 {
18 user2.userSex = "男";
19 db2.Entry(user2).State = EntityState.Modified;
20 db2.SaveChanges();
21
22 Console.WriteLine("测试成功");
23 }
24 catch (Exception)
25 {
26 Console.WriteLine("测试失败");
27 }
28 }
```
测试情况2
  设置RowVersion并发模式为Fixed的情况下两个线程修改不同字段(修改同一个字段一个道理)如果该条数据已经被修改利用DbUpdateConcurrencyException可以捕获异常进行积极并发的冲突处理。测试结果如下
  a.RefreshMode.ClientWins: 1,1,男,1
  b.RefreshMode.StoreWins: 1,ypf,1,1
  c.ex.Entries.Single().Reload(); 1,ypf,1,1
```
1 {
2 //1.创建两个EF上下文模拟代表两个线程
3 var db1 = new ConcurrentTestDBEntities();
4 var db2 = new ConcurrentTestDBEntities();
5
6 UserInfor1 user1 = db1.UserInfor1.Find("1");
7 UserInfor1 user2 = db2.UserInfor1.Find("1");
8
9 //2. 执行修改操作
10 //db1的线程先执行完修改操作并保存
11 user1.userName = "ypf";
12 db1.Entry(user1).State = EntityState.Modified;
13 db1.SaveChanges();
14
15 //db2的线程在db1线程修改完成后执行修改操作
16 try
17 {
18 user2.userSex = "男";
19 db2.Entry(user2).State = EntityState.Modified;
20 db2.SaveChanges();
21
22 Console.WriteLine("测试成功");
23 }
24 catch (DbUpdateConcurrencyException ex)
25 {
26 Console.WriteLine("测试失败:" + ex.Message);
27
28 //1. 保留上下文中的现有数据(即最新,最后一次输入)
29 //var oc = ((IObjectContextAdapter)db2).ObjectContext;
30 //oc.Refresh(RefreshMode.ClientWins, user2);
31 //oc.SaveChanges();
32
33 //2. 保留原始数据(即数据源中的数据代替当前上下文中的数据)
34 //var oc = ((IObjectContextAdapter)db2).ObjectContext;
35 //oc.Refresh(RefreshMode.StoreWins, user2);
36 //oc.SaveChanges();
37
38 //3. 保留原始数据而Reload处理也就是StoreWins意味着放弃当前内存中的实体重新到数据库中加载当前实体
39 ex.Entries.Single().Reload();
40 db2.SaveChanges();
41 }
42 }
```
测试情况3
  在不设置RowVersion并发模式为Fixed的情况下(也不需要RowVersion这个字段)单独设置userName字段的并发模式为Fixed两个线程同时修改该字段利用DbUpdateConcurrencyException可以捕获异常进行积极并发的冲突处理,但如果是两个线程同时修改userName以外的字段将不能捕获异常将走EF默认的处理方式后执行的覆盖先执行的。
  a.RefreshMode.ClientWins: 1,ypf2,1,1
  b.RefreshMode.StoreWins: 1,ypf,1,1
  c.ex.Entries.Single().Reload(); 1,ypf,1,1
View Code
  (2). 针对CodeFirst模式需要有这样的一个属性 public byte[] RowVersion { get; set; },并且给属性加上特性[Timestamp],这样该表中所有字段都监控并发。如果不想监视所有列在不添加RowVersion的情况下只需给特定的字段加上特性 [ConcurrencyCheck],这样只有被设置的字段被监测并发。
  除了再配置上不同于DBFirst模式以为是通过加特性的方式来标记并发其它捕获并发和积极并发的几类处理方式均同DBFirst模式相同。这里不做测试了
1. 积极并发处理的三种形式总结:
  利用DbUpdateConcurrencyException可以捕获异常然后
    a. RefreshMode.ClientWins:保留上下文中的现有数据(即最新,最后一次输入)
    b. RefreshMode.StoreWins:保留原始数据(即数据源中的数据代替当前上下文中的数据)
    c.ex.Entries.Single().Reload(); 保留原始数据而Reload处理也就是StoreWins意味着放弃当前内存中的实体重新到数据库中加载当前实体
3. 该方案总结:
  这种模式实质上就是获取异常告诉程序,让开发人员结合需求自己选择怎么处理,但这种模式是解决代码层次上的并发冲突,并不是解决大数量同时访问崩溃问题的。
解决方案三:利用队列来解决业务上的并发(架构层次上其实也是这种思路解决的)
1.先分析:
  前面说过所谓的高并发,就是海量的用户同时向服务器发送请求,进行某个业务处理(比如定时秒杀的抢单),而这个业务处理是需要 一定时间的。
2.处理思路:
  将海量用户的请求放到一个队列里(如Queue),先不进行业务处理,然后另外一个服务器从线程中读取这个请求(MVC框架可以放到Global全局里),依次进行业务处理,至于处理完成后,是否需要告诉客户端,可以根据实际需求来定,如果需要的话(可以借助Socket、Signalr、推送等技术来进行).
  特别注意:读取队列的线程是一直在运行,只要队列中有数据,就给他拿出来.
  这里使用Queue队列可以参考http://www.cnblogs.com/yaopengfei/p/8322016.html
  PS架构层次上的处理方案无非队列是单独一台服务器执行从队列读取的是另外一台业务服务器处理思想是相同的
队列单例类的代码:
```
1 /// <summary>
2 /// 单例类
3 /// </summary>
4 public class QueueUtils
5 {
6 /// <summary>
7 /// 静态变量由CLR保证在程序第一次使用该类之前被调用而且只调用一次
8 /// </summary>
9 private static readonly QueueUtils _QueueUtils = new QueueUtils();
10
11 /// <summary>
12 /// 声明为private类型的构造函数禁止外部实例化
13 /// </summary>
14 private QueueUtils()
15 {
16
17 }
18 /// <summary>
19 /// 声明属性,供外部调用,此处也可以声明成方法
20 /// </summary>
21 public static QueueUtils instanse
22 {
23 get
24 {
25 return _QueueUtils;
26 }
27 }
28
29
30 //下面是队列相关的
31 System.Collections.Queue queue = new System.Collections.Queue();
32
33 private static object o = new object();
34
35 public int getCount()
36 {
37 return queue.Count;
38 }
39
40 /// <summary>
41 /// 入队方法
42 /// </summary>
43 /// <param name="myObject"></param>
44 public void Enqueue(object myObject)
45 {
46 lock (o)
47 {
48 queue.Enqueue(myObject);
49 }
50 }
51 /// <summary>
52 /// 出队操作
53 /// </summary>
54 /// <returns></returns>
55 public object Dequeue()
56 {
57 lock (o)
58 {
59 if (queue.Count > 0)
60 {
61 return queue.Dequeue();
62 }
63 }
64 return null;
65 }
66
67 }
```
PS这里的入队和出队都要加锁因为Queue默认不是线程安全的不加锁会存在资源竞用问题从而业务出错或者直接使用ConcurrentQueue线程安全的队列就不需要加锁了关于队列线程安全问题详见http://www.cnblogs.com/yaopengfei/p/8322016.html
临时存储数据类的代码:
```
1 /// <summary>
2 /// 该类用来存储请求信息
3 /// </summary>
4 public class TempInfor
5 {
6 /// <summary>
7 /// 用户编号
8 /// </summary>
9 public string userId { get; set; }
10 }
```
模拟高并发入队,单独线程出队的代码:
```
1 {
2 //3.1 模拟高并发请求 写入队列
3 {
4 for (int i = 0; i < 100; i++)
5 {
6 Task.Run(() =>
7 {
8 TempInfor tempInfor = new TempInfor();
9 tempInfor.userId = Guid.NewGuid().ToString("N");
10 //下面进行入队操作
11 QueueUtils.instanse.Enqueue(tempInfor);
12
13 });
14 }
15 }
16 //3.2 模拟另外一个线程队列中读取数据请求标记,进行相应的业务处理(该线程一直运行,不停止)
17 Task.Run(() =>
18 {
19 while (true)
20 {
21 if (QueueUtils.instanse.getCount() > 0)
22 {
23 //下面进行出队操作
24 TempInfor tempInfor2 = (TempInfor)QueueUtils.instanse.Dequeue();
25
26 //拿到请求标记,进行相应的业务处理
27 Console.WriteLine("id={0}的业务执行成功", tempInfor2.userId);
28 }
29 }
30 });
31 //3.3 模拟过了一段时间(6s后),又有新的请求写入
32 Thread.Sleep(6000);
33 Console.WriteLine("6s的时间已经过去了");
34 {
35 for (int j = 0; j < 100; j++)
36 {
37 Task.Run(() =>
38 {
39 TempInfor tempInfor = new TempInfor();
40 tempInfor.userId = Guid.NewGuid().ToString("N");
41 //下面进行入队操作
42 QueueUtils.instanse.Enqueue(tempInfor);
43
44 });
45 }
46 }
47 }
```
3.下面案例的测试结果:
  一次输出100条数据6s过后再一次输出100条数据。
1. 总结:
  该方案是一种迂回的方式处理高并发,在业内这种思想也是非常常见,但该方案也有一个弊端,客户端请求的实时性很难保证,或者即使要保证(比如引入实时通讯技术)
也要付出不少代价.

View File

@@ -0,0 +1,88 @@
# 一致性哈希算法
## 一致性Hash算法背景
一致性哈希算法在1997年由麻省理工学院的Karger等人在解决分布式Cache中提出的设计目标是为了解决因特网中的热点(Hot spot)问题初衷和CARP十分类似。一致性哈希修正了CARP使用的简单哈希算法带来的问题使得DHT可以在P2P环境中真正得到应用。
但现在一致性hash算法在分布式系统中也得到了广泛应用研究过memcached缓存数据库的人都知道memcached服务器端本身不提供分布式cache的一致性而是由客户端来提供具体在计算一致性hash时采用如下步骤
首先求出memcached服务器节点的哈希值并将其配置到0232的圆continuum上。
然后采用同样的方法求出存储数据的键的哈希值,并映射到相同的圆上。
然后从数据映射到的位置开始顺时针查找将数据保存到找到的第一个服务器上。如果超过232仍然找不到服务器就会保存到第一台memcached服务器上。
![](image/2021-09-09-15-21-22.png)
从上图的状态中添加一台memcached服务器。余数分布式算法由于保存键的服务器会发生巨大变化而影响缓存的命中率但Consistent Hashing中只有在园continuum上增加服务器的地点逆时针方向的第一台服务器上的键会受到影响如下图所示
![](image/2021-09-09-15-22-11.png)
## 2 一致性Hash性质
考虑到分布式系统每个节点都有可能失效并且新的节点很可能动态的增加进来如何保证当系统的节点数目发生变化时仍然能够对外提供良好的服务这是值得考虑的尤其实在设计分布式缓存系统时如果某台服务器失效对于整个系统来说如果不采用合适的算法来保证一致性那么缓存于系统中的所有数据都可能会失效即由于系统节点数目变少客户端在请求某一对象时需要重新计算其hash值通常与系统中的节点数目有关由于hash值已经改变所以很可能找不到保存该对象的服务器节点因此一致性hash就显得至关重要良好的分布式cahce系统中的一致性hash算法应该满足以下几个方面
### 平衡性(Balance)
平衡性是指哈希的结果能够尽可能分布到所有的缓冲中去,这样可以使得所有的缓冲空间都得到利用。很多哈希算法都能够满足这一条件。
### 单调性(Monotonicity)
单调性是指如果已经有一些内容通过哈希分派到了相应的缓冲中又有新的缓冲区加入到系统中那么哈希的结果应能够保证原有已分配的内容可以被映射到新的缓冲区中去而不会被映射到旧的缓冲集合中的其他缓冲区。简单的哈希算法往往不能满足单调性的要求如最简单的线性哈希x = (ax + b) mod (P)在上式中P表示全部缓冲的大小。不难看出当缓冲大小发生变化时(从P1到P2)原来所有的哈希结果均会发生变化从而不满足单调性的要求。哈希结果的变化意味着当缓冲空间发生变化时所有的映射关系需要在系统内全部更新。而在P2P系统内缓冲的变化等价于Peer加入或退出系统这一情况在P2P系统中会频繁发生因此会带来极大计算和传输负荷。单调性就是要求哈希算法能够应对这种情况。
### 分散性(Spread)
在分布式环境中,终端有可能看不到所有的缓冲,而是只能看到其中的一部分。当终端希望通过哈希过程将内容映射到缓冲上时,由于不同终端所见的缓冲范围有可能不同,从而导致哈希的结果不一致,最终的结果是相同的内容被不同的终端映射到不同的缓冲区中。这种情况显然是应该避免的,因为它导致相同内容被存储到不同缓冲中去,降低了系统存储的效率。分散性的定义就是上述情况发生的严重程度。好的哈希算法应能够尽量避免不一致的情况发生,也就是尽量降低分散性。
### 负载(Load)
负载问题实际上是从另一个角度看待分散性问题。既然不同的终端可能将相同的内容映射到不同的缓冲区中,那么对于一个特定的缓冲区而言,也可能被不同的用户映射为不同的内容。与分散性一样,这种情况也是应当避免的,因此好的哈希算法应能够尽量降低缓冲的负荷。
### 平滑性(Smoothness)
平滑性是指缓存服务器的数目平滑改变和缓存对象的平滑改变是一致的。
## 3 原理
### 基本概念
一致性哈希算法Consistent Hashing最早在论文《Consistent Hashing and Random Trees: Distributed Caching Protocols for Relieving Hot Spots on the World Wide Web》中被提出。简单来说一致性哈希将整个哈希值空间组织成一个虚拟的圆环如假设某哈希函数H的值空间为0-2^32-1即哈希值是一个32位无符号整形整个哈希空间环如下
![](image/2021-09-09-15-23-14.png)
整个空间按顺时针方向组织。0和232-1在零点中方向重合。
下一步将各个服务器使用Hash进行一个哈希具体可以选择服务器的ip或主机名作为关键字进行哈希这样每台机器就能确定其在哈希环上的位置这里假设将上文中四台服务器使用ip地址哈希后在环空间的位置如下
![](image/2021-09-09-15-24-05.png)
接下来使用如下算法定位数据访问到相应服务器将数据key使用相同的函数Hash计算出哈希值并确定此数据在环上的位置从此位置沿环顺时针“行走”第一台遇到的服务器就是其应该定位到的服务器。
例如我们有Object A、Object B、Object C、Object D四个数据对象经过哈希计算后在环空间上的位置如下
![](image/2021-09-09-15-24-47.png)
根据一致性哈希算法数据A会被定为到Node A上B被定为到Node B上C被定为到Node C上D被定为到Node D上。
### 容错性与扩展性
下面分析一致性哈希算法的容错性和可扩展性。现假设Node C不幸宕机可以看到此时对象A、B、D不会受到影响只有C对象被重定位到Node D。一般的在一致性哈希算法中如果一台服务器不可用则受影响的数据仅仅是此服务器到其环空间中前一台服务器即沿着逆时针方向行走遇到的第一台服务器之间数据其它不会受到影响。
下面考虑另外一种情况如果在系统中增加一台服务器Node X如下图所示
![](image/2021-09-09-15-24-57.png)
此时对象Object A、B、D不受影响只有对象C需要重定位到新的Node X 。一般的,在一致性哈希算法中,如果增加一台服务器,则受影响的数据仅仅是新服务器到其环空间中前一台服务器(即沿着逆时针方向行走遇到的第一台服务器)之间数据,其它数据也不会受到影响。
综上所述,一致性哈希算法对于节点的增减都只需重定位环空间中的一小部分数据,具有较好的容错性和可扩展性。
### 数据倾斜问题
另外,一致性哈希算法在服务节点太少时,容易因为节点分部不均匀而造成数据倾斜问题。例如系统中只有两台服务器,其环分布如下,
![](image/2021-09-09-15-25-26.png)
此时必然造成大量数据集中到Node A上而只有极少量会定位到Node B上。为了解决这种数据倾斜问题一致性哈希算法引入了虚拟节点机制即对每一个服务节点计算多个哈希每个计算结果位置都放置一个此服务节点称为虚拟节点。具体做法可以在服务器ip或主机名的后面增加编号来实现。例如上面的情况可以为每台服务器计算三个虚拟节点于是可以分别计算 “Node A#1”、“Node A#2”、“Node A#3”、“Node B#1”、“Node B#2”、“Node B#3”的哈希值,于是形成六个虚拟节点:
![](image/2021-09-09-15-26-10.png)
同时数据定位算法不变只是多了一步虚拟节点到实际节点的映射例如定位到“Node A#1”、“Node A#2”、“Node A#3”三个虚拟节点的数据均定位到Node A上。这样就解决了服务节点少时数据倾斜的问题。在实际应用中通常将虚拟节点数设置为32甚至更大因此即使很少的服务节点也能做到相对均匀的数据分布。