侯体宗的博客
  • 首页
  • Hyperf版
  • beego仿版
  • 人生(杂谈)
  • 技术
  • 关于我
  • 更多分类
    • 文件下载
    • 文字修仙
    • 中国象棋ai
    • 群聊
    • 九宫格抽奖
    • 拼图
    • 消消乐
    • 相册

sql语句优化的一般步骤详解

数据库  /  管理员 发布于 5年前   231

前言

本文主要给大家分享了关于sql语句优化的一般步骤,分享出来供大家参考学习,下面话不多说了,来一起看看详细的介绍吧。

一、通过 show status 命令了解各种 sql 的执行频率

mysql 客户端连接成功后,通过 show [session|global] status 命令可以提供服务器状态信息,也可以在操作系统上使用 mysqladmin extend-status 命令获取这些消息。

show status 命令中间可以加入选项 session(默认) 或 global:

  • session (当前连接)
  • global (自数据上次启动至今)
# Com_xxx 表示每个 xxx 语句执行的次数。mysql> show status like 'Com_%';

我们通常比较关心的是以下几个统计参数:

  • Com_select : 执行 select 操作的次数,一次查询只累加 1。
  • Com_insert : 执行 insert 操作的次数,对于批量插入的 insert 操作,只累加一次。
  • Com_update : 执行 update 操作的次数。
  • Com_delete : 执行 delete 操作的次数。

上面这些参数对于所有存储引擎的表操作都会进行累计。下面这几个参数只是针对 innodb 的,累加的算法也略有不同:

  • Innodb_rows_read : select 查询返回的行数。
  • Innodb_rows_inserted : 执行 insert 操作插入的行数。
  • Innodb_rows_updated : 执行 update 操作更新的行数。
  • Innodb_rows_deleted : 执行 delete 操作删除的行数。

通过以上几个参数,可以很容易地了解当前数据库的应用是以插入更新为主还是以查询操作为主,以及各种类型的 sql 大致的执行比例是多少。对于更新操作的计数,是对执行次数的计数,不论提交还是回滚都会进行累加。

对于事务型的应用,通过 Com_commit 和 Com_rollback 可以了解事务提交和回滚的情况,对于回滚操作非常频繁的数据库,可能意味着应用编写存在问题。

此外,以下几个参数便于用户了解数据库的基本情况:

  • Connections : 试图连接 mysql 服务器的次数。
  • Uptime : 服务器工作时间。
  • Slow_queries : 慢查询次数。

二、定义执行效率较低的 sql 语句

1. 通过慢查询日志定位那些执行效率较低的 sql 语句,用 --log-slow-queries[=file_name] 选项启动时,mysqld 写一个包含所有执行时间超过 long_query_time 秒的 sql 语句的日志文件。

2. 慢查询日志在查询结束以后才记录,所以在应用反映执行效率出现问题的时候慢查询日志并不能定位问题,可以使用 show processlist 命令查看当前 mysql 在进行的线程,包括线程的状态、是否锁表等,可以实时的查看 sql 的执行情况,同时对一些锁表操作进行优化。

三、通过 explain 分析低效 sql 的执行计划

测试数据库地址:https://downloads.mysql.com/docs/sakila-db.zip(本地下载)

统计某个 email 为租赁电影拷贝所支付的总金额,需要关联客户表 customer 和 付款表 payment , 并且对付款金额 amount 字段做求和(sum) 操作,相应的执行计划如下:

mysql> explain select sum(amount) from customer a , payment b where a.customer_id= b.customer_id and a.email='[email protected]'\G *************************** 1. row ***************************  id: 1 select_type: SIMPLE table: a partitions: NULL  type: ALLpossible_keys: PRIMARY  key: NULL key_len: NULL  ref: NULL  rows: 599 filtered: 10.00 Extra: Using where*************************** 2. row ***************************  id: 1 select_type: SIMPLE table: b partitions: NULL  type: refpossible_keys: idx_fk_customer_id  key: idx_fk_customer_id key_len: 2  ref: sakila.a.customer_id  rows: 26 filtered: 100.00 Extra: NULL2 rows in set, 1 warning (0.00 sec)
  • select_type: 表示 select 类型,常见的取值有:
         simple:简单表,及不使用表连接或者子查询
         primary:主查询,即外层的查询
         union:union 中的第二个或后面的查询语句
         subquery: 子查询中的第一个 select
  • table : 输出结果集的表
  • type : 表示 mysql 在表中找到所需行的方式,或者叫访问类型,常见类型性能由差到最好依次是:all、index、range、ref、eq_ref、const,system、null:

1.type=ALL,全表扫描,mysql 遍历全表来找到匹配的行:

