# SQL语法
DDL:数据定义语言,定义数据库对象(数据库、表、字段)
DML:数据操作语言,对数据库中的数据进行增删改
DQL:数据查询语言,查询数据库中表的记录
DCL:数据控制语言,用来创建数据库用户、控制数据库的访问权限
DDL
# -------------------------------------------------------------数据库 # 查询所有数据库 show databases; # 查询当前数据库 select database(); # 创建数据库 create database if not exists tr; # 删除数据库 drop database tr; # 使用数据库 user tr; # -------------------------------------------------------------表 # 查询当前数据库所有表 show tables; # 查询表结构 desc emp; # 查询指定表的检表语句 show create table emp; # 创建表 create table emp( 字段1 类型 约束 注释; ); # 删除表 drop table if exists 表名; # 删除指定表, 并重新创建表 truncate table 表名; # 修改表, 添加字段 alter table 表名 add 字段 类型 注释 约束; # 修改字段名和字段类型 alter table 表名 change 旧字段名 新字段名 类型 注释 约束; # 删除字段 alter table 表名 drop 字段名; # 修改表名 alter table 表名 rename to 新表名;
DML
# 给指定字段添加数据 insert into 表名() values(); # 给全部字段添加数据 insert into 表名 values(); # 批量添加数据 insert into 表名() values(), (), (); # 修改数据 update 表名 set 字段1 = 值1, 字段2 = 值2 where id = 1; # 删除数据 delete from 表名 where id = 1;
DQL
# 基础查询: select 字段列表 from 表名列表 where 条件列表 group by 分组字段列表 having 分组后条件列表 order by 排序字段列表 limit 分页查询 # 条件查询 where # 聚合查询 count, max, min, avg, sum # 分组查询 group by # 排序查询 order by # 分页查询 limit
DCL
# 查询用户 select * from user; # 创建用户 create user '用户名'@'主机名' identified by '密码'; # 修改用户名密码 alter user '用户名'@'主机号' identified with mysql_native_password by '密码'; # 删除用户 drop user '用户名'@'主机名';
函数
字符串函数
函数 功能 concat 字符串拼接,将s1,s2...sn拼接成一个字符串 lower(str) 将字符串str全部转换成小写 upper(str) 将字符串str全部转换成大写 lpad(str, n, pad) 左填充,用字符串pad对str的左填充,达到n个字符串长度 rpad(str, n, pad) 右填充,用字符串pad对str的右填充,达到n个字符串长度 trim(str) 去掉字符串头部和尾部的空格 substring(str, start, len) 返回字符串str从start位置起的len个长度的字符串 # concat(str1, str2...strn) 字符串拼接,将s1,s2...sn拼接成一个字符串 select concat('hello', 'mysql'); # lower(str) 将字符串str全部转换成小写 select lower('STR'); # upper(str)将字符串str全部转换成大写 select lower('str'); # lpad(str, n, pad)左填充,用字符串pad对str的左填充,达到n个字符串长度 select lpad('01', 5, '-'); # rpad(str, n, pad) 右填充,用字符串pad对str的右填充,达到n个字符串长度 select rpad('01', 5, '-'); # trim(str) 去掉字符串头部和尾部的空格 select trim(' hello mysql '); # substring(str, start, len) 返回字符串str从start位置起的len个长度的字符串 select substring('hello mysql', 1, 5);
数值函数
函数 功能 ceil(x) 向上取整 floor(x) 向下取整 mod(x, y) 返回 x/y 的模 rand() 返回0~1内的随机数 round(x, y) 求参数x的四舍五入,保留y位小数 # ceil(x) 向上取整 select ceil(1.1); # floor(x) 向下取整 select floor(1.19); # mod(x, y) 返回 x/y 的模 select mod(10, 3); # rand 返回0~1内的随机数 select rand(); # round(x, y) 求参数x的四舍五入,保留y位小数 select round(4.67, 1);
日期函数
函数 功能 curdate() 返回当前日期 curtime() 返回当前时间 now() 返回当前日期和时间 year() 获取指定date年份 month() 获取指定date月份 day(date) 获取指定date日期 date_add(date, intervalexpr type) 返回一个日期/时间值加上一个时间间隔expr后的时间值 satediff(date1, date2) 返回起始时间date1和结束时间date2之间的天数 # curdate() 返回当前日期 select curdate(); # curtime()返回当前时间 select curtime(); # now() 返回当前日期和时间 select now(); # year() 获取指定date年份 select year(now()); # month() 获取指定date月份 select month(now()); # day(date) 获取指定date日期 select day(now()); # date_add(date, intervalexpr type) 返回一个日期/时间值加上一个时间间隔expr后的时间值 select date_add(now(), interval 70 year); # satediff(date1, date2) 返回起始时间date1和结束时间date2之间的天数 select datediff('2021-12-01', '2015-1-8');
流程函数
函数 功能 if(value, t, f) 如果value为true,返回t,否则返回f ifnull(value1, value2) 如果value不为空,返回value1,否则返回value2 case when value1 then result1 ... else default end 如果value1为true,返回result1,否则返回default case expr when value1 then result1 ... else default end 如果expr的值等于value1,返回result1,否则返回default # if(value, t, f) 如果value为true,返回t,否则返回f select if(true, 'ok', 'error') # ifnull(value1, value2) 如果value不为空,返回value1,否则返回value2 select ifnull(null, 'error') # case when value1 then result1 ... else default end 如果value1为true,返回result1,否则返回default select case when 1 then 'ok' else 'error' end; # case expr when value1 then result1 ... else default end 如果expr的值等于value1,返回result1,否则返回default select name, case gender when '男' then '哥们' when '不男不女' then '流汗' else '姐妹' end from emp;
约束
作用于表中字段上的规则,用于限制存储在表中的数据
保证数据库中的正确、有效和完整性
约束 描述 关键字 非空约束 限制该字段的数据不能为空 not null 唯一约束 保证该字段的所有数据都是唯一、不重复 unique 主键约束 主键是一行互数据的唯一标识,要求非空且唯一 primary key 默认约束 保存数据时,如果未指定该字段的值,则采用默认值 default 检查约束 保证地段值满足某一条件 check 外键约束 用来让两张表的数据之间建立联系,保证数据的一致性和完整性 foreing key # 创建学生表 create table Student ( id int primary key auto_increment comment '主键', name varchar(10) not null unique comment '姓名', age int check ( age > 0 && age < 120 ) comment '年龄', gender char(1) comment '性别', status char(1) default '1' comment '状态', student_id int comment '外键' ) comment '学生表'; # 插入学生数据 insert into student(name, age, gender, status) values ('Tom1', 20, '男', '2'), ('Tom2', 25, '女', '1'), ('Tom3', 40, '男', '2'), ('Tom4', 10, '男', '2'); # 创建学生部 create table dept_student ( id int primary key auto_increment comment '主键', name varchar(10) not null unique comment '部门' ) comment '学生部表'; # 插入部门信息 insert into dept_student(name) values ('研发部'), ('教育部'), ('财务部'), ('科研部');
外键
用来让两张表的数据之间建立联系,保证数据的一致性和完整性
添加外键:
# 添加外键 alter table student add constraint fk_student_id foreign key (student_id) references dept_student(id);删除外键:
# 删除外键 alter table student drop foreign key fk_student_id;外键约束:
行为 说明 no action 默认形式,检查如果有外键不允许更新删除 restrict 默认形式,检查如果有外键不允许更新删除 cascade 当父类中删除对应记录时,检查如果有外键也允许更新删除 set null 当父类中删除对应记录时,检查如果有外键,则设置该字段值为null,但是需要该字段允许null set default innodb不支持 # 外键删除与更新 cascade alter table student add constraint fk_student_id foreign key (student_id) references dept_student (id) on update cascade on delete cascade; # 外键删除与更新 set null alter table student add constraint fk_student_id foreign key (student_id) references dept_student (id) on update set null on delete set null;
多表查询
多表查询,需要消除无效的笛卡尔积
内连接
隐式内连接:
# where select * from emp e, dept d where e.dept_id = d.id;显示内连接:
# inner join ~ on select * from emp e inner join dept d on e.dept_id = d.id;
外连接
左外连接:相当于查看左表的所有数据,包括两个表重叠部分
select e*, d.name from emp e left outer join dept d on e.dept_id = d.id右外连接:相当于查看右表的所有数据,包括两个表重叠部分
select d*, e.name from emp e right outer join dept d on e.dept_id = d.id
自连接
自连接查询,可以内连接查询,也可以外连接查询,一张表自己连接自己
# 隐形内连接,查找员工对应的领导,领导在另一张表上,这张表记录了领导表的managerid select a.name '员工', b.name '领导' from emp a, emp b where a.managerid = b.id; #左外连接,查找员工对应的领导,领导在另一张表上,这张表记录了领导表的managerid select a.name '员工', b.name '领导' from emp a left outer join emp b on a.managerid = b.id;
联合查询
union查询, 联合查询就是把多次查询的结果合并在一起,形成一个新的查询结果
使用条件:两条查询语句查询条件需要一样
# union all,直接合并两个表 select * from emp where salary < 5000 union all select * from emp where age < 50;# union,合并两个表并去重 select * from emp where salary < 5000 union select * from emp where age < 50;
子查询
sql语句中嵌套select语句,成为子查询
标量子查询:常用字符:=、<>、>=、<=、<、>
子查询返回的结果是单个值(数字、字符串、日期等),最简单的形式,这种子查询成为标量子查询
列子查询:
常用字符:in、not in、any、some、all
子查询返回的结果是一列(可以是多行),这种子查询称为列子查询
行子查询:
常用字符:=、<>、in、not in
子查询返回的结果是一行(可以是多列),这种子查询称为行子查询
表子查询:
常用字符:in
# 查询与"鹿杖客", "宋毅"的职位和薪资相同的员工 select * from emp where (job, salary) in (select job, salary from emp where name = '鹿杖客' or '宋毅')
事务
事务是一组操作的集合,它是一个不可分割的工作单位,事务会把所有的操作作为-一个整体一起向系统提交或撤销操作请求,即这些操作要么同时成功,要么同时失败。
事务操作:
方式一:
查看/设置事务
# 查看事务 select @@autocommit; # 设置事务 set @@autocommit=0;提交事务
commit;回滚事务
rollback;方式二:
开启事务
# 开启事务 start transaction; # 或者 begin;提交事务
commit;回滚事务
rollback;事务的四大特性:
原子性(Atomicity):事务是不可分割的最小操作单元,要么全部成功,要么全部失败。
一致性(Consistency):事务完成时,必须使所有的数据都保持一致状态。
隔离性(lsolation):数据库系统提供的隔离机制,保证事务在不受外部并发操作影响的独立环境下运行。
持久性(Durability):事务一旦提交或回滚,它对数据库中的数据的改变就是永久的。
并发事务问题:
问题 描述 脏读 一个事务读到另外一个事务还没有提交的数据 不可重复读 一个事务先后读取同一条记录,但两次读取的数据不同,称之为不可重复读 幻读 一个事务按照条件查询数据时,没有对应的数据行,但是在插入数据时,又发现这行数据已经存在,好像出现了幻影” 事务隔离级别:
隔离级别越高越数据安全,但是性能越低
# 查看事务的隔离级别 select @@transaction_isolation; # 设置事务隔离级别 set
引擎
查看引擎
use test; -- 查看这个表的引擎 show create table account;
-- 查看当前数据库支持的引擎 show engines;
存储引擎选择优点
在选择存储引擎时,应该根据应用系统的特点选择合适的存储引擎。对于复杂的应用系统,还可以根据实际情况选择多种存储引擎进行组 合。
lnnoDB:
是Mysq的默认存储引擎,支持事务、外键。如果应用对事务的完整性有比较高的要求,在并发条件下要求数据的一致 性,数据操作除了插入和查询之外,还包含很多的更新、删除操作,那么InnoDB存储引擎是比较合适的选择。
MVISAM :
如果应用是以读操作和插入操作为主,只有很少的更新和删除操作,并且对事务的完整性、并发性要求不是很高,那 么选择这个存储引擎是非常合适的。
MEMORY:
将所有数据保存在内存中,访问速度快,通常用于临时表及缓存。MEMORY的缺陷就是对表的大小有限制,太大的表 无法缓存在内存中,而且无法保障数据的安全性。
指定引擎创建表
-- 创建表并指定MyISAM引擎 create table my_myisam( id int, name varchar(10) )engine = MyISAM;
索引
索引结构
MySQL的索引是在存储引擎层实现的,不同的存储引擎有不同的结构,主要包含以下几种: 索引结构
B+Tree索引:(重点研究)
最常见的索引类型,大部分引擎都支持 B+ 树索引
Hash索引:(其次)
底层数据结构是用哈希表实现的,只有精确匹配索引列的查询才有效,不支持范围查询
R-tree(空间索引):
空间索引是MvSAM引擎的一个特殊索引类型,主要用于地理空间数据类型,通常使用较少
Full-text(全文索引):
是一种通过建立倒排索引快速匹配文档的方式。类似于Lucene Solr,ES
索引选用B+tree的原因:
为什么InnoDB存储引擎选择使用B+tree索引结构?
1.相对于二叉树,层级更少,搜索效率高对于
2.B-tree,无论是叶子节点还是非叶子节点,都会保存数据,这样导致一页中存储的键值减少,指针跟着减少,要同样保存大量数据,只能增加树的高度,导致性能降低
3.相对于Hash索引,B+tree支持范围匹配及排序操作
索引优缺点
优点:
提高数据检索的效率,降低数据库的IO成本 通过索引列对数据进行排序,降低数据排序的成本,降低CPU的消耗。
提高查询性能:索引可以加快数据库的查询速度。通过使用索引,MySQL可以快速定位到满足查询条件的数据行,减少了全表扫描的时间和IO操作的开销,提高了查询的效率
加速排序:当查询需要按特定列进行排序时,索引可以为排序操作提供有序的数据,避免了数据库的临时排序操作,从而加快了排序的速度
约束数据完整性:主键索引和唯一索引可以确保数据的唯一性,避免出现重复或空值的情况。通过定义合适的索引,可以在数据库层面上对数据的完整性进行约束,提高数据的质量和准确性
加速全文搜索:MySQL提供了全文索引功能,用于在文本字段上进行全文搜索。全文索引可以加速对文本内容的搜索操作,提供更高效的全文检索能力
优化连接操作:在连接查询中,索引可以帮助MySQL快速定位到连接条件匹配的数据行,加快连接操作的速度。特别是在涉及到大表的连接查询时,使用合适的索引可以显著提升性能
劣势:
索引列也是要占用空间的。
索引大大提高了查询效率,同时却也降低更新表的速度,如对表进
行INSERT、UPDATE、DELETE时,效率降低。
索引分类
在InnoDB存储引擎中,根据索引的存储形式,又可以分为以下两种:
聚集索引Clustered Index)
将数据存储与索引放到了一块,索引结构的叶子节点保存了行数据,必须有而且只有一个
聚集索引选取规则: 如果存在主键,主键索引就是聚集索引。 如果不存在主键,将使用第一个唯一(UNIQUE)索引作为聚集索引。 如果表没有主键,或没有合适的唯一索引,则innoDB会自动生成一个rowid作为隐藏的聚集索引。
二级索引(Secondary Index)
将数据与索引分开存储,索引结构的叶子节点关联的是对应的主键,可以存在多个
索引的查找过程图
索引的语法
创建索引
-- 1. name字段为姓名字段,该字段的值可能会重复,为该字段创建索引。 create index idx_user_name on tb_user(name); -- 2. phone手机号字段的值,是非空,且唯一的,为该字段创建唯一索引。 create index idx_user_phone on tb_user(phone); -- 3.为profession、age、status创建联合索引。 create index idx_user_pro_age_sta on tb_user(profession, age, status); -- 4.为email建立合适的索引来提升查询效率 create index idx_user_email on tb_user(email);查看索引
show index from tb_user;删除索引
drop index id_user_email on tb_user;SQL提示
sql提示是优化数据库的重要手段之一,简单来说,就是在sql语句中加入一些人为的提示来控制指定索引
# use index:建议使用这个索引 explain select * from tb_user use index(idx_user_pro) where profession = '软件工程'; # ignore index:不使用这个索引 explain select * from tb_user ignore index(idx_user_pro) where profession = '软件工程'; # force index:必须使用这个索引 explain select * from tb_user force index(idx_user_pro) where profession = '软件工程';
性能分析
查询一个数据库中增删改查的次数频率
show global status like 'Com____';
查询慢日志查询开关状态
show variables like 'slow_query_log';
开启慢日志查询
慢查询日志记录了所有执行时间超过指定参数(long_query_time,单位:秒,默认10秒)的所有SQL语句的日志。MySQL的慢查询日志默认没有开启,需要在MySQL的配置文件(/etc/my.cnf)中配置如下信息:
开启mysql慢日志查询开关
slow_query_log=1
设置慢日志的时间为2秒,sql语句执行时间超过2秒,就会视为慢查询,记录慢查询日志
long_query_time=2
profile详情
查看当前mysql是否支持profile
select @@have_profiling;
开启profile
默认profiling默认是关闭的,可以通过set语句在session/global级别开启profiling
set profiling=1;
explain执行计划
索引的使用规则
最左前缀法则
如果索引中索引了多列也就是常说的联合索引,要遵循最左前缀法则。
最左前缀法则通俗点说就是在查询数据时要想联合索引有效,查询索引的列要必须要有联合索引的最左列(位置无关),否则联合索引不生效。
如果跳跃了联合索引中间的某一列,索引将部分失效(后面的字段索引失效)。
下面举个列子
假设数据库中有张表t_user,t_user有id、phone、name、age、status这些字段,而且phone、name、age三个字段设置成联合索引idx_phone_name_age。
# 创建联合索引 explain create index idx_phone_name_age on t_user(phone,name,age); # 索引不会失效,因为遵循最左前缀法则 explain select * from t_user where phone = '1223244' and name = 'Amy' and age = 20; # 索引不会失效,最左前缀法则和查询顺序无关 explain select * from t_user where name = 'Amy' and age = 20 phone = '1223244'; # 索引部分失效,因为跳跃了 name 这里一列 explain select * from t_user where phone = '1223244' and age = 20 and status = 0; # 索引全部失效,因为跳跃了 phone这里一列,将不遵循最左前缀法则 explain select * from t_user where age = 20 and status = 0;
索引运算操作
不要在索引列上进行运算操作,索引将失效
举一个例子:进行字符串截取运算
# 索引失效,截取2~10位 explain select * from tb_user where substring(phone,10,2) = '15';
字符串类型
字符串类型字段,使用时不加引号,索引失效
# 索引失效,截字符串不加引号 explain select * from tb_user where phone = 18797845618;
模糊查询
模糊查询仅仅尾部模糊查询,索引不失效,头部模糊查询,索引失效
# 索引不失效,尾部模糊查询 explain select * from tb_user where profession like '软件%'; # 索引失效,头部模糊查询 explain select * from tb_user where profession like '%工程';
or连接的条件
用or分割开的条件,如果or前的条件中的列有索引,而or后面的列没有索引,那么涉及的索引都不会被用到
# 索引失效,or前面的列(id)有索引,or后面的列(age)没有索引 explain select * from tb_user where id = 10 or age = 23;
数据分布
如果mysql评估使用索引比全表更慢,则不使用索引
绝大部分不满足查询条件,就走索引,绝大部分满足就走全表
# 假设表中10个手机号0~9 # 索引没失效,mysql评估使用索引比全表更快,绝大部分不满足这个查询,走索引 explain select * from tb_user where phone >= '18794568718'; # 索引失效,mysql评估使用索引比全表更慢,绝大部分符合这个查询,走全表 explain select * from tb_user where phone >= '18794568710';
覆盖索引
如果通过explain查询中展示:
using index conditon:查询使用了索引,但是需要回表查询
using where; useing index:查询只用了索引,但是需要数据在索引列中才能找到
面试题:
前缀索引
当字段类型为字符串(varchar,text等)时,有时候需要索引很长的字符串,这会让索引变得很大,查询时,浪费大量的磁盘IO,影响查询效率。此时可以只将字符串的一部分前缀,建立索引,这样可以大大节约索引空间,从而提高索引效率。
-
语法:
create index idx_xxx on table_name(column(n));
-
前缀长度:
可以根据索引的选择性来决定,而选择性是指不重复的索引值(基数)和数据表的记录总数的比值,索引选择性越高则查询效率越高唯一索引的选择性是1,这是最好的索引选择性,性能也是最好的
求取最佳长度公式:
# 此时为1,性能最好,但费大量的磁盘IO,影响查询效率 select count(distinct email) / count(*) from tb_user; # 建立前缀索引,取前5个字符 select count(distinct substring(email,1,5)) / count(*) from tb_user;
单列索引与联合索引
推荐联合索引
单列索引:一个索引只包含单个列
# 创建单列索引 create unqiue index id_user_phone on tb_user(phone); create index id_user_name on tb_user(name); # 有phone单列索引,有name单列索引,但是只phone走了索引,name没有走索引,索引需要进行回表查询 explain select id, phone, name from tb_user where phone = '18456879451' and name - '韩信';联合索引:一个索引包含了多个列
# 创建联合索引 create unqiue index id_user_phone_name on tb_user(phone,name); # 有phone和name的联合索引,会走phone索引足矣,因为phone是唯一索引,但是我们可以指定使用联合索引 explain select id, phone, name from tb_user where phone = '18456879451' and name - '韩信'; # 指定使用联合索引,走覆盖索引不需要回表查询 explain select id, phone, name from tb_user user index(id_user_phone_name) where phone = '18456879451' and name - '韩信';
索引设计原则
-
针对于数据量较大,且查询比较频繁的表建立索引。
-
针对于常作为查询条件 (where)、排序 (order by)、分组 (group by) 操作的字段建立索引
-
尽量选择区分度高的列作为索引,尽量建立唯一索引,区分度越高,使用索引的效率越高。
-
如果是字符串类型的字段,字段的长度较长,可以针对于字段的特点,建立前缀索引。
-
尽量使用联合索引,减少单列索引,查询时,联合索引很多时候可以覆盖索引,节省存储空间,避免回表,提高效率
-
要控制索引的数量,索引并不是多多益善,索引越多,维护索引结构的代价也就越大,会影响增删改的效率。
-
如果索引列不能存储NULL值,请在创建表时使用NOT NULL约束它。当优化器知道每列是否包含NUL值时,它可以更好地确定哪个索引最有效地用于查询。
SQL优化
主键优化
在InnoDB存储的引擎中,表数据都是根据顺序组织存放的,这种存放的方式的表被称为索引组织表
页分裂
页可以为空,也可以填充一半,也可以填充100%,每一页包含2~N行数据(如果一行的数据太大,会行溢出),根据主键排列
当也删除的记录达到默认页的50%,InnoDB会寻找靠近的页(前或后),看看是否可以将两个页合并以优化空间使用
主键设计原则
满足业务的需求情况下,尽量降低主键的长度(二级索引最底下挂的是主键)
插入数据时,尽量选择顺序插入,选择使用(auto_increment)自增主键
尽量不要使用UUID做主键,或者是其他的自然主键,如身份证(容易无序插入产生也分列)
业务操作时,避免主键进行修改
insert优化
当数据有多条时,建议:
insert into 表 values(值,值,...),(值,值,...),...;当数据较多时,建议:
# 开启事务 start transaction; insert into 表 values(值,值,...),(值,值,...),...; insert into 表 values(值,值,...),(值,值,...),...; insert into 表 values(值,值,...),(值,值,...),...; ... commit;当数据比较庞大时,建议:
# 客户端连接服务器端,加上参数 --local-infile mysql --local-infile -u root -p # 查看local_infile 开关,默认0(关闭),打开开关 select @@local_infile; # 设置全局参数 local_infile 为1,开启从本地加载文件导入数据的开关 set global local_infile = 1; # 执行load指令将准备好的数据,加载到表结构中, load data local infile '/文件路径/文件名' into table `表名` fields terminated by ',' lines terminated by '\n';
order by优化
Using filesot:通过表的索引或全表扫描,读取满足条件的数据行,然后在排序缓冲区sort buffer中完成排序操作,所有不是通过索引直接返回排序结果的排序都叫FileSot 排序
Using index:通过有序索引顺序扫描直接返回有序数据,这种情况即为using index,不需要额外排序,操作效率高。
反面扫描索引
创建对应的索引,默认为升序排列,属于using index,但是如果order by倒叙查询时,会有额外的反面扫描索引,如果一部分进行倒叙也会进行额外排序
不满足最左前缀法则的查询,也会产生额外扫描
优化原则
根据排序字段建立合适的索引,多字段排序时,也遵循最左前缀法则。
尽量使用覆盖索引。
多字段排序,一个升序一个降序,此时需要注意联合索引在创建时的规则 (ASC/DESC)
如果不可避免的出现filesort,大数据量排序时,可以适当增大排序缓冲区大小 sort buffer size(默认256K)
group by优化
group by 实质是先排序后分组,遵照索引建的最佳左前缀法则
limit优化
# 优化思路 select * from tb_sku limit 9000000, 10; # 防止回表查询,优化 select id from tb_sku order by id limit 9000000, 10; select s.* from tb_sku s, (select id from tb_sku order by id limit 9000000, 10) a where s.id = a.id
count优化
count查看为null时,就不计数
MyISAM引擎把一个表的总行数存在了磁盘上,因此执行count(*)的时候会直接返回这个数,效率很高
InnODB引警就麻烦了,它执行 ount(*)的时候,需要把数据一行一行地从引擎里面读出来,然后累积计数
count用法:
count (字段):
没有not null约束:lnnoDB引擎会遍历整张表把每一行的字段值都取出来,返回给服务层,服务层判断是否为null,不为null,计数累加。
有notnull约束:InnoDB引擎会遍历整张表把每一行的字段值都取出来,返回给服务层,直接按行进行累加。
count (主键):InnoDB引擎会遍历整张表,把每一行的主键id 值都取出来,返回给服务层,服务层拿到主键后,直接按行进行累加(主键不可能为null)。
count (1):nnoDB引擎遍历整张表,但不取值。服务层对于返回的每一行,放一个数字“1”进去,直接按行进行累加。
count (*):InnoDB引擎并不会把全部字段取出来,而是专门做了优化,不取值,服务层直接按行进行累加。
按照效率排序的话,count(字段) < count(主键 id) < count(1) < count( * ),所以尽量使用 count( * )
update优化:
注意:
InnoDB
的行锁是针对索引加的锁,不是针对记录加的锁,并且该索引不能失效,否则行锁升级为表锁避免行锁升级为表锁:
使用索引:
确保你的
update
语句中的where
子句所涉及的列都有索引。这有助于数据库引擎快速定位需要更新的行,从而减少锁定的范围。避免在没有索引的列上进行范围查询,因为这可能导致更多的行被锁定。
批量更新:
如果可能,将大的
update
操作分解为多个小的批次。这样可以减少每次操作锁定的行数,降低锁升级的风险。使用低隔离级别:
根据你的业务需求,考虑使用较低的事务隔离级别(如
read committed
而不是repearable read
)。较低的隔离级别通常意味着更少的锁定和更高的并发性。避免长时间的事务:
尽量缩短事务的持续时间,以减少锁持有的时间。长时间的事务可能导致更多的行或表被锁定,增加锁升级的风险。
存储对象
视图
视图(View)是一种虚拟存在的表,视图中的数据并不在数据库中实际存在,行和列数据来定义视图的查询中使用的表,并且在使用视图是动态生成,简单来说就是,视图只保存查询的sql逻辑,不保存查询的结果,所以创建视图的时候,主要工作就落在创建这条sql查询语句上
视图语法
创建视图:
create or replace view stu_v_1 as select id, name from student where id <= 10;查询视图:
# 展示创建视图时候执行的语句 show create view stu-v_1; # 查询视图中的数据 select * from stu-v_1; select * from stu-v_1 where id < 3;修改视图:
# 方式一: create or replace view stu_v_1 as select id, name, number from student where id <= 10; # 方式二: alter view stu_v_1 as select id, name from student where id < 10;删除视图:
drop view if exists stu_v_1;
视图数据操作语法
插入:
insert into stu_v_1 value(6, "Tom");
视图检查选项
cascaded:会检查当前视图以及依赖的视图with cascaded check option
# with cascaded check option检查语法,cascaded可以换成local,默认为cascaded create or replace view stu_v_1 as select id, name, number from student where id <= 10 with cascaded check option;# 创建视图 create or replace view stu_v_1 as select id, name from student where id <= 20; # 创建视图 create or replace view stu_v_2 as select id, name from stu_v_1 where id >= 10 with cascaded check option; # 插入数据,报错不满足stu_v_2 insert into stu_v_2 value(7, "Tom"); # 插入数据,报错不满足stu_v_1,在stu_v_1中26不能展示,有with cascaded check option; insert into stu_v_2 value(26, "Tom"); # 插入数据,成功 insert into stu_v_2 value(15, "Tom"); # 再次创建视图 create or replace view stu_v_3 as select id, name from stu_v_2 where id <= 15; # 插入数据,成功 insert into stu_v_3 value(11, "Tom"); # 插入数据,执行成功,但是不满足stu_v_3,不能展示 insert into stu_v_3 value(17, "Tom"); # 插入数据,报错不满足stu_v_3 insert into stu_v_3 value(28, "Tom");local:只检查当前定义的视图,不会检查基于的视图with local check option
相当于递归查找
视图更新以及作用
更新
要使视图可更新,视图中的行与基础表的行之间必须存在一对一的关系,如果视图包含以下任一项,则不可更新
聚合函数或者窗口函数
distinct
group by
having
union 或者 union all
作用
简化复杂性: 数据库中的表可能包含大量的列和行,而有时用户只关心表中的某一部分数据。通过创建视图,可以将复杂的查询和过滤操作封装在一个视图中,用户只需关注视图的结构而不必处理底层表的复杂性。
数据安全性: 视图可以限制用户对数据库中数据的访问权限。通过只允许用户访问视图而不是直接访问表,可以确保用户只能看到他们被授权查看的数据。
数据独立性: 视图提供了一种逻辑数据独立性,允许数据库管理员更改底层表的结构而不影响用户对视图的访问。帮助用户屏蔽真实表结构带来的影响
# sudentname as name不会影响原表的结构 create or replace view stu_v_1 as select id, sudentname as name from student where id <= 15 with cascaded check option;
存储过程
存储过程是事先经过编译并存储在数据库中的一段 SOL语句的集合,调用存储过程可以简化应用开发人员的很多工作,减少数据在数据库和应用服务器之间的传输,对于提高数据处理的效率是有好处的 存储过程思想上很简单,就是数据库 SOL 语言层面的代码封装与重用
特点:封装、复用、可以接收参数、也可以返回数据、减少网络交互,效率提高
基本语法
创建:
create procedure p1() begin select count(*) from student; end; # 如果通过命令行执行。需要指定结束符 delimiter $$ # 再次执行,加上$$符 create procedure p1() begin select count(*) from student; end$$调用:
call p1();查看:
# 查看指定数据库的存储过程以及状态信息 select * from information_schema.ROUTINES where ROUTINES_SCHEMA = 'itcast'; # 查看某个存储过程的定义 show create procedure p1;删除:
drop procedure if exists p1;
变量
系统变量:
系统变量 是MySQL服务器提供,不是用户定义的,属于服务器层面。分为全局变量(GLOBAL)、会话变量(SESSION)
查看系统变量:
# 查看所有系统变量,加上级别,默认为会话级别变量(SESSION) show session variables; # 全局级别变量(GLOBAL) show global variables; # 通过like模糊匹配查询 show session variables like 'auto%'; # 查看指定变量的值 select @@autocommit; select @@session.autocommit; select @@global.autocommit;设置系统变量:
# 举例子:关闭自动提交 set session autocommit = 0;用户自定义变量:
用户定义变量,是用户根据需要自己定义的变量,用户变量不用提前声明,在用的时候直接用“@变量名”使用就可以。其作用域为当前连接。
赋值:
set @myname = 'iacast'; set @myage := 10; set @mygender := '男', @myhobby := 'java'; # 为指定字段赋值查询出来的结果 select count(*) into @mycount from tb_user;使用:
select @myname, @myage, @mygender, @myhobby;局部变量:
局部变量 是根据需要定义的在局部生效的变量,访问之前,需要DECLARE声明。可用作存储过程内的局部变量和输入参数,局部变量的范围是在其内声明的BEGIN... END块
声明:变量类型就是数据库字段类型:INT、BIGINT、CHAR、VARCHAR、DATE、TIME等
# 创建存储过程,加上局部变量,在begin end 之间生效,并指定字段类型,可以设置默认值 create procedure p2() begin declare stu_count int default 0; # 为stu_count字段赋值 select count(*) into stu_count from student; # 展示stu_count字段结果 select stu_count; end; # 调用p2()存储过程 call p2();
if判断
create procedure p3() begin declare score int default 58; declare result varchar(10); if score >= 85 then set result := '优秀'; elseif score >= 60 then set result := '及格'; else set result := '不及格'; end if; select result; end; #调用p3存储过程 call p3();
参数
参数类型:
IN:该类参数作为输入,也就是需要调用时传入值,默认
OUT:该类参数作为输出,也就是该参数可以作为返回值
INOUT:既可以作为输入参数,也可以作为输出参数
参数用法:
# in score int, out result varchar(10) create procedure p4(in score int, out result varchar(10)) begin if score >= 85 then set result := '优秀'; elseif score >= 60 then set result := '及格'; else set result := '不及格'; end if; end; #调用p4存储过程,68, @result call p4(68, @result); select @result;# 创建存储过程 create procedure p5(inout score double) begin set score := score * 0.5; end; # 调用 set @score := 78; call p5(@score); select @score;
case
# 创建存储过程 create procedure p6(in month int) begin declare result varchar(10); case when month >= 1 and month <= 3 then set result := '第一节度'; when month >= 4 and month <= 6 then set result := '第二季度'; when month >= 7 and month <= 9 then set result := '第三节度'; when month >= 10 and month <= 12 then set result := '第四季度'; else set result := '非法参数'; end case; select concat('你输入的月份为:', month, '所属的季度为:', result); end; # 调用 call p6(12);
while
while 循环是有条件的循环控制语句。满足条件后,再执行循环体中的SQL语句
# 创建存储过程 create procedure p7(in n int) begin declare total int default 0; while n > 0 do set total := total + n; set n := n - 1; end while; select total; end; # 删除 drop procedure p7; # 调用 call p7(10);
repeat
repeat是有条件的循环控制语句,当满足条件的时候退出循环,先执行一次后判断
# 创建存储过程 create procedure p8(in n int) begin declare total int default 0; repeat set total := total + n; set n := n - 1; until n <= 0 end repeat; select total; end; # 删除 drop procedure p8; # 调用 call p8(10);
loop
LOOP实现简单的循环,如果不在SL逻辑中增加退出循环的条件,可以用其来实现简单的死循环。LOOP可以配合一下两个语句使用 leave:配合循环使用,退出循环 iterate:必须用在循环中,作用是跳过当前循环剩下的语句,直接进入下一次循环
# 创建存储过程 create procedure p10(in n int) begin declare total int default 0; sum: loop if n <= 0 then leave sum; end if; if n % 2 != 0 then set n := n - 1; iterate sum; end if; set total := total + n; set n := n - 1; end loop sum; select total; end; # 删除 drop procedure p10; # 调用 call p10(10);
游标cursor加上条件处理程序
游标(CURSOR)
是用来存储查询结果集的数据类型,在存储过程和函数中可以使用游标对结果集进行循环的处理。游标的使用包括游标的声明、open、fetch 和 close,其语法分别如下。
条件处理程序
可以用来定义在流程控制结构执行过程中遇到问题时相应的处理步骤
# 创建存储过程 create procedure p11(in uage int) begin # 声明中间变量,必须在游标之前声明 declare u_name varchar(10); declare u_age int; # 声明游标 declare u_emp cursor for select name, age from emp where age <= uage; # 声明条件处理程序,执行状态码,满足什么条件退出程序 exit:终止当前程序,最后关闭游标 declare exit handler for sqlstate '02000' close u_emp; #创建表结构 drop table if exists tb_emp_age; create table if not exists tb_emp_age ( id int primary key auto_increment, name varchar(10), age int ); # 开启游标 open u_emp; while true do # 循环获取游标中的数据 fetch u_emp into u_name, u_age; # 把获取的数据插入表中 insert into tb_emp_age values (null, u_name, u_age); end while; end; # 删除 drop procedure p11; drop table tb_emp_age; # 调用,执行之后才会显示表tb_emp_age call p11(30);
存储函数
存储函数是有返回值的存储过程,存储函数的参数只能是in类型的
# 创建存储函数 create function fun1(n int) returns int deterministic # 指定参数 begin declare total int default 0; while n > 0 do set total := total + n; set n := n - 1; end while; return total; end; # 删除 drop function fun1; # 调用 select fun1(10);
触发器
触发器是与夷有关的数据库对象,指在 nset/update/delete 之前或之后,发并执行触发器中定义的SOL语句集合。触发器的这种特性可以协助应用在数据库端确保数据的完整性,日志记录,数据校验等操作。 使用别名 OLD 和 NEW 来引用触发器中发生变化的记录内容,这与其他的数据库是相似的。现在触发器还只支持行级触发,不支持语句级触发。
insert:
# 创建一个存储日志中的表 create table emo_logs ( id int(11) not null auto_increment, operation varchar(20) not null comment '操作类型, insert/update/delete', operate_time datetime not null comment '操作时间', operate_id int(11) not null comment '操作ID', operate_params varchar(500) comment '擦作参数', primary key (`id`) ) engine = innodb default charset = utf8;插入数据触发器,for each row 行级触发器
create trigger emp_insert_trigger after insert on emp for each row begin insert into emp_logs(id, operation, operate_time, operate_id, operate_params) values (null, 'insert', now(), new.id, concat('插入的数据为:id=', NEW.id, 'work_id=', NEW.workId, 'name=', NEW.name, 'age=', NEW.age, 'gender=', new.gender, 'id_card=', NEW.idCard, 'work_address=', new.workAddress, 'work_date=', NEW.workDate)); end;
查看触发器
show triggers;
删除触发器
drop trigger emp_insert_trigger;
- **update:** ```mysql # 创建一个存储日志中的表 create table emo_logs ( id int(11) not null auto_increment, operation varchar(20) not null comment '操作类型, insert/update/delete', operate_time datetime not null comment '操作时间', operate_id int(11) not null comment '操作ID', operate_params varchar(500) comment '擦作参数', primary key (`id`) ) engine = innodb default charset = utf8; # 修改数据触发器,for each row 行级触发器 create trigger emp_update_trigger after update on emp for each row begin insert into emp_logs(id, operation, operate_time, operate_id, operate_params) values (null, 'update', now(), new.id, concat('更新之前的数据:id=', OLD.id, 'work_id=', OLD.workId, 'name=', OLD.name, 'age=', OLD.age, 'gender=', OLD.gender, 'id_card=', OLD.idCard, 'work_address=', OLD.workAddress, 'work_date=', OLD.workDate, '更新之后的数据:id=', NEW.id, 'work_id=', NEW.workId, 'name=', NEW.name, 'age=', NEW.age, 'gender=', new.gender, 'id_card=', NEW.idCard, 'work_address=', new.workAddress, 'work_date=', NEW.workDate)); end; # 查看触发器 show triggers; # 删除触发器 drop trigger emp_update_trigger;
delete:
# 删除数据触发器,for each row 行级触发器 create trigger emp_delete_trigger after delete on emp for each row begin insert into emp_logs(id, operation, operate_time, operate_id, operate_params) values (null, 'delete', now(), OLD.id, concat('删除之前的数据:id=', OLD.id, 'work_id=', OLD.workId, 'name=', OLD.name, 'age=', OLD.age, 'gender=', OLD.gender, 'id_card=', OLD.idCard, 'work_address=', OLD.workAddress, 'work_date=', OLD.workDate)); end; # 查看触发器 show triggers; # 删除触发器 drop trigger emp_delete_trigger;
锁
锁是计算机协调多个进程或线程并发访问某一资源的机制。在数据库中,除传统的计算资源(CPU、RAM、I/0)的争用以外,数据也是一种供许多用户共享的资源。如何保证数据并发访问的一致性、有效性是所有数据库必须解决的一个问题,锁冲突也是影响数据库并发访问性能的一个重要因素。
全局锁:锁定数据库的所有表
表级锁:每次操作锁定整张表
行级锁:每次操作锁定对应的行数据
全局锁:
全局锁就是对整个数据库实例加锁,加锁后整个实例就处于只读状态,后续的DML(INSERT、SELECT、UPDATE和DELETE等命令)的写语句,DDL(定义关系模式、删除关系、修改关系模式以及创建数据库中的各种对象,比如表、索引、视图、函数、存储过程和触发器等)语句,已经更新操作的事务提交语句都将被阻塞。
其典型的使用场景是做全库的逻辑备份,对所有的表进行锁定,从而获取一致性视图,保证数据的完整性。
特点:
如果在主库上备份,那么在备份期间都不能执行更新,业务基本上就得停摆
如果在从库上备份,那么在备份期间从库不能执行主库同步过来的二进制日志(binlog),会导致主从延迟
在innoDB引擎中,我们可以在备份时加上参数 --single-transaction 参数来完成不加锁的一致性数据备份。
mysqldump --single-transaction -uroot -p123456 itcast > itcast.sql# 加锁,只能查不能写 flush tables with read lock; # 解锁 unlock tables;
表级锁:
表锁
元数据锁
意向锁
表锁:
表共享读锁(read lock):只能对当前表select
# 加锁,只能查不能写 lock tables emp read; # 解锁 unlock tables;
表独占写锁(write lock):当前客户端可以进行读改,其他客户端禁止操作
# 加锁,只能查不能写 lock tables emp write; # 解锁 unlock tables;元数据锁:
MDL加锁过程是系统自动控制,无需显式使用,在访问一张表的时候会自动加上。MDL锁主要作用是维护表元数据的数据一致性,在表上有活动事务的时候,不可以对元数据进行写入操作,简单来说,某一张表上存在未提交的事务,我们不能修改这个表上的数据,为了DDL和DML互斥
当某一张表上存在未提交的事务,进行增删改查时候,会加上共享读锁(共享),会兼容
当某一张表上存在未提交的事务,对表结构进行变更操作时候,会加上独占写锁(排他),会阻塞,直到提交事务
# 查看所有元数据锁 select object_type,object_schema,object_name,lock_type,lock_duration from performance_schema.metadata_locks;意向锁:
加意向锁的目的是为了表明某个事务正在锁定一行或者将要锁定一行。表名加锁的意图。
# 查看所有意向共享锁 select object_schema, object_name, index_name, lock_type, lock_mode, lock_data from performance_schema.data_locks;
意向共享锁(IS)表示事务意图在表中的单个行上设置共享锁
# 开启事务 begin; # 会添加共享锁,还有意向共享锁 select * from emp where id = 3 lock in share mode; # 可以添加共享读锁,因为是共享的,但是不能添加排它锁,与排它锁互斥 lock tables emp read; # 提交事务,会释放所有的意向锁 commit;意向排他锁(IX)表明事务意图在表中的单个行上设置独占锁
# 开启事务 begin; # 会添加意向排它锁,但是不能添加共享写锁和排它锁,都互斥,意向锁之间不会互斥 update emp set name='D' where id=3; # 提交事务,会释放所有的意向锁 commit;
行级锁
行级锁,每次操作锁住对应的行数据。锁定粒度最小,发生锁冲突的概率最低,并发度最高。应用在innoDB存储引擎中lnnoDB的数据是基于索引组织的,行锁是通过对索引上的索引项加锁来实现的,而不是对记录加的锁。对于行级锁,主要分为以下三类:
行锁(Record Lock): 锁定单个行记录的锁,防止其他事务对此行进行update和delete。在RC、RR隔离级别下都支持
间隙锁(Gap Lock):锁定索引记录间隙(不含该记录),确保索引记录间隙不变,防止其他事务在这个间隙进行inse,产生幻读。在RR隔离级别下都支持
临键锁(Next-KeyLock):行和间锁组合,同时锁住数据,并锁住数据前面的间隙Gap。在RR隔离级别下支持
行锁:InnoDB实现了以下的两种类型锁:
InnoDB的行锁是针对索引加的锁,不通过索引条件检索数据,那么InnoDB会将表的所有记录加上锁,升级为表锁
# 开启事务 begin; # 查看当前表的索引,只有id为主键索引 show index from emp; # 查看行锁情况 select object_schema, object_name, index_name, lock_type, lock_mode, lock_data from performance_schema.data_locks; # 在当前线程下,不通过主键索引更改数据,name='A',加上排它锁 update emp set age=10 where name='A'; # 在其他线程下,更改其他记录的数据,也会阻塞,此时行锁升级为表锁 update emp set age=10 where id='4'; # 提交事务 commit;
共享锁S:允许一个事务去读一行,阻止其他事务获得相同数据集的排它锁
# 在当前他线程下开启事务 begin; # 在当前线程下不加锁,正常查询语句 select * from emp; # 查看行锁情况 select object_schema, object_name, index_name, lock_type, lock_mode, lock_data from performance_schema.data_locks; # 在其他线程下加上共享锁,可以再加共享锁,兼容 select * from emp where id = 3 lock in share mode; # 在其他线程下,继续操作id=3的行,加上排它锁,阻塞状态,但是别的行可以操作 update emp set name='A' where id=3; # 在当前线程下提交事务 commit;排它锁X:允许获取排它锁的事务更新数据,阻止其他事物获得相同数据集的共享锁以及排它锁
# 开启事务 begin; # 查看行锁情况 select object_schema, object_name, index_name, lock_type, lock_mode, lock_data from performance_schema.data_locks; # 在当前线程下,更新id=3数据,加上排它锁 update emp set name='A' where id=3; # 在其他线程下,更新id=3数据,加上排它锁,会阻塞状态,互斥作用,但是其他行没问题 update emp set name='A' where id=3; # 在其他线程下,加上共享锁,会阻塞状态,互斥作用,但是其他行没问题 select * from emp where id=3 lock in share mode; # 提交事务 commit;查看行锁
# 查看行锁 select object_schema, object_name, index_name, lock_type, lock_mode, lock_data from performance_schema.data_locks;间隙锁/临键锁:
默认情况下,InnoDB在repeatable read 事务隔离级别运行,InnoDB使用next-key锁进行搜索和索引扫描,防止幻读
索引上的等值查询(唯一索引),给不存在的记录加锁时,优化为间隙锁
索引上的等值查询(普通索引),向右遍历时最后一个值不满足查询需求时,next-keylock 退化为间隙锁
索引上的范围查询(唯一索引)--会访问到不满足条件的第一个值为止。
注意:间隙锁唯一目的是防止其他事务插入间隙。间隙锁可以共存,一个事务采用的间隙锁不会阻止另一个事务在同一间隙上采用间隙锁。
InnoDB存储引擎
架构
mysql默认使用InnoDB存储引擎,他擅长处理事务,具有崩溃恢复特性
内存结构:
Buffer Pool: 缓冲池是主内存中的一个区域,里面可以缓存磁盘上经常操作的真实数据,在执行增删改查操作时,先操作缓冲池中的数据(若缓冲池没有数据,则从磁盘加载并缓存),然后再以一定频率刷新到磁盘,从而减少磁盘10,加快处理速度。
Change Buffer:更改缓冲区(针对于非唯一二级索引页),在执行DML语句时,如果这些数据Page没有在Buffer Pool中,不会直接操作磁盘,而会将数据变更存在更改缓冲区 Change Buffer 中,在未来数据被读取时,再将数据合并恢复到Buffer Pool中,再将合并后的数据刷新到磁盘中
Adaptive Hash Index: 自适应hash索引,用于优化对Buffer Pool数据的查询。InnoDB存储引擎会监控对表上各索引页的查询,如果观察到hash索引可以提升速度,则建立hash索引,称之为自适应hash索引,无需人工干预,系统自己管理
Log Buffer:日志缓冲区,用来保存要写入到磁盘中的log日志数据 (redo log、undo log),默认大小为 16MB,日志缓冲区的日志会定期刷新到磁盘中。如果需要更新、插入或删除许多行的事务,增加日志缓冲区的大小可以节省磁盘0。
磁盘结构:
System Tablespace:系统表空间是更改缓冲区的存储区域。如果表是在系统表空间而不是每个表文件或通用表空间中创建的,它也可能包含表和索引数据。(在MSOL5x版本中还包含innoDB数据字典、undolog等)
GeneralTablespaces:通用表空间,需要通过CREATE TABLESPACE 语法创建通用表空间,在创建表时,可以指定该表空间。
Doublewrite Buffer Files:双写缓冲区,innoDB引擎将数据页从Buffer Pool刷新到磁盘前,先将数据页写入双写缓冲区文件中,便于系统异常时恢复数据。
Redo Log:重做日志,是用来实现事务的持久性。该日志文件由两部分组成:重做日志缓冲(redo log buffer)以及重做日志文件 (redo log),前者是在内存中,后者在磁盘中。当事务提交之后会把所有修改信息都会存到该日志中,用于在刷新脏页到磁盘时,发生错误时,进行数据恢复使用。
后台线程:
Master Thread:核心后台线程,负责调度其他线程,还负责将缓冲池中的数据异步刷新到磁盘中,保持数据的一致性,还包括脏页的刷新、合并插入缓存、undo页的回收。
IOThread:在InnoDB存储引擎中大量使用了AIO来处理IO请求,这样可以极大地提高数据库的性能,而IOThread主要负责这些IO请求的回调。
线程类型 默认个数 职责 Read thread 4 负责读操作 Write thread 4 负责写操作 Log thread 1 负责将日志缓冲区内容刷新到磁盘 Insert buffer thread 1 负责将写缓冲区内容刷新到磁盘 Purge Thread:主要用于回收事务已经提交了的undo log,在事务提交之后,undo log可能不用了,就用它来回收。
Page Cleaner Thread:协助 Master Thread 刷新脏页到磁盘的线程,它可以减轻Master THread 的工作压力,减少阻塞。