使用IN查询的Mysql更新强制索引

时间:2018-01-07 22:27:53

标签: mysql indexing sql-update

问题:当IN (...)中包含超过396个项目时,Mysql未在更新查询中使用索引。甚至我强迫引擎使用主键。

表格' n:

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

1 个答案:

答案 0 :(得分:0)

如果MySQL优化器认为使用索引会比简单扫描表格更加昂贵,那么它就不会使用索引。

类比将是书后面的索引。为什么不包括像“the”这样的常用词?因为它最终会告诉你这本常用词出现在书的每一页上。这不是帮助您找到正确页面的有效方法。

MySQL做了类似的事情,如果它认为你要搜索的值发生在表的一个足够大的子集上,它会跳过索引。这没有记录的阈值,但根据我的经验,当优化器估计您的条件匹配表的20%以上时会发生这种情况。

FORCE INDEX优化器提示旨在解决此问题,前提是您知道使用索引优于表扫描,尽管优化程序估计了。但也许这只能避免表扫描,而不是索引扫描。

另一种可能性:MySQL不能很好地优化像(a,b) IN ((val, val)...)这样的元组比较谓词。这是一个new feature in MySQL 5.7,看起来它仍然不如简单的谓词那么好。

以下是我使用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是更有利的优化。并且没有临时表。