额外的列会影响MySQL的性能表现。

6

我有一个仓库表格,看起来像这样:

CREATE TABLE Warehouse (
  id BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
  eventId BIGINT(20) UNSIGNED NOT NULL,
  groupId BIGINT(20) NOT NULL,
  activityId BIGINT(20) UNSIGNED NOT NULL,
  ... many more ids,
  "txtProperty1" VARCHAR(255),
  "txtProperty2" VARCHAR(255),
  "txtProperty3" VARCHAR(255),
  "txtProperty4" VARCHAR(255),
  "txtProperty5" VARCHAR(255),
  ... many more of these
  PRIMARY KEY ("id")
  KEY "WInvestmentDetail_idx01" ("groupId"),
  ... several more indices
) ENGINE=INNODB;

现在,以下查询在查询时间花费约0.8秒,在提取时间花费约0.2秒,总共约一秒钟。该查询返回大约67,000行数据。

SELECT eventId
FROM Warehouse
WHERE accountId IN (10, 8, 13, 9, 7, 6, 12, 11)
  AND scenarioId IS NULL
  AND insertDate BETWEEN DATE '2002-01-01' AND DATE '2011-12-31'
ORDER BY insertDate;

在选择子句中添加更多的ID实际上不会改变性能。

SELECT eventId, groupId, activityId, insertDate
FROM Warehouse
WHERE accountId IN (10, 8, 13, 9, 7, 6, 12, 11)
  AND scenarioId IS NULL
  AND insertDate BETWEEN DATE '2002-01-01' AND DATE '2011-12-31'
ORDER BY insertDate;

然而,添加一个“属性”列会使其获取时间为0.6秒,查询时间为1.8秒。
SELECT eventId, txtProperty1
FROM Warehouse
WHERE accountId IN (10, 8, 13, 9, 7, 6, 12, 11)
  AND scenarioId IS NULL
  AND insertDate BETWEEN DATE '2002-01-01' AND DATE '2011-12-31'
ORDER BY insertDate;

现在,让我们来谈一些真正让你叹为观止的事情。使用txtProperty2代替txtProperty1,可以将时间缩短到0.8秒的提取和24秒的查询!

SELECT eventId, txtProperty2
FROM Warehouse
WHERE accountId IN (10, 8, 13, 9, 7, 6, 12, 11)
  AND scenarioId IS NULL
  AND insertDate BETWEEN DATE '2002-01-01' AND DATE '2011-12-31'
ORDER BY insertDate;

这两列的数据类型基本相同:大多数非空,并且都没有索引(不过这应该没有影响)。为了确保表本身健康,我对其进行了分析/优化。
这真的让我感到困惑。我可以理解仅在选择子句中添加列可能会略微增加提取时间,但它不应该改变查询时间,特别是不应该有明显的变化。如果您有任何关于导致这种减速的原因的想法,我将不胜感激。
编辑-更多数据点
实际上,SELECT * 的性能优于 txtProperty2-0.8秒查询,8.4秒提取。太糟糕了,我不能使用它,因为提取时间(预计)太长。

这些计时有多可重复? - Rowland Shaw
2
不过它们都没有被索引(不过这也不应该有影响)。但其实也可以做一些优化,比如使用覆盖索引。参见:http://en.wikipedia.org/wiki/Index_%28database%29#Covering_Index - Markus Winand
@Rowland - 是的,我已经在两台开发笔记本电脑和一个QA服务器上重复了这些时间。显然,服务器更快,但模式仍然存在。 - Monkey Boson
@Markus - 这不能解释txtPropert1和txtProperty2之间不同的时间,这两者都没有索引。即使可以解释,被覆盖的索引是否会比常规索引提供如此大的性能提升(0.8秒-> 24秒)? - Monkey Boson
如果查询可以仅通过索引满足(where子句和select列表中的每个列都在索引中),则根本不需要访问表。如果您添加了另一个未包含在索引中的列,则必须访问该表,这可能会产生巨大的性能差异。但是,如果两个列都没有在任何索引中,那么它无法解释最后两个语句之间的差异:( - Markus Winand
1
你能否发布一个好的查询和一个坏的查询的EXPLAIN结果呢? - Andrew
6个回答

1

MySQL InnoDB引擎的文档建议,如果您的varchar数据不适合页面(即B树结构的节点),则信息将在溢出页面上引用。因此,在您的宽Warehouse表上,可能txtProperty1在页面上,而txtProperty2在页面外,因此需要额外的I/O来检索。

不太确定为什么SELECT *更好;它可能能够利用顺序读取数据,而不是在磁盘上四处寻找。


这种情况在我的数据中是完全可能的。虽然我对检索时间从2秒增加到24秒感到有些惊讶。你有什么想法可以改善查询时间吗? - Monkey Boson
我没有任何实际经验:似乎有两种获取更多页面数据的潜在方法。a)您可以尝试使页面更宽:通过设置KEY_BLOCK_SIZE,或b)您是否对数据类型大小有任何灵活性,例如您需要数字为BIGINT(无符号INT或MEDIUMINT是否可行?),和/或VARCHAR可以只有100个长度吗? - richaux
看起来 SHOW TABLE STATUS 将会显示当前的 KEY_BLOCK_SIZE。那个值是多少?并且到 txtProperty1 列的大小加起来是多少? - Harold L

0

表空间碎片化?尝试使用空的alter table命令:

ALTER TABLE tbl_name ENGINE=INNODB

抱歉...它没有起作用。这是否与InnoDB中的优化达到了相同的效果? - Monkey Boson
我不这么认为。我仍然认为可能有某种表空间错误可以解释几乎相同的列之间的显著差异。 - igelkott

0

我承认这有点猜测,但我会尝试一下。

您有一个id——第一个字段——作为主键。我不确定MySQL如何进行聚集索引查询,但可以合理地怀疑,在给定 ID 的任何记录中,都存在指向该 ID 的记录的某些“指针”。

当所有先前的字段具有固定宽度时,很容易找到字段的起始位置。所有您的BIGINT(20)字段都有定义大小,使得数据库引擎可以在给定记录起始位置的情况下轻松找到该字段;这是一个简单的计算。同样,第一个VARCHAR(255)字段的开头也很容易找到。之后,因为字段是VARCHAR字段,所以数据库引擎必须考虑数据来查找下一个字段的起始位置,这比简单计算该字段应该在哪里要慢得多。因此,在txtProperty1之后的任何字段中,你都会遇到这个问题。

如果将所有的VARCHAR(255)字段更改为CHAR(255)字段,会发生什么?很可能你的查询速度会更快,尽管以使用每个CHAR(255)字段的最大存储空间为代价,而不考虑它实际包含的数据。

抱歉,没有结果。将前5个属性更改为CHAR(255)实际上使查询在98秒内运行,1.5秒内提取。然而,在这个区域进行测试导致我发现另一个奇怪的问题:选择txtProperty8会遭受与txtProperty1相同的惩罚(仅2秒)。txtProperty7介于两者之间(大约5秒)。整个情况非常,非常奇怪。 - Monkey Boson

0

由于我是SQL Server用户而不是MySQL专家,所以这是一个冒险。在SQL Server中,聚集索引就是表格。所有表格数据都存储在聚集索引中。其他索引存储排序后的索引数据的冗余副本。

我的推理是这样的。随着您向查询中添加越来越多的数据,提取时间仍然可以忽略不计。我认为这是因为在查询阶段从聚集索引中获取了所有数据,因此在提取阶段实际上没有剩下的事情要做。

SELECT *之所以起作用,是因为您的表格非常宽。只要您只请求关键字和一两个其他列,最好在查询期间获取所有内容。一旦您要求所有内容,将在两个阶段之间分离提取变得更加便宜。我猜想,如果您逐个添加查询列,您将发现查询分析器从在查询阶段执行所有提取到在提取阶段执行大部分提取的边界。


这听起来像是其他人提到的“覆盖索引”技术。如果txtProperty1和txtProperty2都不是任何索引的一部分,那还是这种情况吗? - Monkey Boson

0

您应该发布两个查询的解释计划,这样我们才能看到它们是什么。

我猜快速的那个正在使用“覆盖索引”,而慢的那个没有。

这意味着慢的那个必须执行67,000个主键查找,如果表不在内存中(通常需要67k IO操作,如果表任意大且每行在自己的页面中)将非常低效。

在MySQL中,如果正在使用覆盖索引,则EXPLAIN将显示“Using index”。


两种情况下的解释是相同的。尽管where子句中的项目已经被索引,但MySQL在这两种情况下都决定执行全表扫描(可能是因为67000代表了整个表大小的相当大的一部分)。在我提到的最后两个查询中,都不能使用“覆盖索引”技术,因为它们都包含未被索引的列。 - Monkey Boson

0

我曾经遇到过类似的问题,创建适当大小的索引可以帮助解决问题。此外,使用分区数据库表和调整数据库内存也有所帮助。

例如,为表格添加如下索引(eventId, txtProperty2)

注意: 我看到你提到了"仓库(Warehouse)",需要记住,如果你正在处理一个巨大的数据库表格,那么每增加一个条件都会导致额外的延迟。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接