mysql 遇到最愚蠢的查询性能问题,求解决

2025 年 1 月 14 日
 godall

mysql 8.0 Windows 版本。

有这么一个股市行情表:##

tb_quote (
 op_date  ,
 code  ,
 price  ,
 ratio  ,
 hsl  ,
   index unique idx_1 (op_date,code) ,
   index idx_2(code)
 )

每个交易日 6000 条,一年几百万条规模。

现在有一个简单需求:##

  1. 筛选某一天涨幅在( 3%,5%)区间的股票,且换手率在( 5%-10%)区间之间
  2. 查看后一天的股票涨跌情况。

实现语句很简单:

方案一: 第一条语句在 1 秒内返回结果:(20 条记录)

select code from tb_quote where op_date='yyyy-mm-dd' and ratio >3 and ratio <5 and hsl>5 and hsl <10 

嵌套第二条后:

 select a.* from tb_quote a join ( 
   select code from tb_quote b where op_date='yyyy-mm-d1' and ratio >3 and ratio <5 and hsl>5 and hsl <10 ) t
 on a.code=t.code and a.op_date='yyyy-mm-d2' 

执行时间就超过 1 分钟!

** 查看 mysql 解释: **

  1. simple b,key idx_1 ,key_len=3,ref=const ,rows=s5373,filterd 0.05, Extra:using where
  2. simple a, key idx_2, key_len=30, ref=tb_quote.code, rows=465 ,filtered 0.21, Extra:using where

** 方案二: ** 但是如果把第一条语句的结果保存在一个表 tmp 里面,并且 code 建立普通索引以后,变成:

select a.* from tb_quote a join (
 select code from tmp ) t
on a.code=t.codeand a.op_date='yyyy-mm-d3'

执行时间不超过 1 秒!!!

** 查看 mysql 解释:**

  1. simple a, key idx_1, key_len=3, ref=const, rows=5365 ,filtered 100, Extra:useing where
  2. simple tmp, key idx_code ,key_len=29,ref=a.f12 ,rows=1, filterd 100, Extra:using where

发现差别在:

  1. 次序不同,方案二子表 tmp 在前,方案一子表 b 在后;
  2. filtered 值不同,方案二是 100 ,方案一只有 0.05 ;

不知道 mysql 是怎么优化的?如果不用中间表过渡的话,应该怎么写 sql ?

3071 次点击
所在节点    MySQL
15 条回复
Oldletter
2025 年 1 月 14 日
试试
```sql
SELECT a.*
FROM tb_quote a
WHERE a.op_date = 'yyyy-mm-d2'
AND EXISTS (
SELECT 1
FROM tb_quote b
WHERE b.op_date = 'yyyy-mm-d1'
AND b.ratio > 3
AND b.ratio < 5
AND b.hsl > 5
AND b.hsl < 10
AND b.code = a.code
);
```
或者写 cte
```sql
WITH filtered_codes AS (
SELECT code
FROM tb_quote
WHERE op_date = 'yyyy-mm-d1'
AND ratio > 3
AND ratio < 5
AND hsl > 5
AND hsl < 10
)
SELECT a.*
FROM tb_quote a
JOIN filtered_codes t
ON a.code = t.code
WHERE a.op_date = 'yyyy-mm-d2';
```
或者就只能改你的索引了
shfan
2025 年 1 月 14 日
如果使用 python ,我觉得可以用 dataframe ,而且也可以不用数据库,用 csv 文件本地加载数据更快,你这个数据量加载数据可能大概 1 秒,运算大概几十毫秒就能完成,如果运算条件多了,这种方式速度优势就明显了
LiaoMatt
2025 年 1 月 14 日
子查询的写法试过没
anonydmer
2025 年 1 月 14 日
这个场景为什么要用行数据库? 换个列数据库一下子问题都解决了
godall
2025 年 1 月 14 日
@Oldletter #1 谢谢,你的方案一、方案二查询速度大概 45 几秒,跟我的区别不大。还是我的方案二速度快。

根据 mysql 的执行计划最后发现大表的索引用错了(用了 code 索引)所以慢,强制指定索引(force index)后就飞快:
````
SELECT a.* FROM tb_quote a FORCE INDEX(idx_1) JOIN (
SELECT code FROM tb_quote b
WHERE op_date='2025-01-14' AND ratio BETWEEN 3 AND 5 AND hsl BETWEEN 5 AND 10
) t ON a.code=t.code AND a.op_date='2025-01-10' ;
````
1n0ABUemWaj011zE
2025 年 1 月 14 日
换个 influxdb 试试吧 这种类型的数据 是时序数据库擅长的
encro
2025 年 1 月 14 日
duckdb ,Polars ,PyArrow
sagaxu
2025 年 1 月 14 日
CBO 有时会错判,可能是数据采样失真,也可能其它原因。可以尝试用 ANALYZE TABLE ...刷新下统计信息。
512357301
2025 年 1 月 14 日
换列式数据库吧,行式数据库数据量大了写 SQL 烧脑,各种加索引,索引也占空间啊,列存相当于全局索引,快多了
realpg
2025 年 1 月 15 日
MYSQL 稍微复杂一丁点的查询 一定要手动指定索引
不要让他自动分析,全是最垃圾索引
sagaxu
2025 年 1 月 15 日
@realpg Oracle 我都遇到过,之后大表查询我都手动指定索引,不再相信自动选的。Mysql 是我用过的数据库里最容易选错索引的,不但容易选错索引,它还容易选错执行顺序,还得强制指定连表顺序,连表索引,分组索引,排序索引等等。
realpg
2025 年 1 月 15 日
@sagaxu #11
mysql 你用非常容易选错索引都是抬举他了

只要是稍微复杂的查询,你库里有多条为每条复杂查询设计好的索引,且字段有重复的(就是有不同复合索引包含同一个字段),MYSQL 好像是故意的,他一定会选择错的索引

我们这边的一套系统的血泪经验 接近 99%的错误索引选择率
realpg
2025 年 1 月 15 日
@sagaxu #11
我这边的系统,每个表几乎都是亿行数据起步,这么大的数据规模,基本所有的 WHERE 多条件查询都设计了专用索引,要不根本操作不动
经常是表占 50GB 空间,其中 2/3 是索引占的
然后实际就是,只要你不 FORCE INDEX ,那么 MYSQL 一定会选择最垃圾的索引
几乎没有例外 我们全部测试过
godall
2025 年 1 月 15 日
@sagaxu #11 确实如此,谢谢!
1018ji
2025 年 1 月 15 日
嵌套,我又学了一招护城河,我感觉越来越没有人敢裁我了

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/1105019

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX