【问题标题】:Prevent duplicate rows in MySQL without unique index/constraint?在没有唯一索引/约束的情况下防止 MySQL 中的重复行?
【发布时间】:2011-10-11 19:33:04
【问题描述】:

我正在编写一个需要处理数百万个 URL 的应用程序。它还需要通过 URL 进行检索。

我的桌子目前是这样的:

CREATE TABLE Pages (
  id bigint(20) unsigned NOT NULL,
  url varchar(4096) COLLATE utf8_unicode_ci NOT NULL,
  url_crc int(11) NOT NULL,
  PRIMARY KEY (id),
  KEY url_crc (url_crc)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

这种结构背后的想法是通过 URL 的 CRC32 散列进行查找,因为 b-tree 索引在具有公共前缀的 URL 上效率非常低(InnoDB 不支持散列索引)。 CRC32 中的重复结果通过与完整 URL 的比较进行过滤。示例检索查询如下所示:

SELECT id
FROM Pages
WHERE url_crc = 2842100667
  AND url = 'example.com/page.html';

我遇到的问题是避免插入重复的条目。应用程序总是会在插入新条目之前检查数据库中的现有条目,但在我的应用程序中,可能会同时对同一个新 URL 进行多个查询,并且会输入重复的 CRC32 和 URL。

我不想在 url 上创建唯一索引,因为它会很大。我也不想在每次插入时都锁定表,因为这会破坏并发插入性能。有没有有效的方法来解决这个问题?

编辑:为了更详细地了解使用情况,它是一个用于查找内容以响应 URL 的实时表。通过查找 URL,我可以找到 URL 的内部 id,然后使用它来查找页面的内容。新的 URL 一直被添加到系统中,我不知道这些 URL 会是什么。当引用新的 URL 时,它们可能会被同时引用相同 URL 的请求猛烈抨击,可能每秒数百次,这就是为什么我在添加新内容时担心竞争条件的原因。结果需要立竿见影,不能有读取延迟(亚秒级延迟是可以的)。

首先,每天将仅添加几千个新 URL,但系统需要处理多次,我们才有时间在明年转向更具可扩展性的解决方案。

仅在 url 上使用唯一索引的另一个问题是 URL 的长度可能超过唯一索引的最大长度。即使我放弃了 CRC32 技巧,它也不能解决防止重复 URL 的问题。

【问题讨论】:

  • 如何存储 url (sha1?) 的散列副本并索引该字段?在数据库上使用适当的触发器来更新/填充插入/更新时的哈希,维护开销将非常小。
  • CRC32 是 URL 的哈希值。它只是比 SHA1 小得多的散列(4 个字节对 20 个字节)。我在应用程序端计算它。
  • 是的,但只有 32 位,你大大增加了碰撞的几率,因此误报了。
  • 您能否提供更多有关如何使用该表的信息?例如,如果您正在记录 URL 以供以后进行详细分析,则暂时有重复的条目并在以后剔除它们可能是可以接受的。如果您还要实时从表中读取,那么读取表在条目之后滞后一小段时间是否可以接受(即条目在输入时转到另一个表并且读取表每分钟更新一次)?
  • 数百万个 URL 不会创建一个“巨大”的索引。十亿个 URL 可能会创建一个相当大的索引。

标签: mysql sql query-optimization


【解决方案1】:

您是否真的进行了基准测试并发现 btree 存在问题?我感觉到过早的优化。

其次,如果您担心所有字符串的开头都相同,一个答案是反向索引您的 URL — 最后一个字符在前。我不认为 MySQL 本身可以做到这一点,但您可以在存储之前反转应用程序中的数据。或者干脆不使用 MySQL。

【讨论】:

    【解决方案2】:

    您是否考虑过创建一个 UNIQUE INDEX (url_crc, url) ?它可能是“巨大的”,但随着使用 CRC32 的冲突数量,它可能有助于提高页面检索功能的性能,同时还可以防止重复的 url。

    要考虑的另一件事是允许插入重复项,并使用脚本在每晚(或流量低时)删除它们。

    【讨论】:

    • 很遗憾,所有引用页面的内容都必须使用相同的页面 id 才能一起出现,而不会“丢失”。由于这些页面 id 会在整个系统中传播,因此将来更改它们会很复杂。唯一索引也有长度限制。
    【解决方案3】:

    除了您的 Pages 表之外,还要创建 3 个具有相同列的附加表(PagesInsertA、PagesInsertB 和 PagesInsertC)。插入 URL 时,检查 Pages 是否存在现有条目,如果不存在,则将 URL 插入 PagesInsertA。您可以在该较小的表上使用唯一索引,也可以包括稍后删除重复项的步骤(如下所述)。在轮换时间结束时(可能是一分钟,有关限制,请参阅下面的讨论),切换到将新 URL 插入 PagesInsertB。在 PagesInsertA 上执行以下步骤:删除重复项(如果您没有使用唯一索引),删除任何与 PagesInsertC 中的条目重复的条目(该表第一次将为空,但第二次不会),从PagesInsertA 到 Pages,为空 PagesInsertC。

    在第二阶段结束时,切换到将新 URL 插入 PagesInsertC。在 PagesInsertB 上执行上面讨论的步骤(唯一的区别是您将删除也在 PagesInsertA 中找到的条目并在最后删除空 PagesInsertA)。继续旋转插入新 URL 的表格 (A -> B -> C -> A -> ...)。

    至少需要 3 个插入表,因为在将 URL 插入切换到新的插入表和将前一个插入表中已清理的行插入到 Pages 之间会有延迟。在此示例中,我使用 1 分钟作为切换之间的时间,但只要从 PagesInsertA 插入到 Pages 并清空 PagesInsertC(例如)发生在将新 URL 插入 PagesInsertB 和 PagesInsertC 之间的切换之前,您就可以缩短该时间。

    【讨论】:

      猜你喜欢
      • 2013-11-23
      • 2019-10-26
      • 2014-04-08
      • 1970-01-01
      • 2016-09-06
      • 2010-11-23
      • 2020-02-03
      • 2020-05-11
      • 1970-01-01
      相关资源
      最近更新 更多