数据库连接+查询优化
- Prompt------❤️
- 数据库的相关的操作
-
- 连接
- 辨析
- 视图
-
- [🧩 注意事项](#🧩 注意事项)
- [📈 如何测试视图?------❤️](#📈 如何测试视图?——❤️)
- [[插入操作 失败 也会造成 主键 的自动增长](https://developer.aliyun.com/article/91161)](#插入操作 失败 也会造成 主键 的自动增长)
- 综合案例
-
- prompt
- answer
-
- [✅ **核心需求总结**](#✅ 核心需求总结)
- [✅ 方案一:使用 `INNER JOIN` + 子查询实现分组](#✅ 方案一:使用
INNER JOIN+ 子查询实现分组) - [✅ 方案二:修正后的 JOIN + 子查询(推荐)](#✅ 方案二:修正后的 JOIN + 子查询(推荐))
- [✅ 方案三:用 `LEFT JOIN` + `NOT EXISTS`(排除不匹配项)](#✅ 方案三:用
LEFT JOIN+NOT EXISTS(排除不匹配项)) - [✅ 方案四:使用 `EXISTS` 子查询(适合检查存在性)](#✅ 方案四:使用
EXISTS子查询(适合检查存在性)) - [✅ 方案五:先查 `ads_rb_cd_stage_record`,再关联(反向思路)](#✅ 方案五:先查
ads_rb_cd_stage_record,再关联(反向思路)) - [✅ 方案六:纯子查询(不推荐,性能差)](#✅ 方案六:纯子查询(不推荐,性能差))
- [✅ 推荐方案总结(按优先级排序)](#✅ 推荐方案总结(按优先级排序))
- [✅ 最终推荐 SQL(CTE + JOIN,适用于 MySQL 8+)](#✅ 最终推荐 SQL(CTE + JOIN,适用于 MySQL 8+))
- [📊 测试建议](#📊 测试建议)
- [💡 索引建议(提升性能)](#💡 索引建议(提升性能))
Prompt------❤️
创建表
你是一个DBA,Database Administrator
这个是两个表的ddl语句,临时表的字段都是从里面提取到的,所以可以按照之前的属性值,约束,但是您需要考虑一下,新建的表的索引是否需要变更呢?
请你模仿上方已有的两个数据表的定义,帮我设计一个数据库表ads_rb_alert_record吧,设置相应的索引,以及字段的属性值以及字段约束条件
如果两个表中已有的,按照之前的定义,如果新增加的,考虑一下是否需要修改默认值不为null,因为null可能会造成索引实效的问题,谢谢您
使用场景:
包括但是不限于的字段:
数据库的相关的操作
连接
别样的语法
python
passwd = quote_plus("C@safsdvscd_")
DB_CONNECT_STRING = f"mysql+mysqldb://ai_test:{passwd}@10.11.39.11:11422/ai_delivery?charset=utf8"
- 用户名:密码@主机地址
- 所以如果直接将 @ 放在连接字符串里,解析器会错误地认为 @ 之后的部分是主机地址,从而导致连接失败或安全问题
quote_plus函数(来自urllib.parse模块)会将这些特殊字符进行 URL编码。例如,@ 会被编码为 %40。- 这样做可以确保密码中的特殊字符被安全传输,避免了语法解析错误
quote_plus用于对密码进行 URL 编码,而f-string则用于将编码后的密码动态插入到连接 URI 模板中。这种模式在使用SQLAlchemy等 ORM 框架时非常普遍parse.quote_plus是 Python 标准库 urllib.parse 模块中的一个函数。它用于对字符串进行编码,以便将其作为 URL 的一部分安全地传输- 将字符串中的特殊字符转换为 URL 编码的形式。例如,空格会被转换为 %20,加号 (+) 会被转换为 %2B,斜杠 (/) 会被转换为 %2F,等等
- Python3的urllib.parse常用函数小结(urlencode,quote,quote_plus,unquote,unquote_plus等)
- 虽然
quote_plus解决了语法问题,但将密码以明文形式写在代码中(即使是编码后)仍然是一个严重的安全风险。在生产环境中,强烈建议使用环境变量或配置中心来管理此类敏感信息
Adminer

Database management in a single PHP file
Adminer is available for: MySQL, MariaDB, PostgreSQL, CockroachDB, SQLite, MS SQL, Oracle. Others can be added via plugin: Elasticsearch, SimpleDB, MongoDB, Firebird,
对于本地安装,前提需要配置好一个支持 PHP 模块的 Web 服务器,例如 Apache httpd。然后在 Adminer 官方网站下载相应的 php 文件到服务器根目录下即可
辨析
数据库要允许没有主键的表
有时候,应用程序会在数据层面保证数据的完整性,而不是依赖数据库层面的主键约束。这样做的原因可能是为了更好地控制数据逻辑和业务流程。在这种情况下,数据库允许没有主键的表存在,将完整性的责任交给了应用程序
- 如果定义了主键,那么InnoDB会使用主键作为聚簇索引
- 如果没有定义主键,那么会使用第一非空的唯一索引(NOT NULL and UNIQUE INDEX)作为聚簇索引
- 如果既没有主键也找不到合适的非空索引,那么InnoDB会自动生成一个不可见的名为ROW_ID的列名为GEN_CLUST_INDEX的聚簇索引,该列是一个6字节的自增数值,随着插入而自增
外键一定是另一个表的主键
外键约束并不仅仅可以与另一张表的主键约束相连接,还可以定义为引用另一张表的UNIQUE约束
内存表和临时表
概念
临时表用来给复杂查询保存中间结果,分为内部临时表和外部临时表。
内部临时表:MySQL 内部生成,比如 union 语句、group by 语句可以保存中间结果;
外部临时表:由客户端创建,SQL 语法:CREATE TEMPORARY TABLE。
内存表是指使用 Memory 存储引擎的表,创建语句如下:
create table test_memory(`id` int, `a` VARCHAR(20) DEFAULT NULL, PRIMARY KEY (`id`)) engine=Memory;
生命周期
- 内存表可以给所有客户端看到,客户端连接关闭后不影响内存表。但如果 MySQL 重启服务,内存表的数据会丢失,表结构还在。
可以看到,临时表也可以是内存表。 - 临时表是会话级别的,客户端连接关闭后,临时表自动删除。同时,会话之间的临时表是相互隔离的,不同会话可以创建相同名称的临时表。
内存表则不受会话的影响,只有使用 drop 才能删除掉。
truncate、delete和drop的区别
视图
🧩 注意事项
-
✅ 1. 视图不会自动刷新
-
视图是一个 虚拟表,每次查询它都会重新执行内部 SQL。
-
如果底层数据变化,下一次查询就会看到新结果。
-
不需要手动刷新,但性能取决于底层查询。
-
✅ 2. 性能问题(最常见)
-
两个表都很大 → 查询慢
-
解决方案:
- 添加索引(强烈建议)
建议添加以下索引:
sql
-- 对 ads_module_rb_info 建索引
ALTER TABLE ads_module_rb_info ADD INDEX idx_module_hi_group (module, hi_group);
ALTER TABLE ads_module_rb_info ADD INDEX idx_update_time (update_time);
ALTER TABLE ads_module_rb_info ADD INDEX idx_icode_rb_status_module (icode_path, rb_name, status, module);
-- 对 ads_rb_cd_stage_record 建索引
ALTER TABLE ads_rb_cd_stage_record ADD INDEX idx_module_name_stage (module_name, stage);
ALTER TABLE ads_rb_cd_stage_record ADD INDEX idx_rb_name_module (rb_name, module_name);
⚠️ 特别注意:
GROUP BY rb_name, module_name是消耗较大的操作,必须靠索引加速
-
✅ 3. 字段顺序与命名一致性
-
为了便于使用,建议保持字段顺序一致
-
如果你未来要导出到报表系统,建议命名规范,比如:
cd_update_time明确表示"CD 更新时间"- 避免使用
update_time重复(主表和子表都有)
-
✅ 4. 安全性 & 权限
-
创建视图需要
CREATE VIEW权限 -
使用者需要对
ads_module_rb_info和ads_rb_cd_stage_record有SELECT权限 -
你可以通过
GRANT授权给特定用户:
sql
GRANT SELECT ON v_ads_rb_cd_stage TO 'user'@'host';
-
✅ 5. 视图不能独立于原始表存储数据
-
视图只是 SQL 查询的封装,不保存任何数据
-
所有计算在运行时完成
-
✅ 6. 使用限制(MySQL 特性)
| 特性 | 说明 |
|---|---|
| 支持 CTE? | 不支持(除非 MySQL 8+) |
| 支持子查询? | ✅ 支持 |
支持 LIMIT? |
❌ 视图本身不能有 LIMIT,但可以写在查询上 |
支持 ORDER BY? |
✅ 允许,但查询时仍需加 ORDER BY 和 LIMIT |
📈 如何测试视图?------❤️
-
- 创建视图后,测试是否正常:
sql
-- 查看视图结构
DESCRIBE v_ads_rb_cd_stage;
-- 查询前几条
SELECT * FROM v_ads_rb_cd_stage LIMIT 10;
-
- 测试性能(加入
EXPLAIN):
- 测试性能(加入
sql
EXPLAIN SELECT * FROM v_ads_rb_cd_stage ORDER BY update_time DESC LIMIT 200;
🔍 检查:
- 是否走索引?
- 是否出现
Using temporary; Using filesort?
插入操作 失败 也会造成 主键 的自动增长
- MYSQL不保证AUTO_INCREMENT依次增长(1,2,3,4,5),但是可以保证正向增长(1,3,5,9)所以,当你某次操作失败后,下次AUTO_INCREMENT就不是顺序的了
- 解决的办法:
- Mysql主键如果在insert插入时有值,将不使用自增。也就是说插入数据的时候只要自己把ID加上就按照插入的数进行自增了,这个数通过自己的逻辑判断代码来赋值 ,只要ID不重复就可以了
-每次插入前查询最大ID,然后加1再作为ID插入
综合案例
prompt
你是一个数据库设计师
这里有两个表
CREATE TABLE `ads_module_rb_info` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`icode_path` varchar(255) NOT NULL COMMENT '代码库',
`module` varchar(64) DEFAULT '' COMMENT '模块名',
`rb_name` varchar(255) NOT NULL COMMENT 'rb名称',
`rb_time` DATE NOT NULL COMMENT 'rb拉出来的时间(作为rb日)',
`create_rb_time` datetime DEFAULT NULL COMMENT '拉出rb的时间,精确到秒',
`revision` varchar(255) NOT NULL COMMENT '基于哪个commit拉取的分支',
`status` tinyint(2) NOT NULL COMMENT '0未拉出,1有风险,2已拉出',
`merge_status` tinyint(2) NOT NULL COMMENT '是否合回去主干0否 1是',
`risk_message` text NOT NULL COMMENT 'rb风险信息',
`rd_owner` varchar(32) DEFAULT NULL COMMENT '当前rb的研发负责人,取的拉rb时候最后一个commit研发',
`qa_owner` varchar(32) DEFAULT NULL COMMENT '值班qa',
`update_time` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`relation_story_list` varchar(1024) NOT NULL DEFAULT '' COMMENT '当前rb关联的项目',
`relation_revision_list` text NOT NULL COMMENT '当前rb关联的commitid',
`relation_master_number` int(11) DEFAULT NULL COMMENT '当前rb关联的master build_number',
`rb_stage` int(11) DEFAULT '0' COMMENT 'rb当前到达哪个阶段 0RB拉出阶段, 1RB测试阶段,2RB发布阶段,3set base阶段,4实验CR阶段,5实验quick阶段,6实验部署阶段,7实验确认阶段,8推全CR阶段,9推全quick阶段,10上线完成阶段',
`operator_user` varchar(64) DEFAULT NULL COMMENT '实验确认操作人',
`operator_time` datetime DEFAULT NULL COMMENT '实验确认时间',
`succ_process` varchar(32) DEFAULT NULL COMMENT '实验确认类型',
`hi_group` varchar(32) DEFAULT NULL COMMENT '群组',
`hi_token` varchar(512) DEFAULT NULL COMMENT '群机器人',
PRIMARY KEY (`id`),
UNIQUE KEY `icode_path_rb_name_status_module` (
`icode_path`,
`rb_name`,
`status`,
`module`
),
KEY `icode_path` (`icode_path`),
KEY `update_time` (`update_time`),
KEY `status` (`status`),
KEY `rb_name` (`rb_name`)
) ENGINE = InnoDB AUTO_INCREMENT = 1659549 DEFAULT CHARSET = utf8
第二个:
CREATE TABLE `ads_rb_cd_stage_record` (
`icode_path` varchar(255) NOT NULL COMMENT '代码库',
`module_name` varchar(32) DEFAULT NULL COMMENT '模块名',
`rb_name` varchar(255) NOT NULL COMMENT 'rb分支名',
`stage` tinyint(4) NOT NULL COMMENT '阶段名称 0RB拉出阶段, 1RB测试阶段,2RB发布阶段,3set base阶段,4实验CR阶段,5实验quick阶段,6实验部署阶段,7实验确认阶段,8推全CR阶段,9推全quick阶段,10上线完成阶段',
`stage_status` tinyint(4) NOT NULL COMMENT '阶段状态0成功,1失败,2运行中',
`stage_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '达到当前阶段的时间',
`exempt_user` varchar(255) DEFAULT NULL COMMENT '豁免操作人',
`exempt_type` tinyint(4) DEFAULT '0' COMMENT '豁免类型,人工豁免0,自动豁免1',
`reason` varchar(10240) DEFAULT NULL COMMENT '豁免原因',
`update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
UNIQUE KEY `rb_name_stage_module_name` (
`rb_name`,
`stage`,
`module_name`
),
KEY `rb_name` (`rb_name`),
KEY `module_name` (`module_name`)
) ENGINE = InnoDB DEFAULT CHARSET = utf8
任务一:这里有两个查询,都是成功执行,没有报错
SELECT icode_path, module, rb_name, rd_owner, qa_owner, update_time, rb_stage, hi_group, hi_token
FROM ads_module_rb_info
WHERE module IN ('mixer', 'ranker', 'theseus')
GROUP BY rb_name
ORDER BY update_time DESC
LIMIT 200
SELECT icode_path, module_name, rb_name, MAX(stage) AS cur_stage, stage_status, stage_time, update_time
FROM ads_rb_cd_stage_record
WHERE module_name IN ('mixer', 'ranker', 'theseus')
GROUP BY rb_name
ORDER BY update_time DESC
LIMIT 200
考虑各种结合的可能,保留每个表的所有字段不要结合,给我所有可能合并关联查询的的例子,子查询,关联查询等,我实际运行比较一下选择哪一个
任务2:
ads_rb_cd_stage_record有613262万多条数据,ads_module_rb_info有12802条数据,想一想使用EXISTS_还是_IN_,
answer
你提出的问题非常清晰,目标是将两个表进行关联查询,在满足特定条件(hi_group IS NOT NULL)的基础上,结合 ads_module_rb_info 和 ads_rb_cd_stage_record 的数据 ,并最终返回指定字段,且 limit 200。
✅ 核心需求总结
-
从
ads_module_rb_info中筛选出:hi_group IS NOT NULLmodule IN ('mixer', 'ranker', 'theseus')- 按
rb_name分组(用于关联) - 返回字段:
icode_path, module, rb_name, rd_owner, qa_owner, update_time, rb_stage, hi_group, hi_token
-
从
ads_rb_cd_stage_record中:- 根据同一
rb_name查找 - 按
rb_name分组,取MAX(stage)作为cur_stage - 同时获取
stage_status,stage_time,update_time module_name IN ('mixer', 'ranker', 'theseus')
- 根据同一
-
关联条件:
ads_module_rb_info.rb_name = ads_rb_cd_stage_record.rb_name AND ads_module_rb_info.module = ads_rb_cd_stage_record.module_name -
比较:
ads_module_rb_info.rb_stage == ads_rb_cd_stage_record.cur_stage(即当前阶段是否一致) -
最终结果集:
ads_module_rb_info所有字段(不要丢失)- 加上
cur_stage, stage_status, stage_time, update_time(来自ads_rb_cd_stage_record) - 结果按
ads_module_rb_info.update_time DESC排序 - 限制
LIMIT 200
-
注意:不能丢掉任何字段(原表所有字段都保留,但只展示部分字段?)
❓问题:"保留每个表的所有字段不要结合" → 这个表述有点模糊。我理解为:输出的列应该包含两个表中所有相关字段(即不要合并字段名,而是以结构化形式展示),但最终只显示一部分,比如你列出的字段
✅ 方案一:使用 INNER JOIN + 子查询实现分组
这是最直观的方式。
sql
SELECT
m.icode_path,
m.module,
m.rb_name,
m.rd_owner,
m.qa_owner,
m.update_time,
m.rb_stage,
m.hi_group,
m.hi_token,
r.cur_stage,
r.stage_status,
r.stage_time,
r.update_time AS cd_update_time
FROM
ads_module_rb_info m
INNER JOIN (
SELECT
rb_name,
MAX(stage) AS cur_stage,
stage_status,
stage_time,
update_time
FROM
ads_rb_cd_stage_record
WHERE
module_name IN ('mixer', 'ranker', 'theseus')
GROUP BY
rb_name
) r ON m.rb_name = r.rb_name
WHERE
m.hi_group IS NOT NULL
AND m.module IN ('mixer', 'ranker', 'theseus')
AND m.rb_stage = r.cur_stage
ORDER BY
m.update_time DESC
LIMIT 200;
- ⚠️ 注意:
ads_rb_cd_stage_record需要根据module_name过滤m.module和r.module_name并未做匹配 ------ 可能会有跨模块错误!- 因此必须加上
AND m.module = r.module_name?
但这里有个问题:r 是子查询,没有 module_name 字段了。
所以我们需要 保留 module_name 在子查询中,否则无法匹配。
✅ 方案二:修正后的 JOIN + 子查询(推荐)
sql
SELECT
m.icode_path,
m.module,
m.rb_name,
m.rd_owner,
m.qa_owner,
m.update_time,
m.rb_stage,
m.hi_group,
m.hi_token,
r.cur_stage,
r.stage_status,
r.stage_time,
r.update_time AS cd_update_time,
r.module_name AS record_module_name
FROM
ads_module_rb_info m
INNER JOIN (
SELECT
rb_name,
module_name,
MAX(stage) AS cur_stage,
stage_status,
stage_time,
update_time
FROM
ads_rb_cd_stage_record
WHERE
module_name IN ('mixer', 'ranker', 'theseus')
GROUP BY
rb_name, module_name
) r ON m.rb_name = r.rb_name
AND m.module = r.module_name
WHERE
m.hi_group IS NOT NULL
AND m.module IN ('mixer', 'ranker', 'theseus')
AND m.rb_stage = r.cur_stage
ORDER BY
m.update_time DESC
LIMIT 200;
✅ 优点:逻辑完整,可读性强
✅ 保证了
module匹配正确✅
GROUP BY rb_name, module_name正确,避免多个同名 rb 在不同模块被混淆
✅ 方案三:用 LEFT JOIN + NOT EXISTS(排除不匹配项)
如果你担心某些 rb_name 在 ads_rb_cd_stage_record 中缺失,可以用 LEFT JOIN,然后过滤 cur_stage 不为空的情况:
sql
SELECT
m.icode_path,
m.module,
m.rb_name,
m.rd_owner,
m.qa_owner,
m.update_time,
m.rb_stage,
m.hi_group,
m.hi_token,
r.cur_stage,
r.stage_status,
r.stage_time,
r.update_time AS cd_update_time
FROM
ads_module_rb_info m
LEFT JOIN (
SELECT
rb_name,
module_name,
MAX(stage) AS cur_stage,
stage_status,
stage_time,
update_time
FROM
ads_rb_cd_stage_record
WHERE
module_name IN ('mixer', 'ranker', 'theseus')
GROUP BY
rb_name, module_name
) r ON m.rb_name = r.rb_name AND m.module = r.module_name
WHERE
m.hi_group IS NOT NULL
AND m.module IN ('mixer', 'ranker', 'theseus')
AND m.rb_stage = r.cur_stage
AND r.cur_stage IS NOT NULL -- 确保有记录
ORDER BY
m.update_time DESC
LIMIT 200;
✅ 更安全:允许
ads_module_rb_info存在而cd_record不存在的情况(但会被过滤掉)✅ 如果无记录,则
r.cur_stage为NULL,条件m.rb_stage = NULL会失败 → 自动过滤
✅ 方案四:使用 EXISTS 子查询(适合检查存在性)
sql
SELECT
m.icode_path,
m.module,
m.rb_name,
m.rd_owner,
m.qa_owner,
m.update_time,
m.rb_stage,
m.hi_group,
m.hi_token
FROM
ads_module_rb_info m
WHERE
m.hi_group IS NOT NULL
AND m.module IN ('mixer', 'ranker', 'theseus')
AND EXISTS (
SELECT 1
FROM ads_rb_cd_stage_record r
WHERE
r.rb_name = m.rb_name
AND r.module_name = m.module
AND r.stage = m.rb_stage
)
ORDER BY
m.update_time DESC
LIMIT 200;
✅ 优点:简洁,不需要聚合,仅判断是否存在对应阶段记录
❗ 缺点:无法获取
cur_stage,stage_status,stage_time等信息❗ 所以不符合你的第2点"需要获取这些字段"
👉 所以这个方案 只适用于你想知道"是否匹配",而不关心具体状态,不符合要求。
✅ 方案五:先查 ads_rb_cd_stage_record,再关联(反向思路)
sql
SELECT
m.icode_path,
m.module,
m.rb_name,
m.rd_owner,
m.qa_owner,
m.update_time,
m.rb_stage,
m.hi_group,
m.hi_token,
r.cur_stage,
r.stage_status,
r.stage_time,
r.update_time AS cd_update_time
FROM
ads_rb_cd_stage_record r
INNER JOIN (
SELECT
icode_path, module, rb_name, rd_owner, qa_owner, update_time, rb_stage, hi_group, hi_token
FROM
ads_module_rb_info
WHERE
hi_group IS NOT NULL
AND module IN ('mixer', 'ranker', 'theseus')
) m ON m.rb_name = r.rb_name AND m.module = r.module_name
WHERE
r.stage = m.rb_stage
AND r.stage = (SELECT MAX(stage) FROM ads_rb_cd_stage_record WHERE rb_name = r.rb_name AND module_name = r.module_name)
ORDER BY
m.update_time DESC
LIMIT 200;
❌ 不可行!因为
r.stage = MAX(stage)会导致重复计算,而且MAX(stage)要求GROUP BY,不能直接写在WHERE中。❗ 必须提前聚合!
所以应改为:
sql
-- 先聚合
WITH max_stages AS (
SELECT
rb_name,
module_name,
MAX(stage) AS cur_stage,
stage_status,
stage_time,
update_time
FROM
ads_rb_cd_stage_record
WHERE
module_name IN ('mixer', 'ranker', 'theseus')
GROUP BY
rb_name, module_name
)
SELECT
m.icode_path,
m.module,
m.rb_name,
m.rd_owner,
m.qa_owner,
m.update_time,
m.rb_stage,
m.hi_group,
m.hi_token,
s.cur_stage,
s.stage_status,
s.stage_time,
s.update_time AS cd_update_time
FROM
ads_module_rb_info m
INNER JOIN
max_stages s ON m.rb_name = s.rb_name AND m.module = s.module_name
WHERE
m.hi_group IS NOT NULL
AND m.module IN ('mixer', 'ranker', 'theseus')
AND m.rb_stage = s.cur_stage
ORDER BY
m.update_time DESC
LIMIT 200;
✅ 这是最高效、最易维护的方式之一(CTE + JOIN)
✅ 性能好:预处理
max_stages,避免重复计算
✅ 方案六:纯子查询(不推荐,性能差)
sql
SELECT
m.icode_path,
m.module,
m.rb_name,
m.rd_owner,
m.qa_owner,
m.update_time,
m.rb_stage,
m.hi_group,
m.hi_token,
(
SELECT MAX(stage)
FROM ads_rb_cd_stage_record r2
WHERE r2.rb_name = m.rb_name AND r2.module_name = m.module
) AS cur_stage,
(
SELECT stage_status
FROM ads_rb_cd_stage_record r2
WHERE r2.rb_name = m.rb_name AND r2.module_name = m.module AND r2.stage = (
SELECT MAX(stage)
FROM ads_rb_cd_stage_record r3
WHERE r3.rb_name = m.rb_name AND r3.module_name = m.module
)
) AS stage_status,
(
SELECT stage_time
FROM ads_rb_cd_stage_record r2
WHERE r2.rb_name = m.rb_name AND r2.module_name = m.module AND r2.stage = (
SELECT MAX(stage)
FROM ads_rb_cd_stage_record r3
WHERE r3.rb_name = m.rb_name AND r3.module_name = m.module
)
) AS stage_time,
(
SELECT update_time
FROM ads_rb_cd_stage_record r2
WHERE r2.rb_name = m.rb_name AND r2.module_name = m.module AND r2.stage = (
SELECT MAX(stage)
FROM ads_rb_cd_stage_record r3
WHERE r3.rb_name = m.rb_name AND r3.module_name = m.module
)
) AS cd_update_time
FROM
ads_module_rb_info m
WHERE
m.hi_group IS NOT NULL
AND m.module IN ('mixer', 'ranker', 'theseus')
AND m.rb_stage = (
SELECT MAX(stage)
FROM ads_rb_cd_stage_record r
WHERE r.rb_name = m.rb_name AND r.module_name = m.module
)
ORDER BY
m.update_time DESC
LIMIT 200;
❌ 严重性能问题:每行都要执行多次子查询,N 行 ≈ N×3+N×3... 太慢!
⛔️ 绝对不要用这种方案
✅ 推荐方案总结(按优先级排序)
| 方案 | 名称 | 优点 | 缺点 | 建议 |
|---|---|---|---|---|
| ✅ 1 | CTE + JOIN | 性能好,可读性强,支持复杂逻辑 | 需要 MySQL 8+(支持 CTE) | ✅ 强烈推荐 |
| ✅ 2 | 子查询 + INNER JOIN | 通用性强,MySQL 5.7 支持 | 代码较长 | ✅ 推荐 |
| ⚠️ 3 | LEFT JOIN | 安全性高,容忍缺失 | 可能出现 null 导致性能下降 | 可选 |
| ❌ 4 | EXISTS | 简洁 | 无法获取状态字段 | ❌ 不符合需求 |
| ❌ 5 | 子查询嵌套 | 不可维护 | 极其低效 | ❌ 绝对不要 |
✅ 最终推荐 SQL(CTE + JOIN,适用于 MySQL 8+)
sql
WITH latest_stages AS (
SELECT
rb_name,
module_name,
MAX(stage) AS cur_stage,
stage_status,
stage_time,
update_time
FROM
ads_rb_cd_stage_record
WHERE
module_name IN ('mixer', 'ranker', 'theseus')
GROUP BY
rb_name, module_name
)
SELECT
m.icode_path,
m.module,
m.rb_name,
m.rd_owner,
m.qa_owner,
m.update_time,
m.rb_stage,
m.hi_group,
m.hi_token,
s.cur_stage,
s.stage_status,
s.stage_time,
s.update_time AS cd_update_time
FROM
ads_module_rb_info m
INNER JOIN
latest_stages s ON m.rb_name = s.rb_name AND m.module = s.module_name
WHERE
m.hi_group IS NOT NULL
AND m.module IN ('mixer', 'ranker', 'theseus')
AND m.rb_stage = s.cur_stage
ORDER BY
m.update_time DESC
LIMIT 200;
📊 测试建议
- 分别运行以上三个推荐方案(CTE, 子查询 JOIN, LEFT JOIN)
- 使用
EXPLAIN或EXPLAIN FORMAT=JSON查看执行计划 - 检查:
- 是否走索引(尤其是
rb_name,module,update_time) - 是否有
Using temporary; Using filesort - 查询时间(秒级 vs 分钟级)
- 是否走索引(尤其是
- 如果数据量大(百万级),建议增加索引优化
💡 索引建议(提升性能)
你现在已有以下索引,建议补充或验证:
sql
-- 索引优化建议
ALTER TABLE ads_module_rb_info ADD INDEX idx_module_hi_group (module, hi_group);
ALTER TABLE ads_module_rb_info ADD INDEX idx_update_time (update_time);
ALTER TABLE ads_module_rb_info ADD INDEX idx_icode_rb_status_module (icode_path, rb_name, status, module);
-- 对于 ads_rb_cd_stage_record
ALTER TABLE ads_rb_cd_stage_record ADD INDEX idx_module_name_stage (module_name, stage);
ALTER TABLE ads_rb_cd_stage_record ADD INDEX idx_rb_name_stage (rb_name, stage);
✅ 特别注意:
GROUP BY rb_name, module_name需要(rb_name, module_name)索引才能高效执行