您的位置:首页 > 数据库 > MySQL

mysql之sql语句优化

2020-07-13 05:02 555 查看

mysql之sql语句优化

写此博客的原因

2020.04.16面试了招银科技的java开发工程师的职位,感觉sql语句优化的条理没有捋清楚,于此我的心情状态时下面这样的,我要找点事情去抚平内心的小悸动。
然后时刻提醒自己,上帝既然创造了我们,为什么我们不把上帝给揍一顿,警示他说,小样,还想难到我,你不想活了。

为什么我们的Sql语句需要进行优化?

去他哔的优化,哔哔东西,我们的服务器既然能够还能运行的好好的,Sql语句优化个屁呀。我要举报这个天天就知道吹牛皮的家伙,需求已经让我们疲于奔命了,还要搞什么性能优化,简直吃饱了撑着。
做为对自己有要求的程序员,都会有一个梦想,就是能够达到远方。看,这宁静而致远的生活,阳光夕阳西下。你看,这无尽的小草,多想躺下来,享受下无穷无尽的宁静和安稳。

啪,啪,啪 --快别做梦了,想要诗和远方,那你就好好的理解sql优化吧。
回到主题,我认为分成两种。一种,是被迫的。一种,就是自我要求很高的。
被迫的:生产和测试出来发现你不符合规范,我们要的10ms,你给我走个1s,你是不是厕所里点灯-找*呀。
自我要求很高的:远方还是有可能的,回想下上面美好的期盼,未来在等着你呢。

怎么去测试我们的Sql语句需要进行优化呢?

慢sql的来源。来源一我们日常编码发现mysql执行过慢。来源二数据库慢sql日志。来源三接口链路监控,去分析出相应的慢sql。

通过explain执行计划

+----+-------------+-----------+-------------+------+---------------+---------+---------+-------+------+----------+-------+
| id | select_type | table     | partitions  | type | possible_keys | key     | key_len | ref   | rows | filtered | Extra |
+----+-------------+-----------+-------------+------+---------------+---------+---------+-------+------+----------+-------+
|  1 | SIMPLE      | employees | p0,p1,p2,p3 | ref  | PRIMARY       | PRIMARY | 4       | const |    1 |      100 | NULL  |
+----+-------------+-----------+-------------+------+---------------+---------+---------+-------+------+----------+-------+
1 row in set
partitions type possible_keys key key_len ref rows filtered Extra
匹配的分区 描述如何联接表 指示MySQL可以从中选择查找此表中的行的索引 实际上决定使用索引 MySQL决定使用的索引的数量 与索引比较的列 评估执行的行数 已过滤数据的百分之几 输出额外信息

如根据type去判定当前连表类型(system>const>eq_ref>ref>range>index>ALL),如果type为ALL的时候,往往查询效率比较差,则需要进行优化了。filter和rows去判断过滤的数据,如果rows行数非常的大,则说明可能是需要做其他优化来减少扫描的行数。

通过查看响应时间
判断响应时间是否符合你当前的需求,进行评估是否满足你的需求,不满足你的需求,你就得做别的方案进行优化了。

[SQL]select * from employees where id = '1';
受影响的行: 0
时间: 0.011s

通过执行状态
通过输入输出的字节数去评估是否是输出数据过大,导致时间太长

通过从硬盘读取键的数据块的次数和从硬盘写键的数据块的次数,判断是否磁盘查出太多数据或者其他情况导致了查询非常慢。

慢查询日志信息获取慢sql

Sql语句优化–引擎

主流数据引擎innodb和myisam的选择
innodb和myisam引擎的区别:

区别 innodb myisam
业务 支持事务、适合大量更新和并发量高的业务 适合大量查询的业务
锁级别 支持行锁和表锁 只支持行锁
存储结构 索引和数据存取在同一个文件 索引和数据在不同的文件
碎片空间 不会存在碎片,因为会有定时的去清理碎片的机制 存在碎片,需要定期通过optimize table table-name手动优化

Sql语句优化–字段

表字段的优化方案

优化方案列表 理由
尽可能使用整形去替换浮点型 节约存储空间,且查询比较快
尽可能使用not null 首先null可能会导致我们业务程序空指针。其次null的话是占内存的,而空字符是不占内存的
char和varchar的选择 定长char适合固定大小的字符,其字符相同与varchar相比较,要少一个字节,且比较效率。不定长varchar适合不固定大小的字符
范式选择 符合相应的范式能够减少数据的冗余

NULL 和’'的区别:NULL与其他任何值(甚至)相比,永远不会为真,所以只能是 is NULL。 COUNT(), MIN(),和 SUM()忽略 NULL的值。
三大范式:
第一范式-保证列的原子性
第二范式-消除非主属性与主属性的部分依赖
第三方式-消除非主属性与主属性的传递依赖

Sql语句优化–索引

索引类型选择

索引类型 区别
普通索引 普通索引会存取具体的索引值和行指针,索引可以在一定条件使用覆盖索引,支持范围查询和排序
哈希索引 哈希索引只包含哈希值和行指针,而不存储字段值,所以不会存在普通索引的覆盖索引,避免二次查询。哈希索引数据并不是按照索引值顺序存储的 ,对于范围查询就会使得索引失效,排序非常耗性能

哈希索引常常使用在没有范围查询的条件中,像身份证id之类的

