mysql - 允许在 HAVING 子句中使用别名的性能影响

标签 mysql sql sql-server having

我今天早些时候在 this question 上出丑了。 .问题是使用 SQL Server,正确答案涉及添加 HAVING 子句。我最初犯的错误是认为 SELECT 语句中的别名可以用在 HAVING 子句中,这在 SQL Server 中是不允许的。我犯了这个错误,因为我假设 SQL Server 具有与 MySQL 相同的规则,它允许在 HAVING 子句中使用别名。

这让我很好奇,我在 Stack Overflow 和其他地方四处寻找,找到了一堆 Material 来解释为什么这些规则在两个各自的 RDBMS 上强制执行。但是我在任何地方都找不到关于允许/禁止 HAVING 子句中的别名对性能 影响的解释。

举一个具体的例子,我将复制上述问题中发生的查询:

SELECT students.camID, campus.camName, COUNT(students.stuID) as studentCount
FROM students
JOIN campus
    ON campus.camID = students.camID
GROUP BY students.camID, campus.camName
HAVING COUNT(students.stuID) > 3
ORDER BY studentCount

HAVING 子句中使用别名而不是重新指定 COUNT 会对性能产生什么影响?这个问题可以在 MySQL 中直接回答,希望有人能深入了解如果 SQL 支持 HAVING 子句中的别名会发生什么。

在这种情况下,可能可以同时使用 MySQL 和 SQL Server 标记 SQL 问题的情况很少见,所以享受这阳光下的时刻吧。

最佳答案

狭隘地关注特定查询,并在下面加载示例数据。这确实解决了其他一些查询,例如其他人提到的 count(distinct ...)

HAVING 中的别名 似乎略优于或相当优于其替代方案(取决于查询)。

这使用了一个预先存在的表,其中有大约 500 万行通过此 answer 快速创建我的需要 3 到 5 分钟。

结果结构:

CREATE TABLE `ratings` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `thing` int(11) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=5046214 DEFAULT CHARSET=utf8;

而是使用 INNODB。由于范围预留插入,创建预期的 INNODB 间隙异常。只是说,但没有区别。 470 万行。

修改表格以接近 Tim 的假定架构。

rename table ratings to students; -- not exactly instanteous (a COPY)
alter table students add column camId int; -- get it near Tim's schema
-- don't add the `camId` index yet

以下内容需要一段时间。以 block 的形式一次又一次地运行它,否则您的连接可能会超时。超时是由于更新语句中没有 LIMIT 子句的 500 万行。请注意,我们确实有一个 LIMIT 子句。

因此,我们要进行 50 万行迭代。将列设置为 1到20之间的随机数

update students set camId=floor(rand()*20+1) where camId is null limit 500000; -- well that took a while (no surprise)

继续运行上面的代码,直到没有camId为null。

我跑了 10 次(整个过程需要 7 到 10 分钟)

select camId,count(*) from students
group by camId order by 1 ;

1   235641
2   236060
3   236249
4   235736
5   236333
6   235540
7   235870
8   236815
9   235950
10  235594
11  236504
12  236483
13  235656
14  236264
15  236050
16  236176
17  236097
18  235239
19  235556
20  234779

select count(*) from students;
-- 4.7 Million rows

创建一个有用的索引(当然是在插入之后)。

create index `ix_stu_cam` on students(camId); -- takes 45 seconds

ANALYZE TABLE students; -- update the stats: http://dev.mysql.com/doc/refman/5.7/en/analyze-table.html
-- the above is fine, takes 1 second

创建校园表。

create table campus
(   camID int auto_increment primary key,
    camName varchar(100) not null
);
insert campus(camName) values
('one'),('2'),('3'),('4'),('5'),
('6'),('7'),('8'),('9'),('ten'),
('etc'),('etc'),('etc'),('etc'),('etc'),
('etc'),('etc'),('etc'),('etc'),('twenty');
-- ok 20 of them

运行两个查询:

SELECT students.camID, campus.camName, COUNT(students.id) as studentCount 
FROM students 
JOIN campus 
    ON campus.camID = students.camID 
GROUP BY students.camID, campus.camName 
HAVING COUNT(students.id) > 3 
ORDER BY studentCount; 
-- run it many many times, back to back, 5.50 seconds, 20 rows of output

