使用 group_by 的 MySql 视图非常慢
Posted
技术标签:
【中文标题】使用 group_by 的 MySql 视图非常慢【英文标题】:MySql view with group_by very slow 【发布时间】:2021-11-11 22:47:35 【问题描述】:我有两张桌子,appointments
和 clients
。这些表很大,每个都有大约 1 亿条记录。
id
clients
在 appointment_id
上有一个外键
我正在使用 mysql 5.7
正如预期的那样,以下查询速度很快:
select a.id, count(c.id) client_count
from appointments a
left join clients c on c.appointment_id = a.id
where a.id = 499
group by a.id;
但是,如果我创建以下视图和查询,它会无休止地缓慢:
create view client_counts as
select a.id, count(c.id) client_count
from appointments a
left join clients c on c.appointment_id = a.id
group by a.id;
select id, client_count
from client_counts
where id = 499;
我假设 where 子句没有应用于内部查询(在视图中),而是必须扫描视图的每条记录以查看 499 是否匹配。我认为这是由group by
引起的。
注意:我确实看到我可以use a function in the where clause of the view,但它看起来很笨拙。这是首选方法吗?
我的问题:我可以继续使用视图并使其快速运行的最佳方式是什么?
【问题讨论】:
Edit 问题并提供minimal reproducible example,即涉及的表或其他对象的CREATE
语句,尤其是索引(粘贴文本,不要使用图像,不要链接到外部站点),INSERT
用于示例数据的语句 (dito) 以及使用表格文本格式的示例数据的所需结果。附加两个查询的计划(直接和通过视图)。
对于那个特定的查询,你可能有CREATE INDEX client_app_ndx ON clients(appointment_id); CREATE VIEW client_counts AS SELECT appointment_id AS id, COUNT (*) AS client_count FROM clients
;
处理这个问题的“最佳方法”可能是升级你的 MySQL 版本。 MySQL 5.7 已经相当老了。从那时起,他们可能已经对优化器进行了一些改进。您可能很幸运,他们已经在当前版本中解决了您的问题。在我看来,MySQL 8 是一个巨大的飞跃,它引入了 CTE(普通和递归)和窗口函数。仅出于这些原因,我已经建议升级。
【参考方案1】:
您的第一个查询并没有真正使用c
。所以,我认为不值得讨论。
COUNT(x)
检查每个 x
是否不为 NULL。通常人们使用COUNT(*)
来计算“行数”。
VIEWs
是语法糖;我还没有看到VIEW
比等效的SELECT
运行得更快的例子。我见过很多情况,VIEW
的运行速度似乎明显变慢。
您想讨论如何编写最佳的SELECT
来实现一些“计数”吗?
我认为您的 View + Select 归结为简单
SELECT COUNT(*) FROM clients WHERE appointment_id = 499;
如果clients
有,该查询将闪电般快速
INDEX(appointment_id)
(对不起,MySQL 不够聪明,无法为您推断出这个。)
更多
让我们采取另一种方法。查看以下 VIEW 定义是否适合您:
CREATE VIEW client_counts AS
SELECT a.id,
( SELECT COUNT(*)
FROM clients
WHERE appointment_id = a.id
) AS client_count
FROM appointments a;
请注意,将查询由内向外翻转可避免使用GROUP BY
。这可能有助于提高性能。
检查你是否有这个索引:
clients: INDEX(appointment_id)
我假设a
有PRIMARY KEY(id)
?
【讨论】:
谢谢。我更新了我的问题。我的意思是写计数(c.id)而不是计数(a.id)。我不希望视图比等效选择运行得更快。我希望获得类似的表现。 我想要一个视图,因为它为许多其他将使用它的查询提供了一个很好的抽象。是的,正如你所说的“语法糖”,但对我的情况很有帮助。 @FelixLivni - 是的,“抽象”是拥有视图的一个很好的理由。我重写了视图;看看它是否有效并且速度更快。 谢谢@rick。您的新解决方案比原始视图快得多,但仍然比直接在 where 子句中设置 id 慢很多数量级。我在appointment_id
上有索引,a.id 是主键。注意:如果没有索引,由于使用 c.appointment_id 进行连接,我的第一个查询会非常慢。【参考方案2】:
我认为最好的方法是使用函数;如果您使用的是 Postgres,那么如果记录没有太大变化,也许使用 materialized view
可以解决您的问题......但总的来说,我认为您应该在这里使用一个函数,或者只是进行查询而不是使用视图。
无论如何,检查 id 字段是否被索引是个好主意。
【讨论】:
谢谢。字段被索引。功能很快,但看起来很笨重。把它写成一个大的(我为了发布而简化了)子查询(我正在做另一个分组)也可以。 @FelixLivni - 注意; “简化”查询为您提供“简化”答案。【参考方案3】:看来,MySQL 5.7 中的优化器还不足以完全理解查询并及早应用条件。你是对的,这可能是由于聚合。 DBMS 看到的是这样的:
select id, client_count
from
(
select a.id, count(c.id) client_count
from appointments a
left join clients c on c.appointment_id = a.id
group by a.id;
) client_counts
where id = 499;
它可以减少到
select a.id, count(c.id) client_count
from appointments a
left join clients c on c.appointment_id = a.id
group by a.id
having a.id = 499;
但它没有看到条件可以在聚合之前移动。
我很确定您可以通过不聚合连接的行而仅聚合客户端来解决此问题。这是通过将聚合移动到子查询中并将其放在 select 子句中来完成的。 (MySQL 还没有横向连接,否则您可以选择将相关子查询放在 from 子句中。)
create view client_counts as
select
a.id,
(
select count(*)
from clients c
where c.appointment_id = a.id
) as client_count
from appointments a;
【讨论】:
谢谢@thorsten。我同意你的前两个代码 sn-ps 和以下结论。第三个 sn-p 基本上就是 rick 现在发布的内容。我试了一下,虽然它比我所拥有的(以及你的前两个 sn-ps)要快,但它仍然比直接在 where 子句中设置 id 慢几个数量级。到目前为止,该功能是我遇到的唯一可行的方法。我很可能只是在代码中构建复杂的 sql,并放弃您从视图中获得的漂亮抽象。以上是关于使用 group_by 的 MySql 视图非常慢的主要内容,如果未能解决你的问题,请参考以下文章
NSFetchedResultsController 使加载视图控制器非常慢
《MySQL高级篇》七性能分析工具的使用(慢查询日志 | EXPLAIN | SHOW PROFILING | 视图分析 )