第 69.1 节:不要隐藏列在函数中
一个常见错误是将索引列隐藏在函数调用内部。例如,以下查询无法从索引中受益:
WHERE DATE(dt) = '2000-01-01'
相反,给定 INDEX(dt),以下查询可能使用索引:
WHERE dt = '2000-01-01' -- 如果 'dt' 是 DATE 类型
以下方式适用于 DATE、DATETIME、TIMESTAMP,甚至 DATETIME(6)(微秒):
WHERE dt >= '2000-01-01' AND dt < '2000-01-01' + INTERVAL 1 DAY
第 69.2 节:OR
一般来说,OR 会破坏优化。
WHERE a = 12 OR b = 78
不能使用 INDEX(a,b),并且可能使用或不使用 INDEX(a) 和 INDEX(b) 通过“索引合并”。“索引合并”比什么都没有好,但也仅此而已。
WHERE x = 3 OR x = 5
可以转换为:
WHERE x IN (3, 5)
这可能会使用包含 x 的索引。
第 69.3 节:添加正确的索引
这是一个庞大的主题,但也是最重要的“性能”问题。
新手需要学习的主要教训是“复合”索引。快速示例:
INDEX(last_name, first_name)
对以下查询非常有效:
WHERE last_name = '...'
WHERE first_name = '...' AND last_name = '...' -- WHERE 中的顺序无关紧要
但对以下查询无效:
WHERE first_name = '...' -- 顺序很重要
WHERE last_name = '...' OR first_name = '...' -- "OR" 是杀手
第 69.4 节:使用索引
对于任何非极小的表,加快查询速度的最重要的事情是拥有合适的索引。
WHERE a = 12 --> INDEX(a)
WHERE a > 12 --> INDEX(a)
WHERE a = 12 AND b > 78 --> INDEX(a,b) 比 INDEX(b,a) 更有用
WHERE a > 12 AND b > 78 --> INDEX(a) 或 INDEX(b);无法同时处理两个范围
ORDER BY x --> INDEX(x)
ORDER BY x, y --> INDEX(x,y) 按此顺序
ORDER BY x DESC, y ASC --> 没有索引有帮助,因为混合了 ASC 和 DESC
第 69.5 节:子查询
子查询有多种类型,优化潜力不同。首先,子查询可以是“相关”的或“非相关”的。相关意味着它们依赖于子查询外部的某个值。这通常意味着子查询必须为每个外部值重新求值。
以下相关子查询通常相当不错。注意:它最多只能返回 1 个值。通常作为 LEFT JOIN 的替代方案,尽管不一定更快。
SELECT a, b, (SELECT ... FROM t WHERE t.x = u.x) AS c FROM u
SELECT a, b, (SELECT MAX(x) ... ) AS c FROM u
SELECT a, b, (SELECT x FROM t ORDER BY ... LIMIT 1) AS c FROM u
以下通常是非相关的:
SELECT ... FROM (SELECT ... ) AS a JOIN b ON ...
关于 FROM 中子查询的注意事项:
- 如果它返回 1 行,很好。
- 一个好的范式(同样是“1 行”)是让子查询为
(SELECT @n := 0),从而初始化一个@变量供查询其余部分使用。 - 如果它返回多行且
JOIN的另一个子查询也返回多行,那么效率可能非常糟糕。在 5.6 之前,没有索引,因此变成了CROSS JOIN;5.6+ 会推断临时表的最佳索引并生成它,只在查询完成后丢弃。
第 69.6 节:JOIN + GROUP BY
导致低效查询的常见问题如下所示:
SELECT ... FROM a JOIN b ON ... WHERE ... GROUP BY a.id
首先,JOIN 扩展了行数;然后 GROUP BY 将行数缩减回 a 的行数。
对于这种“膨胀-收缩”问题,可能没有好的选择。一种可能的选项是将 JOIN 转换为 SELECT 中的相关子查询。这也消除了 GROUP BY。
第 69.7 节:正确设置缓存
innodb_buffer_pool_size 应约为可用 RAM 的 70%。
第 69.8 节:反面教训
以下内容不太可能有助于性能。它们源于过时的信息和/或天真。
- InnoDB 已经改进到 MyISAM 不太可能更好的程度。
PARTITIONING很少能带来性能提升,甚至可能损害性能。- 将
query_cache_size设置为大于 100M 通常会影响性能。 - 在
my.cnf中增加许多值可能导致“交换”,这是一个严重的性能问题。 - “前缀索引”(如
INDEX(foo(20)))通常无用。 OPTIMIZE TABLE几乎总是无用的(并且会锁定表)。