mysql - 为什么 MySQL 不在 WHERE IF 子句中使用索引?

标签 mysql indexing where-clause

因为这个设置:

mysql> show global variables like '%indexes';
+-------------------------------+-------+
| Variable_name                 | Value |
+-------------------------------+-------+
| log_queries_not_using_indexes | ON    | 
+-------------------------------+-------+

慢查询日志不断接收:

# Time: 120607 16:58:30
# User@Host: xbtit[xbtit] @  [123.30.53.244]
# Query_time: 0  Lock_time: 0  Rows_sent: 1  Rows_examined: 16006
SELECT * FROM xbtit_files WHERE IF(soha_id is null OR soha_id = '', info_hash, soha_id)='6d63dd4ab199190b531752067414d4d6e6568f90';

试图解释这个查询:

mysql> EXPLAIN SELECT * FROM xbtit_files WHERE IF(soha_id is null OR soha_id = '', info_hash, soha_id)='6d63dd4ab199190b531752067414d4d6e6568f90';
+----+-------------+-------------+------+---------------+------+---------+------+-------+-------------+
| id | select_type | table       | type | possible_keys | key  | key_len | ref  | rows  | Extra       |
+----+-------------+-------------+------+---------------+------+---------+------+-------+-------------+
|  1 | SIMPLE      | xbtit_files | ALL  | NULL          | NULL | NULL    | NULL | 16006 | Using where | 
+----+-------------+-------------+------+---------------+------+---------+------+-------+-------------+

令我惊讶的是为什么 MySQL 不使用索引:

mysql> show index from xbtit_files;
+-------------+------------+-----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+
| Table       | Non_unique | Key_name  | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment |
+-------------+------------+-----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+
| xbtit_files |          0 | PRIMARY   |            1 | info_hash   | A         |       16006 |     NULL | NULL   |      | BTREE      |         | 
| xbtit_files |          1 | filename  |            1 | filename    | A         |       16006 |     NULL | NULL   | YES  | BTREE      |         | 
| xbtit_files |          1 | category  |            1 | category    | A         |           1 |     NULL | NULL   |      | BTREE      |         | 
| xbtit_files |          1 | uploader  |            1 | uploader    | A         |          16 |     NULL | NULL   |      | BTREE      |         | 
| xbtit_files |          1 | bin_hash  |            1 | bin_hash    | A         |       16006 |       20 | NULL   |      | BTREE      |         | 
| xbtit_files |          1 | ix_sohaid |            1 | soha_id     | A         |       16006 |     NULL | NULL   | YES  | BTREE      |         | 
+-------------+------------+-----------+--------------+-------------+-----------+-------------+----------+--------+------+------------+---------+

FORCE INDEX 也不起作用:

mysql> EXPLAIN SELECT * FROM xbtit_files force index (PRIMARY) WHERE IF(soha_id is null OR soha_id = '', info_hash, soha_id)='6d63dd4ab199190b531752067414d4d6e6568f90';
+----+-------------+-------------+------+---------------+------+---------+------+-------+-------------+
| id | select_type | table       | type | possible_keys | key  | key_len | ref  | rows  | Extra       |
+----+-------------+-------------+------+---------------+------+---------+------+-------+-------------+
|  1 | SIMPLE      | xbtit_files | ALL  | NULL          | NULL | NULL    | NULL | 16006 | Using where | 
+----+-------------+-------------+------+---------------+------+---------+------+-------+-------------+

我必须将此查询拆分为 2 个操作吗?

最佳答案

MySQL 中,您不能在表达式上创建索引,并且优化器不够智能,无法将您的查询拆分为两个索引。

使用这个:

SELECT  *
FROM    xbtit_files 
WHERE   soha_id = '6d63dd4ab199190b531752067414d4d6e6568f90'
UNION ALL
SELECT  *
FROM    xbtit_files 
WHERE   soha_id = ''
        AND info_hash = '6d63dd4ab199190b531752067414d4d6e6568f90'
UNION ALL
SELECT  *
FROM    xbtit_files 
WHERE   soha_id IS NULL
        AND info_hash = '6d63dd4ab199190b531752067414d4d6e6568f90'

每个查询都使用自己的索引。

您可以将它组合成一个查询:

SELECT  *
FROM    xbtit_files 
WHERE   (
        soha_id = '6d63dd4ab199190b531752067414d4d6e6568f90'
        OR
        (soha_id = '' AND info_hash = '6d63dd4ab199190b531752067414d4d6e6568f90')
        OR
        (soha_id IS NULL AND info_hash = '6d63dd4ab199190b531752067414d4d6e6568f90')
        )

并在 (soha_id, info_hash) 上创建一个复合索引以使其快速运行。

MySQL 还可以使用 index_merge 将来自两个索引的结果合并在一起,因此您有可能在第二个查询的计划中看到这一点,即使您不创建复合索引。

关于mysql - 为什么 MySQL 不在 WHERE IF 子句中使用索引?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/10929808/

相关文章:

sql - 左连接和有条件的地方没有给出预期的结果?

mysql - 带有索引列的 WHERE 子句中的 "CASE WHEN"

MySQL 更改表中的所有列

mysql - 在 VB6 中连接到 MySQL ODBC 时使用 ADODB.Recordset.Index

php - Symfony3 queryBuilder 如何使用 OR 进行搜索以及如何搜索子字符串?

PHP - 类似银行的每月交易

sql - 需要在 AND 语句中索引列吗?

python - Pandas 数据框 : simplest way to find arow and select an element from that row

mysql - 连接表上的索引是否用于多对多关系?

mysql - 在 UPDATE 中使用 WHERE 和 SET