nic*_*dnk 11 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不需要全局唯一 - 它只需要对每个日期都是唯一的.这就是为什么我可以允许它成为复合键的一部分.
Ric*_*mes 11
在之前的答案中存在一些缺陷和误解; 而不是指出它们,我将从头开始.
仅提到InnoDB ......
INDEX(包括UNIQUE和PRIMARY KEY)是BTree.基于BTree排序的密钥,BTree非常有效地定位一行.(按键顺序扫描也很有效.)MySQL中典型BTree的"扇出"大约为100.因此,对于一百万行,BTree约为3级(log100(百万)) ); 对于万亿行,它只有两倍深(大约).因此,即使没有缓存任何内容,也只需要3次磁盘命中就可以找到百万行索引中的一个特定行.
我在这里放松了"索引"与"表",因为它们基本相同(至少在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 非常随机,因此INSERT的"下一行"将位于"随机"位置.如果表比缓冲区缓存大得多,则新行需要进入的块很可能不会被缓存.这会导致磁盘命中以将块拉入缓存(缓冲池),并最终将另一个磁盘命中以将其写回磁盘.
由于PRIMARY KEY是一个UNIQUE 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,则新记录将添加到数据BTree中的"last"块.当它满了(每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)一天的数据适合缓冲池(但整个表没有),那么这就是每天早上发生的事情...... INSERT正在突然添加新的记录到"结束"该表因为新的"日期".这些插入在新日期内随机出现.buffer_pool中的块被推送到磁盘以为新块腾出空间.但是,很好,你看到的是平滑,快速,INSERT.这与您在PRIMARY KEY(UUID)中看到的不同,当许多行必须等待磁盘读取时才能检查UNIQUEness.今天的所有块都保持缓存,您不必等待I/O.
但是,如果你变得如此之大以至于你无法将一天的数据放入缓冲池中,那么事情将开始放缓,首先是在一天结束时,然后随着INSERT频率的增加,它会越来越早地蠕动.
顺便说一下,PARTITION BY RANGE(日期)和PRIMARY KEY(uuid,date)有一些相似的特征.(是的,我故意翻过PK栏.)
在表中插入大量数据时,请记住,数据最终会物理存储在某个磁盘上.为了实际从磁盘读取和写入数据,MySQL(以及大多数其他RDBMS)使用称为聚簇索引的东西.如果在表上指定主键或唯一索引,则参与键/索引的一列或多列将成为聚簇索引键.这意味着在磁盘上,数据的物理存储顺序与密钥列中的值相同.
通过利用聚簇索引,数据库引擎可以快速确定值是否已存在,而无需扫描整个表.理论上,如果表包含N = 1.000.000条记录,则引擎平均需要log2(N)= 20次操作来检查值是否存在,而不管索引中有多少列.对于二级索引,通常使用B树或哈希表(在Web上搜索这些术语,以获取有关它们如何工作的详细说明).
这篇文章的结论是错误的:
"...... MySQL无法缓冲足够的数据以保证值是唯一的,因此导致每个插入执行大量读取以保证唯一性"
这是不正确的.检查唯一性并不需要任何额外的工作,因为引擎必须找到插入新记录的位置.导致性能下降的原因是UUID的使用.请记住,只要插入新记录,就会随机生成UUID.这意味着需要将新记录插入磁盘上的随机物理位置,这会导致现有数据被转移,以容纳新记录.另一方面,如果索引列是一个单调增加的值(例如自动增量INT),则总是会在最后一条记录之后插入新记录,这意味着不需要移动任何现有数据.
在您的情况下,案例1和案例2之间不会有任何性能差异.但由于UUID的随机性,您仍然会遇到麻烦.如果使用自动递增值而不是UUID会好得多.此外,由于UUID本质上总是唯一的,因此用UNIQUE约束索引它们确实没有多大意义.或者,如果您确实必须使用UUID,请确保您的表上有一个主键,即基于自动递增的INT,以确保永远不会在磁盘上随机插入新记录.
归档时间: |
|
查看次数: |
2587 次 |
最近记录: |