我刚刚在我刚刚开始使用的系统中偶然发现了几行代码,但我并没有真正得到。系统有一个大表,可以保存许多具有唯一ID的实体,并在不再需要它们时将其删除,但它永远不会重复使用它们。所以表格看起来像这样
------------------------
| id |info1|info2|info3|
------------------------
| 1 | foo1| foo2| foo3|
------------------------
| 17 | bar1| bar2| bar3|
------------------------
| 26 | bam1| bam2| bam3|
------------------------
| 328| baz1| baz2| baz3|
------------------------
etc.
在代码库的一个地方有一个while循环,其目的是遍历数据库中的所有实体并对它们做些事情,现在这就像这样解决了
int lastId = fetchMaxId()
int id = 0
while (id = fetchNextId()){
doStuffWith(id)
}
其中fetchMaxId是直接的
int fetchMaxId(){
return sqlQuery("SELECT MAX(id) FROM Table")
}
但是fetchNextId让我很困惑。它实现为
int fetchNextId(currentId, maxId){
return sqlQuery("
SELECT id FROM Table where id > :currentId and id <= :maxId LIMIT 1
")
}
这个系统已经生产了几年,所以它显然有效,但是当我试图寻找解决方案来解决这个原因时,我只发现人们说的是我认为我知道的相同的东西。 MySQL DB返回结果的顺序不容易确定,不应该依赖,所以如果你不想使用ORDER BY子句。但是有时候你可以安全地忽略ORDER BY吗?此代码已工作了12年,并继续通过多个数据库更新工作。我们是幸运还是我在这里错过了什么?在我看到这段代码之前,我会说如果你打电话给
fetchNextId(1, 328)
你最终可以得到17或26作为答案。
有关其工作原理的一些线索可能是id列是相关表的主键,并且它设置为自动增量但我找不到任何可以解释原因的文档
fetchNextId(1, 328)
在上面给出的table-snippet上调用时,应该总是返回17。
答案 0 :(得分:2)
你的问题的答案是肯定的。如果查看MySQL documentation,您会看到只要表具有主键,就会有相关的索引。
查看documentation for indexes时,您会看到他们会将主键作为一种索引提及。
所以在您的特定情况下:
SELECT id FROM Table where id > :currentId and id <= :maxId LIMIT 1
由于LIMIT 1
,查询一旦找到值就会停止执行。
没有LIMIT 1
,它将返回17,24和328。
然而,所有这些都表示我不认为当主键自动递增时会遇到任何订单问题,但是只要有场景,主键就是唯一的员工号码。而不是自动递增字段我不相信结果的顺序,因为文档还指出MySQL按顺序读取,因此可能存在主键可能超出WHERE
子句条件并被跳过。
答案 1 :(得分:1)
简短回答是肯定的,主键有一个订单,所有索引都有一个订单,主键只是一个唯一索引。
正如您所说,您不应该依赖于按照数据存储顺序返回的数据,优化器可以按照自己喜欢的顺序自由返回,这将取决于查询计划。但是,我会尝试解释为什么您的查询已经工作了12年。
您的聚簇索引只是您的表数据,您的聚类键定义了它的存储顺序。数据存储在叶子上,聚类键帮助根(和中间注释)充当快速指针到达正确的叶子来检索数据。非聚簇索引是一种非常相似的结构,但最低级别只包含一个指向聚簇索引叶子上正确位置的指针。
在MySQL中,主键和聚簇索引是同义词,因此主键是有序的,但它们基本上是两个不同的东西。在其他DBMS中,您可以定义主键和聚簇索引,当您执行此操作时,主键将成为唯一的非聚簇索引,其指针将返回聚簇索引。
在最简单的术语中,您可以想象一个具有ID列的表是主键,而另一列(A),您的聚簇索引的B-Tree结构将类似于:
Root Node
+---+
| 1 |
+---+
Intermediate Nodes
+---+ +---+ +---+
| 1 | | 4 | | 7 |
+---+ +---+ +---+
Leaf
+-----------+ +-----------+ +-----------+
ID -> | 1 | 2 | 3 | | 4 | 5 | 6 | | 7 | 8 | 9 |
A -> | A | B | C | | D | E | F | | G | H | I |
+-----------+ +-----------+ +-----------+
实际上叶页会更大,但这只是一个演示。每个页面还有一个指向下一页和上一页的指针,以便于遍历树。因此,当您执行以下查询时:
SELECT ID, A
FROM T
WHERE ID > 5
LIMIT 1;
您正在扫描唯一索引,因此这很可能是顺序扫描。但很可能无法保证。
MySQL将扫描Root节点,如果存在潜在的匹配,它将移动到中间节点,如果该子句类似WHERE ID < 0
,则MySQL会知道没有任何进一步的结果比根节点。
一旦移动到中间节点,它就可以识别它需要从第二页(4到7之间)开始,以开始搜索ID > 5
。因此,它将从第二个叶子页面开始顺序扫描叶子,已经识别出LIMIT 1
它将在找到匹配后停止(在这种情况下为6)并从叶子返回此数据。在这样一个简单的例子中,这种行为似乎是可靠和合乎逻辑的。我试图通过选择我知道位于叶页末尾的ID值来强制异常,以查看是否将以相反的顺序扫描叶子,但是由于无法产生这种行为,这并不意味着它不会发生,或者MySQL的未来版本在我测试过的场景中不会这样做。
简而言之,只需添加一个订单,或使用MIN(ID)并完成它。我不会失去太多的睡眠,试图深入研究查询优化器的内部工作,以查看在查询计划中观察聚簇索引的不同排序需要哪种碎片或数据范围。