一亿行的 MySQL 表,主键 ID 不连续,怎样随机取 30 行速度最快? - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
请不要在回答技术问题时复制粘贴 AI 生成的内容
kisshere
V2EX    程序员

一亿行的 MySQL 表,主键 ID 不连续,怎样随机取 30 行速度最快?

  • &nbp;
  •   kisshere 2021-02-19 11:35:17 +08:00 6424 次点击
    这是一个创建于 1695 天前的主题,其中的信息可能已经有所发展或是发生改变。

    ORDER BY rand()速度太慢,主键又不连续(类似于 1,6,7,33,78,93 这种递增),要不然都可以用 php 生成 30 个随机数 id 去 query 了

    46 条回复    2021-02-20 23:08:56 +08:00
    huabalance
        1
    huabalance  
       2021-02-19 11:37:45 +08:00 via Android
    不断 rand 直到取慢三十个 可能还要去重
    huabalance
        2
    huabalance  
       2021-02-19 11:38:26 +08:00 via Android
    我是说 php 生成随机数。。。
    0TSH60F7J2rVkg8t
        3
    0TSH60F7J2rVkg8t  
       2021-02-19 11:38:36 +08:00
    随机生成 30 个数字,WHERE ID >= 你的某个随机数字 LIMIT 1,执行 30 次查询,这样呢?
    honeycomb
        4
    honeycomb  
       2021-02-19 11:38:51 +08:00 via Android
    另外建一个索引表,从那个表里取随机值?
    leeyuzhe
        5
    leeyuzhe  
       2021-02-19 11:40:45 +08:00
    @ahhui 要去重啊,跟等于实际上是一样的,不断去取然后去重直到取满
    caola
        6
    caola  
       2021-02-19 11:56:43 +08:00
    ```
    SELECT * FROM XXX
    WHERE id >= ((SELECT MAX(id) FROM XXX)-(SELECT MIN(id) FROM XXX)) * RAND() + (SELECT MIN(id) FROM XXX)
    limit 30;
    ```

    在网上抄来的
    Rocketer
        7
    Rocketer  
       2021-02-19 11:59:04 +08:00 via iPhone
    @caola 你这不是随机 30 个,而是自然排序取连续 30 个,只是起点随机而已
    Duolingo
        8
    Duolingo  
       2021-02-19 12:05:53 +08:00 via Android
    我觉得只要 sql 里有 rand 就会慢。
    还是维护个 set,保存生成的随机数,然后生成满 30 个随机数每次取一个最快。
    bthulu
        9
    bthulu  
       2021-02-19 12:09:38 +08:00
    每次随机三十个数, 数字间大小至少相差十万万, 比如[5001, 235555, 1053052, ...], 然后查询 id>5001 limit 1 union id>23555 limit1 union id>1053052 limit1 ...
    dafsic
        10
    dafsic  
       2021-02-19 12:11:35 +08:00   1
    随机生成 100 个数,然后 query,结果中大概率会有 30 个
    caola
        11
    caola  
       2021-02-19 12:11:51 +08:00   1
    @Rocketer #7 如果一定要很随机的话,可以把所有的 ID 保存到 redis 并维护这个 ID 列表, 然后 srandmember 命令去取
    rahuahua
        12
    rahuahua  
       2021-02-19 12:27:07 +08:00
    @leeyuzhe 这个方式去重不是问题,问题是要读取三十次数据库
    siweipancc
        13
    siweipancc  
       2021-02-19 14:00:35 +08:00 via iPhone
    不上 redis 还有个折衷的方法,额外生成一列 hash,生成索引,取 30 行列值>=随机生成的 hash 的就行了
    shyling
        14
    shyling  
       2021-02-19 14:13:28 +08:00
    增加一个存随机数的列 a 加 index,查询的时候再生成一个随机数 b,找 a 在 b 附近的 30 条
    Amayadream
        15
    Amayadream  
       2021-02-19 14:16:05 +08:00
    一般这种不符合常理的需求都不是原始需求,可以说一下你的原始需求是什么。
    aeli
        16
    aeli  
       2021-02-19 14:35:06 +08:00
    取最大最小 id,然后计算出差值,再在这个差值中,取 60 个随机数后排序,select * from id in( ( a1> and < a2 )or( a3 > < a4) ....)... 大概的思路吧,取两个值之间的一个。
    lovecy
        17
    lovecy  
       2021-02-19 14:50:06 +08:00
    假设只有 100 条数据
    ```sql
    SELECT * FROM (SELECT * FROM xxx WHERE id > FLOOR(RAND()*100) LIMIT 1) AS n1
    UNION
    SELECT * FROM (SELECT * FROM xxx WHERE id > FLOOR(RAND()*100) LIMIT 1) AS n2
    UNION
    SELECT * FROM (SELECT * FROM xxx WHERE id > FLOOR(RAND()*100) LIMIT 1) AS n3
    #写 30 遍,查出来有重复的再查一次
    ```
    FLOOR(RAND()*100),这一步可以先在代码里算出来,整个方法前提是要知道 id 的最大范围
    laminux29
        18
    laminux29  
       2021-02-19 14:53:54 +08:00
    这种问题明显应该用空间去换时间,比如在数据录入时,就概率性地取出某些行,直接作为最终数据。
    xxxyh
        19
    xxxyh  
       2021-02-19 15:01:38 +08:00
    order by rand 全表扫描,还排序,如果对随机性要求不是很高的话,可以在取最大和最小 id,在应用层随机取 30 个 id,然后 select * from table where id > (随机出来的 id) limit 1,重复 30 次
    xxxyh
        20
    xxxyh  
       2021-02-19 15:08:44 +08:00
    如果一定要非常精确的随机的话,可以 select count(*) from table,然后在应用层随机出来 30 个 id,把这 30 个 id 排序,从最小的 id@1 开始,select * from table limit id@1,1,从第二条开始 select * from table limit (id@2 - id@1),1
    xxxyh
        21
    xxxyh  
       2021-02-19 15:11:53 +08:00
    如果一定要非常精确的随机的话,可以 select count(*) from table,然后在应用层随机出来 30 个 id,把这 30 个 id 排序,从最小的 id@1 开始,select * from table limit id@1,1,从第二条开始 select * from table where id > (第一次取出来的 id) limit (id@2 - id@1),1
    eastphoton
        22
    eastphoton  
       2021-02-19 15:30:51 +08:00
    既然主键 ID 没有更多规律,总得查一下或者干脆拿到所有 ID 才能得知具体某个 ID 存不存在这个信息了。。

    所有 ID 单独维护一份感觉比较合适,上 redis 或者自己维护个 set 。

    要么就用一些不那么随机的方案。
    hannibalm
        23
    hannibalm  
       2021-02-19 15:47:11 +08:00
    再建一个临时表,一列现有主键,一列从 1 开始的序号。然后随机抽 30 个。
    yemoluo
        24
    yemoluo  
       2021-02-19 16:51:08 +08:00
    @dafsic 这个目前是最优,不过 2 倍左右就可以了,如果不够再随机 2 倍去取
    bertonzh
        25
    bertonzh  
       2021-02-19 16:54:25 +08:00
    先加一个自增字段进去...
    zlowly
        26
    zlowly  
       2021-02-19 16:58:31 +08:00
    mariadb.com 的 Knowledge Base 知识库上有一篇专门讲这个问题的文章,里面提到一些较快但没那么随机的方法,你可以搜索来参考下
    data-sampling-techniques-for-efficiently-finding-a-random-row
    stevenkang
        27
    stevenkang  
       2021-02-19 17:14:23 +08:00
    数据库不是用来存取数据的吗,用来做这些复杂的活干嘛。

    直接程序随机生成 ID 呗,如 #3 所说取满 30 个就行了。
    yixiugegegege
        28
    yixiugegegege  
       2021-02-19 17:20:31 +08:00
    SELECT *
    FROM `table` AS t1 JOIN (SELECT ROUND(RAND() * (SELECT MAX(id) FROM `table`)) AS id) AS t2
    WHERE t1.id >= t2.id
    ORDER BY t1.id LIMIT 30;
    Still4
        29
    Still4  
       2021-02-19 17:39:06 +08:00
    我觉得直接说业务吧,啥需求要一亿行里面去随机啊,上面说加自增字段也好,加表也好都是空间换时间,根源上如果需求可以变通的话根本就不需要这么做
    ivanMeng
        30
    ivanMeng  
       2021-02-19 17:42:33 +08:00
    布隆过滤器吧,可以快速判断 key 是否存在,id 乱序的 mysql 聚族索引也没发挥出,又是随机的记录地址完全不相邻,更加缓慢,还有这问题本身就是问题 。1 亿的表 是否有读写分离?上层是否有缓存冷热数据分离?
    mosliu
        31
    mosliu  
       2021-02-19 17:47:23 +08:00
    看你的数据 id 有多稀疏
    不是太稀疏的话
    先获取 3*稀疏倍数*2 个随机数 记为 idlist1
    然后按 id in idlist1 limit 30 取 架设取出 n 个
    然后不够的 再按缺少数量 取随机数 rand 循环( 30-n )次 {取 id>rand and not in (已取) limit 1}
    akira
        32
    akira  
       2021-02-19 17:48:28 +08:00
    同意 25 楼的, 加个连续的自增字段,立马简单起来了
    love2020
        33
    love2020  
       2021-02-19 18:00:12 +08:00
    众所周知,随机数是一个难点
    fox0001
        34
    fox0001  
       2021-02-19 19:19:00 +08:00 via Android
    @bertonzh #25
    @akira #32
    如果是静态数据,这是最简单的方法。然而楼主丢下问题就没有再回复了
    xcstream
        35
    xcstream  
       2021-02-19 19:27:26 +08:00
    最快当然不用 mysql 直接写程序用偏移量读取数据文件
    ipwx
        36
    ipwx  
       2021-02-19 19:34:05 +08:00
    说实话,如果原始 id 不是均匀分布的,那么 id >= RAND() 这种操作去取数字,得出来的结果也不是均匀分布的。

    很简单,举个例子:原始 id 是 1, 5, 6, 7 。那么用 id >= min(id) + rand() * (max(id) - min(id)) limit 1 取数字,每个数字被选取的概率为:

    1 = 4/7
    5 = 1/7
    6 = 1/7
    7 = 1/7
    ipwx
        37
    ipwx  
       2021-02-19 19:34:42 +08:00
    生成第二列连续的 id 然后去取,就不会产生这种不均匀分布的问题了。
    ipwx
        38
    ipwx  
       2021-02-19 19:36:16 +08:00
    1 亿 id 的 int 数组在内存中也就几个 gb 的事情。说实话,单独维护一个索引文件大概都容易搞得定。
    stevefan1999
        39
    stevefan1999  
       2021-02-19 19:39:52 +08:00
    你要先定什是
    譬如我可以定 generateRandomNumber() { return 4 }
    然後按照 random number 做 seed 一 LNG 然後生成一次序...
    因不代表不能重啊 不如果要去重那更加要命了
    xumng123
        40
    xumng123  
       2021-02-19 19:41:26 +08:00 via iPhone
    将 id 放在一个数组里,随机取 39 个后,然后去查一下数据库。
    akira
        41
    akira  
       2021-02-19 22:27:48 +08:00
    @fox0001 就算是动态数据,只要不是动态的太厉害,定时,例如每天,处理一下自增字段 应该没太大问题的
    renmu123
        42
    renmu123  
       2021-02-19 22:36:38 +08:00 via Android
    搞点伪随机,数据量大了之后,谁也不知道你是真随机还是假随机了
    bthulu
        43
    bthulu  
       2021-02-20 11:06:27 +08:00
    @akira 连续的自增字段怎么加, 你如何保证严格的连续性, 删除数据, 插入未提交等造成的不连续你怎么处理
    ckdxc
        44
    ckdxc  
       2021-02-20 11:21:14 +08:00
    随机行号,行不? 感觉不需要随机 id, round(count), 30 个行号必命中
    wakzz
        45
    wakzz  
       2021-02-20 15:09:35 +08:00
    先看看业务,是不是必须要真随机。有很多业务实际上压根不需要真随机,搞个差一点的伪随机就够用了
    akira
        46
    akira  
       2021-02-20 23:08:56 +08:00
    @bthulu 不需要严格保证的连续性呀
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     5498 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 26ms UTC 08:43 PVG 16:43 LAX 01:43 JFK 04:43
    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