mysql> explain select * from film where rating > 9 \G*************************** 1. row ***************************  id: 1 select_type: SIMPLE table: film partitions: NULL type: ALLpossible_keys: NULL  key: NULL key_len: NULL  ref: NULL rows: 1000 filtered: 33.33 Extra: Using where1 row in set, 1 warning (0.01 sec)

2.type=index, 索引全扫描,mysql 遍历整个索引来查询匹配的行

mysql> explain select title form film\G*************************** 1. row ***************************  id: 1 select_type: SIMPLE table: film partitions: NULL type: indexpossible_keys: NULL  key: idx_title key_len: 767  ref: NULL rows: 1000 filtered: 100.00 Extra: Using index1 row in set, 1 warning (0.00 sec)

3.type=range,索引范围扫描,常见于<、<=、>、>=、between等操作:

mysql> explain select * from payment where customer_id >= 300 and customer_id <= 350 \G *************************** 1. row ***************************  id: 1 select_type: SIMPLE table: payment partitions: NULL type: rangepossible_keys: idx_fk_customer_id  key: idx_fk_customer_id key_len: 2  ref: NULL rows: 1350 filtered: 100.00 Extra: Using index condition1 row in set, 1 warning (0.07 sec)

4.type=ref, 使用非唯一索引扫描或唯一索引的前缀扫描,返回匹配某个单独值的记录行,例如:

mysql> explain select * from payment where customer_id = 350 \G *************************** 1. row ***************************  id: 1 select_type: SIMPLE table: payment partitions: NULL type: refpossible_keys: idx_fk_customer_id  key: idx_fk_customer_id key_len: 2  ref: const rows: 23 filtered: 100.00 Extra: NULL1 row in set, 1 warning (0.01 sec)

索引 idx_fk_customer_id 是非唯一索引,查询条件为等值查询条件 customer_id = 350, 所以扫描索引的类型为 ref。ref 还经常出现在 join 操作中:

mysql> explain select b.*, a.* from payment a,customer b where a.customer_id = b.customer_id \G*************************** 1. row ***************************  id: 1 select_type: SIMPLE table: b partitions: NULL type: ALLpossible_keys: PRIMARY  key: NULL key_len: NULL  ref: NULL rows: 599 filtered: 100.00 Extra: NULL*************************** 2. row ***************************  id: 1 select_type: SIMPLE table: a partitions: NULL type: refpossible_keys: idx_fk_customer_id  key: idx_fk_customer_id key_len: 2  ref: sakila.b.customer_id rows: 26 filtered: 100.00 Extra: NULL2 rows in set, 1 warning (0.00 sec)

5.type=eq_ref,类似 ref,区别就在使用的索引时唯一索引,对于每个索引的键值,表中只要一条记录匹配;简单的说,就是多表连接中使用 primary key 或者 unique index 作为关联条件。

mysql> explain select * from film a , film_text b where a.film_id = b.film_id \G*************************** 1. row ***************************  id: 1 select_type: SIMPLE table: b partitions: NULL type: ALLpossible_keys: PRIMARY  key: NULL key_len: NULL  ref: NULL rows: 1000 filtered: 100.00 Extra: NULL*************************** 2. row ***************************  id: 1 select_type: SIMPLE table: a partitions: NULL type: eq_refpossible_keys: PRIMARY  key: PRIMARY key_len: 2  ref: sakila.b.film_id rows: 1 filtered: 100.00 Extra: Using where2 rows in set, 1 warning (0.03 sec)

6.type=const/system,单表中最多有一个匹配行,查起来非常迅速,所以这个匹配行中的其他列的值可以被优化器在当前查询中当作常量来处理,例如,根据主键 primary key 或者唯一索引 unique index 进行查询。

mysql> create table test_const ( ->  test_id int, ->  test_context varchar(10), ->  primary key (`test_id`), -> ); insert into test_const values(1,'hello');explain select * from ( select * from test_const where test_id=1 ) a \G*************************** 1. row ***************************  id: 1 select_type: SIMPLE table: test_const partitions: NULL type: constpossible_keys: PRIMARY  key: PRIMARY key_len: 4  ref: const rows: 1 filtered: 100.00 Extra: NULL 1 row in set, 1 warning (0.00 sec)

7.type=null, mysql 不用访问表或者索引,直接就能够得到结果:

mysql> explain select 1 from dual where 1 \G*************************** 1. row ***************************  id: 1 select_type: SIMPLE table: NULL partitions: NULL type: NULLpossible_keys: NULL  key: NULL key_len: NULL  ref: NULL rows: NULL filtered: NULL Extra: No tables used1 row in set, 1 warning (0.00 sec)

  类型 type 还有其他值,如 ref_or_null (与 ref 类似,区别在于条件中包含对 null 的查询)、index_merge(索引合并优化)、unique_subquery (in 的后面是一个查询主键字段的子查询)、index_subquery(与 unique_subquery 类似,区别在于 in 的后面是查询非唯一索引字段的子查询)等。

  • possible_keys : 表示查询时可能使用的索引。
  • key :表示实际使用索引
  • key-len : 使用到索引字段的长度。
  • rows : 扫描行的数量
  • extra:执行情况的说明和描述,包含不适合在其他列中显示但是对执行计划非常重要的额外信息。

