MySQL如何确定INSERT是否唯一?

时间:2015-01-22 09:12:48

标签: mysql insert unique

我想知道在对任何列定义为UNIQUE的表执行INSERT之前是否运行了隐式SELECT。我在INSERT的文档中找不到任何相关内容。

我已经问了一些其他人似乎无法回答的问题 - 也许是因为我没有正确解释自己 - 与上述问题有关。

如果我理解正确,那么我认为以下情况属实:

案例1: 你有一个10亿行的表。每行都有一个唯一的UUID列。如果执行插入,服务器必须执行某种隐式 SELECT COUNT(*) FROM table WHERE UUID = [new uuid]并确定计数是0还是1.正确吗?

案例2: 你有一个10亿行的表。每行都有一个由DATE和UUID组成的复合唯一键。如果执行插入,服务器必须执行某种隐式 SELECT COUNT(*) FROM table WHERE DATE = [date] AND UUID = [new uuid]并检查计数是0还是1.是吗?

我使用了隐含这个词,因为在某个时刻,在某个过程中,服务器必须检查值。如果不是这样,它就要求物理定律规定两条相同的行不能存在 - 而且就我所知道的物理学来说,当涉及到某处写下的数字的唯一性时,它不会发挥重要作用,二进制,在计算机的磁盘上。

让我们假设您的10亿行在2,000个不同日期均匀分布。这不意味着案例2会更快地执行插入,因为它可以查找分割成日期的UUID吗?如果没有,那么将案例1用于插入速度会更好 - 在这种情况下,为什么?

这个问题是理论上的,所以在这种情况下不要考虑常规的SELECT性能。主键不是UUID + DATE索引。

作为对评论的回应:我的案例中的UUID仅用于避免由于连接错误而导致的重复条目。由于您不能两次为不同的日期创建相同的条目(逻辑上不是新条目),因此UUID不需要全局唯一 - 它只需要对每个日期都是唯一的。这就是为什么我可以允许它成为复合键的一部分。

3 个答案:

答案 0 :(得分:11)

以前的答案中存在一些缺陷和误解;而不是指出它们,我将从头开始。

仅提到InnoDB ......

INDEX(包括UNIQUE和PRIMARY KEY)是BTree。基于BTree排序的密钥,BTree非常有效地定位一行。 (按键顺序扫描也很有效。)"扇出" MySQL中典型BTree的数量级为100.因此,对于一百万行,BTree的深度约为3级(log100(百万));对于万亿行,它只有两倍深(大约)。因此,即使没有缓存任何内容,也只需要3次磁盘命中就可以找到百万行索引中的一个特定行。

我在"索引"与" table"因为它们基本相同(至少在InnoDB中)。两者都是BTrees。不同之处在于叶节点中的内容: BTree的叶节点具有所有列。 (我忽略了InnoDB中TEXT / BLOB的块外存储。)INDEX(PRIMARY KEY除外)在叶节点中有一个PRIMARY KEY的副本。这是辅助密钥可以从INDEX BTree到行的其余列的方式,以及InnoDB如何不必存储所有列的多个副本。

PRIMARY KEY是"群集"与数据。那是一个 BTree包含所有行的所有列,并且它是根据PRIMARY KEY规范排序的。

通过PRIMARY KEY查找记录是一个 BTree搜索。通过SECONDARY KEY定位记录是两个 BTree搜索,一个在二级INDEX的BTree中,它为您提供PRIMARY KEY;然后第二个钻取数据/ PK BTree。

PRIMARY KEY(UUID)...由于UUID 非常随机," next"行INSERT将位于'随机'点。如果表比缓冲区缓存大得多,则新行需要进入的块很可能不会被缓存。这导致磁盘命中将块拉入缓存(缓冲池),最终另一个磁盘命中将其写回磁盘。

由于PRIMARY KEY是一个独特的键,所以其他东西同时发生(No SELECT COUNT(*)etc)。在获取块之后并且在决定是否给出"重复键之前检查UNIQUEness"错误,或存储行。此外,如果该块是"已满"然后该块将需要被分割'为新行腾出空间。

INDEX(UUID)或UNIQUE(UUID)......该索引有一个BTree。在INSERT上,一些随机定位块需要被提取,修改,可能被拆分并写回磁盘,这与上面的PK讨论非常相似。如果你有UNIQUE(UUID),那么还会检查UNIQUEness和可能的错误信息。在任何一种情况下,现在和/或之后都有磁盘I / O.

