MySQL innodb:使用LIMIT时可以安全地依赖默认顺序吗?

时间:2015-01-13 23:05:23

标签: mysql innodb

我使用innodb表来存储联系信息。 我显示分页数据。第一页的查询如下所示:

SELECT name, email FROM contacts WHERE userid = 1 LIMIT 0,50

带有userid的复合列电子邮件是主键,因此默认情况下结果按其排序。 第二页的查询如下所示;

SELECT name, email FROM contacts WHERE userid = 1 LIMIT 50,50

现在我的问题:依靠innodb的默认排序顺序是否安全(我知道我可以简单地添加ORDER BY email以确保)? 换句话说,是否可能在第一页和第二页上显示相同的联系人?这将取决于什么?

2 个答案:

答案 0 :(得分:2)

不,依靠InnoDB的任何“默认排序”并不“安全”。

但这实际上取决于你如何定义“安全”。如果没有ORDER BY子句,MySQL会保证以任何特定顺序返回行。 (我们可能会观察到可重复的行为似乎是可靠的,但这不是的保证。所以,我不认为它是安全的。

换句话说,第二个查询(使用LIMIT 50,50)可以返回与第一个查询(LIMIT 0,50)完全相同的行集并且在规范内。 (显然,要实现这一点,需要至少有100行满足查询谓词。)

考虑当DBA升级MySQL或InnoDB插件时会发生什么,并且行为不一样。考虑一下,如果DBA将存储引擎从InnoDB更改为其他更新,更快的存储引擎,会发生什么。考虑如果DBA将主键从一个候选键更改为另一个候选键,会发生什么。

如果您依赖于按特定顺序返回的行,那么在ORDER BY子句中明确指定它是“安全的”。当明确指定时,解密代码的穷人更可能会认识到您的代码期望以特定顺序返回行。查看代码,他是否知道引用的表正在使用InnoDB存储引擎,并且您依赖于某些隐式假设的行为?

-

问:第一页和第二页可能会显示相同的联系人吗?这将取决于什么?

答:是的,这是可能的。如果不做一些改变,你不可能看到它发生。考虑在第一次查询之后以及第二次查询之前执行DELETE操作(例如通过某个其他会话)会发生什么。您的查询模式可能会返回相同的行。如果插入行,也有可能跳过某些行。

有改进的“下一页”查询模式。我使用的是使用ORDER BY唯一键(或一组非唯一列,包括或包含唯一键。保存“ORDER BY”表达式中的所有值上一个查询的最后一行,“下一页”查询包括谓词(WHERE子句),它只返回“跟随”最后检索到的行的行。

<强>更新

假设id是主键或唯一键,对于第一页:

SELECT c.name
     , c.email
     , c.id
  FROM contacts c 
 WHERE c.userid = 1
 ORDER BY c.id
 LIMIT 50

保存上次检索到的行中id列的值,并将其传回“下一页”请求。对于下一页查询,

SELECT c.name
     , c.email
     , c.id
  FROM contacts c 
 WHERE c.userid = 1
   AND c.id > :last_retrieved_value
 ORDER BY c.id
 LIMIT 50

如果您按非独特的方式进行排序,则查询谓词只会稍微复杂一些。例如,如果您按name订购,则按id

订购

首页查询大致相同:

SELECT c.name
     , c.email
     , c.id
  FROM contacts c 
 WHERE c.userid = 1
 ORDER
    BY c.name
     , c.id
 LIMIT 50

保存上次提取的行的nameid中的值。下一页查询使用这些值:

SELECT c.name
     , c.email
     , c.id
  FROM contacts c 
 WHERE c.userid = 1
   AND c.name >= :last_fetched_name
   AND NOT ( c.name = :last_fetched_name AND c.id <= :last_fetched_id )
 ORDER
    BY c.name
     , c.id
 LIMIT 50

如果你想变得更加漂亮,你可以增强它来检查你是否获取了最后一行。将查询更改为LIMIT 51,仅使用前50行(保存第50行的值),并检查是否有第51行。如果你没有得到一行,那么你就在行的末尾,而且不需要启用“下一页”按钮。

答案 1 :(得分:0)

(我假设用户ID不是PK,否则查询没有意义)。

不,这不安全。 InnoDB按照它读取的索引顺序返回行。 比如说,您有索引idx_a (userid, field_a)idx_b (userid, field_b)。出于某种原因,优化器决定对第一个查询使用idx_a,对第二个查询使用idx_b。显然你会得到不同的记录集。

即使有索引idx (userid) MySQL也可能决定从PRIMARY读取(当userid = 1是表记录的大部分时)