防止MySQL中没有唯一索引/约束的重复行?

防止MySQL中没有唯一索引/约束的重复行?,mysql,sql,query-optimization,Mysql,Sql,Query Optimization,我正在编写一个需要处理数百万个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 DEF

我正在编写一个需要处理数百万个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;
SELECT id
FROM Pages
WHERE url_crc = 2842100667
  AND url = 'example.com/page.html';
这种结构背后的思想是通过URL的CRC32散列进行查找,因为b树索引在倾向于使用公共前缀的URL上效率很低(InnoDB不支持散列索引)。CRC32的重复结果通过与完整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;
SELECT id
FROM Pages
WHERE url_crc = 2842100667
  AND url = 'example.com/page.html';
我遇到的问题是避免插入重复条目。在插入新条目之前,应用程序将始终检查数据库中的现有条目,但在我的应用程序中,可能会同时对同一个新URL进行多个查询,并输入重复的CRC32和URL

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

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

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


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

您是否考虑过创建唯一索引(url\u crc,url)?它可能是“巨大的”,但由于使用CRC32会产生大量的冲突,它可能有助于提高页面检索功能的性能,同时防止重复URL


另一个要考虑的事项是允许重复插入,并且每晚用脚本删除它们(或者每当流量很低)。

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

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


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

您是否实际进行了基准测试并发现btree是一个问题?我感觉到过早的优化


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

存储url的散列副本(sha1?)并为该字段编制索引怎么样?在数据库上使用适当的触发器在插入/更新时更新/填充哈希,维护开销将非常小。它只是一个比SHA1小得多的散列(4字节比20字节)。我是在应用程序端计算的。是的,但只有32位,这大大增加了冲突的几率,从而导致误报。您能提供更多关于如何使用该表的信息吗?例如,如果您正在记录URL以便以后进行详细分析,则可以暂时保留重复条目并在以后剔除它们。如果您也要实时读取表中的内容,那么读取表是否可以在条目后短时间延迟(即条目在输入时转到另一个表,读取表每分钟更新一次)?数百万个URL不会创建“巨大”索引。5亿个URL可能会创建一个相当大的索引。不幸的是,引用一个页面的所有内容必须使用相同的页面id才能一起显示,而不会“丢失”。由于这些页面ID会在整个系统中传播,因此将来更改它们将非常复杂。唯一索引也有长度限制。