亿级表优化思路-SQL 篇

初六坐高铁回家,看着窗外铁轨我有一个疑问为啥铁轨旁边有一堆小石头呢?

看着铁轨要不写几篇性能优化吧(这几天我正愁写啥呢),刚好有丢丢经验可以总结下。

拿出电脑打开 XMID 构思中......

于是初八回成都,默默拿出电脑......

好了先介绍下想法吧,亿级表优化分 2 讲,第 1 讲分享 SQL 和索引优化;第 2 讲分享表分区和冷热表分离。

写作背景

为啥会写亿级表优化?网上谈的不少但是不知道大伙儿是否在自己 Owner 的业务中验证过。我总结我的经验分享出来大家讨论。

名词解释

大家工作的领域不同,我尽量简化语义。

SOP

我先介绍项目信息,"SOP"也叫"营销自动化"。所谓 SOP,是 Standard Operating Procedure 三个单词中首字母的大写 ,即标准作业程序,指将某一事件的标准操作步骤和要求以统一的格式描述出来,用于指导和规范日常的工作标准作业程序_百度百科。比如你去餐厅,服务人员先给你拿菜单、点菜、上菜、买单这一套操作就是标准的流程。

"SOP"旨在帮助企业将复杂场景的用户运营策略自动化执行,并提供运营效果量化追踪的平台。比如:自动发短信、预约提醒等。

今天要讲的场景是利用"SOP"给员工自动下发任务,员工手动触达到客户场景。

比如你要在情人节给朋友圈客户发送满减活动。你会经历下面这几步

1、 谁来发「员工」

2、 发给谁「客户」

3、 发什么「内容」

4、 何时发「时间」

5、 怎么发「通道」

员工

用过钉钉或企业微信的应该都知道,员工指的就是公司内部员工。

客户

员工跟进的客户,可以理解为好友,你们可以相互聊天。

员工任务明细模型(sop_user_task_item)

数据库:TiDB,目前单表数据 2 亿+,优化 SQL 前数据 1 亿左右。

我先介绍下模型,可以理解成员工给客户发送记录。模型比较简单如下(下面讲问题定位还,还会有一些模型进入大家视野,但是那几个模型不重要):

字段名 字段类型 字段解释
id varchar(256) 主键id
user_id varchar(256) 员工id
c_id varchar(256) 联系人id
status varchar(256) 状态(已完成、未完成...)
create_time bigint(20) 创建时间
node_id varchar(256) 节点id
sop_id varchar(256) sopid
task_id varchar(256) 任务id
n_name varchar(256) 节点名称
..... .....

TIDB 关键字

分析 SQL 执行计划会用到一些 TIDB 知识,详细可以参考TIDB官方文档自己研究,文档贴在后面。TiDB 执行计划概览 | PingCAP 文档中心

EXPLAIN 和 EXPLAIN ANALYZE:我们要分析 SQL 执行计划肯定会用到这 2 个关键字,但是他们是有区别的, EXPLAIN 实际不会执行查询。EXPLAIN ANALYZE 可用于实际执行查询并显示执行计划。

算子简介

执行顺序

还有 JOIN 的执行计划,用 EXPLAIN 查看 JOIN 查询的执行计划 | PingCAP 文档中心

问题定位

定位方法

问题定位千万不要依赖客户反馈、不要依赖客户反馈、不要依赖客户反馈... 重要的事说 3 遍。如果是客户反馈大概率是全平台问题了,受影响的客户会非常多。项目上线后一定要做好监控、数据看板、告警能及时发现问题。

定位问题一般会多个工具一起使用,1、公司内部的"可观测平台",它能及时给你告警和一些有用信息。2、你也可以利用腾讯平台的 cls 登录 - 腾讯云或者阿里云的 sls 阿里云登录 - 欢迎登录阿里云,安全稳定的云计算服务平台配置看板和告警,我一般是每个模块都会有一个看板,另外还有一些全局的,比如:SQL 、流量等。。下图是一个简单 SQL 看板:

有了看板发现问题就比较简单了,SQL 耗时、reqid、对应的 SQL 明细被记录在以下列表,如下图:

问题验证

通过看板和监控我们搜集了一批耗时长 SQL,这些 SQL 都是直接面向客户的,有一段时间我们也收到了部分客户反馈在业务高峰期页面加载缓慢,最终排查结果锁定 SQL 问题,模拟生产环境举一个案例如下:

ini 复制代码
EXPLAIN ANALYZE
SELECT DISTINCT (ut.id),
                ut.create_time,
                ut.plan_id,
                ut.task_type,
                c.NAME
FROM sop_user_task AS ut
         LEFT JOIN sop_node AS c ON ut.node_id = c.id
    AND ut.company_id = c.company_id
         LEFT JOIN sop AS d ON ut.plan_id = d.id
    AND ut.company_id = c.company_id
         LEFT JOIN sop_user_task_item AS utd ON utd.task_id = ut.id
    AND utd.company_id = ut.company_id
WHERE (
            utd.user_id = '418'
        AND d.is_delete = 0
    )
  AND utd.STATUS IN ('1', '2')
  AND ut.company_id = 'x00000003'
ORDER BY ut.create_time ASC
LIMIT 20 offset 0;

大概解释下模型含义:

1、 sop:sop 基础模型表, is_delete 字段标志 SOP 是否被删除,删除的 SOP 需要撤回员工任务。

2、 sop_node :SOP 节点基础模型表,通过当前表查询节点名称。

3、 sop_user_task :员工任务表模型表,sop_user_task 跟 sop_user_task_item 是 1:n 关系。

4、 sop_user_task_item: 员工任务明细模型表。

需求比较简单可以理解成查询租户 id 为 x00000003,员工 id 为 418 且状态是待执行或者未完成任务、并且是未删除的 SOP,最终按 SOP 节点聚合展示。

业务表不理解没关系,大家只需要关注 sop_user_task_item 表即可,我想给大家表达的是大表别 JOIN。

上段 SQL ,通过 explain analyze 分析 SQL,如下图:

上面这个执行计划比较复杂,我简介绍下有一个 indexRangeScan 带有范围的索引数据扫描操作,扫了 140W 数据。大概应该都知道扫描行数越多,数据库内存占用会越大,执行计划没有截全看不到内存消耗了。

执行计划是有内存、磁盘消耗的,用 EXPLAIN ANALYZE 关键字自行测试。

解决方案

分析完执行计划,我们聊聊优化方案,线上紧急问题肯定是先止血,再设计短期方案,最后才是长期方案。

1、 第一步先止血,利用执行计划分析我们先增加索引,暂时提高 SQL 性能,但"SOP"数据量增加非常快,上面的 SQL 依然存在问题(增加索引只能晚上进行,高峰期是禁止执行的)。

2、 优化 SQL,SQL 优化思路大概是把多表 Join 查询改成单表仅保留 sop_user_task_item 表。

a) 技术测先冗余字段,sop 表 isDelete 字段 冗余到 sop_user_task_item status字段,减少 sop 表 join。

b) 去掉 join sop_user_task 表查询,从 sop_user_task_item 出结果,唯一缺点需要 group by task_id。

c) a、b 步骤后剩下只 join sop_node 表了,员工任务列表有需求是通过节点名称模糊搜索,另外还需要回显示。回显示问题不大,ByIDs 查询一次就好,但是模糊搜索砍掉客户不接受,经过对线上分析,我们发现客户改节点名字的概率基本没有,最后决定损失一部分客户体验,保留模糊搜索,把节点名称冗余到 sop_user_task_item (我还有一种方案,利用子查询,也验证了性能是很好的,本次不讲,主要是优化上线后客户没提)。

经过上面一顿操作猛如虎,优化后的 SQL 如下:

sql 复制代码
EXPLAIN ANALYZE
SELECT node_id,
       task_id,
       user_id
FROM sop_user_task_item
WHERE company_id = 'x00000003'
  AND user_id = '418'
  AND STATUS IN ('1', '2')
GROUP BY node_id,
         task_id
ORDER BY node_id DESC
LIMIT 100 OFFSET 0;

SQL 优化后是不是简单多了?单条 SQL 查询一部分数据,其它回显数据 ByIDs 查询就好了,另外优化后我们发现某一些场景是可以利用覆盖索引的(不知道覆盖索引是啥自己去百度)性能也提高一大截。

优化后的执行计划如下:

我简单解释下执行计划:

第一步:通过 IndexRangeScan,带有范围的索引数据扫描操作,命中索引 cid_uid_ctime 并且行数是 1665。

第二步:因为索引不带 status,还会在 tikv 上过滤 status,过滤后数据量为 32 条。

第三步:SQL 是带 group by 的,通过 HashAgg 聚合算子聚合后,数据还有21 条。

第四步:limit 限制 20 条返回,返回数据量为 20,SQL 执行完毕。

上线后优化效果跟踪

优化前

优化后

优化后相比优化前 1s-5s 和大于5s 的接口已经降低很多了,还有一些慢 SQL,在后面的迭代已经处理掉了。

总结

1、 日常开发中,监控、看板、告警很重要,大家在做系分设计一定要把这个纳入你的设计中,保障你的数据、行为是可以量化和追踪的,另外你可以通过数据看板持续跟踪功能的使用情况。

2、 假设有疑似问题千万别放过(只要 SQL 存在性能问题生产环境一定会出现的,不要抱有侥幸心理。),先排查(找到问题根源,避免胡乱抓)、再验证(确保改造有效,避免做无用功,上线后问题没有解决还浪费资源)、再改造、最后再上线(建议先灰度不要一把梭哈,尤其是改造范围广的,先灰度发现问题减少对客户的影响)。

3、 不要死扣技术,相信技术能解决一切,要具备一些产品思维(要适当损失产品体验降低设计复杂度)。

4、 并不是所有数据表设计都要满足数据库三范式(尤其是大表),可以通过冗余字段来优化跨库、跨表的 JOIN,提高查询性能。

5、 大表在你用多表 JOIN 做业务前一定要导入大量数据验证清楚;我建议不要 JOIN 尽量采用字段冗余、单表出结果+ByIDs 查询这套组合拳。

问题遗留

本次优化并不是最终方案存在一些问题待解决。比如:

1、 is_delete 冗余到 user_task_item,如果 SOP 被删除,假设某个 SOP 有 100W 明细数据,删除后更新这批数据,每次大批量更新数据,数据库就会有波动。

2、 user_task_item 本来就是大表,数据表没做分区表以及冷热数据分离,系统运行越长问题迟早也会暴露。

思考题

1、 你们平时定位问题的思路和步骤是啥呢?欢迎讨论

2、 你们有遇到这么大的数据量吗?解决的思路是什么?

写在最后

本文数据模型均是经过脱敏、简化、混淆的。并不能指导大家设计某一个功能,仅仅提供问题定位、 SQL 优化思路。

本文封面来源于网图,侵删。

如果大家对 SOP 感兴趣可以看看"神策数据" family.demo.sensorsdata.cn/sf/flows?pr...

相关推荐
小奏技术18 分钟前
Kafka要保证消息的发送和消费顺序性似乎没那么简单
后端·kafka
小五Z19 分钟前
Redis--事务
redis·分布式·后端·缓存
Asthenia041220 分钟前
线上服务频繁FullGC分析
后端
牛马baby23 分钟前
Springboot 自动装配原理是什么?SPI 原理又是什么?
java·spring boot·后端
Asthenia041238 分钟前
AtomicStampedReference实现原理分析
后端
Starwow1 小时前
微服务之gRPC
后端·微服务·golang
Asthenia04121 小时前
AtomicMarkableReference如何解决ABA问题:深入分析
后端
Asthenia04121 小时前
Fail-Fast与快照机制深入解析及并发修改机制拷打
后端
Pasregret1 小时前
观察者模式:从博客订阅到消息队列的解耦实践
后端·观察者模式
考虑考虑2 小时前
Springboot捕获feign抛出的异常
spring boot·后端·spring