【问题标题】:MySQL with InnoDB: How to avoid using COUNT?MySQL 与 InnoDB:如何避免使用 COUNT?
【发布时间】:2014-01-22 09:07:38
【问题描述】:

所以我有一个表,我在其中填充了大约 700K 条目进行测试,这表明它在 MySQL 查询中很痛苦。

有问题的表:

CREATE TABLE `trades` (
 `tradeId` int(11) NOT NULL AUTO_INCREMENT,
 `userId` int(11) NOT NULL,
 `visible` int(11) NOT NULL DEFAULT '1',
 `sourceItem` int(11) NOT NULL,
 `sourceKeyTierId` int(11) DEFAULT NULL,
 `sourceKeyTypeId` int(11) DEFAULT NULL,
 `sourceKeyAmount` int(11) DEFAULT NULL,
 `sourceModId` int(11) DEFAULT NULL,
 `sourceModLevel` int(11) DEFAULT NULL,
 `destinationItem` int(11) NOT NULL,
 `destinationPlatinum` int(11) DEFAULT NULL,
 `destinationKeyTierId` int(11) DEFAULT NULL,
 `destinationKeyTypeId` int(11) DEFAULT NULL,
 `destinationKeyAmount` int(11) DEFAULT NULL,
 `destinationModId` int(11) DEFAULT NULL,
 `destinationModLevel` int(11) DEFAULT NULL,
 `added` datetime NOT NULL,
 PRIMARY KEY (`tradeId`),
 KEY `userId` (`userId`),
 KEY `sourceKeyTierId` (`sourceKeyTierId`),
 KEY `sourceKeyTypeId` (`sourceKeyTypeId`),
 KEY `sourceModId` (`sourceModId`),
 KEY `destinationKeyTierId` (`destinationKeyTierId`),
 KEY `destinationKeyTypeId` (`destinationKeyTypeId`),
 KEY `destinationModId` (`destinationModId`),
 CONSTRAINT `trades_ibfk_1` FOREIGN KEY (`userId`) REFERENCES `users` (`userId`),
 CONSTRAINT `trades_ibfk_2` FOREIGN KEY (`sourceKeyTierId`) REFERENCES `keytiers` (`keyTierId`),
 CONSTRAINT `trades_ibfk_3` FOREIGN KEY (`sourceKeyTypeId`) REFERENCES `keytypes` (`keyTypeId`),
 CONSTRAINT `trades_ibfk_4` FOREIGN KEY (`sourceModId`) REFERENCES `mods` (`modId`),
 CONSTRAINT `trades_ibfk_5` FOREIGN KEY (`destinationKeyTierId`) REFERENCES `keytiers` (`keyTierId`),
 CONSTRAINT `trades_ibfk_6` FOREIGN KEY (`destinationKeyTypeId`) REFERENCES `keytypes` (`keyTypeId`),
 CONSTRAINT `trades_ibfk_7` FOREIGN KEY (`destinationModId`) REFERENCES `mods` (`modId`)
) ENGINE=InnoDB AUTO_INCREMENT=732544 DEFAULT CHARSET=latin1

现在在获取结果集时,我想计算结果的数量,以决定是否显示未找到结果的消息。

SELECT SUM(count) AS sum 
FROM
(
  (SELECT COUNT(1) AS count
   FROM trades t
   WHERE t.sourceItem = 1 AND t.destinationItem = 1)
UNION ALL
  (SELECT COUNT(1) AS count
   FROM trades t
   WHERE t.sourceItem = 1 AND t.destinationItem = 2)
UNION ALL
  (SELECT COUNT(1) AS count
   FROM trades t
   WHERE t.sourceItem = 1 AND t.destinationItem = 3)
UNION ALL
  (SELECT COUNT(1) AS count
   FROM trades t
   WHERE t.sourceItem = 2 AND t.destinationItem = 1)
UNION ALL
  (SELECT COUNT(1) AS count
   FROM trades t
   WHERE t.sourceItem = 2 AND t.destinationItem = 2)
UNION ALL
  (SELECT COUNT(1) AS count
   FROM trades t
   WHERE t.sourceItem = 2 AND t.destinationItem = 3)
) AS derived

