繁体   English   中英

处理 Visits 表中数百万行的最佳方法是什么?

[英]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
  • 在应用 Rick James 的答案之前,查询需要 90 到 105 秒
  • 应用 Rick James 的答案后,查询需要 55 到 65 秒
SELECT COUNT(*) FROM VISITS WHERE USER_ID = I2I7CZParyMatRKnf8NiByujQ0F3
  • 在应用 Rick James 的答案之前,查询需要 90 到 105 秒
  • 应用 Rick James 的答案后,查询需要 20 到 30 秒
SELECT COUNT(*) FROM VISITS WHERE USER_ID = EJ12BBKcjAr2I0h0TxKvP7uuHtEg
  • 在应用 Rick James 的答案之前,查询耗时 90 到 105 秒 在应用 Rick James 的答案之后,查询耗时 4 到 8 秒
SELECT COUNT(*) FROM VISITS WHERE USER_ID = VgqUQRn3W6FWAutAnHRg2K3RTvVL
  • 在应用 Rick James 的答案之前,查询需要 90 到 105 秒
  • 应用 Rick James 的答案后,查询需要 1 到 3 秒
SELECT COUNT(*) FROM VISITS WHERE USER_ID = M7jwwsuUE156P5J9IAclIkeS4p3L
  • 在应用 Rick James 的答案之前,查询需要 90 到 105 秒
  • 应用 Rick James 的答案后,查询需要 1 到 3 秒

正如您在应用索引之前所看到的,即使用户有几行(访问),也需要 90 到 105 秒来计算特定用户的访问。

应用索引后情况变得更好,但问题是:

  1. 如果我访问C9XzpOxWtuh893z1GFB2sD4BIko2配置文件,访问配置文件需要 55 到 65 秒。
  2. 如果我访问I2I7CZParyMatRKnf8NiByujQ0F3配置文件,访问配置文件需要 20 到 30 秒。
  3. ETC...

有几行(访问)的用户会很幸运,因为他的个人资料会加载得更快。

我可以忽略上面的所有内容并在 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 中删除记录前天访问的行。此脚本需要:

  1. 确定每个用户前天的访问次数
  2. 将这些计数与“前天”日期一起插入表 3。
  3. 将计数值添加到表 2 中每个用户的“D2toD7”值。
  4. 从表 1 中删除“前天”行。
  5. 在表 3 中查找每个用户的(刚刚变为的)D8 的值。将此值从每个用户的“D2 到 D7”值递减。
  6. 对于“D8toD30”、“D31toD183”等字段中的每一个,对于现在属于时间段的那一天递增,根据从时间段中退出的那一天递减。 使用存储在表 3 中的值。

记住要保持分寸; 183 天的时间大约为六个月,足以满足任何实际访问计数目的。

概述:您无法快速计算数百万行。 利用这些是永远不会改变的历史人物的事实。 因为您有最新计数的表 1,所以您只需每天更新一次历史期间计数。 多个(甚至几十个)非常非常快速的查询将很快为您提供准确的结果。

这不是答案,而是一个建议。

  1. 如果他们不需要实时数据,我们能不能运行一个调度程序并每 x 分钟将这些数据插入一个汇总表中。 然后我们可以访问该汇总表以供您计算。

注意:如果您需要按时间计算的登录计数,我们可以在您的表中添加同步时间列。 (然后你的夏季表也会动态增加)

表列前:

PK_Column、用户ID、访问次数、sync_time

  1. 我们可以为您的前端使用异步(反应式)实现。 这意味着,数据将在一段时间后加载,但用户永远不会在工作中遇到这种延迟。

  2. 创建一个汇总表,每天上午 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 小时”。

我建议对该“错误”采取以下折衷方案:

  • 对于较长的时间跨度,请使用汇总表,并从另一个表中计算今天的点击次数。
  • 为了让“24 小时”到达昨天,将另一张桌子更改为回到昨天早上。 也就是说,仅在 24 小时后清除,而不是 1 个日历日。

要一次获取所有计数器,请在子查询中完成所有工作。 有两种方法,可能同样快,但结果是行或列:

-- 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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM