第八章 优化(二十一)—— 窗口函数优化
8.2 优化SQL语句
8.2.1 优化 SELECT 语句
8.2.1.21 窗口函数优化
【译者:本节涉及到SQL高级函数——窗口函数,这里做简单的介绍,这些内容是搜索而来,不是官方文档上的:
窗口函数,也叫OLAP函数(Online Anallytical Processing,联机分析处理),可以对数据库数据进行实时分析处理。
窗口函数的基本语法如下:
<窗口函数()> over (partition by <用于分组的列名>
order by <用于排序的列名>) as 别名
窗口函数有以下两类函数:
专用窗口函数,包括
rank
,dense_rank
,row_number
等函数。例如:select *, rank() over (partition by 班级 order by 成绩 desc) as ranking from 班级表
结果如下:(rank函数用于排名)
聚合函数,如
sum. avg, count, max, min
等因为窗口函数是对
WHERE
或者GROUP BY
子句处理后的结果集进行操作,所以窗口函数原则上只能写在SELECT
子句中。为什么叫“窗口”函数呢?这是因为
partition by
分区后的结果称为“窗口”,这里的窗口不是我们家里的门窗,而是表示“区域”的意思。
】
窗口函数(Window functions)会影响优化器考虑的策略:
如果子查询中使用了窗口函数,则派生于子查询的表合并将被禁止。因为子查询总是物化的(materialized)。
半连接不适用于窗口函数优化,因为半连接适用于
WHERE
和JOIN ... ON
中的子查询,而它不能包含窗口函数。优化器按顺序处理具有相同排序要求的多个窗口,因此第一个窗口之后的窗口的排序可以跳过。
优化器不尝试合并可以在单个步骤中计算的窗口(例如,当多个
OVER
子句包含相同的窗口定义时)。解决方法是在window
子句中定义窗口,并在OVER
子句中引用该窗口名称。
没有作为窗口函数使用的聚合函数将在最外层可能的查询中要求聚合。例如,在以下查询中,MySQL发现,因为COUNT(t1.b)
放在WHERE
子句中,因此它不能存在于外部查询中:
SELECT * FROM t1
WHERE t1.a = (SELECT COUNT(t1.b) FROM t2);
因此,MySQL在子查询中进行聚合,将t1.b
视为常量并返回t2
表的行数。
【译者:进行测试:
CREATE TABLE t1(
a INT,
b INT
);
CREATE TABLE t2(
a INT
);
INSERT t1(a, b) VALUES(1, 2),(3, 4),(4, 5),(5, 6);
INSERT t2(a) VALUES(1), (2), (3), (4);
SELECT * FROM t1
WHERE t1.a = (SELECT COUNT(t1.b) FROM t2); # 子查询的结果是4,是t2表的行数!与t1.b没有关系。
+-----+-----+
| a | b |
+-----+-----+
| 4 | 5 |
+-----+-----+
】
如果将WHERE
替换为HAVING
将导致一个错误:
mysql> SELECT * FROM t1
HAVING t1.a = (SELECT COUNT(t1.b) FROM t2);
ERROR 1140 (42000): In aggregated query without GROUP BY, expression #1
of SELECT list contains nonaggregated column 'test.t1.a'; this is
incompatible with sql_mode=only_full_group_by
# 译者对以上进行翻译:
# 错误 1140 (42000):在没有分组的聚合查询中,选择列表中包含的第一个表达式是非聚合列'test.t1.a';
# 这是与sql_mode=only_full_group_by(sql模式=只能完全分组)不兼容。
发生错误的原因是:HAVING
子句中可以存在COUNT(t1.b)
,所以要求外部查询也要使用聚合。
窗口函数(包括用作窗口函数的聚合函数)没有前面的那么复杂。它们总是在书写它们的子查询中进行聚合,永远不会在外部查询中进行聚合。
窗口函数的计算可能会受到windowing_use_high_precision
(窗口使用高精度)系统变量的影响,该变量决定是否在不损失精度的情况下进行窗口计算操作。默认情况下,该变量是启用的。
对于某些移动帧的聚合,可以应用逆向聚合函数从聚合中移除值。这可以提高性能,但可能会降低精度。例如,将一个非常小的浮点值添加到一个非常大的值上会导致这个非常小的值被大的值“隐藏”。当以后反转该大值时,这个小值的影响就消失了。
由于逆向聚合而导致的精度损失,仅仅是对浮点(近似值)数据类型进行操作的一个因素。对于其他类型,逆向聚合是安全的,包括有小数部分的 DECIMAL
类型,它是一种精确的值类型。
为了更快地执行,在安全的情况下,MySQL总是使用逆向聚合:
对于浮点值,逆向聚合并不总是安全的,可能会导致精度损失。默认是避免逆向聚合的,这样执行速度较慢,但保留了精度。如果允许为速度而牺牲安全性,则可以禁用
windowing_use_high_precision
以允许逆向聚合。对于非浮点数据类型,逆向聚合始终是安全的,并且无论
windowing use high precision
值是多少都可以使用。windowing_use_high_precision
对MIN()
和MAX()
没有影响,因为它们在任何情况下都不会使用逆向聚合。
对于方差函数STDDEV_POP()、STDDEV_SAMP()、VAR_POP()、VAR_SAMP()
及其同义词的计算,可以在优化模式或默认模式下进行计算。而优化模式可能在最后的有效数字中产生略微不同的结果。如果允许这样的差异,可以禁用windowing_use_high_precision
,以允许优化模式。
对于EXPLAIN,窗口执行计划信息过于宽泛,无法以传统输出格式显示。要查看窗口信息,请使用EXPLAIN FORMAT=JSON
并查找窗口元素。