show warnings 命令

执行explain 后再执行 show warnings,可以看到sql 真正被执行之前优化器做了哪些 sql 改写:

MySQL [sakila]> explain select sum(amount) from customer a , payment b where 1=1 and a.customer_id = b.customer_id and email = '[email protected]'\G*************************** 1. row ***************************  id: 1 select_type: SIMPLE table: a partitions: NULL  type: ALLpossible_keys: PRIMARY  key: NULL key_len: NULL  ref: NULL  rows: 599 filtered: 10.00 Extra: Using where*************************** 2. row ***************************  id: 1 select_type: SIMPLE table: b partitions: NULL  type: refpossible_keys: idx_fk_customer_id  key: idx_fk_customer_id key_len: 2  ref: sakila.a.customer_id  rows: 26 filtered: 100.00 Extra: NULL2 rows in set, 1 warning (0.00 sec)MySQL [sakila]> show warnings;+-------+------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+| Level | Code | Message       |+-------+------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+| Note | 1003 | /* select#1 */ select sum(`sakila`.`b`.`amount`) AS `sum(amount)` from `sakila`.`customer` `a` join `sakila`.`payment` `b` where ((`sakila`.`b`.`customer_id` = `sakila`.`a`.`customer_id`) and (`sakila`.`a`.`email` = '[email protected]')) |+-------+------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+1 row in set (0.00 sec)

从 warning 的 message 字段中能够看到优化器自动去除了 1=1 恒成立的条件,也就是说优化器在改写 sql 时会自动去掉恒成立的条件。

explain 命令也有对分区的支持.

MySQL [sakila]> CREATE TABLE `customer_part` ( -> `customer_id` smallint(5) unsigned NOT NULL AUTO_INCREMENT, -> `store_id` tinyint(3) unsigned NOT NULL, -> `first_name` varchar(45) NOT NULL, -> `last_name` varchar(45) NOT NULL, -> `email` varchar(50) DEFAULT NULL, -> `address_id` smallint(5) unsigned NOT NULL, -> `active` tinyint(1) NOT NULL DEFAULT '1', -> `create_date` datetime NOT NULL, -> `last_update` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, -> PRIMARY KEY (`customer_id`) ->  -> ) partition by hash (customer_id) partitions 8;Query OK, 0 rows affected (0.06 sec)MySQL [sakila]> insert into customer_part select * from customer;Query OK, 599 rows affected (0.06 sec)Records: 599 Duplicates: 0 Warnings: 0MySQL [sakila]> explain select * from customer_part where customer_id=130\G*************************** 1. row ***************************  id: 1 select_type: SIMPLE table: customer_part partitions: p2  type: constpossible_keys: PRIMARY  key: PRIMARY key_len: 2  ref: const  rows: 1 filtered: 100.00 Extra: NULL1 row in set, 1 warnings (0.00 sec)

可以看到 sql 访问的分区是 p2。

四、通过 performance_schema 分析 sql 性能

旧版本的 mysql 可以使用 profiles 分析 sql 性能,我用的是5.7.18的版本,已经不允许使用 profiles 了,推荐用
performance_schema 分析sql。

五、通过 trace 分析优化器如何选择执行计划。

mysql5.6 提供了对 sql 的跟踪 trace,可以进一步了解为什么优化器选择 A 执行计划而不是 B 执行计划,帮助我们更好的理解优化器的行为。

使用方式:首先打开 trace ,设置格式为 json,设置 trace 最大能够使用的内存大小,避免解析过程中因为默认内存过小而不能够完整显示。

MySQL [sakila]> set optimizer_trace="enabled=on",end_markers_in_json=on;Query OK, 0 rows affected (0.00 sec)MySQL [sakila]> set optimizer_trace_max_mem_size=1000000;Query OK, 0 rows affected (0.00 sec)

接下来执行想做 trace 的 sql 语句,例如像了解租赁表 rental 中库存编号 inventory_id 为 4466 的电影拷贝在出租日期 rental_date 为 2005-05-25 4:00:00 ~ 5:00:00 之间出租的记录:

