MySql 没有正确优化查询

Ega*_*ian 3 mysql

我有一个表结构如下:

CREATE TABLE `sale_product_inventories` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `sale_id` int(11) NOT NULL,
  `product_id` int(11) NOT NULL,
  `size` varchar(50) COLLATE utf8_unicode_ci DEFAULT NULL,
  `tier_number` int(11) NOT NULL DEFAULT '1',
  `sale_product_pool_id` int(11) DEFAULT NULL,
  `inventory` int(11) NOT NULL,
  `in_cart_units` int(11) DEFAULT '0',
  `size_display_order` tinyint(4) NOT NULL DEFAULT '0',
  `last_updated_by` int(11) DEFAULT '0',
  `created_by` int(11) DEFAULT '0',
  `status` enum('active','inactive') COLLATE utf8_unicode_ci NOT NULL DEFAULT 'active',
  `created_at` datetime DEFAULT NULL,
  `updated_at` datetime DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `UNIQUE` (`sale_id`,`product_id`,`tier_number`,`size`,`sale_product_pool_id`)
) ENGINE=InnoDB AUTO_INCREMENT=92872 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
Run Code Online (Sandbox Code Playgroud)

注意:我有一个索引 UNIQUE = sale_id, product_id, tier_number, size,sale_product_pool_id

当我运行此查询时:

select * from sale_product_inventories 
where 
sale_id in (502,504)  and 
(sale_id, product_id) in ((502,2),(502,1), (502,3),(502,4) ,(504,2) ,(504,3) )
Run Code Online (Sandbox Code Playgroud)

上面查询的查询计划 MySql 使用索引 Unique 执行时间为 0.7 毫秒

当我运行此查询时

select * from sale_product_inventories 
where 
(sale_id, product_id) in ((502,2),(502,1), (502,3),(502,4) ,(504,2) ,(504,3) )
Run Code Online (Sandbox Code Playgroud)

第二个查询的查询计划

MySql 不使用 UNIQUE 索引,执行时间为 76 毫秒。

Mysql:5.5.27 InnoDB 版本:1.1.8

我的问题是为什么 mysql 会以这种方式运行。有人可以帮我解决这个问题吗?

编辑:
我遇到了这个,所以认为添加 MySQL 可能有用,除非列在查询中被隔离,否则通常不能在列上使用索引。“隔离”列意味着它不应该是表达式的一部分或在查询中的函数内。

Mic*_*bot 6

MySQL 优化器无法优化以下格式的表达式:

WHERE (col_1,col_2) IN ((a,b),(c,d),(e,f))
Run Code Online (Sandbox Code Playgroud)

这不是让索引正确的问题——它似乎只是没有实现。

优化器不明白这等价于...

WHERE (col_1,col_2) IN ((a,b)) 
   OR (col_1,col_2) IN ((c,d)) 
   OR (col_1,col_2) IN ((e,f)) 
Run Code Online (Sandbox Code Playgroud)

... 或者 ...

WHERE (col_1 = a AND col_2 = b)
   OR (col_1 = c AND col_2 = d)
   OR (col_1 = e AND col_2 = f)
Run Code Online (Sandbox Code Playgroud)

有一个Bug #35819,我最初是在这篇文章中发现的,在这篇文章的评论中也提到了

不幸的是,直到我已经在 MySQL 5.6 中打开了新的优化器跟踪并通过它运行了一些测试用例,我才找到了这些。如果 5.6 不能处理它,那么以前的版本也不能处理它,这似乎是一个安全的赌注。

事实证明,MySQL 5.6 确实无法处理它。“set in set of set”结构似乎根本就不是优化器所捕捉到的东西。所以在这种情况下,优化器选择全表扫描而不是其他计划并不是问题——优化器实际上得出结论,甚至没有任何其他可能的计划需要考虑。

这仅适用于IN. 对于单个表达式,优化器执行其操作并意识到这等效于col_1 = a AND col_2 = b

WHERE (col_1,col_2) IN ((a,b))    # is optimized correctly
WHERE (col_1,col_2) IN (ROW(a,b)) # is an equivalent expression in MySQL
Run Code Online (Sandbox Code Playgroud)

有趣的是,您的原件EXPLAIN表明唯一索引的使用方式与您可能认为的使用方式完全不同,无论如何。它仅用于查找具有所需 sale_id 的行......而不是两个值。

您会注意到原始文件EXPLAIN中的key_len显示为 4,这意味着将只检查索引最左边的 4 个字节——sale_id,一个 4 字节INT将是该索引中最左边的 4 个字节。这Using where意味着优化器意识到可能需要对范围扫描返回的行进行额外过滤,以消除任何不满足WHERE子句其余部分的行——所有具有 sale_id 502 和 504 的行都将通过索引检索,而不管它们的 product_id 值,然后结果行随后将被过滤以满足WHERE.

最佳路径可能是在 where 子句中坚持使用 (expr and expr) or (expr and expr) or (expr and expr) 。它在逻辑上是等价的,优化器理解它。

附加说明,关于您的一些评论......根据我上面讨论的内容,索引提示将无济于事,因为优化器似乎不知道您使用的表达式与它可以处理的其他表达式的等效性。 .. 但作为参考点,它在语法上无效的原因是您必须使用索引的名称,而不是索引中的列列表。您已将唯一索引称为“UNIQUE”,因此将其用作索引提示的方式将采用以下格式:

USE INDEX(`UNIQUE`)
Run Code Online (Sandbox Code Playgroud)