[求优化] mysql 百万数据 IN 查询 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
请不要在回答技术问题时复制粘贴 AI 生成的内容
jss
V2EX    程序员

[求优化] mysql 百万数据 IN 查询

  •  2
     
  •   jss 2020-05-06 10:54:23 +08:00 7937 次点击
    这是一个创建于 1987 天前的主题,其中的信息可能已经有所发展或是发生改变。

    ##mysql 城市 IN 查询超时

    MySql 代码

    SELECT * FROM `user_info` WHERE ( `city_id` IN (45757,45967,46044,46126,46288,46473,46642,46769,46919,47078,47119,45758,45762,45786,45811,45822,45839,45850,45870,45877,45892,45905) ) AND `user_info`.`delete_time` IS NULL ORDER BY `id` DESC 
    第 1 条附言    2020-05-06 13:17:00 +08:00

    感谢 @ElmerZhang

    用 force index 强制使用 city_id 索引, 秒查结果集

    SELECT * FROM `user_info` FORCE INDEX(idx_city_id) WHERE ( `city_id` IN (45757,45967,46044,46126,46288,46473,46642,46769,46919,47078,47119,45758,45762,45786,45811,45822,45839,45850,45870,45877,45892,45905) ) AND `user_info`.`delete_time` IS NULL ORDER BY `id` DESC 
    64 条回复    2022-09-13 20:49:41 +08:00
    barbery
        1
    barbery  
       2020-05-06 11:01:17 +08:00
    这应该性能没啥问题吧 你确定走了 city_id 这个索引了吗?
    airfling
        2
    airfling  
       2020-05-06 11:10:05 +08:00   1
    city_id 索引建了没,想排序不要直接 select *,应该 selelct id,然后再根据 id 去 select *
    justfindu
        3
    justfindu  
       2020-05-06 11:10:41 +08:00
    这么点数据 in 真的毫无压力, 当然前提是 mysql 5.7 及以上.
    nita22
        4
    nita22  
       2020-05-06 11:14:55 +08:00
    city_id 建索引,select * 如果可以的话就改为覆盖索引。explain 看看实际有没有用上
    jss
        5
    jss  
    OP
       2020-05-06 11:17:15 +08:00
    @airfling 有建 city_id 索引
    jss
        6
    jss  
    OP
       2020-05-06 11:17:25 +08:00
    @barbery 有建 city_id 索引
    jsrgjcy1
        7
    jsrgjcy1  
       2020-05-06 11:20:48 +08:00
    semi-join
    yourssheng
        8
    yourssheng  
       2020-05-06 11:21:11 +08:00
    你 order by id 了。mysql 会 select 出来结果然后排序,如果 select 出来的结果很多还用到外部排序会很慢。
    nita22
        9
    nita22  
       2020-05-06 11:21:27 +08:00
    @jss 看下 city.id 是不是 varchar 类型,自动类型转换也会让索引用不上。具体还是 explain 看下
    zy445566
        10
    zy445566  
       2020-05-06 11:21:53 +08:00
    你这表多大,百万级别应该毫无压力才对,否则就是你服务器垃圾
    iyaozhen
        11
    iyaozhen  
       2020-05-06 11:23:03 +08:00
    口说无凭 explain xxx
    show create table user_info
    HunterPan
        12
    HunterPan  
       2020-05-06 11:23:08 +08:00
    符合数据的条数如果过多,可以分页搞
    jss
        13
    jss  
    OP
       2020-05-06 11:24:39 +08:00
    @nita22 有建 city_id 索引 ,possible_keys 中 有 city_id
    jss
        14
    jss  
    OP
       2020-05-06 11:26:21 +08:00
    @nita22 city_id 类型是 int, explain 看了 possible_keys 中 有 city_id
    mccreefei
        15
    mccreefei  
       2020-05-06 11:26:50 +08:00
    看下 explain 是不是 Using filesort,是的话建议使用(city_id, id)联合索引
    nita22
        16
    nita22  
       2020-05-06 11:27:34 +08:00
    @jss 实际用到的索引是看的 key 吧,还得看 Extra 里面的内容
    jss
        17
    jss  
    OP
       2020-05-06 11:28:37 +08:00
    @HunterPan 加了 LIMIT 0,20 还是一样
    jss
        18
    jss  
    OP
       2020-05-06 11:30:01 +08:00
    @mccreefei 是:Using index condition; Using where
    rrfeng
        19
    rrfeng  
       2020-05-06 11:36:28 +08:00
    直接贴出来,一段一段发干毛
    jss
        20
    jss  
    OP
       2020-05-06 11:41:31 +08:00
    @yourssheng 当 IN 城市数少于 20 个 去掉排序效果 明显 ,但是,我有 50 个以上 city_id 时 一样超时
    ConradG
        21
    ConradG  
       2020-05-06 11:43:35 +08:00
    user_info 的 city_id……索引失效概率不低。
    ClaudeCode
        22
    ClaudeCode  
       2020-05-06 11:46:29 +08:00
    那么多人建议 explain xxx 你怎么就是不听呢
    ElmerZhang
        23
    ElmerZhang  
       2020-05-06 11:47:57 +08:00   2
    用 force index 强制使用 city_id 那个索引

    SELECT * FROM `user_info` FORCE INDEX(idx_city_id) WHERE ( `city_id` IN (45757,45967,46044,46126,46288,46473,46642,46769,46919,47078,47119,45758,45762,45786,45811,45822,45839,45850,45870,45877,45892,45905) ) AND `user_info`.`delete_time` IS NULL ORDER BY `id` DESC

    把 idx_city_id 换成你实际的 city_id 那个索引的名字
    GM
        24
    GM  
       2020-05-06 11:50:31 +08:00
    @yaocai321 估计不懂什么叫 explain
    zhou451971886
        25
    zhou451971886  
       2020-05-06 12:02:06 +08:00
    试试关闭 ICP 再查询

    SET [GLOBAL] optimizer_switch='index_condition_pushdown=off';
    Alucns
        26
    Alucns  
       2020-05-06 12:05:34 +08:00
    估计是这个 ORDER BY `id` DESC 比较耗时,可以取出数据在程序上进行排序。
    wangyzj
        27
    wangyzj  
       2020-05-06 12:10:01 +08:00
    SELECT * FROM `user_info` WHERE ( `city_id` IN (45757,45967,46044,46126,46288,46473,46642,46769,46919,47078,47119,45758,45762,45786,45811,45822,45839,45850,45870,45877,45892,45905) ) ORDER BY `id` DESC

    试一下这句
    ConradG
        28
    ConradG  
       2020-05-06 12:30:31 +08:00
    这个大概率不是语句的问题,而是数据分布的问题。
    cityId 通常是一个百到千级的数据集,而且分布上往往集中于特定的小几十个 key 下。user 表 LZ 说是百万级,那么相比下这个索引的效果本身就很有限。在分布较为平均的情况下预期查出数据都是 cityId 数 × 10000 级别的。再加上后边 delete_time 判 null 必须遍历,根据 id 倒排又是大概率不被优化直接遍历,不慢才是怪事。
    aliipay
        29
    aliipay  
       2020-05-06 12:37:27 +08:00
    @iyaozhen 这牙膏半天挤不完,哈哈哈
    lasuar
        30
    lasuar  
       2020-05-06 12:45:46 +08:00
    可以把 `show create table user_info` 和 explain 结果贴一下
    akagishigeru
        31
    akagishigeru  
       2020-05-06 12:46:57 +08:00 via iPhone
    你检索了多少行呢?很可能是 city id 占了太多,虽然走了索引,但还是检索了全表行数。你可以把 in 分批次检索。
    Heebe
        32
    Heebe  
       2020-05-06 13:03:33 +08:00
    其实这也是业务硬伤,这相当于查询到所有城市的数据之后,然后再进行一次 delete_time is null 的遍历,数据量大了,慢是肯定的。

    我建议做 4 点,
    1,依据 city_id 拆表(垂直水平都行,百万级别我建议 range 分区就行)
    2,拆分 SQL 语句,根据分区分表特征,多次查询后合并
    3,考虑 BY `id` DESC 是否有存在的必要性,假如本身就是 ID 插入的,这里其实可以忽略
    4,delete_time is null,永远都比不上 isDelete = 1 的时候来得快
    jss
        33
    jss  
    OP
       2020-05-06 13:14:17 +08:00
    @ElmerZhang 非常感谢,使用 force index 后 秒查数据
    dog82
        34
    dog82  
       2020-05-06 13:17:15 +08:00
    不要 select * 会回表,效率一下就差了很多;
    order by id 也严重影响效率。
    如果要优化建联合索引吧。
    要看查询的结果集大概多少条?超过 5%还是走全表扫描吧
    jss
        35
    jss  
    OP
       2020-05-06 13:31:53 +08:00
    @Heebe 的确,BY `id` DESC 某些时候不但多余,还影响性能; delete_time is null 我也发现了 值为 0 或 null 比 1 或-1 慢很多
    jss
        36
    jss  
    OP
       2020-05-06 13:33:57 +08:00
    @JaguarJack 发现了,在 IN 查询时并没有走索引
    yourssheng
        37
    yourssheng  
       2020-05-06 14:37:27 +08:00
    @jss explain 一下就知道咯。
    barbery
        38
    barbery  
       2020-05-06 15:08:32 +08:00
    @jss 那就肯定是 order by 影响了 mysql 的优化器,导致没有走 city id 的索引,应该走了主键 id 了,试试 order by id+0
    telami
        39
    telami  
       2020-05-06 15:14:31 +08:00
    让你贴 [可以把 `show create table user_info` 和 explain 结果贴一下] ,就贴下,在这逗人玩呢啊
    yujieyu7
        40
    yujieyu7  
       2020-05-06 15:26:05 +08:00   1
    不上表结构和 explain 语句,这是要人盲猜啊♂
    pushback
        41
    pushback  
       2020-05-06 15:34:45 +08:00
    @jss 默认就是 by id,null 也是要占用内存的,建议设置默认值吧,如果 city_id 有索引,也就是 orderby 受影响了吧,sql 调优我习惯
    select [query column] from table left join foreign_table on [foreign key] where column = [column] order by [order column] 上面 4 个影响点去调试 ,建议 lz 多调调
    hauzi
        42
    hauzi  
       2020-05-06 15:36:24 +08:00
    范围查询一般都不走索引的
    jss
        43
    jss  
    OP
    2020-05-06 15:37:54 +08:00
    @telami 已经处理好了,谢谢
    jss
        44
    jss  
    OP
       2020-05-06 15:38:27 +08:00
    @pushback 感谢,指教...
    owenliang
        45
    owenliang  
       2020-05-06 16:49:29 +08:00
    force index 不建议使用,使用 use index
    bfqymmt
        46
    bfqymmt  
       2020-05-06 17:01:47 +08:00
    学习到了。
    encro
        47
    encro  
       2020-05-06 17:04:22 +08:00
    这个其实很难优化到毫秒级的。
    去掉 delete_time,加 city_id,id 组合索引还可以达到 ms 级别,
    但是如果翻页到 100 页之后也是超过 ms 级别了吧,
    不知道楼主要求是 ms 还是 s 级别。
    jss
        48
    jss  
    OP
       2020-05-06 17:07:36 +08:00
    @owenliang USE INDEX 感觉比 FORCE INDEX 更智能一些,但是像我这种 IN 查询 是不走索引,经测试: 使用 force index 耗时 0.038s ; 使用 use index 耗时 9.969s 。
    jss
        49
    jss  
    OP
       2020-05-06 17:11:30 +08:00
    @encro 其他的不知道,我这问题 使用 force index 分页到 116 页 要 656ms
    fareware
        50
    fareware  
       2020-05-06 18:05:19 +08:00   2
    百万数据对 Mysql 来说也不大,慢查询大多源自索引。如 city_id 有索引还慢,肯定索引失效。
    1. 这里索引失效最大可能是 city_id 区分度过低,类比性别字段,如结果集超过总的 30%(大约),Mysql 会放弃索引走全表扫描,因为非聚簇索引需要回表。使用 force 可以解决但不优雅。
    2. order by id 存在 filesort, 需进行全字段或 rowid 排序,避免方法是放弃或根据其他字段且建立联合索引排序,如联合索引包括 city_id 和排序字段,会用到覆盖索引避免回表且无需使用 force,如联合索引还包括 whre 条件,会用到索引下推。这应该就是理想情况了。
    lasuar
        51
    lasuar  
       2020-05-06 19:11:49 +08:00
    ```
    CREATE TABLE IF NOT EXISTS million_user_info (
    id bigint(7) PRIMARY KEY AUTO_INCREMENT,
    city_id int,
    delete_time TIMESTAMP,
    others VARCHAR(111) DEFAULT "" NOT NULL
    );
    CREATE INDEX idx_city_id ON million_user_info (city_id,id);(无独立 city_id 索引)
    ```
    模拟环境:
    125 万条数据;插入的 city_id 在楼主给出的 city_id 范围内,others 为干扰随机 str
    查询语句:
    ```
    SELECT * FROM `million_user_info` WHERE ( `city_id` IN (45757,45967,46044,46126,46288,46473,46642,46769,46919,47078,47119,45758,45762,45786,45811,45822,45839,45850,45870,45877,45892,45905) ) AND `million_user_info`.`delete_time` IS NULL ORDER BY `id` DESC LIMIT 1000;
    ```
    测试结果:
    当 delete_time=null 的行数实际只有 1 行时,秒查;
    当 delete_time=null 的行数实际有 10w 行时,且 SQL 无 limit,耗费 11s ;
    当 delete_time=null 的行数实际有 10w 行时,且 SQL 加 limit 1000,秒查;
    当 delete_time=null 的行数实际有 10w 行时,且 SQL 包含 force index,无 limit,耗费 15.9s ;
    当 delete_time=null 的行数实际有 10w 行时,且 SQL 包含 force index,加 limit 1000,耗费 4.9s ;
    加上单独的 city_id 索引后,上述测试结果无明显变化。
    Leigg
        52
    Leigg  
       2020-05-06 19:37:31 +08:00 via Android
    @lasuar mysql5.7
    encro
        53
    encro  
       2020-05-06 19:52:58 +08:00
    @lasuar

    作者实际 delete_time=null 应该接近 100%(不会大部分用户被删除了吧),
    所以我前面说很难。
    除非去掉 AND `million_user_info`.`delete_time` IS NULL 这个条件。

    秒查也要看是 s 还是 ms,
    差别可大,一个可能走磁盘占 iops,一个走内存。
    encro
        54
    encro  
       2020-05-06 19:54:10 +08:00
    40 楼提到 Explain 才是解决这类问题的正确办法。
    lasuar
        55
    lasuar  
       2020-05-06 20:01:01 +08:00 via Android
    @Leigg 是的
    lasuar
        56
    lasuar  
       2020-05-07 10:28:37 +08:00
    @encro [环境:mysql 5.7.29 ,测试机远程连接云主机上的 db]
    反复测试后的结果显示,IN 后面跟超过 1 个元素就不会走任何索引,加上 order by id 才走了主键索引,走主键索引比加上 force index(idx_city_id)要快很多,后者 explain 的结果显示走的 idx_city_id 索引,rows 为表总行数的两倍(why?)。另外加了 delete_time is null 也是会走主键索引,记得加上 limit 。
    (我分别把测试表中的 delete_time is null 行数调整为 0/10w/all,与楼主同样的 SQL+limit 10000 都只需要 0.Xs ,楼主执行的 SQL 是不含 limit 的吗?如果不含 limit,我这里的执行时间超 1min,不可能在生产环境不加 limit 吧。)
    luchuxue110
        57
    luchuxue110  
       2020-05-07 10:34:39 +08:00
    马克
    jss
        58
    jss  
    OP
       2020-05-07 11:34:28 +08:00
    @lasuar limit 要加的
    zhangysh1995
        59
    zhangysh1995  
       2020-05-07 14:16:26 +08:00
    @zhou451971886 为什么要关闭这个优化?
    zhangysh1995
        60
    zhangysh1995  
       2020-05-07 14:17:56 +08:00
    不一定需要 `FORCE INDEX`,尝试一下 `USE INDEX`。https://dev.mysql.com/doc/refman/8.0/en/index-hints.html
    zhangysh1995
        61
    zhangysh1995  
       2020-05-07 14:23:28 +08:00
    @hauzi `范围查询一般都不走索引的`,这是从经验来的嘛?我看文档 https://dev.mysql.com/doc/refman/5.6/en/range-optimization.html 说的是 indexed key 都会做优化?
    kanepan19
        62
    kanepan19  
       2020-05-07 17:09:53 +08:00
    @zhangysh1995
    我这边的场景每天 200 万, 查询 2 天 用 USE INDEX 就全表扫描了,必须 force index
    zhangysh1995
        63
    zhangysh1995  
       2020-05-08 13:53:57 +08:00
    @kanepan19 这样的啊,学习了。谢谢回复!
    qwwe01
        64
    qwwe01  
       2022-09-13 20:49:41 +08:00
    挖坟。。。最近再找类似问题发现有这么个 BUG
    https://bugs.mysql.com/bug.php?id=97001

    There is an additional case of the same poor optimization, if queries with large IN() lists are used, however I've not been able to generate a reproducible test case that does not require production data and circumstances. The query could be:
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     2594 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 29ms UTC 15:26 PVG 23:26 LAX 08:26 JFK 11:26
    Do have faith in what you're doing.
    ubao snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86