V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
Xrall
V2EX  ›  数据库

数据库查询优化到底该怎么做?

  •  
  •   Xrall · 76 天前 · 2642 次点击
    这是一个创建于 76 天前的主题,其中的信息可能已经有所发展或是发生改变。
    #### 配置参数前提

    目前数据库是 MySQL5.7
    服务器内存 16g
    单表数据 1G
    数据行数 200W 的数据
    当年的数据截止目前为止大概在 50W
    表数据是 订单和订单详情 清洗后的数据。
    业务要求统计一年内产生的订单相关信息

    #### SQL

    ```sql
    select [指定的一些业务数据需要字段,含一个字段 sum 求和并除以 1000000 单位转换] from t_order_stats where is_collec = 0
    where is_collect=0 and pro_removal_time between '2024-01-01 00:00:00' and '2024-09-05 23:59:59' group by order_id
    -- 就这样的查询在没有缓存的情况下就需要 6818ms
    -- 至于为什么没带其他条件是想的直接拿出来 代码里面全部去分类分组去求。
    ```

    ### explain 计划

    | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
    | ---- | ----------- | ------------- | ----- | ------------------------------------------------------------ | -------------------------- | ------- | ------- | ---- | ----------- |
    | 1 | SIMPLE | t_order_stats | index | t_order_comprehensive_coverage_IDX, t_order_stats_order_id_IDX, t_order_trans_idx | t_order_stats_order_id_IDX | 8 | 2007971 | 50 | Using where |

    `t_order_stats_order_id_IDX` 索引只有 `order_id`

    #### 疑惑

    is_collect 没有辨识度 只有 0/1
    哪怕是加上 limit 1 也异常慢

    所以这种查询到底如何去处理,因为做的是大屏幕展示的,很多的数据都是时间条件查询。

    那到底要去如何优化才能处理好这类查询。

    看到别个百万千万上亿查询都洒洒水。实在想不通。

    至于数据库配置内存已经是升级过一轮了。加不了一点
    第 1 条附言  ·  76 天前

    谢谢大家的回复。按照老哥的说法感觉也是。

    200w的数据一年就要捞50w数据,那要这么多时间也正常。

    那我在琢磨琢磨。配置升级申请换其他软件来搞也不太现实(占用内存小可以尝试)。数据库内存才从8G->16G 申请了无数次。

    总之谢谢大家了。我先试下查询需要的所有字段都加到索引里。

    41 条回复    2024-09-12 12:12:03 +08:00
    Baloneo
        1
    Baloneo  
       76 天前
    为什么不加时间索引?
    opengps
        2
    opengps  
       76 天前
    为什么不加时间 pro_removal_time 索引?
    showB1
        3
    showB1  
       76 天前
    mysql 单表 200W ?不考虑换种数据库么?业务 db 和 olap 的要分开才好。
    非要用 mysql ,时间都是 yyyy-MM-dd HH:mm:ss 吗 /抽出来 yyyy-MM-dd 、yyyy-MM 的加上索引?然后不要用 between 了,我记得 between 大范围捞不走索引了。
    Xrall
        4
    Xrall  
    OP
       76 天前
    @opengps @Baloneo 是加了时间索引的,t_order_comprehensive_coverage_IDX 索引就是一个组合索引,但是 MySQL 并没有选择走它。或者应该单独加一个时间索引的字段?
    Xrall
        5
    Xrall  
    OP
       76 天前
    @showB1 时间格式都是保持一致的,额加数据库,不知道服务器配置能不能拖走,实在是申请服务器太过于困难了
    lbprivateacc
        6
    lbprivateacc  
       76 天前 via Android
    如果只是统计一年内的信息,能预生成一张统计表嘛
    Xrall
        7
    Xrall  
    OP
       76 天前
    @lbprivateacc 应该也是可以的,现在的表其实已经拆过一次了,也是根据统计需求拆的。但是拆分后查询全都得改吧。不知道还有其他方案没
    t3zb2xzvjm4yvmn
        8
    t3zb2xzvjm4yvmn  
       76 天前
    可以试试用时间字段做分区
    restkhz
        9
    restkhz  
       76 天前
    7 秒是太离谱了。
    t_order_comprehensive_coverage_IDX 组合了哪些字段? is_collect ,pro_removal_time ?
    看 explain 只用了 t_order_stats_order_id_IDX. 如果我没理解错,这基本也是在扫全表,只不过用了 order_id 的索引然后一条一条根据 where 的条件查。
    顺便一问,pro_removal_time 有单独索引吗?或者作为组合索引的第一个?

    没有的话试着创建一个组合索引,只索引这两个字段。pro_removal_time 放第一位,is_collect 放第二位试试。如果 explain 看他还不用这个,force index 试试。
    我怕你那个优化器直接把 is_collect 当第一个筛选条件,如果绝大多数都是 1 那会不稳定。

    这个内存大小应该不存在什么问题。swap 是关了的,对吧...?
    yor1g
        10
    yor1g  
       76 天前 via Android
    区间数据太多不能走索引的 把区间缩小用 union 查
    sagaxu
        11
    sagaxu  
       76 天前   ❤️ 2
    200W 行里捞 50W 行,加什么索引都没用,按每秒 10 万行算,DB 端消耗 5 秒,加上传输和程序解析,也要 6 秒朝上了。

    这种管理后台跑分析的事情,同步到其它库,不要在业务库里面搞。你可以搞个从库使劲儿造,也可以按天搞个汇总表(只有当日需要从原始表读),或者力大砖飞上大数据那套分析工具。
    yh7gdiaYW
        12
    yh7gdiaYW  
       76 天前
    我们做过类似的需求,不过数量级比这个更大一点,初期大约是几亿数据里捞几百万(现在是万亿数据里捞几千万)。
    最终结论是,mysql 就不适合做几百万以上数据集上的统计查询,建议切到专门的 OLAP 数据库,可以试试 StarRocks 或者 TiDB (开 TiFlash )。
    如果一定要在 MySQL 上做,建议升级硬件配置,换上你们速度最快的 SSD 和单核性能最强的 CPU ,这两个是我们发现的最大卡点,楼上说索引的我想应该没做过这种业务
    restkhz
        13
    restkhz  
       76 天前
    @restkhz 回复我自己
    我看差了,我以为是你想 50w 里捞几百几千条...
    统计一年的数据这的确不是索引的事情了。

    各位不要理我之前的回复。
    chenqh
        14
    chenqh  
       76 天前
    你把 is_collect=0 去掉,需要多长时间?
    moult
        15
    moult  
       76 天前
    按小时汇总数据,然后在这个汇总表里面查。不加索引都可以。
    pinerge
        16
    pinerge  
       76 天前
    explain analyze 看看结果
    NowTime
        17
    NowTime  
       76 天前 via Android
    我们业务有张表差不多几百万行数据,也是基于时间范围查询比如过去 90d 数据,对于某个用户数据量大的插座就比较慢。

    偶然一次实验发现,先定位到 90d 前最早的 id 值,查询时带上 id >= {查询值},速度就变得很快。
    yh7gdiaYW
        18
    yh7gdiaYW  
       76 天前   ❤️ 1
    另外这就是 MySQL 做的稀烂的地方,这个场景如果是 MongoDB ,自带的分析工具很容易能看出瓶颈在 fetch (根据索引把需要的行从文件系统读取出来)。加 limit 1 、加索引都没啥用,因为数据量大。
    除了楼上说的提前汇总数据、切 OLAP 数据库这些,我们之前调研时也发现了一个野路子,即把查询需要的所有字段都加到索引里,然后就是见证奇迹的时刻
    liuhuan475
        19
    liuhuan475  
       76 天前
    分区表试试
    Xrall
        20
    Xrall  
    OP
       76 天前
    @liuhuan475 后面都来试试看什么方案最合适。
    oneisall8955
        21
    oneisall8955  
       76 天前
    所以,这个表 50w 行统计出来,会有人看吗?
    Xrall
        22
    Xrall  
    OP
       76 天前
    @oneisall8955 只是数据查询出来 50W 行,代码里面还得分组,他就是要查看一年到头,每个区域有多少个用户 分别各种类型的重量有多少,去了哪里,收的地方又有多少个,收了然后处理的有多少这些数据,所以选择查询出来处理。
    aylsss
        23
    aylsss  
       76 天前 via iPhone
    1.建个联合索引:CREATE INDEX idx_pro_removal_order ON t_order_stats (pro_removal_time, order_id);

    2.查询时把 is_collect=0 放到最后面
    threeBoy
        24
    threeBoy  
       76 天前
    没格式看的头皮发麻, 最简单有效的方式就是再弄一个报表专用数据库 clickhouse 之类的
    xuanbg
        25
    xuanbg  
       76 天前
    纯粹是捞的数据太多造成的,啥都不好使。再怎么优化,也就 5 秒的水平。
    Xrall
        26
    Xrall  
    OP
       76 天前
    @xuanbg 😰是的最后改了 SQL 那 id 去划分查询 十条已经正常速度了,但是捞一年还是捞不动
    lancelotfh
        27
    lancelotfh  
       76 天前
    先把数据捞出来,建张临时表去跑
    yh7gdiaYW
        28
    yh7gdiaYW  
       76 天前
    @Xrall 这个改法是分页查询的跳 id 优化,取的数量大了就没用,解决不了你的需求
    redog
        29
    redog  
       76 天前
    表里有主键吗?把主键去掉,建立一个,按 is_collect ,pro_removal_time ,id 的 UNIQUE 索引,这样应该会形成聚簇索引,第一次创建时因为你已经有 200W 的记录了,会对这 200W 记录重新物理排序,所以会很慢,另外就是插入和更改 is_collect 时会慢一点,但这个数据量来看应该不会有太大感知。
    好处是会按上面的顺序物理排序,这样你前面的条件一出,回表取值的速度会大大加快,捞数据是顺序读取的。
    另外一种就是建一个覆盖索引,按你之前的条件,前面必须是 is_collect ,pro_removal_time ,id ,然后继续跟所需要的业务字段,那个求和的也要算进来。
    SDYY
        30
    SDYY  
       76 天前
    一个月前的数据还会修改吗,先缓存历史数据,最新数据再计算后汇总
    wxf666
        31
    wxf666  
       76 天前
    @xuanbg #25 现在千元消费级固态,不是都支持 100W 随机查找/秒 了吗?

    换句话说,即使服务器内存只有几百 MB ,MySQL 完全用不了缓存,所有读写都走固态 IO ,

    随机查找 50W 行,也应该 0.5 秒就能搞定?



    xuanbg
        32
    xuanbg  
       75 天前
    @wxf666 读 5000 条可以百十毫秒搞定,50w 不是简单 x100 就够了。
    wxf666
        33
    wxf666  
       75 天前
    @xuanbg #32 按理说,现在平民级固态完全做得到呀?

    5000 条就算全走固态 IO ,应该 0.005 秒就好?


    耗时在什么地方呢?网络 IO 吗?还是 MySQL 的锁啥的?

    showB1
        34
    showB1  
       75 天前
    @Xrall between 大范围捞数据,引擎判断大量的回表很慢就自己优化不走索引了
    showB1
        35
    showB1  
       75 天前
    @wxf666 还是你牛逼,楼主快把数据存固态硬盘里。。。。。。。
    Xrall
        36
    Xrall  
    OP
       75 天前
    @showB1 #35 已经选择放弃 MySQL 干这种事情了。拿 ES 应付应付 只不过 es 查询也有其他问题,哎最主要还是没办法升级设备用更专业的来处理这个了
    wxf666
        37
    wxf666  
       75 天前
    @showB1 #35 你的意思是,虽然现在消费级固态,能做到 100W 随机读写 / 秒,

    但 MySQL 没能完全利用好这个资源,所以做不到?


    请教一下,主要是什么方面耗时过多了呢?

    - 网络 IO ?
    - 等待锁?
    - ……?

    showB1
        38
    showB1  
       74 天前
    @Xrall 用 ck 、doris ,数仓随便找一个也比 es 对口啊
    Xrall
        39
    Xrall  
    OP
       74 天前
    @showB1 #38 谢谢提出这些方案。可惜 es 有现成的,其他的都没内存上了。真的就是申请设备异常难,我也去查询过文档最低都要 4G 内存吧。实在没有余粮了。。
    dode
        40
    dode  
       69 天前
    如果没有固态硬盘,换成固态硬盘基本可以解决问题
    showB1
        41
    showB1  
       69 天前
    @wxf666 你问的这个问题说明了多问题,文件系统、数据库、计算引擎等,我水平有限,没办法给你讲清楚
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   5351 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 41ms · UTC 08:29 · PVG 16:29 · LAX 00:29 · JFK 03:29
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.