索引失效原因

索引失效列表 原因
不满足最左原则 因为mysql查询的时候,是根据最左原则进行查询的
使用OR,但存在左右存在一个没有索引 因为查询的时候,存在一个没有走索引,那么是需要全表扫描的
索引字段上使用not,<>,!= 因为在实际执行的时候,会导致索引失效
组合索引没有使用最左索引键 最左原则,会导致索引失效

Sql语句优化–缓存

缓存类型 注意事项
使用mysql的查询缓存 即只能是sql语句是一模一样的,否则缓存失效
使用应用程序缓存 存在着数据一致性的问题

Sql语句优化–分库分表

为什么需要进行分库分表操作呢?
因为单库单表的数据都有其的限制,包括磁盘限制、连接限制、数据量限制

分成几种方式
垂直拆分(针对的是列)
垂直分库:即将不同业务分配到不同的数据库中,减少单库的压力。如果将单机改成多台机器的话,会减少单机cpu的占用和磁盘的使用。
垂直分表:即可能存在一个数据量非常大的表。可能是因为数据字段非常多,导致了数据量的增加,于是可以将多个表分成多个业务表。
水平拆分(针对的是单表)
水平分表:与垂直分表不同之处,在于水平分表是基于表的。而垂直分表是基于表中的列。减少单表的数据量,提高sql操作的效率
水平分库分表:水平分库分表是基于表的,将一个单表分到多个数据库和数据表中,他们之间的区别在于其数据的范围不同。从而提高磁盘的容量、增加连接数、减少cpu的消耗、突破IO的极限。

水平分表和水平分库分表策略
1、通过Hash值计算其库和表的值
2、根据时间进行分库分表
3、根据地理位置进行分库分表
4、根据范围进行分片

性能优化指标-来判断你的优化是OK

SELECT * FROM msg_info WHERE msg_id = '1';

1、前后响应时间-最直观

2、explain的执行计划
大多数都是通过type的类型判断其走到索引的快慢,性能好坏的依次下架(system>const>eq_ref>ref>range>index>ALL)
3、show profiles进行性能分析
1)首先获取到相应的查询id

SHOW PROFILES;

2)根据上面的的命令获取到Query_ID

##模板
SHOW PROFILE ALL for query Query_ID;
##实际查询-查询所有的性能指标-很多,我就不截图了
SHOW PROFILE ALL for query 78;

以下的图是这个命令的结果

##查询CPU/BLOCK_IO的性能指标
show PROFILE CPU,BLOCK IO FOR QUERY 78;

问题

出现一个问题,就是在查询订单(2000万)的时候,根据订单时间(一周)和订单状态,手机号和用户id去统计用户的消费金额,在测试环境订单量大概是(200万)数据无丝毫压力,但是在2000万数据的时候出现了超时的现象,一条大概耗时了3分钟。
背景
手机号有索引/订单时间也有索引/状态也存在索引
利用explain去进行解析发现,走的是类型是range

select sum(price) totalTradingPrice from tb_order_info where trading_status = 0 and (phone= 1823 OR user_id= 165465465) and order_time >= '2020-06-28 00:00:00.000' and order_time < '2020-07-04 00:00:00.000'

大家觉得好奇,为什么即查user_id又要查phone,直接查user_id不行吗 ?
我也问过,他们说历史遗留问题,可能存在user_id但是手机号不存在的情况,也可能存在存在手机号,但是user_id不存在的情况。(反正我就吐口水了)

发现在业务层无法解决此问题,所以就尝试利用其order_time的索引和主键索引去提高性能。
具体实现方式(缩书签法):

##根据时间段去查询最小的订单id
select min(id) from tb_order_info where order_time >= '2020-06-28 00:00:00.000' and order_time <='2020-07-04 00:00:00.000'
##然后加上上面查询的id条件,即1000
select sum(price) totalTradingPrice from tb_order_info where id >=10000 and trading_status = 0 and (phone= 1823 OR user_id= 165465465) and order_time >= '2020-06-28 00:00:00.000' and order_time < '2020-07-04 00:00:00.000'

大家又会想,为什么你不直接大于特定时间,找到最小id就不行了吗?如下面所写。

select id from tb_order_info where order_time >= '2020-06-28 00:00:00.000' limit 1

我们这边业务上是不行,因为存在一种情况就是订单的序号不一定是对应的订单时间的序号,所以我要找到最小的id,然后针对[id值,上限]的范围进行计算,当然你可以取出最大的id,然后进行[id下限,id上限]进行查询:

id order_time
12 2020-06-29 20:10:21.000
17 2020-06-28 20:10:21.000

所以最后的方案是如下sql,算出下限的id,从原来的3分钟,骤降到600多ms

##算出下限的id
select min(id) from tb_order_info where order_time >= '2020-06-28 00:00:00.000' and order_time <='2020-07-04 00:00:00.000'
##然后根据下限的id去查询
select sum(price) totalTradingPrice from tb_order_info where id >=10000 and trading_status = 0 and (phone= 1823 OR user_id= 165465465) and order_time >= '2020-06-28 00:00:00.000' and order_time < '2020-07-04 00:00:00.000'

总结:在mysql优化中,分页查询,存在两种方式可以借于大家参考的,一个缩书签法,一个是延迟关联。

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: