MySQL中有序视图的性能问题

Posted

技术标签:

【中文标题】MySQL中有序视图的性能问题【英文标题】:Performance problems with an ordered view in MySQL 【发布时间】:2017-07-17 12:33:06 【问题描述】:

我是 mysql 性能调优的新手,我需要您的帮助来解决我们稍后在设计中将替换表的视图。

要替换的表名为users,具有以下属性: users2 视图具有以下属性:

当我对两个对象执行普通 SELECT 时,它们会同时响应:

SELECT * 
FROM `users`

SELECT * 
FROM `users2`

但是这些查询的有序版本会导致不同的性能:表有点慢(不到两秒),这次视图大约需要十倍:

SELECT * 
FROM `users`
ORDER BY `lastName`, `firstName`

SELECT * 
FROM `users2`
ORDER BY `lastName`, `firstName`

为了找出原因,我让EXPLAIN这两个cmets:

显然,属性 Countries_ID 上的表 'a' (addresses) 上的 ALL 正在制造麻烦,所以我做了以下:

ALTER TABLE addresses ADD INDEX (Countries_ID);

这个索引根本没有改变任何东西。所以,我问你的意见,什么可以做得更好。

注意 1:有没有办法在临时列 Countries_ID_2 上创建索引? 注意 2:users2 视图是使用以下 SQL 查询创建的:

CREATE OR REPLACE VIEW users2 AS
(SELECT p.username
    , p.password
    , p.firstName
    , p.lastName
    , p.eMail AS email
    , a.settlement AS city
    , s.name AS country
    , pl.languages
    , p.description
    , p.ID AS ID
    , p.phone1
    , p.phone2
    , CONCAT_WS(' ', a.street, a.addition) AS address
    , p.status
    , p.publicMail
    , ad.name AS Betreuer
FROM addresses a
    INNER JOIN addresses_have_persons ap ON a.ID = ap.Addresses_ID
    INNER JOIN countries c ON a.Countries_ID = c.ID
    INNER JOIN persons p ON a.ID = p.addressID
        AND ap.Persons_ID = p.ID
    INNER JOIN states s ON a.States_ID = s.ID
    INNER JOIN persons_language pl ON p.ID = pl.ID
LEFT JOIN advisors ad ON p.advisorID = ad.ID
-- LEFT JOIN titles t ON t.ID = ad.titleID
);

注意3:虽然persons表中有很多字段是NULL,但是没有一行这些字段都是NULL

编辑:

CREATE OR REPLACE VIEW persons_language AS
(SELECT lp.Persons_ID AS ID
    , GROUP_CONCAT(DISTINCT l.name ORDER BY l.name SEPARATOR ', ') AS languages
FROM languages l
    , languages_have_persons lp
WHERE l.ID = lp.Languages_ID
GROUP BY lp.Persons_ID);

没有ORDER BY,语言名称不是按字母顺序排列的,这是我目前想要的。也许,我们可以决定以任何顺序获得它们,但我们会看到。

目前,我做了以下修改,没有任何性能提升:

ALTER TABLE addresses ADD INDEX (Countries_ID);
ALTER TABLE addresses ADD INDEX (States_ID);
ALTER TABLE addresses_have_persons ADD INDEX (Addresses_ID);
ALTER TABLE languages ADD INDEX (name);
ALTER TABLE persons ADD INDEX (addressID);
ALTER TABLE persons ADD INDEX (address2ID);
ALTER TABLE persons ADD INDEX (address3ID);
ALTER TABLE persons ADD INDEX (advisorID);

编辑 2:

我也在另一个网站上讨论过这个问题。那里的讨论让我进行了以下更改以更接近第三范式:

CREATE OR REPLACE TABLE accounts
(ID INT NOT NULL AUTO_INCREMENT PRIMARY KEY
, username VARCHAR(50) NOT NULL UNIQUE
, password VARCHAR(255) NOT NULL
, eMail VARCHAR(100) NOT NULL
, Persons_ID INT NOT NULL
);

INSERT INTO accounts (username, password, eMail, Persons_ID)
SELECT username, password, eMail, ID
FROM persons;

persons 表确实只包含最必要的东西,现在具有以下结构:

新表 persons_information 包含所有附加信息:

我使用以下命令重新创建了 users2

CREATE OR REPLACE VIEW users2 AS
(SELECT ac.username
    , ac.password
    , p.firstName
    , p.lastName
    , ac.eMail AS email
    , adr.settlement AS city
    , s.name AS country
    , pl.languages
    , pi.description
    , ac.Persons_ID AS ID
    , pi.phone1
    , pi.phone2
    , CONCAT_WS(' ', adr.street, adr.addition) AS address
    , p.status
    , pi.publicMail
    , adv.name AS Betreuer
FROM accounts ac 
    INNER JOIN persons p ON ac.Persons_ID = p.ID
    INNER JOIN persons_information pi ON p.ID = pi.ID
    INNER JOIN addresses adr ON adr.ID = pi.addressID
    INNER JOIN addresses_have_persons ap ON adr.ID = ap.Addresses_ID
        AND ap.Persons_ID = p.ID
    INNER JOIN countries c ON adr.Countries_ID = c.ID
    INNER JOIN states s ON adr.States_ID = s.ID
    INNER JOIN persons_language pl ON p.ID = pl.ID
LEFT JOIN advisors adv ON pi.advisorID = adv.ID
-- LEFT JOIN titles t ON t.ID = adv.titleID
);

SELECT _ FROM users2 很快,但是如果我添加一个 ORDER BY lastName, firstName,大约需要 25 秒才能得到响应。

