如何获取逗号分隔Product Id串中每个ID在Orders表的首条记录
性能问题根因
用FIND_IN_SET处理千级ID查询慢的核心原因是:该函数无法命中索引,会触发Orders表全表扫描,每扫描一行都要做一次字符串匹配计算,传入ID越多,匹配开销线性上涨,数据量上来后性能必然明显下降。
高性能实现方案
核心思路是把ID列表的解析从SQL匹配逻辑里剥离,用带索引的临时表做等值关联,直接走数据库原生索引能力,整套逻辑都可以在存储过程里完成,不需要改上层调用逻辑:
步骤1:创建内存临时表存储目标ID
在存储过程入口处先建内存临时表,给Product ID字段加主键索引,全内存操作无磁盘IO开销,连接断开后会自动清理:
-- 放在存储过程最开头即可 CREATE TEMPORARY TABLE IF NOT EXISTS tmp_target_pid ( product_id INT UNSIGNED NOT NULL PRIMARY KEY ) ENGINE = MEMORY; TRUNCATE TABLE tmp_target_pid; -- 清空同连接上次调用的残留数据
步骤2:批量拆分逗号分隔ID串写入临时表
不要写循环逐行拆分插入,直接用集合操作批量写入,千个ID的拆分耗时在1毫秒内:
- MySQL 8.0及以上版本直接用JSON函数拆分,兼容性最好:
-- 假设存储过程入参名为 in_pid_str,类型为VARCHAR(65535),传入值为"1,2,3,46,15..." INSERT INTO tmp_target_pid (product_id) SELECT CAST(val AS UNSIGNED) FROM JSON_TABLE( CONCAT('["', REPLACE(in_pid_str, ',', '","'), '"]'), '$[*]' COLUMNS(val VARCHAR(16) PATH '$') ) t WHERE val REGEXP '^[0-9]+$'; -- 过滤空值、非法格式值,避免隐式转换导致索引失效
- 如果是MySQL 5.7版本,用内置数字序列表拆分即可,逻辑和上述写法一致。
步骤3:关联查询每个Product ID的首条订单
提前给Orders表建联合覆盖索引idx_pid_date_oid (product_id, order_date, order_id, total_cost),建好后查询可以直接走索引不需要回表,性能拉满。
两种写法选其一即可,性能差异极小:
写法1:关联子查询(索引适配最好,推荐)
SELECT o.* FROM tmp_target_pid t JOIN Orders o ON o.order_id = ( SELECT order_id FROM Orders o1 WHERE o1.product_id = t.product_id ORDER BY o1.order_date ASC, o1.order_id ASC -- 按业务定义的"首次"规则排序,这里取最早下单、同日期下最小订单号的记录 LIMIT 1 );
写法2:窗口函数(语义更直观)
SELECT order_id, product_id, order_date, total_cost FROM ( SELECT o.*, ROW_NUMBER() OVER (PARTITION BY o.product_id ORDER BY o.order_date ASC, o.order_id ASC) AS rn FROM Orders o INNER JOIN tmp_target_pid t ON o.product_id = t.product_id ) filtered WHERE rn = 1;
性能效果对比
同样千级传入ID、Orders表百万行数据的场景:
- 原
FIND_IN_SET写法:查询耗时通常在3~10秒,走全表扫描+逐行字符串匹配,CPU占用高 - 临时表+索引关联写法:查询耗时通常在10~50毫秒,全程走索引,无多余计算开销
样例数据对应结果
测试Orders表数据:
Order Id Product Id Order Date Total Cost 101 1 2023-01-05 99 107 2 2023-01-06 199 115 1 2023-01-12 89 123 3 2023-01-08 299 129 2 2023-01-20 179 传入ID串为
"1,2,3"时,返回结果:
Order Id Product Id Order Date Total Cost 101 1 2023-01-05 99 107 2 2023-01-06 199 123 3 2023-01-08 299
注意事项
- 不要用MyISAM或者InnoDB引擎建临时表存ID,MEMORY引擎的内存操作速度快一个数量级以上
- 排序取首条的规则可以按业务调整,如果业务定义"首次出现"就是最小Order Id,直接把ORDER BY子句改成
ORDER BY order_id ASC即可 - 前文提到的联合覆盖索引必须建,建好后查询不需要回表,性能还能再提升30%以上
内容的提问来源于stack exchange,提问作者Aazarus
相关产品推荐
相关产品推荐