mysql> select rental_id from rental where 1=1 and rental_date >= '2005-05-25 04:00:00' and rental_date <= '2005-05-25 05:00:00' and inventory_id=4466;+-----------+| rental_id |+-----------+| 39 |+-----------+1 row in set (0.06 sec)MySQL [sakila]> select * from information_schema.optimizer_trace\G*************************** 1. row ***************************    QUERY: select * from infomation_schema.optimizer_trace    TRACE: { "steps": [ ] /* steps */}MISSING_BYTES_BEYOND_MAX_MEM_SIZE: 0  INSUFFICIENT_PRIVILEGES: 01 row in set (0.00 sec)

六、 确定问题并采取相应的优化措施

经过以上步骤,基本就可以确认问题出现的原因。此时可以根据情况采取相应的措施,进行优化以提高执行的效率。

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作能带来一定的帮助,如果有疑问大家可以留言交流,谢谢大家对的支持。


  • 上一条:
    寻找sql注入的网站的方法(必看)
    下一条:
    PostgreSQL 正则表达式 常用函数的总结
  • 昵称:

    邮箱:

    0条评论 (评论内容有缓存机制,请悉知!)
    最新最热
    • 分类目录
    • 人生(杂谈)
    • 技术
    • linux
    • Java
    • php
    • 框架(架构)
    • 前端
    • ThinkPHP
    • 数据库
    • 微信(小程序)
    • Laravel
    • Redis
    • Docker
    • Go
    • swoole
    • Windows
    • Python
    • 苹果(mac/ios)
    • 相关文章
    • 分库分表的目的、优缺点及具体实现方式介绍(0个评论)
    • DevDB - 在 VS 代码中直接访问数据库(0个评论)
    • 在ubuntu系统中实现mysql数据存储目录迁移流程步骤(0个评论)
    • 在mysql中使用存储过程批量新增测试数据流程步骤(0个评论)
    • php+mysql数据库批量根据条件快速更新、连表更新sql实现(0个评论)
    • 近期文章
    • 在go中实现一个常用的先进先出的缓存淘汰算法示例代码(0个评论)
    • 在go+gin中使用"github.com/skip2/go-qrcode"实现url转二维码功能(0个评论)
    • 在go语言中使用api.geonames.org接口实现根据国际邮政编码获取地址信息功能(1个评论)
    • 在go语言中使用github.com/signintech/gopdf实现生成pdf分页文件功能(0个评论)
    • gmail发邮件报错:534 5.7.9 Application-specific password required...解决方案(0个评论)
    • 欧盟关于强迫劳动的规定的官方举报渠道及官方举报网站(0个评论)
    • 在go语言中使用github.com/signintech/gopdf实现生成pdf文件功能(0个评论)
    • Laravel从Accel获得5700万美元A轮融资(0个评论)
    • 在go + gin中gorm实现指定搜索/区间搜索分页列表功能接口实例(0个评论)
    • 在go语言中实现IP/CIDR的ip和netmask互转及IP段形式互转及ip是否存在IP/CIDR(0个评论)
    • 近期评论
    • 122 在

      学历:一种延缓就业设计,生活需求下的权衡之选中评论 工作几年后,报名考研了,到现在还没认真学习备考,迷茫中。作为一名北漂互联网打工人..
    • 123 在

      Clash for Windows作者删库跑路了,github已404中评论 按理说只要你在国内,所有的流量进出都在监控范围内,不管你怎么隐藏也没用,想搞你分..
    • 原梓番博客 在

      在Laravel框架中使用模型Model分表最简单的方法中评论 好久好久都没看友情链接申请了,今天刚看,已经添加。..
    • 博主 在

      佛跳墙vpn软件不会用?上不了网?佛跳墙vpn常见问题以及解决办法中评论 @1111老铁这个不行了,可以看看近期评论的其他文章..
    • 1111 在

      佛跳墙vpn软件不会用?上不了网?佛跳墙vpn常见问题以及解决办法中评论 网站不能打开,博主百忙中能否发个APP下载链接,佛跳墙或极光..
    • 2017-06
    • 2017-08
    • 2017-09
    • 2017-10
    • 2017-11
    • 2018-01
    • 2018-05
    • 2018-10
    • 2018-11
    • 2020-02
    • 2020-03
    • 2020-04
    • 2020-05
    • 2020-06
    • 2020-07
    • 2020-08
    • 2020-09
    • 2021-02
    • 2021-04
    • 2021-07
    • 2021-08
    • 2021-11
    • 2021-12
    • 2022-02
    • 2022-03
    • 2022-05
    • 2022-06
    • 2022-07
    • 2022-08
    • 2022-09
    • 2022-10
    • 2022-11
    • 2022-12
    • 2023-01
    • 2023-03
    • 2023-04
    • 2023-05
    • 2023-07
    • 2023-08
    • 2023-10
    • 2023-11
    • 2023-12
    • 2024-01
    • 2024-03
    Top

    Copyright·© 2019 侯体宗版权所有· 粤ICP备20027696号 PHP交流群

    侯体宗的博客