高效查找最近 k 天内未更新的记录
Posted
技术标签:
【中文标题】高效查找最近 k 天内未更新的记录【英文标题】:Finding records not updated in last k days efficiently 【发布时间】:2014-11-20 15:15:49 【问题描述】:我有一张表,其中包含过去 n 天的记录。该表中的记录约为 1 亿条。我需要找到最后k个没有更新的记录
我对这个问题的解决方案是
在 k1 上对表进行分区。时间戳列的索引。现在不要更新时间戳(以便不重建索引),而是执行删除 + 插入。通过这样做,我认为查找过去 k 天内未更新的记录的查询会很快。
还有其他更好的方法来优化这些操作吗?
例如,
假设我们有很多用户,每个用户可以使用不同的产品。用户也可以随时开始使用(成为所有者)新产品。如果用户在 n 天内未使用产品,他的所有权将到期。现在我们需要找到一个用户在过去 k 天内没有使用过的所有产品。用户数量为10000个订单,他可以选择的产品数量为100,000个订单。
我使用具有 (user_id, product_id, last_used) 架构的表对这个问题进行了建模。 product_id 是用户正在使用的产品的 ID。每当用户使用产品 last_used 时都会更新。此外,如果用户在 n 天内未使用产品,用户对产品的所有权也会过期。我在 user_id 上的表上分区并索引 last_used(timestamp)。我也没有更新,而是执行了删除+创建。我进行了分区和索引以优化查询以获取用户在过去 k 天内未更新的记录。
有没有更好的方法来解决这个问题?
【问题讨论】:
您已经很好地描述了设置,但请进一步描述问题。您正在删除超过 x 天的记录并且它会影响读取访问权限?您一次要删除多少条记录?典型的删除查询是什么样的?创新数据库?删除记录时不会对索引进行任何“重建”,只是对其进行一些更新。 请举例说明您希望从查询中得到的结果。 什么是主键?你想做什么? @MarcusAdams 假设我们有一个用户表(user_id、product_id、last_used)。 product_id 是用户正在使用的产品的 ID。每当用户使用产品 last_used 时都会更新。用户也可以随时开始使用新产品。现在我需要为一个用户找到他在过去 k 天内没有使用过的所有产品。此外,如果用户在 n 天内未使用产品,用户对产品的所有权也会过期。如果索引在 last_used 并且 last_used 被修改,则索引将被“重新构建”。 这张表还有什么?还是只有这三列? 【参考方案1】:你说过你需要“找到”,我认为在一定天数后“过期”属于特定用户的记录。
看,即使在具有良好索引的大表中也可以做到这一点,而不会带来太多麻烦。我向你保证,对表进行分区将是一件麻烦。您已经断言,由于更新,在您的应用程序中为您的last_used
列添加索引太昂贵了。但是,考虑到维护分区表的初始和持续费用,我强烈建议您首先证明该断言。您可能对维护索引的成本有误。
(用索引的列更新一行不会重建索引,它会修改它。我向你保证,mysql 存储引擎开发人员已经优化了这个用例。)
我相信您知道,此查询将检索特定用户的旧记录。
SELECT product_id
FROM tbl
WHERE user_id = <<<chosen user>>>
AND last_used <= CURRENT_DATE() - <<<k>>> DAY
将生成您的产品列表。如果您在(user_id, last_used, product_id)
上有一个复合覆盖索引,这确实会非常有效。如果您不知道复合覆盖索引是什么,您真的应该使用您最喜欢的搜索引擎来查找。这将随机访问特定用户,然后在 last_used 日期进行范围扫描。然后它将从索引中返回产品 ID。
如果你想摆脱所有旧记录,我建议你编写一个主机程序,在循环中重复这个查询,直到你发现它已经处理了零行。在您的应用程序的非高峰时间运行它。 LIMIT
子句将防止每个单独的查询花费太长时间并干扰表的其他用途。为了加快此查询的速度,您需要在last_used
上建立一个索引。
DELETE FROM tbl
WHERE last_used <= CURRENT_DATE() - <<<k>>> DAY
LIMIT 500
我希望这会有所帮助。它来自某人犯了一个代价高昂的错误,试图对不需要分区的东西进行分区。
【讨论】:
感谢您的回复。我将阅读有关复合覆盖指数的信息。但是如果 last_used 列的更新非常频繁,是不是每次更新操作都会修改索引,导致更新成本高昂? 缓存删除(快速),然后在服务器不忙时实际更新索引,但您仍然希望避免长时间运行的事务,因此一次删除有限数量不是如果花费的时间太长,这是一个坏主意。【参考方案2】:当您修改索引值时,MySQL 不会“重建”索引(不完全)。事实上,它甚至不会对记录进行重新排序。它只是将记录移动到正确的 16KB 页面。
在一个页面中,记录按添加顺序排列。如果您按顺序插入,则它们是按顺序插入的,否则它们不是。
所以,当他们说 MySQL 的聚集索引是按物理顺序排列时,它只在页面级别是正确的,而不是在页面内。
聚集索引仍然具有页面数据与索引在同一页面上的优势,因此如果行数据足够小以适合页面,则无需进一步查找。读取速度更快,但重组速度较慢,因为您必须使用索引移动数据。二级索引的更新速度要快得多,但要实际检索数据(覆盖索引除外),必须通过二级索引产生的主键进行进一步查找以检索实际数据。
示例
第 1 页可能保存姓氏从 A 到 B 开头的人的用户记录。第 2 页可能保存姓名 C 到 D 等。如果 Bob 将自己重命名为 Chuck,他的记录只会从第 1 页复制到第 2 页。他的记录将始终放在第 2 页的末尾。键保持排序,但它们指向的数据不排序。
如果页面已满,MySQL 将拆分页面。在这种情况下,假设 C 和 D 之间均匀分布,则第 1 页为 A 到 B,第 2 页为 C,第 3 页为 D。
当一条记录被删除时,空间被压缩,如果记录不到半满,MySQL将合并相邻的页面,并可能释放中间的页面。
所有这些更改都被缓冲,MySQL 会在它不忙时进行实际的写入。
该示例对聚集(主)索引和二级索引的工作方式相同,但请记住,对于聚集索引,键指向实际表数据,而对于二级索引,键指向的值等于主键。
总结
一段时间后,随机插入导致的页面拆分将导致页面在磁盘上变得不连续。表格将变得“碎片化”。优化表(重建表/索引)解决了这个问题。
删除然后重新插入记录没有任何好处。事实上,您只会增加事务开销。让 MySQL 为您处理更新索引。
现在您对索引有了更多了解,也许您可以更好地决定如何优化您的数据库。
【讨论】:
以上是关于高效查找最近 k 天内未更新的记录的主要内容,如果未能解决你的问题,请参考以下文章
SQL 中高效的“查找最近的数字或日期”,其中日期/数字列被索引覆盖
排行榜的高效数据结构,即记录列表(名称、点数) - 高效搜索(名称)、搜索(排名)和更新(点数)