新闻详情

新闻详情

首页 / 资讯中心 / 详情

DolphinScheduler列表页慢查询优化:MySQL复合索引从10秒到毫秒

发布时间:2026/9/28 13:17:22来源:尧图网络
DolphinScheduler列表页慢查询优化:MySQL复合索引从10秒到毫秒
1. 问题现象页面转圈 10 秒到底卡在哪1.1 现场描述我是在一次生产环境巡检时碰到这个问题的。DolphinScheduler 的工作流实例列表页点开“工作流实例”菜单页面顶部那个 loading 条能转上大概 10 秒才出数据。如果翻页又是一轮 10 秒。如果按工作流名称筛选、按状态筛选有时候直接能等到人失去耐心。一开始我以为是 DolphinScheduler 自身的 API 响应慢或者是前端渲染大数据量卡顿。但顺着页面 Network 面板看下去发现时间几乎全部耗在一个接口请求上/projects/{projectCode}/process/list 这一类查询实例列表的接口。后端接口耗时 9 秒多前端渲染只占了几十毫秒。问题基本锁定了不是 DolphinScheduler 前端的问题是数据库查询慢。DolphinScheduler 在执行调度任务时工作流实例表的数据量增长非常快。我们这套环境每天新增几万条工作流实例记录跑了大半年t_ds_process_instance 表已经攒下 2000 多万行。列表查询在这种数据量级下只要 SQL 没有走到合适的索引10 秒只是起步价。这类问题在很多调度平台运维中都很常见属于典型的“数据量上来之后索引没跟上”的案例。1.2 初步定位先抓慢查询日志遇到页面慢我最先做的不是看 DolphinScheduler 日志而是打开 MySQL 的慢查询日志。因为 DolphinScheduler 会对很多操作做异步处理光看应用日志只能看到“查询超时”之类的结果拿不到真正执行的 SQL 和耗时分布。慢查询日志能直接告诉我们是哪条 SQL 慢、扫描了多少行、有没有用上索引。如果还没开启慢查询日志可以用下面这种方式临时打开注意生产环境建议在低峰期操作SET GLOBAL slow_query_log ON; SET GLOBAL long_query_time 2; SET GLOBAL slow_query_log_file /var/log/mysql/mysql-slow.log;我这里阈值设成 2 秒跑了一段时间后一查罪魁祸首基本浮出水面。慢查询日志里的 SQL 大都是类似这样的形态SELECT id, name, process_definition_code, state, start_time, end_time, run_times FROM t_ds_process_instance WHERE project_code 123 ORDER BY id DESC LIMIT 10;DolphinScheduler 列表页要展示当前项目下的工作流实例然后按 id 倒序取最新记录。这个业务逻辑本身很常规问题在于 WHERE 条件和 ORDER BY 加在一起之后执行计划没有走到理想的索引路径。提示DolphinScheduler 不同版本的表结构会有差异1.x 和 3.x 的字段命名不太一样比如有的版本用 project_code有的版本通过 process_definition_code 关联。看到自己的表结构跟网上文章对不上时先执行SHOW CREATE TABLE t_ds_process_instance;确认实际字段原理是一样的。2. 根因拆解一条 SQL 为什么慢成这样2.1 DolphinScheduler 列表页背后在做什么DolphinScheduler 的工作流实例列表查询和大多数管理后台的分页列表本质上是同一类操作根据项目过滤、按照主键倒序、取当前页数据。但这类操作有一个天然矛盾——你既要替用户过滤出它关心的数据又想把最新创建的记录放前面而过滤条件和排序字段如果没有被同一个索引覆盖MySQL 就必须“先过滤再排序”。更麻烦的是DolphinScheduler 的实例列表接口不止查一页数据。它一般还会执行一个SELECT COUNT(*)来算总记录数用于前端分页组件展示“共多少条”。如果列表查询本身是全表扫描级别的这个 COUNT 查询同样会全表扫一遍。也就是说用户每点一次列表页数据库可能要扫两遍几千万行的表10 秒一点不夸张。我们抓到的慢 SQL 也验证了这点同一时间段内有一条和列表查询几乎同样条件的 COUNT 查询耗时同样在 8 秒左右。所以索引优化不能只盯着主查询关联的 COUNT 查询也得一并考虑。2.2 全表扫描与 filesort 的成本要理解为什么这条 SQL 慢我把执行计划拉出来看了一遍EXPLAIN SELECT id, name, process_definition_code, state, start_time, end_time, run_times FROM t_ds_process_instance WHERE project_code 123 ORDER BY id DESC LIMIT 10;结果里关键的几列是这样的列名优化前执行计划typeALLkeyNULLrows2100 万左右ExtraUsing filesort看到type ALL的时候基本就等于宣告“全表扫描”。2000 多万行的表每行都要读一遍看看project_code符不符合条件再用临时文件排序取倒序的前 10 条这个成本可想而知。生活里类比一下就是你有一整面墙的文件柜要找某个部门最近提交的 10 份文件正常情况下应该先去那个部门的柜子翻。但现在你没有部门索引只能把所有柜门全部打开把所有文件一个个看过去记下符合的再按编号排序抽最新的 10 份。数据量小的时候没什么感觉文件多了以后谁的柜子都会乱套。Using filesort尤其刺眼。它意味着 MySQL 需要先把符合条件的行放到排序缓冲区如果数据量超过缓冲区阈值还会落到磁盘临时文件里做归并排序。2000 万行里筛出来的数据再排一遍序耗时经常比全表扫描本身还长。这两个问题叠加在一起想不慢都难。2.3 现有索引到底缺在哪我还特意看了 t_ds_process_instance 表上现有的索引SHOW INDEX FROM t_ds_process_instance;常见的默认索引配置里表上一般只有主键索引PRIMARY KEY (id)最多再加一个和process_definition_code相关的普通索引。也就是说查询里用到的project_code这个过滤条件在多数默认部署中压根就没有索引覆盖。没有project_code索引MySQL 只能从主键索引的最左边开始把整张表的所有行全部扫一遍。更麻烦的是排序。就算我给project_code建了单列索引查询先把project_code 123的数据找出来了但ORDER BY id DESC需要按 id 排序这时的 id 顺序是散的——因为 B 树索引里同一项目的数据分散在不同的 id 区间里所以 MySQL 仍然需要再来一次 filesort。这也是为什么很多人“建了索引还是慢”的原因单列索引只解决了过滤没解决排序。真正的高效方案是让索引同时服务过滤和排序这就是复合索引要解决的问题。3. 索引设计一个复合索引怎么“一石三鸟”3.1 选字段过滤、排序、覆盖查询设计这条列表查询的索引时我的原则很明确查询的 WHERE 等值条件放最前面ORDER BY 的字段紧跟其后如果还有余力把查询涉及的普通字段也塞进索引里做覆盖索引。以我们的表结构为例列表查询的核心字段是这几个project_code项目过滤条件等值匹配state状态过滤条件等值匹配列表页经常会按状态筛选id排序字段倒序取最新记录查询结果要吐出name、start_time、end_time等字段于是第一个版本的复合索引长这样ALTER TABLE t_ds_process_instance ADD INDEX idx_project_state_id (project_code, state, id);为什么这个顺序MySQL 的复合索引遵循最左前缀原则索引中靠左的字段会先被用来定位数据。只要前两个字段是等值查询条件它们就能精准定位到一个数据区间然后这个区间内id天然有序。也就是说ORDER BY id DESC LIMIT 10到时候可以直接从索引末尾倒着取 10 条连排序都不需要做。用大白话解释复合索引就像一本按“部门 状态 编号”排序的通讯录。你要查“研发部 运行中 编号最新”的人直接翻到研发部定位到运行中这一块再从最后往前数 10 个人就完事了。整个过程不用把整个通讯录都翻一遍也不用单独排序。3.2 复合索引的前缀与排序方向很多人问id放在复合索引里到底有什么讲究它真的能帮上ORDER BY id DESC吗答案是能。InnoDB 的 B 树索引本身是按索引字段从左到右排序的。当project_code和state都走等值匹配后剩下的id字段在索引内部就已经是排好序的。MySQL 的优化器识别到排序字段和索引中下一列一致时会直接按索引顺序倒序扫描不再额外 filesort。这里有个细节索引内部默认是按升序存的但ORDER BY id DESC可以反向扫描InnoDB 对反向扫描的支持已经非常成熟性能上不会有什么问题。所以不需要专门建一个(project_code, state, id DESC)这样带降序定义的索引普通升序索引配合倒序扫描就够了。MySQL 8.0 里当然也支持降序索引但在这个场景下没必要画蛇添足。还有一点如果查询里state不是必选条件比如用户只选了“全部状态”那state ?就会变成范围条件或直接缺失。这时候复合索引(project_code, state, id)里state如果缺失id的排序优势就发挥不出来了因为中间断了。所以我们生产环境的实际索引选择了更保守的策略文章开头提到的优化过程里我最终同时保留了另一个索引ALTER TABLE t_ds_process_instance ADD INDEX idx_project_id (project_code, id);这个索引专门覆盖“只按项目过滤 按 id 排序”的场景。平时列表页如果不带状态筛选走这个索引带了状态筛选走三列复合索引。两条路都被堵死了MySQL 的优化器会根据条件自动挑最优的那条。注意不要一见慢查询就给表建一堆索引。索引不是越多越好每个索引都占用磁盘空间还会拖慢 INSERT、UPDATE、DELETE 的写入性能。这个项目里我总共只加了这两个索引完全够用。3.3 创建索引的 SQL 与风险控制执行ALTER TABLE加索引之前最好先确认一下表的大小和当前写入压力。InnoDB 在 MySQL 5.6 之后支持在线 DDL但具体能不能全程无锁取决于版本和算法。像我们这种 2000 多万行的表直接ALTER TABLE虽然不会一直锁表但可能在拷贝数据阶段占用大量 IO导致主从延迟飙高。我的做法是在从库上先执行一遍索引创建观察耗时和延迟确认 SQL 本身没问题后错峰在主库执行如果生产环境特别严格优先考虑用 gh-ost 或 pt-online-schema-change 这类在线改表工具。我们的环境最后是直接在低峰期执行了下面的语句耗时大约 200 多秒整个过程业务没有明显感知ALTER TABLE t_ds_process_instance ADD INDEX idx_project_state_id (project_code, state, id), ADD INDEX idx_project_id (project_code, id);通过SHOW CREATE TABLE确认索引已经创建成功后下一步就是验证效果了。4. 上线验证从 10 秒到飞起4.1 EXPLAIN 对比执行计划彻底变了索引创建完我第一时间重新执行了一遍之前的 EXPLAINEXPLAIN SELECT id, name, process_definition_code, state, start_time, end_time, run_times FROM t_ds_process_instance WHERE project_code 123 AND state 6 ORDER BY id DESC LIMIT 10;优化后的执行计划长这样列名优化后执行计划typerefkeyidx_project_state_idrows几十行级别ExtraUsing index condition对比一下type 从ALL变成了refkey 从NULL变成了我们新建的复合索引rows 从 2100 万降到了几十行Extra 里的Using filesort也消失了。这几项变化意味着数据库不再翻全表而是通过索引精准定位到需要的记录也不需要再做磁盘排序。再看不带state条件时的情况EXPLAIN SELECT id, name, process_definition_code, state, start_time, end_time, run_times FROM t_ds_process_instance WHERE project_code 123 ORDER BY id DESC LIMIT 10;优化器会自动选择idx_project_id同样没有 filesortrows 降到了该项目下实际实例数的量级。两个查询路径都通畅了。4.2 回归业务接口接口耗时降了两个数量级执行计划好看只是第一步关键还是业务接口的实际响应时间。我在查询接口里手动加了一条日志优化前后各统计了一轮场景优化前优化后总数据量2100 多万行2100 多万行查询某项目列表第一页9.6 秒45 毫秒分页 COUNT 查询8.2 秒36 毫秒按状态筛选后的列表查询10.1 秒52 毫秒从 9 秒多降到几十毫秒至少是 200 倍的提升。页面上的 loading 条基本是一闪而过体感从“卡死人”变成了“秒开”。这里要特别说一句慢查询优化不是只能靠索引索引也不是唯一的优化手段。对于这类列表查询如果你把 WHERE 条件换成创建时间范围比如只查最近 7 天的数据配合时间字段索引效果也不错。问题是 DolphinScheduler 这种表用户就是会翻很久之前的历史实例时间范围过滤不一定总能生效。所以把索引做对是最通用、最稳妥的一步。4.3 别忘了把所有入口的相同查询都扫一遍一个常见教训是只优化了列表页主查询但同一个查询模式在 API 层有很多入口。比如 DolphinScheduler 的实例详情页可能要查单个实例、批量删除要查一批实例、定时调度要按流程定义代码查实例这些 SQL 的条件可能不完全一样但都有一个共同点依赖 project_code 或 process_definition_code 做过滤。所以索引加完之后我又从慢查询日志里把过去一周所有涉及 t_ds_process_instance 的慢 SQL 全部捞了出来逐个核对执行计划。结果发现有一个按process_definition_code和状态查询实例的接口同样在用全表扫描耗时也在 5 秒上下。我随后补了一个对应索引ALTER TABLE t_ds_process_instance ADD INDEX idx_def_code_state_id (process_definition_code, state, id);这种“按流程定义查运行记录”的场景在 DolphinScheduler 里很常见特别是流程实例详情页会展示“这个工作流的全部历史运行记录”那条 SQL 之前根本没人注意到。注意优化完不要急着收工对所有关联表、关联查询做一轮全面体检。慢查询日志是最佳帮手把它打开让它持续记录过两天再来复查一次基本能把漏网之鱼都揪出来。5. 常见问题与排查实录5.1 “为什么我建了索引还是慢”索引建了但没效果是我见过最多的疑问。这里把最经典的几种原因列一下原因表现解决思路最左前缀失效WHERE 没带复合索引最左字段调整索引字段顺序或新增单列索引隐式类型转换project_code 是 VARCHAR却传入了数字统一字段类型避免CAST函数作用在索引列上LIKE 前置通配符对 name 做%关键词%模糊查询这种情况下索引大概率失效考虑全文索引或干脆接受扫描索引列参与运算DATE(start_time) 2024-01-01改写成start_time 2024-01-01 AND start_time 2024-01-02优化器判断全表更快表太小或区分度太低用FORCE INDEX临时验证或增大统计信息采样在这个项目中我们也碰到过一次隐式类型转换问题。有个版本里 project_code 字段是 VARCHAR 类型但业务代码传入的是数值类型MySQL 会自动把字符串转成数字比较结果导致索引失效。检查表结构和查询参数的匹配度永远是排查索引问题的第一步。另外MySQL 8.0 的优化器默认会做索引下推Index Condition Pushdown这意味着复合索引里的部分字段可以在索引层面直接过滤减少回表次数。我们可以通过EXPLAIN的Extra列看到Using index condition这就是下推生效的标志。这个特性对结果影响很大碰到版本比较低的 MySQL 5.6 也需要确认它是否开启。5.2 实例列表里还有哪些隐藏的慢查询列表转圈 10 秒不一定全是列表查询本身慢。DolphinScheduler 的实例列表接口往往会并行查询很多辅助数据比如流程定义名称映射、用户信息、项目信息、定时调度信息。这些可能是分批查出来的每一批都有独立的 SQL。我之前排查过一个案例列表页数据出来了但页面还要根据每个工作流的process_definition_code去查流程定义名称由于代码里是循环查 SQL一次列表加载可能要发几十条小查询。每条查询只要 100 毫秒加起来就是 3 到 5 秒。这种问题就不是索引能彻底解决的得去改代码改成一次性IN查询或者缓存起来。排查页面慢的时候不要假设一定只有一个瓶颈。还有一类容易被忽略的是 ZooKeeper 和数据库连接池相关的耗时。DolphinScheduler 在获取数据库连接时如果连接池已经打满应用日志里会出现获取连接超时的报错。这种情况下页面表现也是转圈很久但慢查询日志里根本没有慢 SQL。这个方向容易被带偏好在我们的环境里连接池还算健康问题确实出在 SQL 这边。5.3 运维视角索引也不是一劳永逸索引优化解决了眼前的 10 秒问题但数据量还在涨。我给这套环境定了一个后续计划同样值得你参考监控慢查询日志长期开启阈值设在 1 秒每周巡检一次持续发现新出现的问题 SQL。定期归档历史工作流实例DolphinScheduler 提供了清理和归档机制把超过 3 个月或 6 个月的老实例迁到归档表主表控制在千万级以内这样索引的维护成本和页面的查询压力都会小很多。关注磁盘空间索引越多表空间膨胀越快。加索引后记得检查一下表的大小变化别让一个 50GB 的表涨到 80GB。只保留必要索引如果某个索引加完之后慢查询日志里几乎没出现过走它的 SQL就要考虑是不是多余索引找低峰期删掉。最后再分享一个小技巧。排查这类列表慢的问题完全可以不必盯着 EXPLAIN 的一堆输出墨迹半天直接看三个关键指标就好type是不是走索引、rows是不是接近实际数据量、Extra里有没有Using filesort。这三个指标正常了SQL 基本就正常了。至于索引选择的细节像最左前缀、覆盖索引、索引下推这些概念本质上都服务于同一个目的让数据库少读数据少干活。我在这套环境上踩了一圈坑之后的最大体会是DolphinScheduler 这类调度平台的性能问题绝大多数不是 DolphinScheduler 本身的代码问题而是底层数据库没有跟上业务增长。你不需要改一行 Java 代码只需要把索引设计到位往往比改动应用逻辑更省事、更安全。数据量大时先在数据库层面拿索引这把尺子量一量通常都会给你惊喜。
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

