MySQL视图查询超慢,求解答
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了MySQL视图查询超慢,求解答相关的知识,希望对你有一定的参考价值。
时间的快慢主要由索引决定,在索引都最优化的情况下才与数据量有关。视图不可能明显加快的查询速度,只是编程方便而已。 参考技术A
mysql 8.0 推出了histogram,也叫柱状图或者直方图。先来解释下什么叫直方图。
关于直方图
我们知道,在DB中,优化器负责将SQL转换为很多个不同的执行计划,完了从中选择一个最优的来实际执行。但是有时候优化器选择的最终计划有可能随着DB环境的变化不是最优的,这就导致了查询性能不是很好。比如,优化器无法准确的知道每张表的实际行数以及参与过滤条件的列有多少个不同的值。那其实有时候有人就说了,索引不是可以解决这个问题吗?是的,不同类型的索引可以解决这个问题,但是你不能每个列都建索引吧?如果一张表有1000个字段,那全字段索引将会拖死对这张表的写入。而此时,直方图就是相对来说,开销较小的方法。
直方图就是在 MySQL 中为某张表的某些字段提供了一种数值分布的统计信息。比如字段NULL的个数,每个不同值出现的百分比、最大值、最小值等等。如果我们用过了 MySQL 的分析型引擎brighthouse,那对这个概念太熟悉了。
MySQL的直方图有两种,等宽直方图和等高直方图。等宽直方图每个桶(bucket)保存一个值以及这个值累积频率;等高直方图每个桶需要保存不同值的个数,上下限以及累计频率等。MySQL会自动分配用哪种类型的直方图,我们无需参与。
MySQL 定义了一张meta表column_statistics 来存储直方图的定义,每行记录对应一个字段的直方图,以json保存。同时,新增了一个参数histogram_generation_max_mem_size来配置建立直方图内存大小。
不过直方图有以下限制:
1. 不支持几何类型以及json。2. 不支持加密表和临时表。3. 不支持列值完全唯一。4. 需要手工的进行键值分布。
那我们来举个简单的例子说明直方图对查询的效果提升。
举例
表相关定义以及行数信息等:
mysql> show create table t2\\G
*************************** 1. row ***************************
Table: t2
Create Table: CREATE TABLE `t2` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`rank1` int(11) DEFAULT NULL,
`rank2` int(11) DEFAULT NULL,
`rank3` int(11) DEFAULT NULL,
`log_date` date DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `idx_rank1` (`rank1`),
KEY `idx_log_date` (`log_date`)
) ENGINE=InnoDB AUTO_INCREMENT=49140 DEFAULT CHARSET=utf8mb4 \\
COLLATE=utf8mb4_0900_ai_ci STATS_PERSISTENT=1 STATS_AUTO_RECALC=0
1 row in set (0.00 sec)
mysql> select count(*) from t2;
+----------+
| count(*) |
+----------+
| 30940 |
+----------+
1 row in set (0.00 sec)
同时对t2克隆了一张表t3
mysql> create table t3 like t2;
Query OK, 0 rows affected (0.13 sec)
mysql> insert into t3 select * from t2;
Query OK, 30940 rows affected (1.94 sec)
Records: 30940 Duplicates: 0 Warnings: 0
给表t3列rank1和log_date 添加histogram
mysql> analyze table t3 update histogram on rank1,log_date;+--------+-----------+----------+-----------------------------------------------------+| Table | Op | Msg_type | Msg_text |+--------+-----------+----------+-----------------------------------------------------+| ytt.t3 | histogram | status | Histogram statistics created for column 'log_date'. || ytt.t3 | histogram | status | Histogram statistics created for column 'rank1'. |+--------+-----------+----------+-----------------------------------------------------+2 rows in set (0.19 sec)
我们来看看histogram的分布状况
mysql> select json_pretty(histogram) result from information_schema.column_statistics where table_name = 't3' and column_name = 'log_date'\\G*************************** 1. row ***************************result: "buckets": [ [ "2018-04-17", "2018-04-20", 0.01050420168067227, 4 ], ... , [ "2019-04-14", "2019-04-16", 1.0, 3 ] ], "data-type": "date", "null-values": 0.0, "collation-id": 8, "last-updated": "2019-04-17 03:43:01.910185", "sampling-rate": 1.0, "histogram-type": "equi-height", "number-of-buckets-specified": 1001 row in set (0.03 sec)
MySQL自动为这个字段分配了等高直方图,默认为100个桶。SQL A:
select count(*) from t2/t3 where (rank1 between 1 and 10) and log_date < '2018-09-01';
SQL A的执行结果:
mysql> select count(*) from t2/t3 where (rank1 between 1 and 10) and log_date < '2018-09-01';+----------+| count(*) |+----------+| 2269 |+----------+1 row in set (0.01 sec)
无histogram的执行计划
mysql> explain format=json select count(*) from t2 where (rank1 between 1 and 10) and log_date < '2018-09-01'\\G*************************** 1. row ***************************EXPLAIN: "query_block": "select_id": 1, "cost_info": "query_cost": "2796.11" , "table": "table_name": "t2", "access_type": "range", "possible_keys": [ "idx_rank1", "idx_log_date" ], "key": "idx_rank1", "used_key_parts": [ "rank1" ], "key_length": "5", "rows_examined_per_scan": 6213, "rows_produced_per_join": 3106, "filtered": "50.00", "index_condition": "(`ytt`.`t2`.`rank1` between 1 and 10)", "cost_info": "read_cost": "2485.46", "eval_cost": "310.65", "prefix_cost": "2796.11", "data_read_per_join": "72K" , "used_columns": [ "rank1", "log_date" ], "attached_condition": "(`ytt`.`t2`.`log_date` < '2018-09-01')"
有histogram的执行计划
mysql> explain format=json select count(*) from t3 where (rank1 between 1 and 10) and log_date < '2018-09-01'\\G*************************** 1. row ***************************EXPLAIN: "query_block": "select_id": 1, "cost_info": "query_cost": "0.71" , "table": "table_name": "t3", "access_type": "range", "possible_keys": [ "idx_rank1", "idx_log_date" ], "key": "idx_log_date", "used_key_parts": [ "log_date" ], "key_length": "4", "rows_examined_per_scan": 1, "rows_produced_per_join": 1, "filtered": "100.00", "index_condition": "(`ytt`.`t3`.`log_date` < '2018-09-01')", "cost_info": "read_cost": "0.61", "eval_cost": "0.10", "prefix_cost": "0.71", "data_read_per_join": "24" , "used_columns": [ "rank1", "log_date" ], "attached_condition": "(`ytt`.`t3`.`rank1` between 1 and 10)" 1 row in set, 1 warning (0.00 sec)
我们看到两个执行计划的对比,有Histogram的执行计划cost比普通的sql快了好多倍。上面文字可以看起来比较晦涩,贴上两张图,看起来就很简单了。无histogram请点击输入图片描述有histogram请点击输入图片描述当然,我这里举得例子相对简单,有兴趣的朋友可以更深入学习其他复杂些的例子。
MySql 查询超慢
【中文标题】MySql 查询超慢【英文标题】:MySql Query Super Slow 【发布时间】:2012-11-19 17:05:02 【问题描述】:我有以下查询非常慢(仅需要 4-5 秒才能得到结果。我想知道是否有人可以看到我可以用这个查询做不同的事情来加快它的速度?
谢谢!
SELECT
accounts.id AS account_id,
accounts. NAME AS account_name,
accounts.assigned_user_id account_id_owner,
users.user_name AS assigned_user_name,
opportunities_cstm.firstname_c, opportunities_cstm.lastname_c,
opportunities.`name`, TRIM(
Concat(
Ifnull(
opportunities_cstm.firstname_c,
''
),
' ',
Ifnull(
opportunities_cstm.lastname_c,
''
)
)
) AS 'cfull' FROM
opportunities
LEFT JOIN users ON opportunities.assigned_user_id = users.id
LEFT JOIN accounts_opportunities ON opportunities.id = accounts_opportunities.opportunity_id
LEFT JOIN accounts ON accounts_opportunities.account_id = accounts.id
LEFT JOIN opportunities_cstm ON opportunities.id = opportunities_cstm.id_c
WHERE
(
(
opportunities.sales_stage IN (
'Prospecting',
'Appointment Set',
'MeetAndGreet',
'Qualification',
'Needs Analysis',
'Locating Vehicle',
'Demo',
'Trade Evaluation',
'Negotiation',
'Manager T/O',
'Write Up',
'Credit App Submitted',
'Pending Finance',
'Loan Approval',
'Deposit',
'Delayed Decision',
'Sold-Vehicle Ordered',
'Sold-Pending Finance',
'Sold/Pending Delivery',
'Price Quoted',
'Service Pending'
)
)
)
AND (
accounts_opportunities.deleted IS NULL
OR accounts_opportunities.deleted = 0
)
AND (
accounts.deleted IS NULL
OR accounts.deleted = 0
)
AND opportunities.deleted = 0
ORDER BY
opportunities.date_entered DESC,
opportunities.id DESC
LIMIT 0,21
这是来自同一查询的解释:
╔═════════════╦════════════════════════╦════════╦═ ═════════════════════════╦═════════════════════╦══ ═══════╦══════════════════════════════════════════ ══╦═══════╦═════════════════════════════╗ ║ select_type ║ table ║ type ║ possible_keys ║ key ║ key_len ║ ref ║ rows ║ extra ║ ╠═════════════╬════════════════════════╬════════╬═ ═════════════════════════╬═════════════════════╬══ ═══════╬══════════════════════════════════════════ ══╬═══════╬══════════════════════════════ ║简单║机会║范围║sales_stage,idx_deleted║sales_stage║78║null║25161║使用where;使用文件排序║ ║ 简单 ║ 用户 ║ eq_ref ║ PRIMARY, idx_id_deleted ║ PRIMARY ║ 108 ║ version4.opportunities.assigned_user_id ║ 1 ║ ║ ║ simple ║ accounts_opportunities ║ ref ║ idx_oppid_del_accid ║ idx_oppid_del_accid ║ 111 ║ version4.opportunities.id ║ 1 ║ 使用 where;使用索引║ ║ 简单 ║ 账户 ║ eq_ref ║ PRIMARY,idx_accnt_id_del ║ PRIMARY ║ 108 ║ version4.accounts_opportunities.account_id ║ 1 ║ 使用where ║ ║ 简单 ║ 机会_cstm ║ eq_ref ║ PRIMARY ║ PRIMARY ║ 108 ║ version4.opportunities.id ║ 1 ║ ║ ╚═════════════╩════════════════════════╩════════╩═ ═════════════════════════╩═════════════════════╩══ ═══════╩══════════════════════════════════════════ ══╩═══════╩═════════════════════════════╝【问题讨论】:
Using filesort
是最糟糕的。
是的,如果你看到Using filesort
,你基本上就死定了,尤其是有大量受影响的行。您将不得不考虑在 ORDER
列上合并索引的方法。
你已经设置了哪些索引?
这是我在机会表上的索引。为了避免使用文件排序,可以做些什么不同的事情?我们有大量内存并且它没有被最大化,所以我知道它没有诉诸基于文件的表格等。这是我拍摄的索引屏幕截图的链接:d.pr/i/7oEz
Using filesort
并不意味着它会进入磁盘;这意味着有必要对派生的结果集进行排序。当您像您一样在大胖连接上使用ORDER BY ... LIMIT
时,通常会出现这种情况。
【参考方案1】:
我看到了两个问题。
首先,您使用了两个不同的WHERE (... IS NULL OR ... = 0)
标准。那些慢得无法形容。这是因为索引对于查找 NULL 值没有用。如果您可以摆脱那些deleted
列中的NULL 可能性,也许通过声明它们NOT NULL DEFAULT 0
您可以将这些标准更改为WHERE ... = 0
。这应该加快很多事情。这是因为索引对于查找 NULL 值没有用处。
其次,您正在创建一个很棒的大型联合结果集,然后对其进行排序以查找最新的项目。
您可以尝试在加入之前从“机会”表中预先选择项目。做这样的事情:
SELECT whatever....
FROM (
SELECT *
FROM opportunities
WHERE opportunities.deleted = 0
AND opportunities.sales_stage IN (
'Prospecting',
'Appointment Set', etc etc ...
'Service Pending' )
ORDER BY opportunities.date_entered DESC,
opportunities.id DESC
LIMIT 0,21
) opportunities
LEFT JOIN users ON opportunities.assigned_user_id = users.id
...
ORDER BY
opportunities.date_entered DESC,
opportunities.id DESC
LIMIT 0,21
这很可能可以通过从联接的右侧删除一堆记录来减少 LEFT JOIN 操作的基数来加快速度。
【讨论】:
我还要补充一点,date_entered、deleted 和 sales_stage 上的多列索引(按此顺序)可能也会有所帮助。我真的希望 MySQL 能够实现降序排序的索引。 奥利 - 感谢您的反馈。我刚刚尝试了您在上面提供的查询。但是,我收到一条错误消息。这是我收到的错误:您的 SQL 语法有错误;检查与您的 MySQL 服务器版本相对应的手册,以获取正确的语法,以便在第 37 行的“LEFT JOIN users ON opportunity.assigned_user_id = users.id LEFT JOIN accounts”附近使用我没有机会调试我的查询,很抱歉。 (我没有你的数据。)【参考方案2】:
我不确定它是否会有所改进,但我会尝试这样做:
SELECT your_fields
FROM
(SELECT * --or just the fields you need
FROM
opportunities
WHERE
opportunities.deleted = 0 AND
opportunities.sales_stage IN (stage1, stage2, ...)
) opportunities1
LEFT JOIN users ON opportunities1.assigned_user_id = users.id
LEFT JOIN accounts_opportunities ON opportunities1.id = ...etc...
WHERE
(accounts_opportunities.deleted IS NULL
OR accounts_opportunities.deleted = 0)
AND (accounts.deleted IS NULL OR accounts.deleted = 0)
ORDER BY ...etc...
如果它有任何改进,请告诉我(但也可能会更慢)。另一个想法是使用包含您需要过滤的所有阶段的表格:
CREATE TABLE filter_stage (
stage varchar(255));
(255或尽量匹配sales_stage的实际长度,最好也索引此列)在此处输入所有要过滤的字符串:
INSERT INTO filter_stage VALUES ('Prospecting'), ('Appointment Set'), ('...'), ...
然后你从你的第一个查询中删除你的 IN 子句,然后你的 FROM 变成:
FROM
opportunities INNER JOIN filter_stage
ON opportunities.sales_stage = filter_stage.stage
LEFT JOIN ...
让我知道它是否有效!
【讨论】:
fthiella - 我试过你的例子,但它让它变长了。这次大约 6-7 秒。 @swhitlow 有时它可以工作,但它可能会使其变慢......您是否也尝试使用 filter_stage 表?但如果没有实际数据,我无法向您提供其他建议...我建议您尝试使用不同的子查询组合,或尝试使用括号对连接表进行分组...【参考方案3】:不要使用 IN。 IN 在 mysql 中运行缓慢,使用 Exists
DROP TABLE IF EXISTS tempTable;
CREATE TEMPORARY TABLE tempTable ( sales_stage VARCHAR(50) NOT NULL );
insert into tempTable () values ('Prospecting'),('Appointment Set'),('MeetAndGreet'),...,('Service Pending');
SELECT
...
WHERE EXISTS(select sales_stage from tempTable where opportunities.sales_stage = sales_stage);
【讨论】:
Boris - 我不确定我将如何使用存在于字段中。你能解释一下吗? 您能解释一下“IN 在 mysql 中运行缓慢”吗?如果 IN 是子查询,我会同意...但在这种情况下我必须不同意,因为他使用的是文字值列表。 请看这里jortk.nl/2008/07/exists-much-faster-then-in-in-mysql和这里dev.mysql.com/doc/refman/5.0/en/…以上是关于MySQL视图查询超慢,求解答的主要内容,如果未能解决你的问题,请参考以下文章