为什么 Mysql 不在嵌套查询中的主查询上使用索引

tar*_*rma 9 mysql index subquery mysql-5.5

我有两个表:search_criteriapricing

表中的列和表中的search_id列都有一个索引。search_criteriapricing_idpricing

但是运行这个嵌套查询不会在search_criteria表上使用索引。

explain 
select *  
from  search_criteria USE INDEX (idx_search_id) 
where  search_id in 
    (select search_id 
    from pricing
     where pricing_id = '009330be-d041-444f-a624-ca652f3f61ed');


+----+--------------------+---------------------+------+------------------------------+----------------+---------+-------+----------+-------------+
| id | select_type        | table               | type | possible_keys                | key            | key_len | ref   | rows     | Extra       |
+----+--------------------+---------------------+------+------------------------------+----------------+---------+-------+----------+-------------+
|  1 | PRIMARY            | search_criteria | ALL  | NULL                         | NULL           | NULL    | NULL  | 19582252 | Using where |
|  2 | DEPENDENT SUBQUERY | pricing      | ref  | idx_pricing_id,idx_search_id | idx_pricing_id | 36      | const |        1 | Using where |
+----+--------------------+---------------------+------+------------------------------+----------------+---------+-------+----------+-------------+
Run Code Online (Sandbox Code Playgroud)

如果我在没有嵌套查询的情况下使用此表,它将使用索引

explain extended select *  from  search_criteria where  search_id in ('36afabcc-e896-48b6-ad0f-c683845d4a4f')

+----+-------------+---------------------+------+---------------+---------------+---------+-------+------+----------+-------------+
| id | select_type | table               | type | possible_keys | key           | key_len | ref   | rows | filtered | Extra       |
+----+-------------+---------------------+------+---------------+---------------+---------+-------+------+----------+-------------+
|  1 | SIMPLE      | search_criteria | ref  | idx_search_id | idx_search_id | 103     | const |    1 |   100.00 | Using where |
Run Code Online (Sandbox Code Playgroud)

为什么会这样?

MYSQL 变量

+-------------------------+------------------------------+
| Variable_name           | Value                        |
+-------------------------+------------------------------+
| innodb_version          | 1.1.8                        |
| protocol_version        | 10                           |
| slave_type_conversions  |                              |
| version                 | 5.5.16-log                   |
| version_comment         | MySQL Community Server (GPL) |
| version_compile_machine | x86_64                       |
| version_compile_os      | linux2.6                     |
+-------------------------+------------------------------+
Run Code Online (Sandbox Code Playgroud)

Jynus 回复后,我修改了问题。

执行的查询:

explain 
  select s_c.*  
  from search_criteria s_c USE INDEX (idx_search_id) 
  JOIN pricing p USING (search_id)
  WHERE p.pricing_id = '009330be-d041-444f-a624-ca652f3f61ed';



    ----+-------------+-------+------+------------------------------+----------------+---------+-------+----------+--------------------------------+
| id | select_type | table | type | possible_keys                | key            | key_len | ref   | rows     | Extra                          |
+----+-------------+-------+------+------------------------------+----------------+---------+-------+----------+--------------------------------+
|  1 | SIMPLE      | p     | ref  | idx_pricing_id,idx_search_id | idx_pricing_id | 36      | const |        1 | Using where                    |
|  1 | SIMPLE      | s_c   | ALL  | NULL                         | NULL           | NULL    | NULL  | 19663904 | Using where; Using join buffer |
+----+-------------+-------+------+------------------------------+----------------+---------+-------+----------+--------------------------------+
Run Code Online (Sandbox Code Playgroud)

显示search_criteria的创建sql;

CREATE TABLE `search_criteria` (
 `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
 `search_id` varchar(100) DEFAULT NULL,
 `origin` varchar(45) NOT NULL,
 `dest` varchar(45) NOT NULL,
 `adults` int(1) NOT NULL,
 `children` int(1) NOT NULL,
 `infants` int(1) NOT NULL,
 `trip_type` varchar(1) NOT NULL,
 `flight_type` enum('INT','DOM') DEFAULT NULL,
 `depart_date` datetime DEFAULT NULL,
 `arrival_date` datetime DEFAULT NULL,
 `created_on` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
 PRIMARY KEY (`id`),
 KEY `idx_search_id` (`search_id`),
 KEY `idx_created` (`created_on`)
)ENGINE=InnoDB AUTO_INCREMENT=288339047 DEFAULT CHARSET=latin1 |


 CREATE TABLE `pricing` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `pricing_id` char(36) CHARACTER SET latin1 NOT NULL,
  `search_id` varchar(100) DEFAULT NULL,
  `created_on` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `supplier_codes` varchar(64) DEFAULT NULL,
  `price` double DEFAULT NULL,
   PRIMARY KEY (`id`),
   KEY `idx_pricing_id` (`pricing_id`),
   KEY `idx_created_on` (`created_on`),
   KEY `idx_search_id` (`search_id`),
) ENGINE=InnoDB AUTO_INCREMENT=9264209 DEFAULT CHARSET=utf8 |
Run Code Online (Sandbox Code Playgroud)

jyn*_*nus 7

您正在尝试执行semijoin。这是 MySQL 5.6 之前的 MySQL 优化器的一个众所周知的问题。MySQL 知道如何执行它的唯一方法是对左表执行全表扫描并每行执行一次内部查询,因此无法使用索引。

您有几种选择:

  • 迁移到 5.6(或 MariaDB 5.5):它将被正确优化(我认为在这种情况下,透明地转换为 JOIN)
  • 将查询重写为标量子查询(这只适用于您只希望收到一个值,即 Pricing_id 是唯一的):

    explain 
    select *  
    from search_criteria USE INDEX (idx_search_id) 
    where search_id = 
      (select search_id 
       from pricing
       where pricing_id = '009330be-d041-444f-a624-ca652f3f61ed');
    
    Run Code Online (Sandbox Code Playgroud)
  • 将查询重写为 JOIN。这仅适用于某些半连接(一次不会匹配多行的那些),但我认为这对你来说是这样的:

    explain 
    select s_c.*  
    from search_criteria s_c USE INDEX (idx_search_id) 
    JOIN pricing p USING (search_id)
    WHERE p.pricing_id = '009330be-d041-444f-a624-ca652f3f61ed';
    
    Run Code Online (Sandbox Code Playgroud)

顺便说一句,一旦您更改了查询,您就不需要USE INDEX.

  • @tarnisharma 查询实际上已经解决了`DEPENDENT SUBQUERY` 问题,请使用新解释的格式化输出和两个表的`SHOW CREATE TABLE` 编辑问题以进行进一步的工作。您可能只需要一个关于 `pricing (pricing_id, search_id)` 的索引和另一个关于 `search_criteria (search_id)` 的索引。您可以添加`STRAIGHT_JOIN`,但现在这似乎不是一个好主意。 (2认同)

ype*_*eᵀᴹ 6

search_id甚至不考虑索引的原因是因为两个表 - 因此两search_id列 - 具有不同的字符集。一个有CHARSET = latin1,另一个CHARSET = utf8。类型相同varchar(100)但字符集不同,这很重要。用于连接或比较的列应该具有相同的数据类型和字符集(和排序规则),否则索引将变得无用。

您应该更改两个表之一的字符集或仅更改两列之一的字符集,以便字符集匹配。

正如@jynus 已经建议的那样,复合索引(pricing_id, search_id)也应该提高查询的计划和效率。