查询正在运行,但需要 2.63 秒,这太长了。

如何优化这个?我以为我几乎已经完成了我能做的所有事情,除了一件事:

  • 由于sourceItem 的可能值为(1, 2)destinationItem 的可能值为(1, 2, 3),我可以创建另一个表并通过TRIGGER ON INSERT 写入包含这些值的表。

同样重要的是,查询是由依赖于 POST 变量的 PHP 脚本创建的,这意味着 UNION ALL 中的每个 SELECT 可能存在也可能不存在。不幸的是,这个问题并不像返回全表的最大值那么简单。

也欢迎所有其他建议。

更新: 显然查询的实际构造方式存在一些混淆,如下所示:

  • sourceItem 有 2 个复选框,对应于 12
  • destinationItem 有 3 个复选框,对应于 123

用户可以以任何他想要的方式检查它们。

更新 2: 似乎我的原始查询不会削减它,即使使用索引,有人可以这么好心地考虑一个完全不同的设置,基本上消除了对 COUNT 的需要或SUM 或类似的东西?

更新 3:我忘记了我的问题中一个非常重要的部分,如下:

  • 拥有sourceItem = 1可能有(sourceKeyTierId = ? AND sourceKeyTypeId = ?)与之关联
  • 拥有sourceItem = 2可能有(sourceModId = ?)与之关联
  • 拥有destinationItem = 2可能有(destinationKeyTierId = ? AND destinationKeyTypeId = ?)与之关联
  • 拥有destinationItem = 3可能有(destinationModId = ?)与之关联

您仍然可以在带有复选框的示例中看到它,但是选中某些复选框也会导致输入另一个数字的选项(在实际场景中它是一个选择下拉菜单),不需要选择。

我的第一个未优化查询的更新示例是以下查询可能也会发生,但这是最大的示例:

SELECT SUM(count) AS sum 
FROM
(
  (SELECT COUNT(1) AS count
   FROM trades t
   WHERE t.sourceItem = 1 AND t.destinationItem = 1 AND t.sourceKeyTierId = ? AND t.sourceKeyTypeId = ?)
UNION ALL
  (SELECT COUNT(1) AS count
   FROM trades t
   WHERE t.sourceItem = 1 AND t.destinationItem = 2 AND t.sourceKeyTierId = ? AND t.sourceKeyTypeId = ? AND t.destinationKeyTierId = ? AND t.destinationKeyTypeId = ?)
UNION ALL
  (SELECT COUNT(1) AS count
   FROM trades t
   WHERE t.sourceItem = 1 AND t.destinationItem = 3 AND t.sourceKeyTierId = ? AND t.sourceKeyTypeId = ? AND t.destinationModId = ?)
UNION ALL
  (SELECT COUNT(1) AS count
   FROM trades t
   WHERE t.sourceItem = 2 AND t.destinationItem = 1 AND t.sourceModId = ?)
UNION ALL
  (SELECT COUNT(1) AS count
   FROM trades t
   WHERE t.sourceItem = 2 AND t.destinationItem = 2 AND t.sourceModId = ? AND t.destinationKeyTierId = ? AND t.destinationKeyTypeId = ?)
UNION ALL
  (SELECT COUNT(1) AS count
   FROM trades t
   WHERE t.sourceItem = 2 AND t.destinationItem = 3 AND t.sourceModId = ? AND t.destinationModId = ?)
) AS derived

【问题讨论】:

  • 正如你所说的Now when obtaining a result set I want to count the amount of results to decide whether to display a message that no results were found, or not. 你运行这个查询只是为了检查它是否会返回一些东西吗?
  • @Manu 是的,处理查询更高级(有多个连接),但也使用LIMITORDER BY,平均包含 50 个结果
  • 那么,当事先不知道要计数的请求组合时,您想知道有多少结果与sourceItemdestinationItem 的特定组合匹配?例如,第一个查询可能是“有多少 或 ?”,而下一个查询可能是“有多少 ”?
  • @bishop 基本上是的,更新 2 之前的问题确实如此,但由于我刚刚添加了更新 3,因此还有更多因素需要注意。这……相当复杂
  • 即使您进行了更新,组合点似乎仍然正确。如果是这样,您能否制作sourceItemdestinationItem 以及与您的计数位字段相关的任何其他字段?这样,每个可能的组合都会先验地被枚举出来,您可以使用简单的WHERE?

标签: php mysql sql innodb


【解决方案1】:

将您的条件放入sum()。像这样你得到单次计数

SELECT SUM(sourceItem = 1 AND destinationItem = 1) AS count1,
       SUM(sourceItem = 1 AND destinationItem = 2) AS count2
FROM trades

要获得完整的条件计数

SELECT SUM(case when sourceItem > 0 and destinationItem > 0 then 2
                when sourceItem > 0 or destinationItem > 0 then 1
                else 0 
           end) AS complete_sum
FROM trades

SELECT SUM(sourceItem > 0) + sum(destinationItem > 0) AS complete_sum
FROM trades

【讨论】:

  • 不幸的是,sourceItem = 2 可以有destinationItem = 1,但不能有destinationItem = 3,所以我认为完整的查询不会起作用。
  • "这意味着 UNION ALL 中的每个 SELECT 可能存在也可能不存在。"
  • 要么不像你想象的那样工作,要么我没有让你正确。但我在查询中添加了另一个条件。
  • 我刚才对OP添加了更具体的解释。
  • 这个查询也需要1.2s,索引在(sourceItem, destinationItem),当然执行两次是0.0001s,但实际情况并非如此
【解决方案2】:

由于您总是按 sourceItemdestinationItem 进行过滤,因此在这两列上添加索引应该会大大加快查询速度。

ALTER TABLE trades 添加索引(sourceItemdestinationItem);

这样,计数只需要从索引中获取值,这将使您的计数更快。

【讨论】:

  • 在每一列上放置一个单独的索引,或者在两列上放置一个索引有区别吗?
  • 查询每个表实例只能使用一个索引。如果您使用不同的索引,则查询将只能使用其中一个,从而强制然后对另一个值进行搜索。如果您同时对它们进行索引,它将能够同时使用它们。
  • 即使如此,我的原始查询仍然需要 0.37 秒。
  • 我会说这是一个很大的改进。您可以对查询 (explain <your query>) 进行解释,以了解 mysql 计划如何运行它,它可以让您更好地了解需要改进的地方。
  • 我不认为还有什么需要改进的地方。我想在实时环境中使用这个查询,所以我需要想出更好的东西。 (ps我没有否决你的答案)
【解决方案3】:

这对你有用吗?此查询将为您提供相同的所需输出,并具有更好的性能。

SELECT COUNT(*) FROM 
(
SELECT t.sourceItem, t.destinationItem, COUNT(1) AS count
   FROM trades t
   WHERE t.sourceItem IN ( 1, 2 ) AND t.destinationItem IN ( 1, 2, 3 )
GROUP BY t.sourceItem, t.destinationItem 
) as tbl_tmp

您说您正在运行此查询只是为了查看是否需要显示“未找到结果”消息。如果这是您唯一的要求,那么我觉得您根本不需要查询。会有其他来源可以告诉您要展示的东西或不展示的东西!我在这里遗漏了什么吗?

【讨论】:

  • 需要 0.4 秒,所以也不行,不过我有点担心,我认为我过度简化了我的实际问题,因为它实际上更难计算,因为有可选的条件部分以及。我真的认为我需要将它存储在另一个表中,保存计数。
  • 您真的需要准确的计数吗?如果您只需要 0 或 1,我们可以进一步简化它。
猜你喜欢
  • 2011-03-20
  • 1970-01-01
  • 2016-09-12
  • 1970-01-01
  • 2012-08-14
  • 2011-05-14
  • 2015-04-23
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多