分享一下本周SQL优化的两个场景。

如果能对读者有一定的启发,共同探讨,不胜荣幸。

版本信息:mysql,5.7.19

引擎: innodb

场景1

我们有一张常口表,里面的数据由各种数据源合并而来,所以人员可能有多个手机号其中还包括座机号。这点在这篇文章里也分享过。https://juejin.cn/post/7234355976458518586

现在人员详情页面需展示同手机号的人员列表,同手机号是包含,而非等同关系。

在人员列表里手机号页面有做展示,那么点击跳转人员详情的时候,是可以把手机号通过URL带过来的,但前端说参数过多,不好控制,所以只传递了人员ID参数。

所以后端查询的时候先得通过主键ID把手机号查出来。之所以不一次性通过join带出手机号再关联同手机号人员,是关联与被关联人员手机号都可能存在多个。

select * 还是select 指定字段

原通过主键查询手机号的SQL,是直接用的mybatis生成器自动生成的SQL。

<select id="selectPhoneByPrimaryKey" parameterType="java.lang.String" resultMap="BaseResultMap">
select
phone
from t_person_info
where ID = #{id,jdbcType=VARCHAR}
</select>

Base_Column_List可想而知是全部字段,类似于select *,这本身没什么,但其中有一部份字段长度在几百,全部加起来也算是个大字段,全部提取对效率还是有一定的影响,所以改为select phone 查询手机一个字段。

select
phone
from t_person_info
where ID = #{id,jdbcType=VARCHAR}

更追求极致一点,可以添加一个idphone的覆盖索引,避免回表。

这一点的优化相对比较鸡肋,都在1-2ms之间看不出明显差别,但把limit放大的时候,还是能看出差距。

表数据70万左右。

select * form table limit 10000

select phone form table limit 10000

174ms vs 7ms

确实是聊胜于无。

但是到底是select * 还是select 指定字段,确实还是存在着一些争议。

一般情况下,表字段少,且不存在大字段,用select * 确实能减少许多麻烦,加减字段不用改sql,多个查询子功能可以共用等。

而且,页面查询多是分页,不太可能一下子查询10000条这种情况。

占用内存,不必要的IO,增加网络负担,拒绝覆盖索引,确实也是select *的问题。

我觉得需要根据具体情况,自行判断,没必要太过教条。

全文检索

拿到手机号以后,根据手机号去查询关联人员。

因为是包含关系,所以同事一开始用的是like模糊匹配。

 select p.id, p.id as pid,p.name,p.idcard,p.phone,count( w.EVENT_NO ) AS count
from t_person_info p
left join t_other w on w.pid = p.ID
where
<foreach collection="phones" item="phone" separator="or" open="(" close=")">
p.phone like concat("%",#{phone},"%")
</foreach>
and p.id != #{id}
group by p.id

这里的!=有可能会导致索引失效,这时候可以在sql去掉,然后在代码中过滤掉当前人员。

因为where条件中有 p.id != #{id},执行计划倒是从从ALL上升到了range。 耗时1.5秒。

将phone加上全文索引。 where 条件改为

match(p.phone) against (#{phones} IN boolean MODE) and p.id != #{id}

每个手机号需要全匹配,所以这里使用布尔模式,

因为手机号有多个,需要做到or,

又因为涉及到座机号,其中带的-可能会被mysql识别为逻辑运算符。

具体参照我写的这篇文章 https://juejin.cn/post/7234355976458518586

布尔模式的逻辑运算符

  1. +

    select * from t_user where match(phone) AGAINST('a +b' in boolean mode)

    其中 + 会被识别成逻辑运算符,而不是将a +b作为一个整体,以下同理。

    'a +b' 指'a'和'b'必须同时出现才满足搜索条件。
  2. -

    select * from t_user where match(phone) AGAINST('0797 -12345' in boolean mode)

    0797 -123450797必须包含,但不包含12345才能满足搜索条件。

    以下查询排除了包含0797-12345的记录。



    注意-前后空格 0797 -12345才表示包含0797 同时不包含12345.

    0797-12345等于0797 - 12345,它并不等于0797 -12345

    有图为证:



  3. > <

    提高/降低该条匹配数据的权重值。不管使用>还是 <,其权重值均大于没使用其中任何一个的。

    select * from t_user where match(phone) AGAINST('0797(>94649 <12345)' in boolean mode)

    表示匹配0797,同时包含94649的列往前排,包含12345的往后排

    select * from t_user where match(phone) AGAINST('a > b' in NATURAL LANGUAGE mode)

  4. ()

    相当于表达式分组,参考上一个例子。
  5. *

    通配符,只能在字符串后面使用
  6. "

    完全匹配,被双引号包起来的单词必须整个被匹配。

    select * from t_user where match(phone) AGAINST('"0797-1789"' in boolean mode)

    "0797-1789"中不可再分。其它包含0797-1234等记录就不再匹配。

  7. 空格表示 or

这里使用6,7来解决上述的两种问题。

如下SQL,与以下4个手机号其中一个全区配的人员都将被筛选出来。

#{phone}参数应为"135****6" "136****9" "1387****2" "0791-123"格式 。

耗时从1.5秒降到了2毫秒。

场景2

还是常口表,列表查询。

排序

每个用户呢会关联一些事件,无需理会什么是事件,反正这张表中的每条记录与事件表形成一对多的关联关系。

事件实时进入。然后再用户列表展示的时候需要根据关联的事件数来进行排序。

实时join关联事件表,耗时4.9秒。

sql执行计划 extra为 Using temporary; Using filesort 产生了临时表和IO文件排序。当然快不起来。

这还是在没有查询条件,以及没有深度分页的情况下。

那么很明显,需要在用户表建一个冗余字段,保存用户所关联的事件数,再对这个字段建立索引。

但这会牺牲一定的实时性。

以及需要定时任务去统计用户的关联事件数。

然后需要跟产品沟通,因为我们的产品是2B的,还需要跟客户进行沟通。

结合我们的业务场景,经过我们的努力沟通,客户认为牺牲适当的实时性,换来页面的响应效率,是值得的。

然后耗时降到了3毫秒。

一旦 where having order by 里的字段是通过max,min,count等计算出来的虚拟字段,那么肯定会产生 Using temporary; Using filesort 临时表和IO文件排序。

要想办法消灭,不管从业务还是技术上。

适当的建立冗余字段,或者宽表。

但阿里巴巴java开发手册,禁止3张表以上的关联,毕竟只是比较理想的状态。

幸福的公司都是 相似 的;不幸的公司我看也有相似不幸。

不外乎难搞的产品,多变的客户,睿(s)智(13)的老板。

深度分页

上面小节同样的sql,首页查询只需耗时2ms,但是到了700000以后,耗时达到了2.6秒。

这就是著名的mysql深度分页的问题。

通过执行计划,可以明显的看出,mysql会将前 700015条数据取出来,然后丢掉前700000条,只取后15条数据。

前面读取的700000条数据是不必要耗时操作。

解决深度分页的方式有几种。 看具体情况,没有通用的办法。

利用覆盖索引

或者叫利用不回表。

这里为了便利,用主键索引id来演示,innodb下,主键索引为聚簇索引,本身就是回表啦,相当于普通索引省掉了回表操作。

如此查询只需200毫秒左右。

但是,这里不合适把需要展示的字段全部建成一个覆盖索引。

利用覆盖索引延迟关联

先通过覆盖索引把id拿到,再把这15条数据去关联一次拿到其它字段不就好了吗?

select p.id ,p.name,p.idcard,p.phone
from t_person_info p
inner join (select id from t_person_info order by EVENTCOUNT desc limit 700000,15) p2 on p.id = p2.id

如此同样只需要200毫秒左右。

其它方式

其它方式,通过记录上次的位置,通过子查询,都只适用于id为自增主键的情况。

不适用我的这个业务场景。

类似于 这样的SQL

select id ,name,idcard,phone,EVENTCOUNT from t_person_info where id <=(select id from t_person_info order by EVENTCOUNT limit 700000, 1) limit 15;

由于历史友商等原因,我们的数据ID有部份是UUID,它是不连续的,且人员关联事件数EVENTCOUNT也不连续,大量的人员集中在某一个数量上,这都使得此种方式不可取。

分页插件

在做列表展示时肯定需要分页,分页就需要查询总数。

分页插件pagehelper默认会生成一个查询总数的方法。

假如mapper查询方法为selectList(),那么查询总数的方法名为selectList_COUNT()。

对应的SQL为SELECT count(0) FROM 原sql

在一些比较比较简单的SQL的时候,分页的SQL还是会进行重写,比较去掉多余的select字段,不必要的排序等。

但当SQL比较复杂的时候,那就是直接在原SQL上包一层select count(0)。

这个时候我们就可以自已去实现这个selectList_COUNT()这个方法,让它执行效率更高的自定义SQL.

完。

近期SQL优化实战分享的更多相关文章

  1. Hive使用Calcite CBO优化流程及SQL优化实战

    目录 Hive SQL执行流程 Hive debug简单介绍 Hive SQL执行流程 Hive 使用Calcite优化 Hive Calcite优化流程 Hive Calcite使用细则 Hive向 ...

  2. MySql Sql 优化技巧分享

    有天发现一个带inner join的sql 执行速度虽然不是很慢(0.1-0.2),但是没有达到理想速度.两个表关联,且关联的字段都是主键,查询的字段是唯一索引. sql如下: SELECT p_it ...

  3. SQL优化实战之加索引

    有朋友和我说他的虚机里面的mysql无法跑sql,但是在本地环境是这个sql是可以跑出来的.碰到这个问题第一反应是:死锁. 于是让他查询数据库的几个状态: 发现连即时锁都非常少,不是锁的问题. 进一步 ...

  4. 一个MySql Sql 优化技巧分享

    有天发现一个带inner join的sql 执行速度虽然不是很慢(0.1-0.2),但是没有达到理想速度.两个表关联,且关联的字段都是主键,查询的字段是唯一索引. sql如下: SELECT p_it ...

  5. sql优化实战:从1353秒到135秒(删除索引+修改数据+重建索引)

    最近在优化日结存储过程,日结存储过程中大概包含了20多个存储过程. 发现其有一个存储过程代码有问题,进一步发现结存的数据中有一个 日期字段business_date 是有问题的,这个字段对应的类型是v ...

  6. 关于数据库SQL优化

    1.数据库访问优化   要正确的优化SQL,我们需要快速定位能性的瓶颈点,也就是说快速找到我们SQL主要的开销在哪里?而大多数情况性能最慢的设备会是瓶颈点,如下载时网络速度可能会是瓶颈点,本地复制文件 ...

  7. 工作中遇到的99%SQL优化,这里都能给你解决方案

    前几篇文章介绍了mysql的底层数据结构和mysql优化的神器explain.后台有些朋友说小强只介绍概念,平时使用还是一脸懵,强烈要求小强来一篇实战sql优化,经过周末两天的整理和总结,sql优化实 ...

  8. sql优化(原理,方法,特点,实例)

    整理的有点多,做好心理准备...... 1.资源优化理解: 不同设备,io不同.每种设备都有两个指标:延时(响应时间):表示硬件的突发处理能力:带宽(吞吐量):代表硬件持续处理能力. 每种硬件主要的工 ...

  9. 数据库sql优化总结之4--SQL优化总结

    一.问题的提出 在应用系统开发初期,由于开发数据库数据比较少,对于查询SQL语句,复杂视图的的编写等体会不出SQL语句各种写法的性能优劣,但是如果将应用系统提交实际应用后,随着数据库中数据的增加,系统 ...

  10. SQL Server 性能优化实战系列(一)

    数据库服务器主要用于存储.查询.检索企业内部的信息,因此需要搭配专用的数据库系统,对服务器的兼容性.可靠性和稳定性等方面都有很高的要求.        下面是进行笼统的技术点说明,为的是让大家有一个整 ...

随机推荐

  1. Spring--依赖注入:setter注入和构造器注入

    依赖注入:描述了在容器中建立Bean于Bean之间依赖关系的过程 setter注入 在本来已经在service里面引用了bean的相关方法的基础上,再引用之前已经写过的userDao的对象,即在ser ...

  2. CSPS2019 括号树 题解

    链的部分分 我们设f[i]表示以i结尾的括号序列有多少个,那么i的实际答案就是f的前缀和 显然,所有左括号和不能匹配的右括号的f均为0 对于每一个能匹配的右括号i,我们找到与之匹配的左括号p,以i结尾 ...

  3. 太坑了,我竟然从RocketMQ源码中扒出了7种导致消息重复消费的原因

    大家好,我是三友~~ 在众多关于MQ的面试八股文中有这么一道题,"如何保证MQ消息消费的幂等性". 为什么需要保证幂等性呢?是因为消息会重复消费. 为什么消息会重复消费? 明明已经 ...

  4. flutter widget---->Spacer

    如果你想灵活控制Flex容器(Row, Column)中子组件中的间隔,可以考虑使用Spacer.下面以Row为例子,来为它的子组件添加间距. use Spacer import 'package:f ...

  5. Python爬虫基础教程2

    beautifulsoup4介绍/遍历文档树 bs4 > 从html或xml文件中提取的python库 用它来解析爬取回来的xml 安装:pip install beautifulsoup4 p ...

  6. TypeScript 学习笔记 — 基于对象操作的内置类型的使用(十二)

    目录 1.Partial 转化可选属性 (?) 2.Required 转化必填属性 (-?) 3.Readonly 转化仅读属性 (readonly) Mutate(非内置,与 Readonly 相对 ...

  7. 游戏模拟——Position based dynamics

    目录 Verlet积分 基本积分方法 Verlet 算位置 Verlet 算速度 PBD 基于力的方法解碰撞 过冲问题 基于位置的方法解碰撞 算法流程 求解器借用的思想 关于动量守恒 约束投影 简单约 ...

  8. CentOS8 搭建zabbix监控系统

    哈喽,有些时间没有更新公众号.今日更新一下. 安装MySQL数据库 # 安装wget [root@cby ~]# dnf install wget -y # 下载MySQL源 [root@cby ~] ...

  9. 免费,小巧好用的pdf阅读器以及护眼模式颜色代码

    免费,迷你,小巧pdf阅读器 https://www.sumatrapdfreader.org/downloadafter 网络上流行的眼神RGB值和颜色代码 绿色豆沙可以有效减轻长时间使用电脑的眼睛 ...

  10. pysimplegui之系统托盘图标创建

    在 PySimpleGUI(tkinter 版本)上运行时,系统托盘图标为 PNG 和 GIF 格式.PNG.GIF 和 ICO 格式适用于 Wx 和 Qt 端口. 指定"图标"时 ...