以下是 *EXPLAIN SELECT * FROM users2* 命令的结果:

这里是另一个命令:

我还(重新)创建了以下索引:

ALTER TABLE addresses ADD INDEX (Countries_ID);
ALTER TABLE addresses ADD INDEX (States_ID);
ALTER TABLE addresses_have_persons ADD INDEX (Persons_ID);
ALTER TABLE languages ADD INDEX (name);
ALTER TABLE persons_information ADD INDEX (addressID);
ALTER TABLE persons_information ADD INDEX (address2ID);
ALTER TABLE persons_information ADD INDEX (address3ID);
ALTER TABLE persons_information ADD INDEX (advisorID);

我认为问题的一个原因是 person_language 视图创建如下:

CREATE OR REPLACE VIEW persons_language AS
    (SELECT lp.Persons_ID AS ID
        , GROUP_CONCAT(DISTINCT l.name ORDER BY l.name SEPARATOR ', ') AS languages
    FROM languages l
    INNER JOIN languages_have_persons lp ON l.ID = lp.Languages_ID
GROUP BY lp.Persons_ID);

编辑 3: 对于那些感兴趣的人,我为 persons_language 视图添加了 EXPLAIN:

编辑 4: 今天的数据库会议结束后,我们决定删除所有与地址信息相关的对象并重新创建视图

CREATE OR REPLACE VIEW `users2` AS
(SELECT ac.username
    , ac.password
    , p.firstName
    , p.lastName
    , ac.eMail AS email
    , pl.languages
    , pi.description
    , ac.Persons_ID AS ID
    , pi.phone1
    , pi.phone2
    , p.status
    , pi.publicMail
    , adv.name AS Betreuer
FROM accounts ac 
    INNER JOIN persons p ON ac.Persons_ID = p.ID
    INNER JOIN persons_information pi ON p.ID = pi.ID
    INNER JOIN persons_language pl ON p.ID = pl.ID
    INNER JOIN advisors adv ON pi.advisorID = adv.ID
WHERE ac.password IS NOT NULL
);

我还用

创建了一个索引
CREATE INDEX LanguagesPersonsIndex ON `languages_have_persons` (`Languages_ID`, `Persons_ID`);

EXPLAIN 命令显示新索引正在使用中,并且在带有 ORDER BY 子句的 SELECT 和新索引之后的延迟,较小的视图约为 18 秒。这是新的结果: 我的问题是:我还能做些什么来提高性能?

【问题讨论】:

【参考方案1】:

关键故障一定是问题。 但是根据连接表上的数据量,它无论如何都会变慢。 尝试:

在所有用于建立关系的属性上实现 KeyIndexes。 (ap.Addresses_ID、a.Countries_ID、p.addressID、ap.Persons_ID、a.States_ID、p.advisorID)。 在所有“ID”列上声明 PK。 不要在视图构造中使用 ORDER 或 GROUP。 为最常用于搜索、排序或分组的属性声明键索引。

提示:'INNER' (INNER JOIN) 不是必需的。和'JOIN'一样

您的 VIEW "persons_language" 最好这样:

SELECT lp.Persons_ID AS ID, GROUP_CONCAT(DISTINCT l.name ORDER BY l.name SEPARATOR ', ') AS languages
FROM languages_have_persons lp 
JOIN languages l ON l.ID = lp.Languages_ID
GROUP BY lp.Persons_ID;

比较合适,因为'FROM'和'JOIN'子句在'WHERE'子句之前处理。

您可以提升您的 mysql 内存和缓存配置。 查看我的 mysql 服务器的配置(运行带有权重表和视图的 ERP):

join_buffer_size= 256M
key_buffer = 312M 
key_buffer_size = 768M
max_allowed_packet = 160M 
thread_stack = 192K 
thread_cache_size = 8
query_cache_limit = 64M
innodb_buffer_pool_size = 1512M
table_cache = 1024M 
read_buffer_size = 4M
query_cache_size = 768M
query_cache_limit = 128M

【讨论】:

感谢您的回复,@Renato Tarso!不幸的是,我有一个 ORDER BY 的必要视图。请参阅上面的问题编辑。所有顺序/分组/关系属性都是键。 “...是主键”(当然,意见顾问和persons_language 没有主键) @Sae1962,检查我的答案更改。我希望它有用。祝你好运。 如果您有至少 8GB 的​​ RAM,innodb_buffer_pool_size 应该是大约 70% 的 可用 RAM。如果 RAM 较小,则使用较小的 %。 是的...当然,kkk。这里有 16GB 可用,但服务器运行 apache 和其他服务。【参考方案2】:
SELECT * 
FROM `users`
ORDER BY `lastName`, `firstName`

需要

INDEX(last_name, first_name) -- in that order

提防VIEWs;有些VIEWs 优化得很好,有些则没有。

请为addressesaddresses_have_persons 提供SHOW CREATE TABLE

persons_language,为什么需要DISTINCT?它没有PRIMARY KEY(person, language)(或相反的顺序)吗?让我们看看SHOW CREATE TABLE

对于您想讨论的任何问题,请提供EXPLAIN

【讨论】:

以上是关于MySQL中有序视图的性能问题的主要内容,如果未能解决你的问题,请参考以下文章

MySQL 视图 - 性能不佳

视图的索引字段是不是会提高 MySQL 的性能?

MySQL 中的视图性能

这是在mysql中的同一个表上创建多个视图的好习惯(性能方面)吗

MYSQL 查看查询性能问题

MySql 性能查询与带有“解释”输出的视图