【问题标题】:MySQL is slow SELECT to find a single-digit numeric value on a large InnoDB tableMySQL 在大型 InnoDB 表上查找单个数字值的速度很慢
【发布时间】:2016-05-31 06:40:21
【问题描述】:

我有一个简单的键值存储表,有 85M 行,文件大小为 5GB。 (Wordpress 后元表。)

CREATE TABLE `wp_postmeta` (
  `meta_id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `post_id` bigint(20) unsigned NOT NULL DEFAULT '0',
  `meta_key` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
  `meta_value` longtext COLLATE utf8mb4_unicode_ci,
  PRIMARY KEY (`meta_id`),
  KEY `post_id` (`post_id`),
  KEY `meta_key` (`meta_key`(191)),
  KEY `meta_value` (`meta_value`(100)),
  KEY `meta_value_len_10` (`meta_value`(10)),
  KEY `meta_value_len_1` (`meta_value`(1))
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

当我运行以下查询时:

SELECT post_id
FROM wp_postmeta        
WHERE meta_key = "case"
AND meta_value = "359976";

查找速度很快,不到 50 毫秒。它按预期使用 meta_value 索引,并且可以将扫描范围缩小到 4 行(如 EXPLAIN 中所示)。当我减少 meta_value 的目标值的位数时,行扫描可能会增加到几百,这仍然是微不足道的。

问题案例——查询运行非常慢(10 秒以上)的唯一情况——是目标值同时是一个字符长度和一个数值;基本上,如果它只是 0 到 9。然后,查询会扫描 400,000 行。如果我改用一个单字符的字母,则查询很好,这让我感到困惑。

问题案例:

SELECT post_id
FROM wp_postmeta        
WHERE meta_key = "case"
AND meta_value = "5";

幕后的另一个区别是问题查询是唯一使用 meta_value_len_10 索引的类型。所有其他(包括个位数的 alpha)都使用原版 meta_value 索引。

即使我运行 FORCE INDEX(meta_key,meta_value_len_1) 来定位那个单个数字,也没有什么区别。我还尝试创建一个 4 个字符长的索引,但没有任何区别。

请注意,在任何情况下,EXPLAIN 始终显示“使用 where”作为查找方法。没有“使用文件排序”或任何指示磁盘 I/O 的内容(除了问题案例中的绝对行数)。

【问题讨论】:

  • 性能问题应该包括EXPLAIN ANALYZE和一些关于表大小、索引、当前时间性能、期望时间等的信息。Slow是一个相对术语,我们需要一个真实的值来比较。 MySQL
  • 谢谢,我添加了表大小和特定查询时间。我认为这是暗示,因为我在谈论正确的索引,我们都知道不想要的性能和想要的性能之间的速度差异可能是 5 到 10 倍。
  • 这是帮助用户改进问题的通用注释。对于您的情况,最重要的是您没有为您的测试提供 EXPLAIN 结果。
  • 键值模式糟糕的另一个原因。
  • @RickJames:这只是我们在使用 EAV 模型时体验到的那种快乐的一个小例子。

标签: mysql database indexing query-optimization


【解决方案1】:

为了获得更好的查询性能,请考虑添加索引(键),例如

... ON (`meta_key`(191), `meta_value`(10), `post_id`)

MySQL 只会使用一个索引进行查询。在 meta_keymeta_value 列上使用相等谓词(WHERE 子句中的相等比较),我们希望它们成为索引中的前导列。

我在索引中包含(相对较短的)post_id 列是为了使其成为查询的覆盖 索引。 (正如 RickJames 在他的评论中指出的那样,我相信他是对的,前缀长度阻止了索引被用作覆盖索引。)

定义此索引后,仅单例 meta_key(191) 上的索引将是多余的。

作为替代方案,您可以考虑交换索引中前两列的位置,具体取决于值的基数(特定值的选择性)。如果您在 @987654326 中有数万个不同的值@,但只有几十个 meta_key 值,那么也许:

 ... ON (`meta_value`(10), `meta_key`(191), `post_id`)

我们希望 EXPLAIN 输出显示“正在使用索引”,这意味着可以完全从索引中满足查询,而无需从基础表中查找页面。

我们希望 MySQL 能够利用该索引来满足谓词(meta_valuemeta_key 列的相等比较)。

使用原始查询和索引,MySQL 必须访问基础表中的页面以检查其他列的值。也就是说,如果它使用meta_value 索引,它仍然需要查找具有该行的页面,因此它可以检查meta_key 的值。

而且我认为 MySQL 没有任何合理的方法可以仅在前缀长度为 1 的情况下使用索引。'12''195/55R16' 的值,或任何以 1 为前导字符的字符串都会出现在索引中有相同的条目,这可能是索引中的大量重复值。 (所有“从 1 开始”的值都将具有相同的索引值),并且 MySQL 将不得不在表中查找以验证实际值实际上是“等于 1”,而不是其他一些值“以 1 开头”。

【讨论】:

  • 前缀使Using index 不可能——并非所有值都在索引中。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-08-04
  • 2012-03-14
  • 2020-01-15
  • 2016-06-08
  • 1970-01-01
相关资源
最近更新 更多