时间范围查询如何优化? - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
MySQL 5.5 Community Server
MySQL 5.6 Community Server
Percona Configuration Wizard
XtraBackup 搭建主从复制
Great Sites on MySQL
Percona
MySQL Performance Blog
Severalnines
推荐管理工具
Sequel Pro
phpMyAdmin
推荐书目
MySQL Cookbook
MySQL 相关项目
MariaDB
Drizzle
参考文档
http://mysql-python.sourceforge.net/MySQLdb.html
myd
V2EX    MySQL

时间范围查询如何优化?

  •  
  •   myd 2021 年 2 月 23 日 4500 次点击
    这是一个创建于 1885 天前的主题,其中的信息可能已经有所发展或是发生改变。

    以用户上线 /下线记录表为例:

    CREATE TABLE `online_log` ( `id` int(11) AUTO_INCREMENT, `user_id` int(11), `online_time` datetime COMMENT '上线时间', `offline_time` datetime COMMENT '下线时间', PRIMARY KEY (`id`) ); 

    查询某个时间点在线的用户:

    select user_id from online_log where "2021-01-23 12:00:00" between online_time and offline_time; 

    存在的问题:

    1. 加联合索引(online_time, offline_time)显然不行,用不上;
    2. 加 2 个普通索引 online_time 和 offline_time,只能用一个,也有可能都用不上,扫描的记录很多,速度慢。

    如何优化?

    16 条回复    2021-02-24 17:39:44 +08:00
    Altale
        1
    Altale  
       2021 年 2 月 23 日
    方案没有,但是提个小建议,第二点问题可能是错的

    “加 2 个普通索引 online_time 和 offline_time,只能用一个,也有可能都用不上”

    这个方案最理想的情况是两个索引都使用上,最终取交集,具体情况要看 DBMS 的统计数据认为交集的方案开销是否比其他查询路径小。

    另一个小建议是如果你这个表很大,还有其他字段,那么加这两个索引:
    - online_time - user_id
    - offline_time - user_id

    将取交集的操作继续缩小为不需要回表的方案,性能可能有大幅提升(安利测试看看)
    rund11
        2
    rund11  
       2021 年 2 月 23 日 via Android
    多加几个字段年月日,时间点,多个索引会快
    qiayue
        3
    qiayue  
    PRO
       2021 年 2 月 23 日
    用时间戳会不会好一点呢
    JustLookBy
        4
    JustLookBy  
       2021 年 2 月 23 日   1
    联合索引怎么会用不上。
    查询的时候 online_time<'' and offline_time>'' 不就是正常联合索引的操作吗?
    546L5LiK6ZOt
        5
    546L5LiK6ZOt  
       2021 年 2 月 23 日
    我理解联合索引用不上,是因为两个字段的比较是不一样的,一个大于一个小于( online_time <= "2021-01-23 12:00:00" and offline_time >= "2021-01-23 12:00:00")。那么可以来个骚操作,变成一样的。假设 online_time 和 offline_time 都是存时间戳,但 offline_time 存的是时间戳的负数。那么条件就变成了 online_time <= 时间戳 and offline_time <= 时间戳的负数 。这样子就可以建联合索引了。
    myd
        6
    myd  
    OP
       2021 年 2 月 23 日
    @Altale 是的,数据量小会取交集。但是数据量大的时候,很可能就是全表扫描。MySQL 数据库
    myd
        7
    myd  
    OP
       2021 年 2 月 23 日
    @rund11 但是上线时间和下线时间可能跨日,甚至跨月,跨年
    myd
        8
    myd  
    OP
       2021 年 2 月 23 日   1
    @JustLookBy
    @546L5LiK6ZOt
    联合索引用不上,是因为,根据最左前缀原则,只要有一个索引字段使用了范围查询(>、<、<=、>=),后面的索引字段就不生效了。
    Huelse
        9
    Huelse  
       2021 年 2 月 23 日
    两个日期之间的天数用 DATEDIFF()怎么样?
    Huelse
        10
    Huelse  
       2021 年 2 月 23 日
    还有什么 timediff 、datediff 、timestampdiff,可以测试下性能
    Altale
        11
    Altale  
       2021 年 2 月 24 日
    @myd 那提供的条件太有限了,无法知道具体的查询 pattern,范围,这些建议都给不了
    Altale
        12
    Altale  
       2021 年 2 月 24 日
    其实让数据库执行你的查询,同一条 sql 在数据分布不同的情况下执行路径也会大有不同,问题太过 general 了,问人还不如相信数据库的分析
    JustLookBy
        13
    JustLookBy  
       2021 年 2 月 24 日
    @myd 我想当然了,复合索引确实不能用
    你这问题可以用空间索引 spatial index 来解决。
    具体方案如下:
    1. 去掉 online offline 俩个字段,用 online_range 来表示在线时间访问,type 为 linestring.
    2. 加入索引 online_range, index type 设为 spatial
    3. 用 int 表示时间,自己设定一个开始时间 为 0,这个方案如果精确度到秒,那区间只能有十几年。2^32/86400/365
    4. 插入数据 `insert log (online_range) values (ST_GeomFromText('LINESTRING(online_timestamp 0,offline_timestamp 0)')))。 这里你的情况只要线段范围就行,所以 y 左边都设为 0 即可。
    5. 查询数据 ` select * from log where
    MBRContains(online_range,ST_GeomFromText('point(28302301 0)'))`
    myd
        14
    myd  
    OP
       2021 年 2 月 24 日
    @JustLookBy

    有点像 online_time 和 offline_time,数据量少的时候可以使用索引。

    数据量 100w 时,也是全表扫描。SQL:
    ```sql
    EXPLAIN
    select * from online_log where
    MBRContains(online_range,ST_GeomFromText('point(1614152940 0)'));

    +----+-------------+------------+------------+------+--------------------+------+---------+------+---------+----------+-------------+
    | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
    +----+-------------+------------+------------+------+--------------------+------+---------+------+---------+----------+-------------+
    | 1 | SIMPLE | online_log | NULL | ALL | index_online_range | NULL | NULL | NULL | 1343396 | 31.03 | Using where |
    +----+-------------+------------+------------+------+--------------------+------+---------+------+---------+----------+-------------+
    1 row in set, 1 warning (0.00 sec)
    ```
    这条 SQL 最后筛选出来的数据大约 100 条。


    优化过程:
    ```
    ......
    "analyzing_range_alternatives": {
    "range_scan_alternatives": [
    {
    "index": "index_online_range",
    "ranges": [
    "online_range unprintable_geometry_value"
    ],
    "index_dives_for_eq_ranges": true,
    "rowid_ordered": false,
    "using_mrr": false,
    "index_only": false,
    "rows": 425368,
    "cost": 510443,
    "chosen": false,
    "cause": "cost"
    }
    ],
    "analyzing_roworder_intersect": {
    "usable": false,
    "cause": "too_few_roworder_scans"
    }
    }
    ......
    ```
    JustLookBy
        15
    JustLookBy  
       2021 年 2 月 24 日
    @myd 不能直接用时间戳,值大于 2^32 就不能用到索引了。 我在第三点里面说了,但是没说清
    ```自己设定一个开始时间 为 0,这个方案如果精确度到秒,那区间只能有十几年。```
    你百万数据查询耗时多少?我这是一秒内,结果是十万条左右
    myd
        16
    myd  
    OP
       2021 年 2 月 24 日
    @JustLookBy 现在我用的时间戳是 4 字节的 int,没有超过 2^32 。百万数据 1 秒是正常的,全表扫描就是这个速度。
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     5610 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 62ms UTC 06:26 PVG 14:26 LAX 23:26 JFK 02:26
    Do have faith in what you're doing.
    ubao msn 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