![](/img/trans.png)
[英]Best way to handle a MySQL table with millions of records updating and large readings
[英]What is the best way to handle millions of rows inside the Visits table?
根据这个问题,答案是正确的,使查询更好,但不能解决整个问题。
CREATE TABLE `USERS` (
`ID` char(255) COLLATE utf8_unicode_ci NOT NULL,
`NAME` char(255) COLLATE utf8_unicode_ci NOT NULL,
PRIMARY KEY (`ID`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci
USERS 表中只有 5 行。
ID | 姓名 |
---|---|
C9XzpOxWtuh893z1GFB2sD4BIko2 | ... |
I2I7CZParyMatRKnf8NiByujQ0F3 | ... |
EJ12BBKcjAr2I0h0TxKvP7uuHtEg | ... |
VgqUQRn3W6FWAutAnHRg2K3RTvVL | ... |
M7jwwsuUE156P5J9IAclikeS4p3L | ... |
CREATE TABLE `VISITS` (
`USER_ID` char(255) COLLATE utf8_unicode_ci NOT NULL,
`VISITED_IN` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
KEY `USER_ID` (`USER_ID`,`VISITED_IN`),
CONSTRAINT `VISITS_ibfk_1` FOREIGN KEY (`USER_ID`) REFERENCES `USERS` (`ID`) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci
VISITS 表内的索引:
键名 | 类型 | 独特的 | 包装好的 | 柱子 | 基数 | 整理 | 无效的 | 评论 |
---|---|---|---|---|---|---|---|---|
用户身份 | BTREE | 不 | 不 | 用户身份 已访问_IN |
3245 5283396 |
一个 一个 |
不 不 |
VISITS 表中有 5,740,266 行:
C9XzpOxWtuh893z1GFB2sD4BIko2 = 4,359,264 次个人资料访问
I2I7CZParyMatRKnf8NiByujQ0F3 = 1,237,286 次个人资料访问
EJ12BBKcjAr2I0h0TxKvP7uuHtEg = 143,716 次个人资料访问
VgqUQRn3W6FWAutAnHRg2K3RTvVL = 0 个人资料访问
M7jwwsuUE156P5J9IAclIkeS4p3L = 0 个人资料访问
查询耗时:(秒会根据行数变化)
SELECT COUNT(*) FROM VISITS WHERE USER_ID = C9XzpOxWtuh893z1GFB2sD4BIko2
SELECT COUNT(*) FROM VISITS WHERE USER_ID = I2I7CZParyMatRKnf8NiByujQ0F3
SELECT COUNT(*) FROM VISITS WHERE USER_ID = EJ12BBKcjAr2I0h0TxKvP7uuHtEg
SELECT COUNT(*) FROM VISITS WHERE USER_ID = VgqUQRn3W6FWAutAnHRg2K3RTvVL
SELECT COUNT(*) FROM VISITS WHERE USER_ID = M7jwwsuUE156P5J9IAclIkeS4p3L
正如您在应用索引之前所看到的,即使用户有几行(访问),也需要 90 到 105 秒来计算特定用户的访问。
应用索引后情况变得更好,但问题是:
C9XzpOxWtuh893z1GFB2sD4BIko2
配置文件,访问配置文件需要 55 到 65 秒。I2I7CZParyMatRKnf8NiByujQ0F3
配置文件,访问配置文件需要 20 到 30 秒。有几行(访问)的用户会很幸运,因为他的个人资料会加载得更快。
我可以忽略上面的所有内容并在 USERS 表中创建一个列来计算用户访问并在捕获新访问时增加它而不创建数百万行但这对我不起作用,因为我允许用户像这样过滤访问:
最后 60 分钟
过去 24 小时
过去 7 天
过去 30 天
过去 6 个月
过去 12 个月
整天
我应该怎么办?
问题是您正在评估并不断重新评估非常大的行数,这些行数实际上是历史的一部分并且永远不会改变。 您不能每次都计算这些行,因为这需要很长时间。 您想提供以下计数:
最后 60 分钟
过去 24 小时
过去 7 天
过去 30 天
过去六个月
整天
您需要四个表:
表 1:一个小而快的表,保存今天和昨天的访问记录
表 2:从“前天(“D-2”)到“D-7”、“D2toD7”字段、“D8toD30”、“D31toD183”和“D184andEarlier”期间的更小、非常快速的表持有计数'
表 3:包含每个用户每天的访问次数的表
表 4:您已经拥有的非常大且速度慢的表,每次访问都记录了时间戳
然后,您可以通过对表 1 进行直接查询来获得“过去 60 分钟”和“过去 24 小时”的计数,这将非常快。 “过去 7 天”是表 1 中所有记录的计数(对于您的用户)加上表 2 中的 D2toD7 值(对于您的用户)。“过去 30 天”是表 1 中所有记录的计数(对于您的用户) 加上 D2toD7,加上 D8toD30。 “过去六个月”是表 1 加上 D2toD7、D8toD30 和 D31toD183。 “所有时间”是表 1 加上 D2toDy,加上 D8toD30,加上 D31toD183,加上 D184andEarlier。
我将运行 php 脚本来检索这些值——无需尝试在一个复杂的查询中完成所有操作。 几个,甚至几个,非常快速地点击数据库,收集数字,返回结果。 该脚本将在不到一秒的时间内运行。
那么,如何更新表 2 中的计数? 这就是您需要表 3 的地方,其中包含每个用户每天的访问次数。 创建表 3 并使用包含所有访问、GROUP BY 用户和日期的庞大表中的数据的 COUNT 值填充它,这样您就可以知道每个用户每天的访问次数。 您只需要创建和填充表 3 一次。 您现在需要一个 CRON 作业/脚本或类似的,每天运行一次。 此脚本将从表 1 中删除记录前天访问的行。此脚本需要:
记住要保持分寸; 183 天的时间大约为六个月,足以满足任何实际访问计数目的。
概述:您无法快速计算数百万行。 利用这些是永远不会改变的历史人物的事实。 因为您有最新计数的表 1,所以您只需每天更新一次历史期间计数。 多个(甚至几十个)非常非常快速的查询将很快为您提供准确的结果。
这不是答案,而是一个建议。
注意:如果您需要按时间计算的登录计数,我们可以在您的表中添加同步时间列。 (然后你的夏季表也会动态增加)
表列前:
PK_Column、用户ID、访问次数、sync_time
我们可以为您的前端使用异步(反应式)实现。 这意味着,数据将在一段时间后加载,但用户永远不会在工作中遇到这种延迟。
创建一个汇总表,每天上午 12 点运行一项工作,并将用户明智和日期明智的最后一次访问的摘要放入该表中。
user_visit_Summary 表:PK_Column、用户 ID、Number_of_Visites、VISIT_Date
注意:为用户 ID 和日期字段创建索引
当您检索数据时,您将通过 DB 函数访问它
Select count(*) + (Select Number_of_Visites from VISITS
where user_id = xxx were VISIT_Date <= ['DATE 12:00 AM' -1] PK_Column desc limit 1) as old_visits
where USER_ID = xxx and VISITED_IN > 'DATE 12:00 AM';
对于一天或更长时间的任何查询,请使用汇总表。
即构建并维护一个包含 3 列 user_id、date、count 的 Summary 表; PRIMARY KEY(user_id, date)
对于“所有时间”和“上个月”,查询将是
SELECT CUM(count) FROM summary WHERE user_id='...';
SELECT CUM(count) FROM summary
WHERE user_id='...'
AND date >= CURDATE() - INTERVAL 1 MONTH
在每晚午夜,将您当前的表格向上滚动到汇总表格中每个用户的一行,然后清空表格。 该表将继续用于较短的时间跨度。
这为每个用户在每个时间范围内实现了速度。
但是,有一个“错误”。 我强迫“day”/“week”/etc 是午夜到午夜,不允许你真的说“过去 24 小时”。
我建议对该“错误”采取以下折衷方案:
要一次获取所有计数器,请在子查询中完成所有工作。 有两种方法,可能同样快,但结果是行或列:
-- rows:
SELECT 'hour', COUNT(*) FROM recent ...
UNION ALL
SELECT '24 hr', COUNT(*) FROM recent ...
UNION ALL
SELECT 'month', SUM(count) FROM summary ...
UNION ALL
SELECT 'all', SUM(count) FROM summary ...
;
-- columns:
SELECT
( SELECT COUNT(*) FROM recent ... ) AS 'hour'.
( SELECT COUNT(*) FROM recent ... ) AS '24 hr',
( SELECT SUM(count) FROM summary ... ) AS 'last month'
( SELECT SUM(count) FROM summary ... ) AS 'all time'
;
“……”是
WHERE user_id = '...'
AND datetime >= ... -- except for "all time"
将多个查询滚动到一个查询中(无论哪种方式)都有一个优势——这可以避免多次往返服务器和多次调用优化器。
forpas 提供了另一种方法https://stackoverflow.com/a/72424133/1766831 ,但需要对其进行调整以达到两个不同的表格。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.