使用 group_by 的 MySql 视图非常慢

Posted

技术标签:

【中文标题】使用 group_by 的 MySql 视图非常慢【英文标题】:MySql view with group_by very slow 【发布时间】:2021-11-11 22:47:35 【问题描述】:

我有两张桌子,appointmentsclients。这些表很大,每个都有大约 1 亿条记录。

每个都有一个主键id clientsappointment_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)

我假设aPRIMARY 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 查询和更新速度慢

php响应变得非常慢(使用mysql)

使用 Alamofire 加载图像非常慢

从视图插入临时表非常慢

《MySQL高级篇》七性能分析工具的使用(慢查询日志 | EXPLAIN | SHOW PROFILING | 视图分析 )