mysql在查询中更新强制索引

ct3nt3jp  于 2021-06-21  发布在  Mysql
关注(0)|答案(1)|浏览(454)

问题:当 IN (...) 里面有超过396件物品。我甚至强迫引擎使用主键。
表定义:

CREATE TABLE `store_product` (
  `store_id` bigint(20) NOT NULL,
  `product_id` bigint(20) NOT NULL,
  `created_at` timestamp NOT NULL,
  `last_updated_at` timestamp NOT NULL,
  `status` varchar(255) NOT NULL,
  `active_promotion_id` bigint(20) DEFAULT NULL,
  PRIMARY KEY (`store_id`,`product_id`),
  KEY `store_product_status_index` (`status`),
  KEY `store_product_active_promotion_id_index` (`active_promotion_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4

查询:

UPDATE store_product FORCE INDEX (PRIMARY) SET active_promotion_id = NULL,  WHERE (store_id, product_id) IN ((x1, y1), ..., (xn, yn))

其中n>396(总共59m行)。
这个神奇的数字是什么意思?为什么mysql不听我的,甚至不使用 primary key 更新时?
版本:mysql-5.7.17.r1

vvppvyoh

vvppvyoh1#

如果mysql优化器认为使用索引比简单地硬扫描表更昂贵,那么它不会使用索引。
类似于书后面的索引。为什么不包括像“the”这样的常用词呢?因为它最终会告诉你,这个普通的词出现在书的每一页上。这不是帮助你找到正确页面的有效方法。
mysql也做了类似的事情,如果它认为您要搜索的值出现在表的足够大的子集上,它就会跳过索引。这方面没有文档化的阈值,但根据我的经验,当优化器估计您的条件匹配表的20%以上时,就会发生这种情况。
这个 FORCE INDEX 优化器提示旨在解决这个问题,当您知道使用索引比表扫描更好时,不管优化器如何估计。但也许这样做只是为了避免表扫描,而不是索引扫描。
另一种可能性是:mysql不优化元组比较 predicate ,比如 (a,b) IN ((val, val)...) 很好。这是MySQL5.7中的一个新特性,看起来它仍然不能像更简单的 predicate 那样工作。
下面是我用explain测试查询时得到的优化计划,其中只有两个值对 IN() 条款。

+----+-------------+---------------+-------+---------------+---------+---------+------+------+------------------------------+
| id | select_type | table         | type  | possible_keys | key     | key_len | ref  | rows | Extra                        |
+----+-------------+---------------+-------+---------------+---------+---------+------+------+------------------------------+
|  1 | SIMPLE      | store_product | index | NULL          | PRIMARY | 16      | NULL |    1 | Using where; Using temporary |
+----+-------------+---------------+-------+---------------+---------+---------+------+------+------------------------------+

这个 type: index 不是个好兆头。这意味着它要扫描整个主键索引。这几乎和表格扫描一样糟糕。
这个 Using temporary 这也是一项代价高昂的行动。我猜这是在创建一个临时表来存储 IN() 谓语。
下面是一个等价的查询,它可以在不使用元组比较的情况下获得更好的优化计划:

EXPLAIN UPDATE store_product SET active_promotion_id = NULL
WHERE (store_id=1 AND product_id=1) OR (store_id=2 AND product_id=2)

+----+-------------+---------------+-------+---------------+---------+---------+-------------+------+-------------+
| id | select_type | table         | type  | possible_keys | key     | key_len | ref         | rows | Extra       |
+----+-------------+---------------+-------+---------------+---------+---------+-------------+------+-------------+
|  1 | SIMPLE      | store_product | range | PRIMARY       | PRIMARY | 16      | const,const |    1 | Using where |
+----+-------------+---------------+-------+---------------+---------+---------+-------------+------+-------------+

这个 type: range 是一个更有利的优化。没有临时工作台。

相关问题