尽可能快地获取我的 MySQL 的第一行(也在大表上)

Posted

技术标签:

【中文标题】尽可能快地获取我的 MySQL 的第一行(也在大表上)【英文标题】:Get top row my MySQL as fast as possible (also on large tables) 【发布时间】:2020-05-03 22:04:06 【问题描述】:

我有一个用于工业机器的简单软件,它使用 mysql 作为数据存储。任何时候数据库上只有一个连接和一个用户。

我的表很简单:

id   data            fetched 
int  varchar         boolean

1     KDINNALSKDGJ     0
2     F34LNALNLIJA     0

等等。 id 总是连续的,并且有一个索引。

我需要的是始终获取第一个“数据”(具有最低 id),其中 fetched 为 0。然后将“fetched”更新为“1”,因为我收到了数据。

我使用类似的东西

SELECT id, data FROM mytable WHERE fetched=0 LIMIT 0,1

这可行,但每次调用都会按顺序变慢。这是我真正的问题。我可以忍受前 100 秒左右的估计 0,005 秒,但在 50.000+ 时,我会遇到 0.3 秒。

我认为这是因为数据库每次查找第一个匹配项时都会从顶部搜索。

按数字约束索引要快得多:

SELECT id, data FROM mytable WHERE id> :myLastID  and fetched=0 LIMIT 0,1

.. 但这在 40.000 左右之后也会变慢,在 80.000 处我大约是 20 毫秒(第一次大约是 6 毫秒)

我的最终数据库可能在数百万范围内,但通常可能在 2-500.000 左右

有什么方法可以让 MySQL 更快地返回“下一条记录”?使用 MySQL 中的 CURSOR 吗?

我将使用 Delphi 来连接 MySQL。我尝试了存储过程并使用 2 个查询来选择 /update。结果几乎一样。

【问题讨论】:

你试过'TOP'吗?我不知道它是否适用于 MySQL,但它应该。 w3schools.com/sql/sql_top.asp 请注意,没有 ORDER BY 的 LIMIT 是毫无意义的 看来您需要的是 UPDATE,而不是 SELECT @P2000 我必须保留数据,因为我稍后会做报告。数据的处理不仅仅是“获取” - 也可以是“处理”和“验证”。我想我最终会得到“查询大块”,然后用事务批量更新。在断电时当然会冒着不一致的风险,我试图用单一查询的想法来防止这种情况。 是的,如果您缓存在易失性内存上,电源中断是一个问题,当然,这就是 S/W 结构/变量所在的位置。您可以在数据库表中镜像加载的缓存/块“数组”。如果镜像在并发线程中,则可以最大限度地减少延迟问题。我猜你知道这一点:如果操作系统仍在内存中缓冲 R/W,即使 DB 也可能不安全。电源中断最好在 H/W 级别使用 UPS 和断电中断来处理,以退出已知和安全的状态(服务器、医疗设备、机械、军事设备......) 【参考方案1】:

首先:您的查询需要一个order by 子句,否则实际上未定义首先返回哪一行(不能保证它将是具有最小id 的行)。

所以你应该这样表述:

select id, data 
from mytable 
where fetched = 0 
order by id
limit 1

然后为了性能,我建议添加以下索引:

create index myindex on mytable(fetched, id, data)

逻辑是:

索引的第一列fetched匹配where子句中的谓词

第二列是排序列(id

第三列是select子句中的剩余列(data

这为您提供了一个覆盖索引:MySQL 应该能够通过仅查看索引来执行整个查询(即不查看数据本身)。

【讨论】:

感谢您的建议。我确实实现了它们,但奇怪的是性能(每次查询的时间)大致相同。我的猜测是,如果不查看不同的策略,我无法获得所需的“selectUpdate”性能。我的 SQL 服务器上的时间增加到大约 50 毫秒。不过欢迎您的建议。 我会避免将data 添加到索引中。首先,它会使索引变得更大,其次,由于您需要更新记录,因此无论如何您都需要访问数据。 @MyICQ 您应该使用EXPLAIN 来检查 MySQL 如何执行查询。如果使用正确的索引应该很快。 我的印象是“data”是一个唯一的token,所以“id”和“data”是独立唯一的。如果是这样,这意味着索引只需要跨越“id”和“fetched”。包含“数据”会抵消索引的好处,这可能是 OP 正在观察的。 @P2000: 不 - 这是索引中的最后一列,所以即使它是唯一的也不是问题(如果它在索引中是第一列)。这仍然为您提供了一个可用且覆盖的索引 - 这是我们在这里要加快查询速度。【参考方案2】:

如果只有一个连接,我们是否也可以假设也只有一个“用户”? 如果是这样,为什么不简单地将 id 值保存在程序的内存中,并通过简单地询问“下一个”记录来询问第一个下一个数字。例如。 SELECT data FROM table WHERE id = @x AND fetched = 0?如果没有返回任何内容,那么您就知道之前已经获取了该值并且某些内容不同步(或者表中的记录已用完)。这应该是异常的,我猜你必须恢复到旧查询才能回到正轨,但同样,它应该是异常的。

不需要任何其他更改(当您说“索引”时,我假设 id 是 PK)

PS:既然你没有提到,那么 InnoDB 和 MyISAM 有区别吗? (从我所读到的内容中,我更喜欢前者,但TBH我几乎没有任何实践经验)

哦,是的,正如其他人已经提到的,没有ORDR BYLIMIT 1 几乎会给你“随机结果”。这可能是故意的,但这种情况很少见,大多数情况下更喜欢在重新运行时获得相同的结果。

【讨论】:

以上是关于尽可能快地获取我的 MySQL 的第一行(也在大表上)的主要内容,如果未能解决你的问题,请参考以下文章

如何在大表上优化这个 mysql 连接?

MySQL查询在大表上很慢

蟒蛇:MYSQLdb。如何在不执行 select * 在大表中获取列名?

在大表执行DDL的过程中,若临时中断,会发生什么状况,需要特别处理吗?

在 Django 中的大表上的内存效率(常量)和速度优化迭代

JPA 存储库:将实体保存在大表中的问题 - 超时错误 [重复]