《MySQL开发规范》过时了,视图的查询性能提升了一万倍

本文探讨MySQL 8.0中的新特性,特别是派生条件下推优化和视图性能提升。派生条件下推优化能减少派生表返回行数,提高查询效率。在MySQL 8.0.29中,即使视图包含union,也能应用此优化。视图查询性能以前因无法使用基表索引而低下,但在新版本中得到了显著改善,查询速度提升可达6万倍。这使得过去禁止使用视图的《MySQL开发规范》需要重新审视。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

MySQL 8.0新特性专栏目录

《MySQL开发规范》过时了,视图查询性能提升了一万倍
你真的会用EXPLAIN么,SQL性能优化王者晋级之路
索引三剑客之降序索引和不可见索引
千呼万唤始出来,MySQL 8.0索引三剑客之函数索引
双重密码,MySQL 8.0创新特性
sql_mode兼容性,MySQL 8.0 升级踩过的坑
警惕参数变化,MySQL 8.0 升级避免再次踩坑



前言

视图在数据库中是非常普及的功能。但是长期以来,大多数互联网公司的《MySQL开发规范》中都有一条规范:在MySQL中禁止(或建议不要)使用视图。究其原因,主要是由于在MySQL中视图的查询性能不好,同时带来了管理维护上的高成本。
不过随着MySQL 8.0中派生条件下推特性的引入,尤其是最近GA的MySQL 8.0.29版本中对于包含union子句的派生条件下推优化,MySQL中视图查询的性能得到了质的提升。
《MySQL开发规范》已经过时了,DBA该考虑考虑将禁止使用视图的规定重新修订一下了。


1. 派生条件下推优化特性

1.1 什么是派生条件下推优化

在讨论视图之前,我们先了解一下什么是派生条件下推优化。派生条件下推优化,是在MySQL 8.0中引入的一项针对优化器的优化特性,对于存在物化派生表的SQL查询,可以实现派生条件下推优化,即将外层查询子句的过滤条件下推到派生表内部,以减少派生表返回行数,同时可以利用派生表上对应的索引以提高查询效率。

如果派生表上没有使用聚合或者是窗口函数,那么可以直接将外层过滤条件下推到派生表的where条件上过滤;如果派生表上使用了聚合查询(group by),那么一般情况下可以将外层过滤条件下推到派生表聚合之后的having子句;如果派生表上使用了窗口函数,那么可以将外层过滤条件下推到派生表的窗口函数的partition子句(视具体情况而定)。
在MySQL 8.0中派生条件下推是默认开启的,由optimizer_switch系统变量的derived_condition_pushdown标志控制。

1.2 派生条件下推的限制条件

派生条件下推的限制:

  • 当派生表上使用了limit限制返回行数时,将无法使用派生条件下推;
  • 外层条件包含子查询时不能使用派生条件下推;
  • 如果派生表是外连接的内表,则不能使用派生条件下推优化;
  • 如果物化派生表是通用表表达式,它会被多次引用,则不会将外层条件下推到通用表表达式;
  • 从MySQL 8.0.28开始,如果派生表的SELECT列表包含对用户变量的任何赋值,则条件不能被下推。

1.3 优化器应用派生条件下推的几个场景

场景一: SQL查询的派生表上没有使用聚合或者窗口函数

例如:

# 原始SQL
SELECT * FROM (SELECT f1, f2 FROM t1) AS dt WHERE f1 < 3 AND f2 > 11
# 优化器转换后SQL
SELECT f1, f2 FROM (SELECT f1, f2 FROM t1 WHERE f1 < 3 AND f2 > 11) AS dt

场景二:SQL查询的派生表上使用了group by分组,并且外层过滤条件不是group by分组字段的一部分

例如:

# 原始SQL
SELECT * FROM (SELECT i, j, SUM(k) AS sum FROM t1 GROUP BY i, j) AS dt WHERE sum > 100
# 优化器转换后SQL
SELECT * FROM (SELECT i, j, SUM(k) AS sum FROM t1 GROUP BY i, j HAVING sum > 100) AS dt

场景三:SQL查询的派生表使用了group by分组,并且外城过滤条件是 group by分组字段的一部分

例如:

# 原始SQL
SELECT * FROM (SELECT i,j, SUM(k) AS sum FROM t1 GROUP BY i,j) AS dt WHERE i > 10 
# 优化器转换后SQL
SELECT * FROM (SELECT i,j, SUM(k) AS sum FROM t1 WHERE i > 10 GROUP BY i,j) AS dt

1.4 派生条件下推优化的一个特例

在MySQL 8.0.29之前,如果派生表上使用了union聚合,那么派生条件下推特性将失效。不过从新发布的MySQL 8.0.29开始,即使在派生表上使用了union聚合,MySQL依旧能够使用派生条件下推特性对SQL查询进行优化。

我们照常来举一个实际的例子,这个例子是MySQL 8.0.29的官方文档上的例子的一个改良版(原版的示例是不太合适的,我已经跟官方提了建议)。

示例:
我们先定义一个对两张基表取并集的简单的视图,如下:

# 视图定义
CREATE TABLE t1 (
  id INT NOT NULL AUTO_INCREMENT PRIMARY KEY, 
  c1 INT, 
  c2 varchar(32) DEFAULT NULL,
  KEY i1 (c1)
);

CREATE TABLE t2 (
  id INT NOT NULL AUTO_INCREMENT PRIMARY KEY, 
  c1 INT, 
  c2 varchar(32) DEFAULT NULL,
  KEY i1 (c1)
);

CREATE OR REPLACE VIEW v AS
     SELECT id, c1, c2 FROM t1
     UNION ALL
     SELECT id, c1, c2 FROM t2;

然后,对这个视图进行一次检查的过滤查询,根据explain显示的执行计划,我们可以看到对这个视图的查询使用到了派生条件下推特性,将查询条件c1=12下推到了基表上,并且使用了基表上的二级索引。

# 视图上的查询,使用到了派生条件下推的特性,将查询条件下推到视图的基表上,以使用基表的索引。
mysql> EXPLAIN FORMAT=TREE SELECT * FROM v WHERE c1 = 12\G
*************************** 1. row ***************************
EXPLAIN: -> Table scan on v  (cost=1.26..2.52 rows=2)
    -> Union materialize  (cost=2.16..3.42 rows=2)
        -> Covering index lookup on t1 using i1 (c1=12)  (cost=0.35 rows=1)
        -> Covering index lookup on t2 using i1 (c1=12)  (cost=0.35 rows=1)

1 row in set (0.00 sec)

2. 视图查询性能提升一万倍

2.1 MySQL 的视图查询性能

MySQL的视图查询性能一直以来是一个让开发人员很头疼的问题。以往在很多场景下,譬如MySQL视图的定义中包含了group by 或者union等聚合条件,那么视图上的查询就无法使用到基表的索引,而是对所有基表进行全表扫描后,将返回结果保存到临时表,再进行过滤,这也就直接导致了视图的查询性能非常之差。

视图查询性能的鸡肋,加上管理维护成本,导致大多数互联网公司的《MySQL开发规范》都有一条,那就是不允许或者不建议使用视图。

不过随着MySQL 8.0中派生条件下推特性的引入,这一条规范估计要改写;尤其是MySQL 8.0.29 之后即使视图定义中使用了union子句的派生表,也可以应用派生条件下推的特性来提升视图的查询性能。

派生条件下推这个特性的引入彻底解决了MySQL视图的性能瓶颈。

2.2 MySQL 8.0前后版本的视图查询性能对比

如上所述,MySQL 8.0中引入的派生条件下推特性,尤其是MySQL 8.0.29 之后即使视图定义中使用了union子句的派生表也可以应用派生条件下推的特性,使得MySQL 8.0中视图查询性能有了质的飞跃。

对比MySQL 5.7.26 和 MySQL 8.0.29 版本,我们创建一个视图,基于两张sysbench的测试表的union结果;然后在视图上使用where条件过滤查询,对比不同版本的执行计划的区别和查询性能差异。

# 分别在MySQL 5.7.26 和 MySQL 8.0.29 中创建视图v_sbtest,基于两张100w条记录的sysbench测试表
create or replace view v_sbtest as 
select k, c from sbtest1
union all
select k, c from sbtest2;

首先,在MySQL 5.7.26中查询视图v_sbtest,使用过滤条件where k between 100000 and 200000,可以看到该查询条件无法下推到基表,需要对派生表sbtest1和sbtest2分别进行全表扫描,构建临时表,然后再对返回结果进行过滤。这次查询耗时13.9秒。

[MySQL 5.7.26][test]> explain select * from v_sbtest where k between 100000 and 200000;
+----+-------------+------------+------------+------+---------------+------+---------+------+---------+----------+-------------+
| id | select_type | table      | partitions | type | possible_keys | key  | key_len | ref  | rows    | filtered | Extra       |
+----+-------------+------------+------------+------+---------------+------+---------+------+---------+----------+-------------+
|  1 | PRIMARY     | <derived2> | NULL       | ALL  | NULL          | NULL | NULL    | NULL | 1972800 |    11.11 | Using where |
|  2 | DERIVED     | sbtest1    | NULL       | ALL  | NULL          | NULL | NULL    | NULL |  986400 |   100.00 | NULL        |
|  3 | UNION       | sbtest2    | NULL       | ALL  | NULL          | NULL | NULL    | NULL |  986400 |   100.00 | NULL        |
+----+-------------+------------+------------+------+---------------+------+---------+------+---------+----------+-------------+
3 rows in set, 1 warning (0.00 sec)

[MySQL 5.7.26][test]> select * from v_sbtest where k between 100000 and 200000;
28 rows in set (13.90 sec)

然后,在MySQL 8.0.29中查询视图v_sbtest,使用过滤条件 where k between 100000 and 200000,可以看到这次该查询条件被下推到两张基表sbtest1和sbtest2,并且使用到了基表上的索引。查询耗时仅0.221毫秒。

# MySQL 8.0.29 中查询SQL的执行计划,使用到了基表sbtest1和sbtest2上的索引`k_11`和`k_2`
[MySQL 8.0.29][test]> explain select * from v_sbtest where k between 100000 and 200000;
+----+-------------+------------+------------+-------+---------------+------+---------+------+------+----------+-----------------------+
| id | select_type | table      | partitions | type  | possible_keys | key  | key_len | ref  | rows | filtered | Extra                 |
+----+-------------+------------+------------+-------+---------------+------+---------+------+------+----------+-----------------------+
|  1 | PRIMARY     | <derived2> | NULL       | ALL   | NULL          | NULL | NULL    | NULL |   32 |   100.00 | NULL                  |
|  2 | DERIVED     | sbtest1    | NULL       | range | k_1           | k_1  | 4       | NULL |   18 |   100.00 | Using index condition |
|  3 | UNION       | sbtest2    | NULL       | range | k_2           | k_2  | 4       | NULL |   14 |   100.00 | Using index condition |
+----+-------------+------------+------------+-------+---------------+------+---------+------+------+----------+-----------------------+
3 rows in set, 1 warning (0.00 sec)

[MySQL 8.0.29][test]> explain analyze select * from v_sbtest where k between 100000 and 200000;
+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| EXPLAIN                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |
+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| -> Table scan on v_sbtest  (cost=0.09..2.90 rows=32) (actual time=0.001..0.006 rows=32 loops=1)
    -> Union materialize  (cost=18.21..21.02 rows=32) (actual time=0.203..0.215 rows=32 loops=1)
        -> Index range scan on sbtest1 using k_1 over (100000 <= k <= 200000), with index condition: (sbtest1.k between 100000 and 200000)  (cost=8.36 rows=18) (actual time=0.026..0.112 rows=18 loops=1)
        -> Index range scan on sbtest2 using k_2 over (100000 <= k <= 200000), with index condition: (sbtest2.k between 100000 and 200000)  (cost=6.56 rows=14) (actual time=0.009..0.067 rows=14 loops=1)
 |
+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+

对比同一个视图的查询,在MySQL 5.7.26 和 MySQL 8.0.29不同版本间,前者耗时13.9秒,后者耗时0.221毫秒,查询效率相差6万倍。主要原因就是,MySQL 8.0.29中使用到了派生条件下推特性,利用基表上的索引提前过滤数据,从而大大提升了视图的查询效率。


总结

MySQL 8.0的优化器中,实现了对派生条件的下推优化,可以将外层查询的过滤条件下推到派生表内部,以提前过滤派生表的返回数据,同时可以使用到派生表上的索引以优化查询性能。尤其是MySQL 8.0.29 开始支持对包含union聚合的派生表使用派生条件下推优化。

派生条件下推优化,从根本上解决MySQL 视图的查询无法使用基表的索引导致性能低下这一顽疾。在MySQL 8.0中,《MySQL开发规范》已经过时了,DBA该考虑考虑将禁止使用视图的规定重新修订一下了。

喜欢的同学麻烦点个关注和点赞

<think>嗯,用户问的是数据表运行突然变慢,如何优化。首先,我需要理解“数据表运行突然降低”具体指的是什么。可能是指查询速度下降,或者整个表的操作变慢了。这种情况通常发生在数据量激增、索引失效、锁争用或者资源瓶颈的情况下。 用户之前已经询问过数据库参数调优的问题,现在转向具体的表级优化,说明他们可能已经调整过数据库配置,但遇到了更具体的问题。需要从多个方面入手,比如检查查询性能、索引状态、锁和事务、表结构设计等。 首先应该考虑的是索引问题。用户可能没有合适的索引,或者索引失效,导致查询变慢。比如,频繁的更新操作可能导致索引碎片化,或者新增的数据没有及时统计信息,导致执行计划不准确。需要建议他们使用EXPLAIN分析慢查询,检查索引的使用情况。 其次,数据量增加可能导致全表扫描,这时候需要确认是否有必要的分区或者归档机制。如果表变得非常大,即使有索引,查询也可能变慢。分区表或者分库分表可能是解决方案。 另外,锁争用也是一个常见的问题。长时间运行的事务或者未提交的事务可能持有锁,阻塞其他操作。需要检查是否有死锁或锁等待的情况,特别是在高并发环境下。 表结构设计是否合理也是一个方面。比如,过多的宽表、冗余字段或者不合理的字段类型都可能影响性能。规范化的表结构或者适当的反规范化可能有助于提升性能。 还需要考虑硬件和资源问题,比如磁盘I/O、CPU使用率是否过高,或者内存不足导致频繁的磁盘交换。不过用户之前已经讨论过参数调优,这里可能需要结合系统监控数据来判断。 此外,统计信息的更新也很重要。数据库依赖统计信息来生成执行计划,如果统计信息过时,优化器可能选择低效的执行路径。定期更新统计信息可以避免这个问题。 可能还需要检查是否有触发器、外键约束或者复杂的视图影响了性能。这些对象可能在数据操作时带来额外的开销。 最后,建议用户使用监控工具定位具体瓶颈,比如慢查询日志、性能模式(如MySQL的Performance Schema)或者PostgreSQL的pg_stat_statements,来具体分析问题根源,再针对性地优化。</think>以下是针对 **数据表性能突然下降** 的优化排查与解决方案,按优先级分步骤说明: --- ### 一、紧急排查步骤(5分钟内定位问题) #### 1. **检查实时负载** ```sql -- MySQL SHOW PROCESSLIST; -- 查看阻塞的SQL和锁状态 SHOW ENGINE INNODB STATUS; -- 分析死锁和事务 -- PostgreSQL SELECT * FROM pg_stat_activity WHERE state <> 'idle'; -- 活跃查询 SELECT * FROM pg_locks WHERE granted = false; -- 锁等待 ``` #### 2. **识别高耗时操作** - 若出现大量 **`SELECT ... FOR UPDATE`** 或 **`LOCK WAIT`**,可能存在事务未提交导致的锁争用 - 观察 **`Waiting for table metadata lock`**(MySQL)或 **`AccessExclusiveLock`**(PostgreSQL),可能是DDL操作阻塞读写 #### 3. **快速止血方案** - **终止阻塞进程**(谨慎操作): ```sql -- MySQL KILL [进程ID]; -- PostgreSQL SELECT pg_cancel_backend([PID]); -- 取消查询 SELECT pg_terminate_backend([PID]); -- 终止进程 ``` - **紧急扩容**:临时提升CPU/内存资源(云数据库适用) --- ### 二、系统性优化流程(按优先级排序) #### 🔥 **优先级1:索引失效或缺失** - **现象**:全表扫描(`rows_examined`远大于`rows_sent`) - **优化方法**: 1. 使用 **`EXPLAIN`** 分析慢查询: ```sql EXPLAIN SELECT * FROM table WHERE create_time > '2024-01-01'; ``` 2. **添加复合索引**(覆盖查询条件 + 排序字段): ```sql -- 示例:为时间范围和状态字段建索引 CREATE INDEX idx_time_status ON table(create_time, status); ``` 3. **重建碎片化索引**(索引碎片率>30%时): ```sql -- MySQL ALTER TABLE table ENGINE=InnoDB; ANALYZE TABLE table; -- 更新统计信息 -- PostgreSQL REINDEX INDEX index_name; VACUUM ANALYZE table; ``` #### 🔥 **优先级2:数据量暴增** - **现象**:单表超过 **500万行** 或 **磁盘占用增长10倍+** - **优化方法**: 1. **冷热数据分离**: ```sql -- 将历史数据迁移到归档表 CREATE TABLE archive_table AS SELECT * FROM main_table WHERE create_time < '2023-01-01'; DELETE FROM main_table WHERE create_time < '2023-01-01'; ``` 2. **分区表**(MySQL 8.0+/PostgreSQL): ```sql -- MySQL 按时间范围分区 ALTER TABLE table PARTITION BY RANGE (YEAR(create_time)) ( PARTITION p2023 VALUES LESS THAN (2024), PARTITION p2024 VALUES LESS THAN (2025) ); ``` 3. **分库分表**:使用ShardingSphere、Vitess等工具拆分大表 #### 🔥 **优先级3:锁争用与事务问题** - **现象**:大量 **`Lock wait timeout`** 或 **`Deadlock found`** - **优化方法**: 1. **缩短事务**:避免在事务中执行耗时操作(如文件IO) 2. **降低隔离级别**(谨慎评估): ```sql SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED; ``` 3. **使用乐观锁**:通过版本号控制并发更新 ```sql UPDATE table SET col=new_val, version=version+1 WHERE id=1 AND version=old_version; ``` #### 🔥 **优先级4:统计信息偏差** - **现象**:执行计划突然变差(如索引未被选择) - **优化方法**: - **强制刷新统计信息**: ```sql -- MySQL ANALYZE TABLE table; -- PostgreSQL VACUUM ANALYZE table; ``` - **手动指定索引**(最后手段): ```sql SELECT * FROM table USE INDEX (index_name) WHERE ...; ``` --- ### 三、深度优化方案 #### 1. **表结构设计缺陷** - **问题**:存在 **`TEXT`/`BLOB`大字段**、**冗余列**、**未规范化设计** - **优化**: - 拆分大字段到扩展表 - 用 **`ENUM`** 替代低基数字符串 - 添加 **`NOT NULL`** 约束并设置默认值 #### 2. **硬件/存储瓶颈** - **检测指标**: - 磁盘IO利用率 > 80% - CPU iowait > 20% - **优化**: - 升级SSD并启用 **`TRIM`** - 分离数据文件和日志文件到不同磁盘 #### 3. **查询模式变化** - **分析慢日志**(MySQL示例): ```ini # my.cnf 配置 slow_query_log = 1 long_query_time = 1 -- 捕获>1秒的查询 ``` - **重写低效SQL**: - 避免 `SELECT *` → 明确指定字段 - 用 `JOIN` 替代嵌套子查询 - 分页优化: ```sql -- 低效 SELECT * FROM table LIMIT 1000000, 20; -- 优化(按连续主键) SELECT * FROM table WHERE id > 1000000 LIMIT 20; ``` --- ### 四、监控与防御体系 #### 1. **实时预警配置** | 监控项 | 阈值 | 工具 | |-----------------------|-----------------------|--------------------------| | 慢查询率 | >5% | Prometheus + Grafana | | 锁等待时间 | >5秒 | Percona Monitoring | | 缓冲池命中率(MySQL) | <95% | MySQL Enterprise Monitor | | 索引扫描率(PG) | 全表扫描占比>10% | pg_stat_statements | #### 2. **定期维护任务** ```bash # MySQL 自动化脚本 0 3 * * * mysql -e "ANALYZE TABLE critical_table; OPTIMIZE TABLE archive_table;" # PostgreSQL 自动化 0 4 * * * vacuumdb --analyze --dbname=mydb ``` --- ### 五、典型案例分析 #### 案例:电商订单表查询变慢 - **现象**:`SELECT * FROM orders WHERE user_id=123 AND status='paid'` 耗时从10ms升至2秒 - **根因**: - 数据量:从100万增至2000万行 - 索引:仅存在单列`user_id`索引,复合筛选效率低 - **解决方案**: 1. 创建复合索引:`CREATE INDEX idx_user_status ON orders(user_id, status)` 2. 归档3年前数据到历史表 3. 优化查询:只返回必要字段(如`order_id, amount`) --- 通过以上步骤,80%的表性能问题可快速定位解决。若仍未见效,需结合 **APM工具**(如Arthas、pt-query-digest)进行SQL链路级跟踪分析。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值