【问题标题】:Can a row with a bigger auto-increment value appear sooner than a row with a smaller one?具有较大自动增量值的行是否可以比具有较小自动增量值的行出现得更快?
【发布时间】:2014-12-13 20:58:37
【问题描述】:

以下情况可能吗?

MySQL 版本 5.6.15

CREATE TABLE my_table (
    id int NOT NULL AUTO_INCREMENT.
    data1 int,
    data2 timestamp,
    PRIMARY KEY (id)
) ENGINE=InnoDB;

innodb_autoinc_lock_mode = 1


AUTO_INCREMENT=101

  • 0 毫秒:运行查询 A:INSERT INTO my_table (data1, data2) VALUES (101, FROM_TIMESTAMP(1418501101)), (102, FROM_TIMESTAMP(1418501102)), .. [总共 200 个值] .., (300, FROM_TIMESTAMP(1418501300));
  • 500 毫秒:运行查询 B:INSERT INTO my_table (data1, data2) VALUES (301, FROM_TIMESTAMP(1418501301));
  • 505 毫秒:查询 B 已完成。该行的 id=301。
  • 1000 毫秒:SELECT id FROM my_table WHERE id >= 300; — 将返回一行 (id=301)。
  • 1200 毫秒:查询 A 已完成。这些行的 id=101 到 id=300。
  • 1500 毫秒:SELECT id FROM my_table WHERE id >= 300; — 将返回两行 (id=300, id=301)。

换句话说,是否可以先选择 id=301 的行,然后再选择 id=300 的行?

如果可能的话,如何避免?

【问题讨论】:

  • 你试过了吗?你能在 300 之前插入 ID 301 吗?
  • 我正在调试一个问题,这似乎已经发生了。

标签: mysql innodb auto-increment


【解决方案1】:

为什么查询 A 的运行时间超过一秒?呸!是的,您所看到的正是我所期望的。

在插入新行时会立即保留主键 101 到 300。这需要几毫秒。然后它会花费超过 1 秒的时间重建索引,并且在完成所有这些操作的中途,您运行另一个插入新行的查询,使用下一个可用的 auto_increment:301。

正如 Alin Purcaru 所说,您可以通过更改锁定模式来避免此特定问题,但这会导致性能问题(查询 B 将需要 700 毫秒来执行,而不是在 5 毫秒内执行。

在高负载情况下,问题会呈指数级恶化,您最终会看到“连接过多”错误,从而有效地使您的整个数据库脱机。

还有其他一些罕见的情况,auto_increment 会给出“无序”的增量,这不会通过锁定来解决。

我个人认为最好的选择是使用 UUID 而不是 auto_increment 作为主键,然后有一个时间戳列(作为带有微秒的双精度)来确定将哪些顺序行插入到数据库中。

【讨论】:

  • 这些是交付想法的大致时间,但不会太远 - 数据库加载了许多其他内容,而不仅仅是这两个查询。而且这种比平常更长的查询不时发生,并且是很少出现的问题的原因(因此更难调试)。 :) 问题是在我的情况下(使用我拥有的日志)我不完全确定发生了什么(我没有精确的 ms 时间或所有操作的顺序),但似乎很可能,这就是为什么我问。
  • 所以,如果我使用带有微秒 (DEFAULT CURRENT_TIMESTAMP) 的时间戳列,然后按它排序,id=301 的行将是第一个,id=101 到 id 的行=300 会有更大的时间戳,因此会在之后出现?然后我可以使用该列值将未处理的行与已处理的行分开。
  • 我个人不会使用DEFAULT CURRENT_TIMESTAMP。我只是将其设为 double 并在 PHP 中使用 microtime(true)(或其他任何语言的等价物)。当我自己编写代码而不是依赖 MySQL 的内部(并且通常过于复杂)行为时,我发现错误更少。另外我认为 MySQL 时间戳列只存储秒数,因此插入的所有 200 行将具有完全相同的值,并且您将不知道它们插入的顺序。PHP 中的mycrotime(true) 为您提供微秒。
  • 我想依靠 MySQL 来注册插入顺序,因为在不同的服务器上有几个独立的程序来执行插入。我做了一个实际测试,证实了所描述的情况确实会发生。不幸的是,在这方面,CURRENT_TIMESTAMP 列与 AUTO_INCREMENT 列没有任何不同:比新行更早被选中的旧行不仅具有更大的 id,而且具有更大的插入时间值。
  • 如果您希望时间戳是插入的结尾而不是插入的开头,只需执行update my_table set created_date = current_timestamp where uuid = '...'
【解决方案2】:

所以...我为你读了the docs

当您在示例中拥有两个简单插入时,根据他们的说法(多插入仍然被认为是简单的,即使它有多行,因为您知道要插入多少行,因此可以确定最终的自动增量值),并且当您拥有innodb_autoinc_lock_mode = 1 时,不会在表上设置任何锁定,您可以在第一个查询之前完成第二个查询,并让该行之前可用。

如果你想避免这种情况,你需要设置innodb_autoinc_lock_mode = 0,但你可能会在可扩展性方面付出代价。

免责声明:我没有尝试过,答案基于我对 MySQL 文档的理解。

【讨论】:

  • 这也是我在阅读文档后的怀疑,但我希望得到更了解 MySQL 的人的确认。 :)
猜你喜欢
  • 1970-01-01
  • 2010-09-26
  • 2011-11-04
  • 2012-05-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-09-11
相关资源
最近更新 更多