懒惰地评估MySQL视图

时间:2016-12-15 09:10:43

标签: mysql sql optimization view subquery

我有一些MySQL视图,它们根据一些相对简单的子查询定义了许多额外的列。该数据库也是多租户的,因此每一行都有一个公司ID。

我遇到的问题是我的视图会在被公司ID过滤之前对每一行进行评估,从而产生巨大的性能问题。是否有任何方法可以懒惰地评估视图,因此外部查询中的“where”子句适用于视图中的子查询。或者是否有类似于我可用于添加额外字段的视图。我想在SQL中计算它们,因此计算的字段可用于过滤/搜索/排序/分页。

我已经看过解释可用算法的MySQL文档,并且我知道这些视图不能被视为“合并”,因为它们包含子查询。

查看

create view companies_view as
select *,
(
    select count(id) from company_user where company_user.company_id = companies.id
) as user_count,
(
    select count(company_user.user_id)
    from company_user join users on company_user.user_id = users.id
    where company_user.company_id = companies.id
        and users.active = 1
) as active_user_count,
(
    select count(company_user.user_id)
    from company_user join users on company_user.user_id = users.id
    where company_user.company_id = companies.id
        and users.active = 0
as inactive_user_count
from companies;

查询

select * from companies_view where company_id = 123;

我希望在从主查询范围应用'where company_id = 123'之后评估视图中的子查询。我无法在视图中对公司ID进行硬编码,因为我希望视图可用于任何公司ID。

2 个答案:

答案 0 :(得分:2)

您无法更改由MySQL服务器设置的评估顺序。

但是,在这种特殊情况下,您可以重写整个sql语句以使用连接和条件计数而不是子查询:

select c.*,
       count(u.id) as user_count,
       count(if(u.active=1, 1, null)) as active_user_count,
       count(if(u.active=0, 1, null)) as inactive_user_count 
from companies c
left join company_user cu on c.id=cu.company_id
left join users u on cu.user_id = u.id
group by c.company_id, ...

如果你有MySQL v5.7,那么你可能不需要在group by子句中添加任何其他字段,因为companies表中的其他字段在功能上依赖于company_id。在早期版本中,您可能必须列出companies表中的所有字段(取决于sql模式设置)。

优化此类查询的另一种方法是使用非规范化。您的userscompany_user表可能比companies表的记录多得多。您可以在user_count表中添加active_user_countinactive_user_countcompanies字段,在company_user表之后添加插入/更新/删除触发器并在更新users表后更新这两个字段。这样您就不需要在视图中进行连接和条件计数。

答案 1 :(得分:1)

可能说服优化器使用MERGE算法处理带标量子查询的视图......你只需要在自己的游戏中击败优化器。

对于某些人来说,这似乎是非常不正统的,但这是我在成功需要时使用的模式。

创建一个存储函数来封装每个子查询,然后在视图中引用存储的函数。优化器仍然没有意识到函数将调用子查询。

CREATE FUNCTION user_count (_cid INT) RETURNS INT
DETERMINISTIC
READS SQL DATA
RETURN (SELECT count(id) FROM company_user WHERE company_user.company_id = _cid);

请注意,带有单个语句的存储函数不需要BEGIN / END或更改DELIMITER

然后在视图中,将子查询替换为:

user_count(id) AS user_count,

并为每个子查询重复此过程。

然后,优化器将视图作为MERGE视图处理,根据外部WHERE从公司表中选择一个适当的行,调用函数,然后...解决问题。< / p>