MySQL高级(持续更新中···)

逻辑架构

存储过程 procedure

存储过程是一组为了完成特定功能的SQL语句集合。使用存储过程的目的是将常用或复杂的工作预先用SQL语句写好并用一个指定名称存储起来,这个过程经编译和优化后存储在数据库服务器中,因此称为存储过程。当以后需要数据库提供与已定义好的存储过程的功能相同的服务时,只需用 CALL 调用这个存储过程即可。

创建

CREATE PROCEDURE 存储过程名(IN|OUT|INOUT 参数名 参数类型,...) 
BEGIN
    存储过程体 
END

delimiter 命令:

在 MySQL 中,服务器处理 SQL 语句默认是以分号作为语句结束标志的。然而,在创建存储过程时,存储过程体可能包含有多条 SQL 语句,这些 SQL 语句如果仍以分号作为语句结束符,那么 MySQL 服务器在处理时会以遇到的第一条 SQL 语句结尾处的分号作为整个程序的结束符,而不再去处理存储过程体中后面的 SQL 语句,这样显然不行。为解决这个问题,通常可使用 DELIMITER 命令将结束命令修改为其他字符。在创建存储过程最后的 end 后写上修改的结束符,表示语句结束。

delimiter // 将结束符修改为//

delimiter ; 将结束符修改为;

例:创建不带参数的存储过程

mysql> delimiter //
mysql> create procedure test()
    -> begin
    -> update t_user set c_name = "hello" where id = 1;
    -> end //
mysql> delimiter ;

# 调用存储过程
mysql> call test;

例:创建带参数的存储过程

mysql> delimiter //
mysql> create procedure test
    -> (IN name varchar(30))
    -> begin
    -> update t_user set c_name = name where id = 1;
    -> end //
mysql> delimiter ;

# 调用存储过程
mysql> call test('hello')

删除

mysql> drop procudure test;

查看

# 查看所有存储过程
mysql> show procedure status;

# 查看指定存储过程
mysql> show procedure status like 存储过程名;

参考

MySQL之存储过程(PROCEDURE) – 菜菜成长记 – 博客园 (cnblogs.com)

InnoDB引擎

存储数据结构

索引

索引的声明和使用

索引分类

  • 从功能逻辑上说,索引主要有 4 种,分别是普通索引、唯一索引、主键索引、全文索引。
  • 按照物理实现方式,索引可以分为 2 种:一级索引(聚簇索引)和二级索引(非聚簇索引)。
  • 一级索引:索引和数据存储在一起,都存储在同一个B+tree中的叶子节点。一般主键索引都是一级索引。
  • 二级索引树的叶子节点存储的是主键而不是数据。也就是说,在找到索引后,得到对应的主键,再回到一级索引中找主键对应的数据记录。
  • 按照作用字段个数进行划分,分成单列索引和联合索引。

创建索引

# 建表时建立索引
CREATE TABLE table_name [col_name data_type] 
[UNIQUE | FULLTEXT | SPATIAL] [INDEX | KEY] [index_name] (col_name [length]) [ASC | DESC]

# 在已存在的表上创建索引
ALTER TABLE table_name 
ADD [UNIQUE | FULLTEXT | SPATIAL] [INDEX | KEY] [index_name] (col_name[length],...) [ASC | DESC]

CREATE [UNIQUE | FULLTEXT | SPATIAL] INDEX index_name 
ON table_name (col_name[length],...) [ASC | DESC]
  • UNIQUEFULLTEXTSPATIAL为可选参数,分别表示唯一索引、全文索引和空间索引;
  • INDEXKEY为同义词,两者的作用相同,用来指定创建索引;
  • index_name指定索引的名称,为可选参数,如果不指定,那么MySQL默认col_name为索引名;
  • col_name为需要创建索引的字段列,该列必须从数据表中定义的多个列中选择;
  • length为可选参数,表示索引的长度,只有字符串类型的字段才能指定索引长度;
  • ASCDESC指定升序或者降序的索引值存储。

查看索引

SHOW INDEX FROM table_name

删除索引

ALTER TABLE table_name DROP INDEX index_name;
或
DROP INDEX index_name ON table_name;

隐藏索引(8.0新特性)

从 MySQL8.x 开始支持隐藏索引(invisible indexes),只需要将待删除的索引设置为隐藏索引,使查询优化器不再使用这个索引(即使使用force index(强制使用索引),优化器也不会使用该索引),确认将索引设置为隐藏索引后系统不受任何响应,就可以彻底删除索引。这种通过先将索引设置为隐藏索引,再删除索引的方式就是软删除。

# 切换成隐藏索引 
ALTER TABLE tablename ALTER INDEX index_name INVISIBLE; 

# 切换成非隐藏索引
ALTER TABLE tablename ALTER INDEX index_name VISIBLE; 

# 也可以在创建索引时指定 INVISIBLE 
# 索引创建时默认是 VISIBLE

(当索引被隐藏时,其内容仍是实时更新的,因此不推荐长期隐藏索引)

创建索引原则(推荐创建索引的场景)

字段的数值有唯一性的限制

索引本身可以起到约束的作用,比如唯一索引、主键索引都可以起到唯一性约束的,因此在我们的数据表中,如果某个字段是唯一的,就可以直接创建唯一性索引,或者主键索引。这样可以更快速地通过该索引来确定某条记录。

业务上具有唯一特性的字段,即使是组合字段,也必须建成唯一索引。(来源:Alibaba)

说明:不要以为唯一索引影响了insert速度,这个速度损耗可以忽略,但提高查找速度是明显的。

频繁作为 WHERE 查询条件的字段

某个字段在SELECT语句的 WHERE 条件中经常被使用到,那么就需要给这个字段创建索引了。尤其是在数据量大的情况下,创建普通索引就可以大幅提升数据查询的效率。

经常 GROUP BY 和 ORDER BY 的列

某个字段在 SELECT 语句的 WHERE 条件中经常被使用到,那么就需要给这个字段创建索引了。尤其是在数据量大的情况下,创建普通索引就可以大幅提升数据查询的效率。索引就是让数据按照某种顺序进行存储或检索,因此当我们使用 GROUP BY 对数据进行分组查询,或者使用 ORDER BY 对数据进行排序的时候,就需要对分组或者排序的字段进行索引。如果待排序的列有多个,那么可以在这些列上建立组合索引

UPDATE、DELETE 的 WHERE 条件列

对数据按照某个条件进行查询后再进行 UPDATE 或 DELETE 的操作,如果对 WHERE 字段创建了索引,就能大幅提升效率。原理是因为我们需要先根据 WHERE 条件列检索出来这条记录,然后再对它进行更新或删除。如果进行更新的时候,更新的字段是非索引字段,提升的效率会更明显,这是因为非索引字段更新不需要对索引进行维护。

查询 DISTINCT 字段

有时候我们需要对某个字段进行去重,使用 DISTINCT,那么对这个字段创建索引,也会提升查询效率。

多表 JOIN

  • 连接表的数量尽量不要超过 3 张,因为每增加一张表就相当于增加了一次嵌套的循环,数量级增长会非常快,严重影响查询的效率。
  • 对 WHERE 条件创建索引,因为 WHERE 才是对数据条件的过滤。如果在数据量非常大的情况下,没有 WHERE 条件过滤是非常可怕的。
  • 对用于连接的字段创建索引,并且该字段在多张表中的类型必须一致

使用列的类型小的创建索引

我们这里所说的类型大小指的就是该类型表示的数据范围的大小

  • 数据类型越小,在查询时进行的比较操作越快
  • 数据类型越小,索引占用的存储空间就越少,在一个数据页内就可以放下更多的记录,从而减少磁盘 I/O 带来的性能损耗,也就意味着可以把更多的数据页缓存在内存中,从而加快读写效率。

这个建议对于表的主键来说更加适用,因为不仅是聚簇索引中会存储主键值,其他所有的二级索引的节点处都会存储一份记录的主键值,如果主键使用更小的数据类型,也就意味着节省更多的存储空间和更高效的I/O。

使用字符串前缀创建索引

如果某个字段是 varchar 类型的(或者 text 等等),那么其中可能存储了很长的字符串,当建立索引时,可以不用整个字符串建立索引,而是指截取前面部分建立索引

拓展:Alibaba《Java开发手册》

【强制】在 varchar 字段上建立索引时,必须指定索引长度,没必要对全字段建立索引,根据实际文本区分度决定索引长度。

说明:索引的长度与区分度是一对矛盾体,一般对字符串类型数据,长度为 20 的索引,区分度会高达 90% 以上。

区分度计算公式:count(distinct left(列名, 索引长度))/count(*)

区分度高的列适合作为索引

列的基数指的是某一列中不重复数据的个数,比方说某个列包含值 2,5,8,2,5,8,2,5,8,虽然有 9 条记录,但该列的基数却是 3 。也就是说,在记录行数一定的情况下,列的基数越大,该列中的值越分散;列的基数越小,该列中的值越集中。这个列的基数指标非常重要,直接影响我们是否能有效的利用索引。最好为列的基数大的列建立索引,为基数太小的列建立索引效果可能不好。

可以使用公式 select count(distinct a)/count(*) from t1 计算区分度,越接近 1 越好,一般超过 33% 就算是比较高效的索引了。

拓展:联合索引把区分度高(散列性高)的列放在前面。

使用频率高的列放到联合索引的左侧

多个字段都要创建索引的情况下,联合索引优于单值索引

单张表的索引不超过6个

不适合创建索引的场景

  • where 中使用不到的字段不要使用索引
  • 数据量小的表不要使用索引
  • 有大量重复数据的列上不要创建索引
  • 避免对经常更新的表创建过多索引
  • 不建议用无序的值作为索引
  • 删除不再使用或者很少使用的索引
  • 不要定义冗余或重复的索引

插入 1KW 条测试数据

创建测试表t_user

CREATE TABLE `t_user` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `c_user_id` varchar(36) NOT NULL DEFAULT '' COMMENT '用户Id',
  `c_name` varchar(22) NOT NULL DEFAULT '' COMMENT '用户名',
  `c_province_id` int(11) NOT NULL COMMENT '省份Id',
  `c_city_id` int(11) NOT NULL COMMENT '城市Id',
  `create_time` datetime NOT NULL COMMENT '创建时间',
  PRIMARY KEY (`id`),
  KEY `idx_user_id` (`c_user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

创建临时表

CREATE TABLE `tmp_table` (
  `id` int(11) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

用 python 生成 1kw 个数据

[root@fengye tmp]# python -c "for i in range(1, 1+10000000): print(i)" > base.txt

在 mysql 中执行导入命令

load data infile '\home\tmp\base.txt' replace into table  tmp_table

可能出现的报错

1290 - The MySQL server is running with the --secure-file-priv option so it cannot execute this statement

原因:mysql默认没有开启 secure_file_priv

解决:在 my.ini(/etc/my.cnf) 中的 [mysqld] 节点中添加

secure_file_priv = ”,然后重启

说明:

secure_file_prive = null 限制mysqld 不允许导入导出

secure_file_priv = /var/lib/mysql-files/ 限制mysqld的导入导出只能发生在/var/lib/mysql-files/目录下

secure_file_priv = ” 不对mysqld的导入导出做限制

从临时表中的数据插入到 t_user

insert into t_user
select id,uuid(),concat('userNickName',id),floor(rand()*1000),floor(rand()*100),now()
from tmp_table

打乱创建时间

UPDATE t_user SET create_time=date_add(create_time, interval FLOOR(1 + (RAND() * 7)) year);

性能分析工具

查看系统性能参数

SHOW STATUS LIKE '参数'

常用的性能参数:

Connections:连接 MySQL 服务器的次数
Uptime:MySQL服务器上线事件
Slow_queries:慢查询次数
Innodb_rows_read:已select的行数
Innodb_rows_inserted:已inserted的行数Innodb_rows_updated:已updated的行数Innodb_rows_deleted:已deleted的行数
Com_select:查询操作的次数
Com_insert:插入操作的次数
Com_update:更新操作的次数
Com_delete:删除操作的次数

查看上一条SQL语句的查询成本

SHOW STATUS LIKE 'last_query_cost' 

SQL 查询是一个动态的过程,从页加载的角度来看,我们可以得到以下两点结论:

  1. 位置决定效率。如果页就在数据库缓冲池中,那么效率是最高的,否则还需要从内存或者磁盘中进行读取,当然针对单个页的读取来说,如果页存在于内存中,会比在磁盘中读取效率高很多。
  2. 批量决定效率。如果我们从磁盘中对单一页进行随机读取,那么效率是很低的(差不多10ms),而采用顺序读取的方式,批量对页进行读取,平均一页的读取效率就会提升很多,甚至要快于单个页面在内存中的随机读取。

所以说,遇到I/O并不用担心,方法找对了,效率还是很高的。我们首先要考虑数据存放的位置,如果是经常使用的数据就要尽量放到缓冲池中,其次我们可以充分利用磁盘的吞吐能力,一次性批量读取数据,这样单个页的读取效率也就得到了提升。

慢查询日志

MySQL的慢查询日志,用来记录在MySQL中响应时间超过阈值(即 long_query_time ,默认为10s)的语句。

慢查询日志默认为关闭状态,需要手动开启,支持持久化到文件。

建议在测试环境中开启,正式环境中关闭,因为该功能会影响性能

补充说明:

除了 long_query_time 外,还有一个系统变量 min_examined_row_limit,表示查询扫描过的最小记录数。

当一条查询扫描的记录数大于 min_examined_row_limit 同时查询的执行事件超过 long_query_time ,那么这个查询就会被记录到慢查询日志中。

这个值默认是 0 ,可以在 my.ini 中修改,也可用 SET 命令修改。

修改慢查询配置

查看慢查询是否开启

SHOW VARIABLES LIKE 'slow_query_log';

查看慢查询日志位置

SHOW VARIABLES LIKE 'slow_query_log_file';

开启慢查询

SET GLOBAL slow_query_log='ON';

查看慢查询阈值

show variables like '%long_query_time%';

修改慢查询阈值

#测试发现:设置global的方式对当前session的long_query_time失效。对新连接的客户端有效。所以可以一并 执行下述语句 
mysql> set global long_query_time = 1; 
mysql> show global variables like '%long_query_time%'; 

mysql> set long_query_time=1; 
mysql> show variables like '%long_query_time%';

查看慢查询数量

SHOW GLOBAL STATUS LIKE '%Slow_queries%';

慢查询日志分析

#得到按照时间排序的前10条里面含有左连接的查询语句 
mysqldumpslow -s t -t 10 -g "left join" /var/lib/mysql/atguigu-slow.log 

参数:
-s 表示按照何种方式排序
    c 访问次数
    l 锁定时间
    r 返回记录
    t 查询时间
    al 平均锁定时间
    ar 平均返回记录数
    at  平均查询时间
-t 返回前面多少条数据
-g 后边搭配一个正则匹配模式,大小写不敏感

可能会出现的报错:

mysqldumpslow: command not found...

原因:系统默认去 /usr/bin 下查找命令

解决方法:找到 mysql 的安装目录下的 /bin/mysqldumpslow

执行 ln -s /www/server/mysql/bin/mysqldumpslow /usr/bin

重置慢查询日志

mysqladmin -uroot -p flush-logs slow

EXPLAIN 分析查询语句

语法

EXPLAIN 查询语句

EXPLAIN 语句输出的各个列的作用如下

列名描述
id在一个大的查询语句中每个SELECT关键字都对应一个唯一的id
select_typeSELECT关键字对应的那个查询的类型
table表名
partitions匹配的分区信息
type针对单表的访问方法
possible_keys可能用到的索引
key实际上使用的索引
key_len实际使用到的索引长度
ref当使用索引列等值查询时,与索引列进行等值匹配的对象信息
rows预估的需要读取的记录条数
filtered某个表经过搜索条件过滤后剩余记录条数的百分比
Extra一些额外的信息
id

SELECT识别符(执行顺序的标识)。

  1. id值越大优先级越高,越先被执行
  2. id相同时,执行顺序由上至下
select_type
  1. SIMPLE(简单SELECT,不使用UNION或子查询等)
  2. PRIMARY(子查询中最外层查询,查询中若包含任何复杂的子部分,最外层的select被标记为PRIMARY)
  3. UNION(UNION中的第二个或后面的SELECT语句)
  4. DEPENDENT UNION(UNION中的第二个或后面的SELECT语句,取决于外面的查询)
  5. UNION RESULT(UNION的结果,union语句中第二个select开始后面所有select)
  6. SUBQUERY(子查询中的第一个SELECT,结果不依赖于外部查询)
  7. DEPENDENT SUBQUERY(子查询中的第一个SELECT,依赖于外部查询)
  8. DERIVED(派生表的SELECT, FROM子句的子查询)
  9. UNCACHEABLE SUBQUERY(一个子查询的结果不能被缓存,必须重新评估外链接的第一行)
table

显示这一步所访问数据库中表名称(显示这一行的数据是关于哪张表的),有时不是真实的表名字,可能是简称,例如上面的e,d,也可能是第几步执行的结果的简称

type *

对表的访问方式,表示 mysql 在表中找到所需行的方式,又称访问类型。

ALL

不用索引,遍历全表(效率最低)

index

使用索引,遍历全表(效率还是很低)

select count(*) from user;
range

范围查询,用在 between/like/<= 等等

select * from user where user_name like 'test';
ref

通过普通的二级索引进行等值查询(普通指的是不唯一索引,因此查询结果可能有多条)

select * from user where user_name = 'test';
ref_or_null

ref 类似,条件中多了一个 is null 判断

select * from user where address = 'test' or address is null;
eq_ref

连表查询时,连接的条件是主键唯一二级索引,这是连表查询中连接效率最高的

select * from user
join department 
on department.user_id = user.id;
const

根据主键唯一二级索引与常数进行等值匹配

`select * from user where id = 100;
system

const的特例,当查询的表只有一条数据时,用system(innodb不支持)

结果从好到坏:system > const > eq_ref > ref > fulltext > ref_or_null > index_merge > unique_subquery > index_subquery > range > index > ALL

SQL性能优化的目标:至少达到range级别,要求是ref级别,最好是consts级别

possible_keys & key

possible_key 表示可能用到的索引

key 表示实际用到的索引

key_len

实际使用的索引的长度

ref

查询条件的类型

rows *

预计需要读取的行数(越小越好)

filtered

某个表经过搜索条件过滤后剩余记录条数的百分比

extra *

记录了一些额外的信息来帮助理解MySQL实际查询的过程

EXPLAIN 输出格式

EXPLAIN FORMAT=<xxx> ...

  • 传统格式
  • JSON 格式(最详细)
  • TREE 格式(8.0.16+)
  • 可视化输出(Mysql workbench)

分析优化执行计划:trace

# 开启
SET optimizer_trace="enabled=on",end_markers_in_json=on; 
# 设置大小
set optimizer_trace_max_mem_size=1000000;
# 使用
select * from student where id < 10;
select * from information_schema.optimizer_trace\G

MySQL监控分析视图 sys schema

索引情况

#1. 查询冗余索引 
select * from sys.schema_redundant_indexes; 
#2. 查询未使用过的索引 
select * from sys.schema_unused_indexes; 
#3. 查询索引的使用情况 
select index_name,rows_selected,rows_inserted,rows_updated,rows_deleted from sys.schema_index_statistics where table_schema='dbname' ;

表相关

# 1. 查询表的访问量 
select table_schema,table_name,sum(io_read_requests+io_write_requests) as io from sys.schema_table_statistics group by table_schema,table_name order by io desc; 
# 2. 查询占用bufferpool较多的表 
select object_schema,object_name,allocated,data
from sys.innodb_buffer_stats_by_table order by allocated limit 10; 
# 3. 查看表的全表扫描情况 
select * from sys.statements_with_full_table_scans where db='dbname';

语句相关

#1. 监控SQL执行的频率 
select db,exec_count,query from sys.statement_analysis order by exec_count desc; 
#2. 监控使用了排序的SQL 
select db,exec_count,first_seen,last_seen,query
from sys.statements_with_sorting limit 1; 
#3. 监控使用了临时表或者磁盘临时表的SQL 
select db,exec_count,tmp_tables,tmp_disk_tables,query
from sys.statement_analysis where tmp_tables>0 or tmp_disk_tables >0 order by (tmp_tables+tmp_disk_tables) desc;

IO相关

#1. 查看消耗磁盘IO的文件 
select file,avg_read,avg_write,avg_read+avg_write as avg_io
from sys.io_global_by_file_by_bytes order by avg_read limit 10;

Innodb 相关

#1. 行锁阻塞情况 
select * from sys.innodb_lock_waits;

索引优化和查询优化

索引失效的几种情况

参考:mysql索引失效的常见原因和如何用好索引 – 问题大白 – 博客园 (cnblogs.com)

  • 不满足最佳左前缀原则
  • where 条件后使用计算、函数、类型转换
  • 范围条件右边索引失效(指的是联合索引中的范围
  • 使用了 select * (会引起回表)
  • 字段不允许为空,则 is nullis not null 都失效;若字段允许为空,则 is nullref 类型的索引,而 is not nullrange 类型的索引。
  • like 查询左边有 %
  • OR 前后存在非索引的列
  • 使用 not in
  • 使用 !=<>

回表问题和索引覆盖

参考:避免写出致命 SQL,搞懂 MySQL 回表 – 掘金 (juejin.cn)

InnoDB 中,索引分为聚簇索引和普通索引。

  • 聚簇索引的叶子节点存储的是完整的行记录
  • 普通索引的叶子节点存储的是主键

可见,InnoDB 的聚簇索引负责存储完整的行数据,而普通索引只能帮助找到主键。因此,InnoDB 必须也只能有一个聚簇索引。

  1. 如果表定义的主键,那么主键就是聚簇索引
  2. 如果表没有定义主键,那么第一个 not nullunique 列就是聚簇索引
  3. 否则, InnoDB 会创建一个隐藏的 row-id 作为聚簇索引

回表

aHR0cHM6Ly91cGxvYWQtaW1hZ2VzLmppYW5zaHUuaW8vdXBsb2FkX2ltYWdlcy80NDU5MDI0LWE3NWU3NjdkMDE5OGE2YTQ.png

利用普通索引查询到某条数据的主键后,又返回到聚簇索引,重新定位该数据。

回表查询的性能比扫一遍索引树低。

索引覆盖

如果一个索引包含(覆盖)了所需要查询的字段的值,那么就称为索引覆盖

在 InnoDB 中,普通索引树的叶子节点存储的都是主键+索引列值。

为了避免回表,可以对需要查询的数据建立联合索引。

连接查询优化

连接查询原理

参考:MySQL查询优化——连接以及连接原理 – 简书 (jianshu.com)

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