18.Mysql SQL优化

18.SQL优化
18.1 优化SQL语句的一般步骤
18.1.1 通过show status命令了解各种SQL的执行频率
show [session|global] status; -- 查看服务器状态信息
show session status; -- 查看session(当前连接)级别的服务器状态信息,默认session级别
show global status; -- 查看global(数据库启动至今)级别的服务器状态信息
show status like 'Com_%'; -- 查看当前session的所有统计信息,输出为类型和次数。
show status like 'Innodb_rows_%'; -- 查看当前session的Innodb引擎的统计信息,输出为类型和次数。
Innodb_rows_read 50908229195 -- 查询返回行数
Innodb_rows_inserted 1587576 -- 新增操作行数
Innodb_rows_deleted 101485 -- 删除操作行数
Innodb_rows_updated 119280 -- 修改操作行数
show status like 'Com_commit'; -- 提交次数
show status like 'Com_rollback'; -- 回滚次数
show status like 'Connections'; -- 连接服务器次数
show status like 'Uptime'; -- 服务器工作时间
show status like 'Slow_queries'; -- 慢查询次数

18.1.2 定位执行效率较低的SQL语句
1.通过慢查询日志定位那些执行效率低的SQL语句,
slow_query_log :指定是否开启慢查询日志
log_slow_queries :指定是否开启慢查询日志(该参数要被slow_query_log取代,做兼容性保留)
slow_query_log_file :指定慢日志文件存放位置,可以为空,系统会给一个缺省的文件host_name-slow.log
long_query_time :设定慢查询的阀值,超出次设定值的SQL即被记录到慢查询日志,缺省值为10s
min_examined_row_limit :查询检查返回少于该参数指定行的SQL不被记录到慢查询日志
log_queries_not_using_indexes :不使用索引的慢查询日志是否记录到索引
注意:慢查询日志在查询后才记录。
2.使用show processlist命令查看当前mysql的线程,包括线程状态、是否锁表等。

18.1.3 通过explain分析SQL的执行计划
explain SQL; -- 查看SQL的执行计划
show warnings; -- 查看优化器将SQL的改写后的结果
explain partitions SQL; -- 查看分区表SQL的执行计划及所使用的分区

explain SQL输出解析:
select_type:查询类型,包括:simple(单表查询,不使用表连接或子查询),primary(主查询,即外层查询),union(),subquery(子查询的第一个select)。
table:查询的表。
type:访问类型,包括:ALL全表扫描--》index索引全部扫描--》range索引范围扫描--》ref索引前缀扫描--》eq_ref索引唯一扫描--》const,system常量唯一扫描--》NULL不需要访问表或索引
index索引全部扫描:无条件查询索引列,如:select 索引列 from 表
range索引范围扫描:包括如下操作符<,<=,>,>=,between的查询语句
ref索引前缀扫描:使用非唯一索引或唯一索引的前缀扫描,可能返回1行或多行。
eq_ref索引唯一扫描:使用唯一索引扫描(主键索引或唯一键索引),只返回1行。
const,system常量唯一扫描:使用唯一索引扫描(主键索引或唯一键索引)与常量匹配,只返回1行。
NULL:不需要访问表或索引,如:select 1+2。
ref_or_null:非唯一索引扫描,且索引列包含null。
index_merge:索引合并优化
unique_subquery:单行子查询
index_subquery:多行子查询
possible_keys:备选的索引
key :选中的索引
key_len :选中索引的字段长度
rows :扫描的行数
Extra :执行情况的说明和描述,如:using where等

18.1.4 通过show profile分析SQL
Profiling可以对某一条sql的性能进行分析,如查询SQL执行状态,System lock和Table lock,I/O消耗和CPU消耗 。
Profiling是从 mysql5.0.3版本以后才开放的。
启动profile之后,所有查询包括错误的语句都会记录在内。
关闭会话或者set profiling=0 就关闭了。(如果将profiling_history_size参数设置为0,同样具有关闭MySQL的profiling效果。)
--在mysql5.7之后,通过performance_schema.profiling表来查看。
select status,sum(duration) as "总花费时间",count(*) as "执行次数",sum(duration)/count(*) as "平均每次花费时间" from performance_schema.profiling where query_id=@query_id group by state,order by sum(duration) desc;

