mysql 如何高效获取两条相邻推送时间间隔

2024-05-19 13:34:17 +08:00
 Zaden

我有个 mysql8 库,里面有若干张物联网推送数据表,每张表有上千万至上亿条数据,这次涉及表里两个字段 point_id 和 push_time ,想检索历史上有哪些 point_id 断线时间(即期间无 push_time )超过 24h ,我试过用 mysql8 的窗口函数、python 游标读写表感觉效率都不太高,想请教大佬有无高效的办法

2600 次点击
所在节点    MySQL
25 条回复
wxf666
2024-05-24 13:41:13 +08:00
@ys1992 #10 不一定要扫全表吧。。

不断根据索引,查最接近 24 小时前的推送时间,应该只需要检查很少数据量,就能算出来了?


@Zaden 我用最垃圾的 SQLite ,在七年前的 i5-8250U 轻薄本上,效率一般的浏览器 wasm 环境里,试了下,

100 设备、一亿数据(每分钟推送、持续两年),每设备断线十次,每次 1~2 天,

只需 7 秒,就能全找出来了?


## 截图



## SQL 测试代码

```sql
-- V 站吞空格,缩进改成全角空格了

-- 建表,当 (point_id, push_time) 索引用
DROP TABLE IF EXISTS data;
CREATE TABLE data (
   point_id INT,
   push_time INT,
   PRIMARY KEY (point_id, push_time)
) WITHOUT ROWID;

-- 添加一亿条数据( 100 设备、每分钟推送、持续两年)
INSERT INTO data
SELECT point.value, time.value
FROM generate_series(1, 100) point
JOIN generate_series(
   unixepoch('2024-05-24', '-2 year'),
   unixepoch('2024-05-24'), 60) time;

-- 删掉断线数据( 100 设备,每台断线 10 次,第 N 次是 id*N 天前,持续 1, 1.1, ..., 1.9 天)
DELETE FROM data
WHERE (point_id, push_time) IN (
   SELECT point_id, push_time
   FROM generate_series(1, 100) point
   JOIN generate_series(1, 10) nth
   CROSS JOIN data
   WHERE point_id = point.value
   AND push_time >= unixepoch('2024-05-24', format('-%f day', nth.value * (point.value + 0.1) - 0.1))
   AND push_time < unixepoch('2024-05-24', format('-%f day', nth.value * point.value - 1))
);

-- 循环每个设备,从今天开始,不断往前找,最接近 24 小时前的推送时间
-- 若俩时间 >= 24 小时,则属于断线过久
WITH RECURSIVE
   t(id, a, b) AS (
     SELECT point_id, unixepoch('2024-05-24'), NULL
     FROM data
     GROUP BY point_id
     UNION ALL
     SELECT id, ifnull((
         SELECT min(push_time)
         FROM data
         WHERE point_id = t.id
         AND push_time > t.a - 86400
         AND push_time < t.a
      ), (
         SELECT max(push_time)
         FROM data
         WHERE point_id = t.id
         AND push_time < t.a - 86400
      )), a
     FROM t
     WHERE a
  )
SELECT
   id "设备 ID",
   datetime(a, 'auto') "最后在线",
   format('%d 天 %d 小时 %d 分钟', (b-a)/86400, (b-a)%86400/3600, (b-a)%3600/60) "断线时长"
FROM t
WHERE b - a >= 86400
ORDER BY id IN (1, 2, 73) DESC, id, a DESC;
```
Zaden
2024-05-24 17:08:28 +08:00
为啥会这么快,2kw 数据,有索引,我执行最简单的 select point_id,max(push_time) from table group by point_id 都要好几分钟
wxf666
2024-05-25 10:19:32 +08:00
@ys1992 #10

我又改了改,速度提升到,仅 0.2 秒了。。

原来大部分时间,都在遍历这张一亿数据的表,查有哪些独立的 point_id 了。。

如果有 point 表,直接从里面抽出所有 point_id 即可。我是手动用 generate_series(1, 100) 模拟的。




@Zaden #22:

可能你和我原因一样,想分组 point_id ,查最后时间,数据库居然扫全索引去了。。

其他快的原因,我觉得就是,不断跳来跳去,直接查最接近 24 小时前的时间,而不是每两条时间一一对比。

这需要高度依赖 4K 随机读取。比如,浏览器里运行数据库时,会将数据留存在内存里,自然快一些。

- 本地上测试,同样缓存在内存里时,只需 0.1 秒
- 7 年前垃圾固态上(顺序读 420 MB/s ,4K 随机读 25 MB/s ),且清除系统对文件缓存后,需 10 秒
- 10 年前硬盘上(顺序读 150 MB/s ,4K 随机读 0.65 MB/s ),也清除系统对文件缓存后,需 80 秒。。

我也不知道,为啥硬盘 4K 随机读,比固态差近 40 倍,但耗时才慢 8 倍。。

可能夹杂着一些顺序读取(比如有时跳到相邻页上了?),使得差距没这么大吧。。

总之,我浏览器里都能缓存一亿数据(约 1.3 GB ),对你来说应该也不是啥难事的。


## 0.2 秒截图

ys1992
2024-05-28 09:11:27 +08:00
@wxf666 #23 大佬动手能力真强呀,这个和之前 1brc( https://zhuanlan.zhihu.com/p/683955185)有异曲同工之处了,反正按照 po 主的意思,顺序扫一遍全表,然后内存计算推送时间过滤出超过 24 小时的,时间应该是快的,不过 sqllite 纯 SQL 还能这么快,还是挺让人震撼的(之前没怎么接触过 sqllite ,都是 pg 和 mysql 多一点)
LiaoMatt
2024-05-28 10:44:04 +08:00
@Zaden 有联合索引吗, 还得看看数据库的配置

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://ex.noerr.eu.org/t/1041992

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX