苦逼,下班了,遇到一个 SQL 查询慢的问题,和同事讨论很久,结果用代码解决了

2020-04-01 20:04:39 +08:00
 stevenkang

情况是这样的,mysql 5.7.14-log,有两个表,A 表 2000w 条数据,B 表 1000w 条数据。

现有需求:select a.x, b.x from A a left join B b on a.bid = b.id where a.time >= xxx and b.time <= xxx limit 0, 100

a.bid 、a.time 有索引的情况下,查询一次 5000ms 左右,查询频繁了 10000ms+ 导致接口超时。

和同事讨论了很久,各种 sql 子查询什么的都很慢。最后想到用 in 的方式程序拼接 sql 语句。

于是用了下面的流程:

select a.x, a.bid from A a where a.time >= xxx and b.time <= xxx limit 0, 100

程序将上面查询的 a.bid 拼装为 sql: select b.id, b.x from B b where b.id in (xx1, xx2...xx100)

然后程序中用 b.id 作为 key,b.x 作为 value 建立映射

再次循环查询 A 的数据,将 b.x 通过程序设置进去,然后统一返回。

目前这种方案查询下来 50ms,不知道有什么隐患没。

10292 次点击
所在节点    MySQL
57 条回复
TZ
2020-04-01 20:30:24 +08:00
你单查 a 表还能检索 b.time ? a.bid 、a.time 联合索引一下试试?
lurenw
2020-04-01 20:42:09 +08:00
当 a 表中的 bid 在 b 表里不存在的时候, 不就查不够 100 个了么
InternetExplorer
2020-04-01 20:42:16 +08:00
应该没啥隐患,就是以后把这两个表放到两个数据库都不用改 SQL
stevenkang
2020-04-01 20:43:02 +08:00
@TZ 打错了,应该是用 a.time 进行时间范围筛选,a.bid 、a.time 有单独的索引,聚合索引我试试。之前的 sql 通过 explain 查看索引用上了,可查询出来就是慢。。。
fortunezhang
2020-04-01 20:53:27 +08:00
同意二楼的说法。同时分页也是一个问题。
ElmerZhang
2020-04-01 20:58:38 +08:00
如果你前后 SQL 里的 b.time 都是打错了,应该是 a.time 的话,那么拆开写完全没问题。甚至很多大厂的规范中会要求必须拆开写,理由如 #3 所讲,即使未来 a 表和 b 表拆到两个库中,SQL 都不用改。
ElmerZhang
2020-04-01 21:00:33 +08:00
@lurenw @fortunezhang 楼主原 SQL 中就是 left join,是允许 b 表里不存在的。
TZ
2020-04-01 21:00:51 +08:00
@fortunezhang 想多了,没啥问题,left join 有关联行就塞,没关联行就不塞,又不是 inner join 。分 2 次写更好,联合索引都不用建,只是多一次网络请求,业务逻辑层做关联,减少关联查询 mysql 负担
kawowa
2020-04-01 21:01:19 +08:00
select b.id, b.x from B b where b.id in (
select a.bid id from A a where a.time >= xxx and a.time <= xxx limit 0, 100
)
不知道 MySQL 能不能这样用?
TZ
2020-04-01 21:02:51 +08:00
@kawowa in 子查询不走索引
fortunezhang
2020-04-01 21:12:46 +08:00
@TZ 学到了
fortunezhang
2020-04-01 21:12:59 +08:00
@ElmerZhang 疏忽了。尴尬
areless
2020-04-01 21:53:27 +08:00
EXISTS 试试
cgh
2020-04-01 21:59:42 +08:00
可以做子查询先查出符合条件的 100 条 a. id 和 b. id 再连接 a,b 两个表查询,减少回表次数。
leon0903
2020-04-01 22:01:39 +08:00
其实我心中一直有一个疑问, 就是原来的复合 sql 拆分为单表之后,分页是要怎么做? 很多人懂不懂就说不要复杂查询 要分单表 但是这其中的分页怎么处理却从来没提过。 难道都是不分页的么。。。。
djoiwhud
2020-04-01 22:24:21 +08:00
Navicat 分析看看执行过程呢。不一定是索引执行的,有可能全表扫描了,你不知道而已。
ffeii
2020-04-01 23:23:18 +08:00
我经常用的两种方式
1 、子查询:
select a.x, (select x from b where id=a.bid) from A where a.time >= xxx and a.time <= xxx limit 0, 100
2 、禁止表关联,禁止 for 循环中查询:
查 a 表,得到分页 list
遍历得到 a.bid 的 set
in b 表,得到 list,转 map
再遍历
weizhen199
2020-04-01 23:42:36 +08:00
mysql 用的不多,但是最好贴一个执行计划看看,还有 in 子查询 mysql 不走索引?
stabc
2020-04-01 23:50:04 +08:00
最近用 nosql 用多了,习惯设计表是就灵活一些,不怕重复。你这个案例,我会在 a 表新加一个 b_time 字段方便查询优化。
codelover2016
2020-04-02 00:10:34 +08:00
@leon0903 看情况,拆表的情况下,分页是很蛋疼的。我这边的方案是,做一个逻辑视图来解决分页问题,它甚至可能是个内存分页,查到数据 Id 之后再去查数据。
不过实际应用中,我这里做了是分区表或者做统计表解决。

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

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

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

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

© 2021 V2EX