AUTO_INCREMENT PK ...如果PRIMARY KEY是auto_increment,则会将新记录添加到' last'阻止数据BTree。当它满了(每100个左右的记录)时,(逻辑上)有一个块拆分并将旧块刷新到磁盘。 (实际上,I / O可能会延迟并在后台完成。)

PRIMARY KEY(id)+ UNIQUE(UUID)......两个BTrees。在INSERT上,两者都有活动。与简单的PRIMARY KEY(UUID)相比,这可能更糟。添加上面的磁盘命中以查看我的意思。

"磁盘点击"是大表中的杀手,尤其是UUID。 "统计磁盘命中数"感受性能,特别是在比较两种可能的技术时。

现在为你的秘密酱... PRIMARY KEY(日期,UUID)...你允许在两个不同的日子出现相同的UUID。这可以帮助!回到PK如何工作和检查UNIQUEness ......"复合"在插入记录时检查索引(日期,UUID)的UNIQUEness。记录按日期+ UUID排序,因此今天所有记录都聚集在一起。 IF(这可能是一个很大的IF)一天的数据适合缓冲池(但整个表没有),那么这就是每天早上发生的事情...... INSERTs突然添加新记录到"端"由于新的" date"而导致该表格。这些插入在新日期内随机出现。 buffer_pool中的块被推送到磁盘以为新块腾出空间。但是,很好,你看到的是平滑,快速,INSERT。这与您在PRIMARY KEY(UUID)中看到的不同,当许多行必须等待磁盘读取时才能检查UNIQUEness。今天所有的块都会被缓存,你不必等待I / O.

但是,如果你变得如此之大以至于你无法在缓冲池中放入一天的数据,那么事情将开始放缓,首先是在一天结束时,然后它会越来越早地蔓延,因为INSERT的频率增加。

顺便说一下,PARTITION BY RANGE(日期)和PRIMARY KEY(uuid,date)有一些相似的特征。 (是的,我故意翻过PK栏。)

答案 1 :(得分:6)

在表格中插入大量数据时,请记住,数据最终会物理存储在某个磁盘上。为了实际从磁盘读取和写入数据,MySQL(以及大多数其他RDBMS)使用称为clustered index的东西。如果在表上指定主键或唯一索引,则参与键/索引的一列或多列将成为聚簇索引键。这意味着在磁盘上,数据的物理存储顺序与键列中的值相同。

通过利用聚簇索引,数据库引擎可以快速确定值是否已存在,而无需扫描整个表。理论上,如果表包含N = 1.000.000条记录,则引擎平均需要log2(N)= 20次操作来检查值是否存在,而不管索引中有多少列。对于二级索引,通常使用B树或哈希表(在Web上搜索这些术语,以获取有关它们如何工作的详细说明)。

this article的结论是错误的:

  

" ... MySQL无法缓冲足够的数据来保证值   独特的,因此导致执行了大量的   阅读每个插页以保证唯一性"

不正确。检查唯一性并不需要任何额外的工作,因为引擎必须找到插入新记录的位置。导致性能下降的原因是UUID的使用。请记住,只要插入新记录,就会随机生成UUID。这意味着需要将新记录插入磁盘上的随机物理位置,这会导致现有数据被转移,以容纳新记录。另一方面,如果索引列是一个单调增加的值(例如自动增量INT),则总是会在最后一条记录之后插入新记录,这意味着不需要移动现有数据。

在您的情况下,案例1和案例2之间没有任何性能差异。但是由于UUID的随机性,您仍然会遇到麻烦。如果使用自动递增值而不是UUID会好得多。此外,由于UUID本质上总是独一无二的,因此用UNIQUE约束索引它们确实没有多大意义。或者,如果您真的必须使用UUID,请确保您的表上有一个主键,即基于自动递增的INT,以确保永远不会在磁盘上随机插入新记录

答案 2 :(得分:1)

这是UNIQUE constraint

的目的
  

UNIQUE索引创建一个约束,使索引中的所有值必须是不同的。如果您尝试使用与 [another] 现有行匹配的键值添加新行 [或更新现有行] ,则会出现错误

在同一手册页的前面,有人说

  

表单(col1,col2,...)的列列表会创建多列索引。索引键值是通过连接给定列的值形成的。

如何实现此约束不会被记录,但它必须以某种方式等同于具有要插入/更新的值的初步SELECT。这种检查的成本通常可以忽略不计,因为根据定义,字段被编入索引(这种开销变得相关when dealing with bulk inserts)。

索引所涵盖的列数在性能方面没有意义(例如,与表中的行数相比)。它确实会影响索引占用的磁盘空间,但这在设计决策中应该无关紧要。