sem 会泄露代码吗?遥测、云同意与本地缓存的隐私机制完整说明 2026/9/28 21:12:26

sem 会泄露代码吗?遥测、云同意与本地缓存的隐私机制完整说明

sem 会泄露代码吗?遥测、云同意与本地缓存的隐私机制完整说明 【免费下载链接】sem Semantic version control > entity-level diffs, blame, and impact analysis on top of git. 28 languages via tree-sitter. Built for coding agents. 项目地址: https://…

阅读更多 →
Kubernetes大版本升级避坑指南:Radar升级影响分析如何提前拦截API移除与破坏性变更 2026/9/28 21:12:19

Kubernetes大版本升级避坑指南:Radar升级影响分析如何提前拦截API移除与破坏性变更

Kubernetes大版本升级避坑指南:Radar升级影响分析如何提前拦截API移除与破坏性变更 【免费下载链接】radar The missing open-source Kubernetes UI with a built-in MCP server for AI agents. See whats broken, why, and what changed. Issues, Topology, event timeline, H…

阅读更多 →
Lap 双格式动态照片播放器:Live Photo 与 Motion Photo 都能播的离线照片管理器 2026/9/28 21:12:19

Lap 双格式动态照片播放器:Live Photo 与 Motion Photo 都能播的离线照片管理器