参数说明:
@@have_profiling 当前数据库是否支持Profiling,YES支持
@@profiling 当前数据库是否开启Profiling,1开启,0关闭
开启Profiling set profiling=1;
关闭Profiling set profiling=0;
在开启Profiling的状态下执行SQL语句,
查看SQL语句query_id:show profiles;
通过query_id查看SQL语句分析:show profile for query "query_id";
通过query_id查看SQL语句的I/O和CPU:show profile 分析类型 for query "query_id";
分析类型分为:
all 显示所有性能信息,
block io 显示块IO的次数
context switches 上下文切换相关开销
cpu 显示用户和系统的CPU使用情况
ipc 显示发送和接收的消息数量
memory 显示占用内存情况
page faults 显示页面故障
source 显示源代码的函数名称,以及在源码文件中的位置
swap 显示交换次数相关开销的信息
例子:
show profile all for query 1;
首行列说明
"Status": "query end", 状态
"Duration": "1.751142", 持续时间
"CPU_user": "0.008999", cpu用户
"CPU_system": "0.003999", cpu系统
"Context_voluntary": "98", 上下文主动切换
"Context_involuntary": "0", 上下文被动切换
"Block_ops_in": "8", 块输入操作
"Block_ops_out": "32", 块输出操作
"Messages_sent": "0", 消息发出
"Messages_received": "0", 消息接受
"Page_faults_major": "0", 主分页错误
"Page_faults_minor": "0", 次分页错误
"Swaps": "0", 交换次数
"Source_function": "mys", 源功能
"Source_file": "sql_", 源文件
"Source_line": "4465" 源代码行
首列每行信息(SQL执行状态)说明:
starting: 开始
checking permissions:检查权限
Opening tables: 打开表
init : 初始化
System lock : 系统锁
optimizing : 优化
statistics : 统计
preparing : 准备
executing : 执行
Sending data : 发送数据
Sorting result : 排序
end : 结束
query end : 查询 结束
closing tables : 关闭表 /去除TMP 表
freeing items : 释放项
cleaning up : 清理
一般只查看CPU和BLOCK IO即可:
SHOW profile CPU,BLOCK IO FOR query 1;

18.1.5 通过trace可以分析优化器如何选择执行计划
首先,打开trace,设置格式为JSON,并设置trace的内存大小(10MB),避免解析过程因默认内存(16K)过小不能完全显示。
show variables like '%optimizer_trace%';
+------------------------------+----------------------------------------------------------------------------+
| Variable_name | Value |
+------------------------------+----------------------------------------------------------------------------+
| optimizer_trace | enabled=on,one_line=off |
| optimizer_trace_features | greedy_search=on,range_optimizer=on,dynamic_range=on,repeated_subselect=on |
| optimizer_trace_limit | 1 |
| optimizer_trace_max_mem_size | 16384 |
| optimizer_trace_offset | -1
set optimizer_trace="enabled=on";
set end_markers_in_json=on;
set optimizer_trace_max_mem_size=1000000;
接着,执行SQL语句;
最终,在performance_schema.optimizer_trace表查看分析结果。

18.1.6 确定问题并采取相应的优化措施
show status; --查看系统状态
show processlist; --查看当前线程及状态
通过慢查询日志找到历史上比较慢的的SQL;
通过explain分析执行计划;
通过performance_schema.profiling分析SQL在哪个状态产生了CPU耗时时长或IO高;
通过performance_schema.optimizer_trace分析执行计划为什么和预期的不一致;
最终通过增加索引、改建索引、改写SQL的措施优化SQL执行速度。

18.2 索引问题
18.2.1 索引的存储分类
索引由存储引擎实现,每种存储引擎支持的不同类型的索引。
InnoDB仅支持B-Tree索引。
MyISAM支持B-Tree索引、R-Tree索引、Full-Text索引。
Memory支持B-Tree索引、Hash索引。
Mysql不支持函数索引,但支持前缀索引,前缀索引在Order by和Group by操作中不能使用。
前缀索引即只对某个列的前N个字符创建索引,
语法:
create index 索引名 on 表名(列名(前缀长度));
例子:
create index idx_emp_ename on emp(ename(3));
Hash索引只支持等值比较(=),不支持范围比较(<、<=、>、>=)。
B-Tree索引是将索引列构造成平衡多叉树,包括一个根节点,多个分支节点,多个叶子节点。
B-Tree索引可用于关键字等值比较、关键字范围比较和关键字前缀比较等。
创建B-Tree索引语法:
create index 索引名 on 表名(列名); -- 如果包含多个列名,则称为复合索引。
alter table 表名 add index 索引名(列名);
删除B-Tree索引语法:
drop index 索引名;
alter table 表名 drop index 索引名;
18.2.2 mysql如何使用索引
访问类型type=const
当where条件全部使用到B-Tree索引,且索引列使用等值比较,且右值为常量时访问类型type=const。
访问类型type=range
当where条件全部使用到B-Tree索引,且索引列使用范围比较,且右值为常量时访问类型type=range。
当where条件使用到B-Tree索引(前缀索引),且索引列或复合索引的首使用右模糊比较(like 'xxx%')时,访问类型type=range。
访问类型type=ref
当where条件部分使用到B-Tree索引(复合索引的首列),且为等值比较,且右值为常量时访问类型type=ref。
当where条件全部使用到B-Tree索引(复合索引)时,且首列为精确匹配,非首列为范围匹配时,访问类型type=ref。
当where条件索引列 is null时,访问类型type=ref。
当where条件部分使用到B-Tree索引(复合索引的非首列)时,将不会使用索引。
访问类型type=eq_ref
当where条件部分使用B-Tree索引(唯一索引) 时,访问类型type=eq_ref。
Extra=using where
表示通过索引找到记录的编号,再去表里查找记录的详细信息。
Extra=using index
表示通过索引找到记录的信息后,不需要再去表里查找记录的详细信息。
Extra=using index condition
表示根据复合索引首列的条件在索引里找,再根据非首列的条件在索引里进行二次过滤,最后再去表里查找记录的详细信息。

索引失效:
左模糊(like '%xxx')和全模糊(like '%xxx%')将导致索引失效;
索引列出现数据类型隐式转换时(字符列=数字)将导致索引失效;
where条件中不包括复合索引的首列时,将导致复合索引失效;
当使用索引查询到的记录是全部记录的20%以上时,使用索引比全表扫描更慢,将导致索引失效;
or操作将导致索引失效;

18.2.3 查看索引使用情况
show status like 'Handler_read%';
Handler_read_key :表示一个行被索引值读的次数,值高时考虑增加索引;
Handler_read_rnd_next :表示在数据文件中读取下一行的请求数,值高时说明全表扫描多,应考虑增加索引。

18.3 两个简单实用的优化方法
18.3.1 定期分析表和检查表
分析表语法:
analyze [local|no_write_to_binlog] table 表名,...;
用于分析和存储表的关键字分布,分析的结果将可以使系统得到准确的统计信息,使生成的SQL执行计划更准确。
分析过程会对表进行锁定。
检查表语法:
check table 表名,... [{quick|fast|medium|extended|changed}]
用于检查一个或多个表(或视图)是否有错误。

18.3.2 定期优化表
优化表语法:
optimize [local|no_write_to_binlog] table 表名,...;
用于合并空间碎片,删除语句和修改语句(由大改小)会导致产生空间碎片。
innodb_file_per_table参数可以为每个表设置独立的表空间,即每个表单独一个数据文件(.ibd),表数据和表上索引的数据均存在该文件中。
另一种回收空间碎片的方法是修改表(不修改存储引擎),alter table 表名 engine=innodb;
analyze、check、optimize、alter table执行期间都会对表进行锁定,请避开业务高峰期。

18.4 常用SQL的优化
18.4.1 大批量插入数据
对于MyISAM存储引擎,在导入数据前可disable keys,即只插入数据不插入索引。
例子:
alter table 表名 disable keys;
load data infile '/home/mysql/文件名.txt' into table 表名;
alter table 表名 enable keys;
对于InnoDB存储引擎,可以将导入数据按照主键列有序排列,关闭唯一约束检查,关闭自动提交等优化速度。
set unique_checks=0;
set autocommit=0;
load data infile '/home/mysql/文件名.txt' into table 表名;
set unique_checks=1;
set autocommit=1;

18.4.2 优化insert语句
尽量使用多行插入,以减少客户端与数据库之间的连接、关闭等消耗;
insert into 表名 values (值列表1),(值列表2),...;
使用insert delayed 语句,将数据先存放在内存队列中;
将索引文件和数据文件放在不同的磁盘上(MyISAM);
增加参数bulk_insert_buffer_size的值(MyISAM);
使用load data infile。

18.4.3 优化order by语句
检查表上的索引:
show index from 表名;
-- cardinality 不同值的个数
-- index_type 索引类型
通过索引顺序扫描的结果是有序的,不需要额外的排序操作,Extra=using index。
未通过索引顺序扫描的结果是无序的,需要对结果进行再排序,Extra=using filesort。
排序操作根据排序缓冲区(sort_buffer_size)容量设置和结果记录量大小决定是否使用磁盘文件或临时表。
filesort排序算法:将结果在参数sort_buffer_size设置的内存中进行排序;
如果该内存装载不能装载全部结果数据,将按照参数sort_buffer_size设置将结果数据分块,
每块在内存中排序后分别保存在硬盘中,最终合并各个块中结果数据输出。
sort_buffer_size为每个进程分配单独的sort buffer排序区。
1.优化的目标是:尽量减少额外的排序,通过索引直接返回数据。
当where条件和order by使用相同的索引,且是相同的升降序,否则需要额外排序。
使用索引排序的例子:
select * from 表名 order by key_part1,key_part2;
select * from 表名 order by key_part1 desc,key_part2 desc;
select * from 表名 where key_part1=xxx order by key_part1 desc,key_part2 desc; -- 范围比较将导致需要排序
需要额外排序的例子:
select * from 表名 order by key_part1 desc,key_part2 asc;
select * from 表名 order by key1,key2;
select * from 表名 where key1=xxx order by key2;
2.filesort优化
自动选择两次扫描算法或一次扫描算法来优化排序。
两次扫描算法:首先根据条件取出排序字段和行指针信息,根据排序字段在内存中排序,根据排序后的行指针顺序的回表去读行信息。
优点:内存开销少,能尽量避免排序时内存与硬盘交换数据。
缺点:需要从表中读取两次数据,且第二次读取时回产生大量的随机I/O操作。
一次扫描算法:一次性取出满足条件的行的所有信息,然后在排序区排序后输出结果。
优点:只用读取一次数据,效率较高。
缺点:内存开销大。
Mysql根据参数max_length_for_sort_data与SQL结果进行比较,参数max_length_for_sort_data更大时采用一次扫描算法;否则采用两次扫描算法。

18.4.4 优化group by语句
Mysql对所有 group by col1,col2进行排序,
如果SQL包含了相同列的 order by语句,则只会进行一次排序;
如果SQL包含了不同列的 order by语句,则会进行两次排序;
如果SQL包含了order by null 语句,则不会进行排序。

18.4.5 优化嵌套查询
子查询可以将多步操作一次完成,避免事务或者锁表。
子查询一般可等价的写成表连接,且连接的效率要优于子查询。

18.4.6 优化or条件
or 关键字可等价的替换为in或union。

18.4.7 优化分页查询
在索引上完成分页操作,再通过行指针取表中查询当前页的记录。
select * from 表名 order by 排序列 limit 之前页数*每页显示行数,每页显示行数;
在排序列上建索引后,可改写为
select * from 表名 a,(select 主键列 from 表名 order by 排序列 limit 之前页数*每页显示行数,每页显示行数) b where a.主键列=b.主键列;
把分页查询转换为位置查询,即将前一页排序列的临界值传递给下一页的查询语句作为查询条件。
上述两种方法在遇到排序列有重复值时均不能保证结果正确,不建议使用。

