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

时间:2012-06-07 10:14:48

标签: 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个操作吗?

5 个答案:

答案 0 :(得分:3)

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将两个索引的结果合并在一起,所以即使你没有创建一个,你也有可能在第二个查询的计划中看到这个综合指数。

答案 1 :(得分:2)

因为功能是黑盒子:http://use-the-index-luke.com/sql/where-clause/functions/case-insensitive-search

修改 - 给了你太少的背景,抱歉。

相关部分是:

It is a trap we all fall into. We instantly recognize the relation between 
LAST_NAME and UPPER(LAST_NAME) and expect the database to “see” it as well.
In fact, the optimizer’s picture is more like that:

SELECT first_name, last_name, phone_number
  FROM employees
 WHERE BLACKBOX(...) = 'WINAND';

The UPPER function is just a black box. The parameters to the function are
not relevant because there is no general relationship between the function’s
parameters and the result.

这适用于所有功能:UPPER,IF,无论......

MySQL已被删除,因为该问题的解决方案(在页面下方进一步描述)不适用于MySQL。

答案 2 :(得分:2)

您可以阅读this以了解OR运算符在索引数据库时不适用的原因。

答案 3 :(得分:1)

使用函数可能会降低性能(LEFT函数除外)。尝试此查询

SELECT * FROM xbtit_files WHERE 
((soha_id is null OR soha_id = '') AND (info_hash='6d63dd4ab199190b531752067414d4d6e6568f90')) OR
( (soha_id='6d63dd4ab199190b531752067414d4d6e6568f90'))

答案 4 :(得分:0)

主键基于torrent的哈希值,但您可以添加字段ID并使用主键定义它 像这样:

ALTER TABLE `xbtit_files` DROP PRIMARY KEY;
ALTER TABLE `xbtit_files` ADD `id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY FIRST;
ALTER TABLE `xbtit_files` ADD UNIQUE (`info_hash`);

不要忘记将字段info_hash放到UNIQUE