SELECT students.camID, campus.camName, COUNT(students.id) as studentCount 
FROM students 
JOIN campus 
    ON campus.camID = students.camID 
GROUP BY students.camID, campus.camName 
HAVING studentCount > 3 
ORDER BY studentCount; 
-- run it many many times, back to back, 5.50 seconds, 20 rows of output

所以时间是相同的。各跑十几次。

EXPLAIN两者的输出相同

+----+-------------+----------+------+---------------+------------+---------+----------------------+--------+---------------------------------+
| id | select_type | table    | type | possible_keys | key        | key_len | ref                  | rows   | Extra                           |
+----+-------------+----------+------+---------------+------------+---------+----------------------+--------+---------------------------------+
|  1 | SIMPLE      | campus   | ALL  | PRIMARY       | NULL       | NULL    | NULL                 |     20 | Using temporary; Using filesort |
|  1 | SIMPLE      | students | ref  | ix_stu_cam    | ix_stu_cam | 5       | bigtest.campus.camID | 123766 | Using index                     |
+----+-------------+----------+------+---------------+------------+---------+----------------------+--------+---------------------------------+

使用 AVG() 函数,通过以下两个查询的 having 中的别名(具有相同的 EXPLAIN 输出),我的性能提高了大约 12%。

SELECT students.camID, campus.camName, avg(students.id) as studentAvg 
FROM students 
JOIN campus 
    ON campus.camID = students.camID 
GROUP BY students.camID, campus.camName 
HAVING avg(students.id) > 2200000 
ORDER BY students.camID; 
-- avg time 7.5

explain 

SELECT students.camID, campus.camName, avg(students.id) as studentAvg 
FROM students 
JOIN campus 
    ON campus.camID = students.camID 
GROUP BY students.camID, campus.camName 
HAVING studentAvg > 2200000
ORDER BY students.camID;
-- avg time 6.5

最后,DISTINCT:

SELECT students.camID, count(distinct students.id) as studentDistinct 
FROM students 
JOIN campus 
    ON campus.camID = students.camID 
GROUP BY students.camID 
HAVING count(distinct students.id) > 1000000 
ORDER BY students.camID; -- 10.6   10.84   12.1   11.49   10.1   9.97   10.27   11.53   9.84 9.98
-- 9.9

 SELECT students.camID, count(distinct students.id) as studentDistinct 
 FROM students 
 JOIN campus 
    ON campus.camID = students.camID 
 GROUP BY students.camID 
 HAVING studentDistinct > 1000000 
 ORDER BY students.camID; -- 6.81    6.55   6.75   6.31   7.11 6.36   6.55
-- 6.45

使用相同的 EXPLAIN 始终快 35% 的别名输出。见下文。因此,相同的 Explain 输出已显示两次,但不会导致相同的性能,而是作为一般线索。

+----+-------------+----------+-------+---------------+------------+---------+----------------------+--------+----------------------------------------------+
| id | select_type | table    | type  | possible_keys | key        | key_len | ref                  | rows   | Extra                                        |
+----+-------------+----------+-------+---------------+------------+---------+----------------------+--------+----------------------------------------------+
|  1 | SIMPLE      | campus   | index | PRIMARY       | PRIMARY    | 4       | NULL                 |     20 | Using index; Using temporary; Using filesort |
|  1 | SIMPLE      | students | ref   | ix_stu_cam    | ix_stu_cam | 5       | bigtest.campus.camID | 123766 | Using index                                  |
+----+-------------+----------+-------+---------------+------------+---------+----------------------+--------+----------------------------------------------+

优化器目前似乎支持 having 中的别名,尤其是 DISTINCT.

关于mysql - 允许在 HAVING 子句中使用别名的性能影响,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/38187913/

相关文章:

sql-server - LINQ 在字符串上放置不需要的尾随空格

sql - SQL Server 2005 中的数据聚合

MYSQL 类似替换不起作用

c# - 在 Entity Framework Core 中创建非聚集索引

mysql - MySQL 中使用阿拉伯语的非法排序规则混合

MYSQL - 如何获得零值

sql - 我想在大型 SQL 查询中重用一个条件,这可能吗?

python - 如何以编程方式为 Django 中的给定模型生成 CREATE TABLE SQL 语句?

MySQL 仅返回日期之间的第一个事务

php - mysqli 准备阵列?