我有一个自己运行的软件堆栈,但也部署到客户端。
有一个特定的查询在我的环境中运行得非常好,但在客户的环境中运行得非常糟糕。
我已确认使用EXPLAIN
我的环境的查询计划程序会发现有一个很棒的索引可用(并使用它)。而客户环境中的相同查询未在possible_keys
下提供该索引。
这是完整的查询,有点匿名:
SELECT t0.*,
t1.*,
t2.*,
t3.value
FROM table0 t0
LEFT OUTER JOIN table1 t1
ON t0.id = t1.table0_id
LEFT OUTER JOIN table2 t2
ON t1.id = t2.table1_id
AND t2.deleted = 0
LEFT OUTER JOIN table3 t3
ON t0.id = t3.table0_id
AND t3.type = 'whatever'
WHERE t0.business IN ('workcorp')
AND '2016-11-01 00:00:00' <= t0.date
AND t0.date < '2016-12-01 00:00:00'
ORDER BY t0.date DESC
我们的环境不同的阶段在JOIN
到table3
。所以从理论上讲,你可以忽略大量的查询,并将其想象如下:
SELECT t0.*
t3.value
FROM table0 t0
LEFT OUTER JOIN table3 t3
ON t0.id = t3.table0_id
AND t3.type = 'whatever'
我们的两个环境的查询计划都同意如何JOIN
到t1
和t2
。但他们在如何JOIN
到t3
的计划上有所不同。
我的环境正确识别JOIN
t3
的两个可能索引,并正确识别table0_id
是此查询的最佳选择:
+----+-------------+-------+------+--------------------------+-----------+---------+------+-------+----------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------+--------------------------+-----------+---------+------+-------+----------+-------------+
| 1 | SIMPLE | t3 | ref | table0_id,type_and_value | table0_id | 108 | func | 2 | 100.00 | Using where |
+----+-------------+-------+------+--------------------------+-----------+---------+------+-------+----------+-------------+
客户的环境不认为索引table0_id
是一个选项,而是回到type_and_value
(这是一个非常糟糕的选择):
+----+-------------+-------+------+----------------+----------------+---------+-------+----------------+----------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------+----------------+----------------+---------+-------+----------------+----------+-------------+
| 1 | SIMPLE | t3 | ref | type_and_value | type_and_value | 257 | const | (far too many) | 100.00 | Using where |
+----+-------------+-------+------+----------------+----------------+---------+-------+----------------+----------+-------------+
如果我们FORCE INDEX
会怎样?
EXPLAIN EXTENDED SELECT t0.*,
t1.*,
t2.*,
t3.value
FROM table0 t0
LEFT OUTER JOIN table1 t1
ON t0.id = t1.table0_id
LEFT OUTER JOIN table2 t2
ON t1.id = t2.table1_id
AND t2.deleted = 0
LEFT OUTER JOIN table3 t3 FORCE INDEX (table0_id)
ON t0.id = t3.table0_id
AND t3.type = 'whatever'
WHERE t0.business IN ('workcorp')
AND '2016-11-01 00:00:00' <= t0.date
AND t0.date < '2016-12-01 00:00:00'
ORDER BY t0.date DESC
在我的环境中,我得到了:
+----+-------------+-------+------+---------------+-----------+---------+------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+---------------+-----------+---------+------+------+-------------+
| 1 | SIMPLE | t3 | ref | table0_id | table0_id | 108 | func | 2 | Using where |
+----+-------------+-------+------+---------------+-----------+---------+------+------+-------------+
与我原来的查询计划(提出 2 possible_keys
)相比:这将选择范围缩小到只有一个。
但客户得到了不同的结果:
+----+-------------+-------+------+---------------+------+---------+-------+---------+----------+----------------------------------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+-------+------+---------------+------+---------+-------+---------+----------+----------------------------------------------------+
| 1 | SIMPLE | t3 | ALL | NULL | NULL | NULL | NULL | (loads) | 100.00 | Using where; Using join buffer (Block Nested Loop) |
+----+-------------+-------+------+---------------+------+---------+-------+---------+----------+----------------------------------------------------+
添加FORCE INDEX
会将客户的possible_keys
从一个不良选择缩小到无选项。
那么为什么客户的环境在possible_keys
中没有相同的索引呢?当然,我被怀疑“也许他们不拥有该索引”。所以我们做了SHOW INDEXES FROM table3
。这是我的环境(用于比较):
+--------+------------+-----------------+--------------+-----------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |
+--------+------------+-----------------+--------------+-----------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
| table3 | 0 | PRIMARY | 1 | id | A | 16696 | NULL | NULL | | BTREE | | |
| table3 | 1 | table0_id | 1 | table0_id | A | 16696 | NULL | NULL | | BTREE | | |
| table3 | 1 | type_and_value | 1 | type | A | 14 | NULL | NULL | | BTREE | | |
| table3 | 1 | type_and_value | 2 | value | A | 8348 | NULL | NULL | | BTREE | | |
+--------+------------+-----------------+--------------+-----------------+-----------+-------------+----------+--------+------+------------+---------+---------------+
他们的环境具有相同的索引,table0_id
可用:
+--------+------------+-----------------+--------------+-----------------+-----------+-------------------+----------+--------+------+------------+---------+---------------+
| Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment | Index_comment |
+--------+------------+-----------------+--------------+-----------------+-----------+-------------------+----------+--------+------+------------+---------+---------------+
| table3 | 1 | table0_id | 1 | table0_id | A | (same as PRIMARY) | NULL | NULL | | BTREE | | |
+--------+------------+-----------------+--------------+-----------------+-----------+-------------------+----------+--------+------+------------+---------+---------------+
我也小心翼翼地问“这是奴隶吗?主人是同一个人吗?”:他们向我保证所有实例都有这个索引,视需要而定。
所以我想“也许指数在某种程度上被打破了?”并建议他们依靠该索引执行最简单的查询:
EXPLAIN EXTENDED SELECT *
FROM table3
WHERE table0_id = 'whatever'
在这个案例中:他们的环境行为与我的相同(并且正确),建议使用索引table0_id
:
+----+-------------+--------+------+---------------+-----------+---------+-------+------+----------+-----------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+--------+------+---------------+-----------+---------+-------+------+----------+-----------------------+
| 1 | SIMPLE | table3 | ref | table0_id | table0_id | 108 | const | 1 | 100.00 | Using index condition |
+----+-------------+--------+------+---------------+-----------+---------+-------+------+----------+-----------------------+
所以他们肯定拥有那个索引。并且他们的查询计划器可以识别它有资格使用(至少对于某些查询)。
这里发生了什么?为什么table0_id
不适用于某些查询,但仅适用于客户的环境?可能是指数在某种程度上被打破了吗?或者查询规划器出错?
我是否可以做任何其他测试来弄清楚为什么它没有使用此查询的索引?
答案 0 :(得分:1)
原来它是charsets(和/或collations)。
我使用此查询来揭示每个环境中的字符集:
SELECT table_name, column_name, character_set_name FROM information_schema.`COLUMNS`
WHERE table_schema = "my_cool_database"
AND table_name IN ("t0", "t3")
ORDER BY 1 DESC, 2
对于奖励积分,我检查了每个环境中的角色整理:
SHOW FULL COLUMNS FROM t0;
SHOW FULL COLUMNS FROM t3;
在我的环境中:两个表中的所有列都有utf8
字符集和utf8_unicode_ci
归类。
在客户的环境中:t0
与我的环境完全匹配,但t3
是一种独特的雪花;它的列有latin1
字符集和latin1_swedish_ci
整理。
所以,我们看到的是t3.table0_id
(latin1
列)上存在的索引可以不用于JOIN
到{ {1}}表。因此该指数适用于:
utf8
然而索引可以不用于:
SELECT *
FROM table3
WHERE table0_id = 'whatever'
Percona blog,John Watson's blog和Baron Schwartz's blog上描述了类似的症状。