Thi*_*ark 5 mysql optimization
在下面的查询中,有重复的计算,例如对 的三个调用SUM(p.amount)
。MySQL 是否为每个函数调用重新计算,或者是否有某种内存优化?如果没有,如何优化这种查询以获得最大性能?
似乎在第一次计算后通过别名获得下一个计算会更快total_payemnts
,但这只会引发错误。
SELECT LEFT(c.last_name, 1) AS 'last_names',
SUM(p.amount) AS 'total_payments',
COUNT(p.rental_id) AS 'num_rentals',
SUM(p.amount) / COUNT(p.rental_id) AS 'avg_pay'
FROM customer c
JOIN payment p ON p.customer_id = c.customer_id
GROUP BY LEFT(c.last_name, 1)
ORDER BY SUM(p.amount) DESC;
Run Code Online (Sandbox Code Playgroud)
此查询在MySQL Sakila示例数据库上运行。
一个明确的答案确实很难。然而,一些试验可能会给我们一些提示。
我们从一些数据开始:
CREATE TABLE customers
(
customer_id integer PRIMARY KEY,
last_name varchar(50) NOT NULL
) ;
CREATE TABLE payments
(
rental_id integer not null,
customer_id integer not null, -- references customer(customer_id),
amount numeric(10,2) not null
) ;
INSERT INTO customers
VALUES
(1, 'A - Customer 1'),
(2, 'B - Customer 2'),
(3, 'C - Customer 3'),
(4, 'D - Customer 4') ;
INSERT INTO payments
VALUES
(1, 1, 900.0),
(2, 1, 800.0),
(3, 1, 500.0),
(4, 1, 900.0),
(5, 2, 500.0),
(6, 2, 500.0) ;
Run Code Online (Sandbox Code Playgroud)
我们首先定义一个DETERMINISTIC
(尽管它有副作用)函数来记录调用:
CREATE FUNCTION log_numeric(_n numeric(10,2), _counter character(2))
RETURNS numeric(10,2)
LANGUAGE SQL
DETERMINISTIC
BEGIN
UPDATE log SET n = n + 1 WHERE counter = _counter ;
RETURN _n ;
END ;
Run Code Online (Sandbox Code Playgroud)
我们之前定义了一个log
表,用于存储计数:
CREATE TABLE log
(
counter character(2) primary key,
n integer default 0
) ENGINE=MyISAM;
INSERT INTO log
VALUES
('x', 0),
('L', 0),
('T', 0),
('N', 0),
('L2', 0),
('T2', 0),
('N2', 0) ;
Run Code Online (Sandbox Code Playgroud)
此时,如果 MySQL 能够执行公共子表达式消除优化(对于编译器来说很常见),那么当我们发出以下命令时,它只会进行一次调用:
SELECT
log_numeric(30, 'x') AS c1,
log_numeric(30, 'x') AS c2 ;
Run Code Online (Sandbox Code Playgroud)
因为在两种情况下log_numeric(30, 'x')
都会返回相同的值(因为该函数是确定性的)。
事实上,它对该函数进行了两次调用,如观察计数器“x”所证明的那样;
SELECT
*
FROM
log
WHERE
counter = 'x' ;
Run Code Online (Sandbox Code Playgroud)
[注意:使用 MySQL 5.7.12 进行测试]
MySQL可能认为该函数有一些副作用,或者用户定义函数的工作方式与内置函数不同。内置函数可以优化,而 UDF 则不能。无论如何,如果 MySQL 只需一次调用就可以优化这两个表达式,那么很明显,这种优化是标准的。由于事实并非如此,我们可以做出有根据的猜测(这与证明不同)并假设函数被调用的次数与它们出现的次数一样多。log_numeric
对于发布的案例也可以这样做:
SELECT
log_char(LEFT(c.last_name, 1), 'L') AS last_names,
log_numeric(SUM(p.amount), 'T') AS total_payments,
log_numeric(COUNT(p.rental_id), 'N') AS num_rentals,
log_numeric(SUM(p.amount), 'T') / log_numeric(COUNT(p.rental_id), 'N') AS avg_pay
FROM
customers c
JOIN payments p ON p.customer_id = c.customer_id
GROUP BY
log_char(LEFT(c.last_name, 1), 'L')
ORDER BY
log_numeric(SUM(p.amount), 'T') DESC;
Run Code Online (Sandbox Code Playgroud)
结果表明,所有调用都会被评估多次。
您可以在http://rextester.com/ZKS91815检查所有这些测试。
注意:出于好奇,postgreSQL (9.6.1) 做得更好一点:它将 ORDER BY 和 GROUP BY 识别为 上的“已计算” SELECT
,但它没有针对公共子表达式消除进行优化。有改进的余地。
如果您担心计算量超出必要范围,请使用:
SELECT
last_names, total_payments, num_rentals,
total_payments / num_rentals AS avg_pay
FROM
(
SELECT
log_char(LEFT(c.last_name, 1), 'L') AS last_names,
log_numeric(SUM(p.amount), 'T') AS total_payments,
log_numeric(COUNT(p.rental_id), 'N') AS num_rentals
FROM
customers c
JOIN payments p ON p.customer_id = c.customer_id
GROUP BY
log_char(LEFT(c.last_name, 1), 'L')
) AS q0
ORDER BY
total_payments DESC ;
Run Code Online (Sandbox Code Playgroud)
老实说,我认为差异不会太大。整个查询所花费的时间中函数的权重可能很小。如果功能复杂的话可能会很重要。根据 MySQL 处理子查询(可能是临时表)的方式,这实际上可能会更糟。