18.4.8 使用SQL提示
SQL提示是在SQL语句中增加提示信息告诉优化器怎么执行SQL语句。
use index提示:告诉SQL解释器使用指定的索引
select * from 表名 use index(索引名) where ...;
force index提示:告诉SQL解释器强制使用指定的索引
select * from 表名 force index(索引名) where ...;
ignore index提示:告诉SQL解释器忽略指定的索引
select * from 表名 ignore index(索引名) where ...;

18.5 常用SQL技巧
18.5.1 正则表达式的使用
正则表达式是指一个用来描述或者匹配一系列符合某个句法规则的字符串的单个字符串。
Mysql提供regexp命令来实现正则表达式,regexp中模式串是区分大小写的。
"^"在字符串开始处进行匹配,返回值:1匹配,0不匹配。
select 'abcdefg' regexp '^a';
"$"在字符串末尾处进行匹配,返回值:1匹配,0不匹配。
select 'abcdefg' regexp 'g$';
"."匹配任意单个字符,包括换行符,返回值:1匹配,0不匹配。
select 'abcdefg' regexp '.h','abcdefg' regexp '.f'; -- 查找字符串中是否包含.后个哪个字符
"[...]"匹配[]内的任意字符,返回值:1匹配,0不匹配。
select 'abcdefg' regexp "[fhk]"; -- 查找字符串中是否包含[]内的任意一个或多个字符
"[^...]"不匹配[]内的任意字符,返回值:0匹配,1不匹配。
select 'abcdefg' regexp "[^hk]"; -- 查找字符串中是否不包含[]内的任意一个或多个字符
a* 匹配0个或多个a
a+ 匹配1个或多个a
a? 匹配0个或1个a
a|b 匹配a或b
a(m) 匹配m个a
a(m,) 匹配大于等于m个a
a(,n) 匹配0到n个a
a(m,n) 匹配m个到n个a
(...) 将模式元素组成单一元素

18.5.2 Rand()函数
Rand()函数 产生随机数;
order by rand() 对记录进行随机排序;
order by rand() limit n 随机输出n行记录。
随机抽样例子:
select * from 表名 order by rand() limit 100;

18.5.3 利用group by的with rollup子句
SQL中使用group by的with rollup子句可以对每个分组的信息再进行聚合。
例子1:对所有分组再次聚合
select 分组列1,sum(聚合列),count(聚合列),avg(聚合列),max(聚合列),min(聚合列)
from 表名
where 1=1 group by 分组列1 with rollup;
例子2:先对分组按分组列1再聚合,再对所有分组再次聚合
select 分组列1,分组列2,sum(聚合列),count(聚合列),avg(聚合列),max(聚合列),min(聚合列)
from 表名
where 1=1 group by 分组列1,分组列2 with rollup;
注意:with rollup子句后不能再跟order by子句,但可以跟limit子句。

18.5.4 用bit group functions做统计
bit_and()函数:按位与操作;
bit_or()函数:按位或操作;
例子:分组后对聚合列进行按位与、按位或操作
select 分组列,bit_and(聚合列),bit_or(聚合列) from 表名 where 1=1 group by 分组列;

18.5.5 数据库名、表名大小写问题
操作系统大小写敏感性决定了数据库名、表名、表别名的大小写敏感性。
Unix和Linux对大小写是敏感的,Windows对大小写不敏感。
建议数据库名、表名统一采用大写或小写。
列名、索引名、存储过程名、函数名、触发器名在所有平台大小写均不敏感。
Mysql参数lower_case_tables_name=0,依据操作系统大小写敏感性保存并查找;
Mysql参数lower_case_tables_name=1,mysql自动转小写保存并查找;
Mysql参数lower_case_tables_name=2,依据操作系统大小写敏感性保存,mysql自动转小写查找。

18.5.6 使用外键需要注意的问题
InnoDB存储引擎支持外键;其余存储引擎不支持外键(不会报错,但不存在外键约束)。

18.6 小结

转载于:https://www.cnblogs.com/BradMiller/p/10123397.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值