游客发表
SQL里写WHERE id IN (1,量查2,3,4,5),谁都会。询多可当括号里躺着一千个ID,条数事情就变味了。量查
我见过最粗暴的询多写法,是条数把这一千个ID用OR拼起来,SQL文本长达几十KB,量查数据库收到后先愣三秒,询多然后开始全表扫描。条数执行计划一看,量查走了个烂索引,询多耗时八秒七。条数旁边监控大屏直接飘红,量查DBA的询多钉钉群里瞬间炸锅。
批量查询的条数核心矛盾,从来不是“怎么把多条数据查出来”,而是“怎么让数据库别被查询条件噎死”。
拿MySQL举例。IN列表里的元素超过某个阈值(大概几百),优化器就可能放弃索引下推,转而走全表扫描。更隐蔽的问题是,IN列表越长,解析SQL的耗时越高——这是纯CPU开销,跟数据量无关。有一次我往IN里塞了两万个ID,光解析就花了400毫秒,数据查询本身才用200毫秒。白花花的资源烧在语法树上,冤。
所以第一招:拆批。每批500个ID,循环查五次,每次走索引,总耗时可能比一次性查完还快。别觉得多几次网络往返就亏了,数据库省下的解析时间和IO成本,远大于那几次RTT。实测案例:某订单系统把2000个订单号拆成4批查询,总耗时从4.2秒降到1.1秒。代价是应用层多写个for循环,值。
第二招,别用IN,用JOIN。把ID列表先插入一张临时表,然后SELECT ... FROM target JOIN temp ON target.id = temp.id。这招对MySQL尤其管用,因为临时表能走hash join,而IN列表本质上是逐值比较。PostgreSQL用户可能觉得这多此一举,但MySQL 8.0之前的优化器对IN列表的处理确实笨拙。临时表方案让优化器有更多选择空间,执行计划往往更聪明。
第三招,针对极端场景:ID列表本身是连续区间。比如查ID从1000到2000的数据,直接WHERE id BETWEEN 1000 AND 2000。别笑,我真见过有人把区间内的每个整数都列进IN列表里——整整一千个数,还带逗号,SQL文本比小说还长。问为什么不用BETWEEN,答曰“怕漏数据”。漏不漏数据,取决于业务逻辑,不取决于写法。如果ID存在空洞,BETWEEN照样能查全,因为空洞本来就没数据。
还有一招比较冷门:利用VALUES ROW()语法构造派生表。MySQL 8.0+支持SELECT * FROM (VALUES ROW(1), ROW(2), ROW(3)) AS v(id) JOIN target ON target.id = v.id。这招比临时表更轻量,不用建表,适合一次性查询。但别在循环里反复执行这种SQL,解析开销依然存在。
最后说个容易踩的坑:批量查询的结果集顺序不保证和IN列表顺序一致。MySQL默认按主键或索引顺序返回,不是按你IN里的顺序。如果你需要保持原始ID顺序,要么在应用层排序,要么用ORDER BY FIELD(id, 5, 3, 1)——但这会让索引失效,数据量小无所谓,数据量大就等着哭吧。
批量查询这事,说穿了是个资源博弈。数据库不是神,你给它多大的IN列表,它就要做多大的决策。与其让数据库硬扛,不如在应用层多动脑子。拆批、临时表、派生表,三选一,基本能覆盖99%的场景。剩下那1%,直接改需求吧——一次性查两万条数据,为什么不考虑分页?
友情链接