额外的列破坏了MySQL的性能

时间:2010-10-01 15:59:40

标签: mysql performance

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

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.8s,在获取时间中花费0.2s,总共大约一秒钟。查询返回~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;

在select子句中添加更多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.6s提取时间和1.8s查询时间。

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;

现在真的要把你的袜子吹走了。而不是 txtProperty1 ,使用 txtProperty2 将时间更改为0.8s fetch,24s query!

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子句中添加列可以略微增加获取时间,但它不应该改变查询时间,尤其不会显着。对于造成这种放缓的原因,我不胜感激。

编辑 - 更多数据点

SELECT *实际上优于txtProperty2 - 0.8s查询,8.4s提取。太糟糕了,我无法使用它,因为获取时间(预期)太长了。

6 个答案:

答案 0 :(得分:1)

InnoDB引擎的MySQL documentation表明,如果您的varchar数据不适合页面(即b树结构的节点),那么将在溢出页面。因此,在您的宽仓库表上,可能是txtProperty1在页面上而txtProperty2在页外,因此需要额外的I / O来检索。

确定SELECT *更好的原因;它可以利用顺序读取数据,而不是绕过磁盘。

答案 1 :(得分:0)

我承认这是一个猜测,但我会试一试。

您有id - 第一个字段 - 作为主键。我不是100%确定MySQL如何对查询进行聚簇索引,但有理由怀疑,对于任何给定的ID,都有一些带有该ID的记录的“指针”。

当所有先前字段具有固定宽度时,相对容易找到字段的开头。所有BIGINT(20)字段都有一个已定义的大小,这使得db引擎可以很容易地找到给定指向记录开头的指针的字段;这是一个简单的计算。同样,第一个VARCHAR(255)字段的开头很容易找到。之后,由于字段是VARCHAR字段,因此数据库引擎必须考虑数据才能找到下一个字段的开头,这比简单地计算该字段应该在哪里要慢得多。因此,对于txtProperty1之后的任何字段,您将遇到此问题。

如果您将所有VARCHAR(255)字段更改为CHAR(255)字段会怎样?您的查询很可能会更快,尽管以每个CHAR(255)字段使用最大存储空间为代价,无论其实际包含哪些数据。

答案 2 :(得分:0)

碎片表空间?尝试使用null alter table:

ALTER TABLE tbl_name ENGINE=INNODB

答案 3 :(得分:0)

由于我是SQL Server用户而不是MySQL人,所以这是一个很长的镜头。在SQL Server中,聚簇索引是表。所有表数据都存储在聚簇索引中。其他索引存储按适当排序顺序排序的索引数据的冗余副本。

我的理由是这样的。在向查询添加越来越多的数据时,获取时间仍然可以忽略不计。我认为这是因为您在查询阶段从聚集索引中获取所有数据,并且在获取阶段实际上没有任何事情可做。

SELECT *的工作原理是因为你的表格太宽了。只要您只是请求密钥和一个或两个附加列,最好只在查询期间获取所有内容。一旦你要求所有东西,分离两个阶段之间的提取变得更便宜。我猜测,如果您一次向查询添加一列,您将发现查询分析器从查询阶段中的所有提取切换到在提取阶段执行大部分提取的边界。

答案 4 :(得分:0)

您应该发布两个查询的解释计划,以便我们可以看到它们是什么。

我的猜测是,快速的使用“覆盖索引”,慢速的则不是。

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

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

答案 5 :(得分:0)

我有一个类似的问题,并创建其他正确大小的索引帮助显着。使用分区数据库表和调整数据库ram也有帮助。

即。为(eventId,txtProperty2)

的表添加索引

注意:我注意到你说过“仓库”。请记住,如果您有一个庞大的数据库表,那么您可能需要在每个增加的条件下进行额外的延迟。