Lap 双格式动态照片播放器:Live Photo 与 Motion Photo 都能播的离线照片管理器 【免费下载链接】lap An offline-first photo manager for large local libraries 项目地址: https://gitcode.com/GitHub_Trending/lap3/lap Lap 是一款离线优先的本地照片管理…

阅读更多 →
java程序员必备ai技能 2026/9/28 21:12:19

java程序员必备ai技能

Java程序员必备AI技术栈(偏工程落地,不是算法科研)定位:Java后端做AI应用、RAG、Agent、大模型服务对接,不用深度学习训练。一、基础概念(必须懂) LLM基础:大模型、Token、上下文窗口…

阅读更多 →
网页图片编辑器如何添加文字:字体、换行与导出一致性 2026/9/28 21:12:19

网页图片编辑器如何添加文字:字体、换行与导出一致性

在网页图片编辑器中,添加文字看似只是调用 fillText,真正容易出问题的是字体还没加载就开始测量、预览和导出使用了两套换行规则,以及选择框尺寸没有跟着多行文字更新。本文结合图片猫 www.piccat.cn 前端的文字编辑器实现,说明怎…

阅读更多 →
一个HTML文件构建可交互原型:Effective HTML从加载到错误恢复的状态机详解 2026/9/28 21:12:19

一个HTML文件构建可交互原型:Effective HTML从加载到错误恢复的状态机详解

一个HTML文件构建可交互原型:Effective HTML从加载到错误恢复的状态机详解 【免费下载链接】effective-html Agent skills for useful HTML artifacts, wireframes, interactive prototypes, plans, and diagrams. 项目地址: https://gitcode.com/gh_mirrors/ef/e…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞 ✉