SQL优化步骤

27 阅读6分钟

定位低效SQL语句

:通过慢查日志等工具定位执行效率低的SQL语句,这是优化的第一步。

执行计划分析

:使用EXPLAIN分析SQL的执行计划,重点关注type、rows、filtered、extra等参数。

  • type 连接类型 system > const > eq_reg > ref > range > index > ALL
  • rows 根据表统计信息及索引选用情况,大致估算出找到所需的记录所需要读取的行数。不是结果集里的行数。
  • filtered 显示了通过条件过滤出的行数的百分比估计值
    • 表示存储引擎返回数据后,经过服务器层进一步筛选后 剩余有效数据的预估百分比
    • filtered = 100 ,表示所有从存储引擎读取的行均满足查询条件,无需额外过滤(理想状态)
    • filtered=18,表明仅 18% 的数据通过查询条件筛选,剩余 82% 被丢弃,存在优化空间
  • extra 解析查询的额外信息

性能分析

:通过SHOW PROFILE对SQL进行性能分析,找出瓶颈所在。

mysql> SHOW VARIABLES LIKE 'profiling';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| profiling     | OFF   |
+---------------+-------+
1 row in set, 1 warning (0.00 sec)

mysql> SET profiling = 1;
Query OK, 0 rows affected, 1 warning (0.00 sec)

mysql> select * from user;
+----+----------+------+--------------------+-------------+---------+
| id | name     | age  | id_no              | phone       | address |
+----+----------+------+--------------------+-------------+---------+
|  1 | zhangsan |   19 | 341223199008212345 | 18365265123 | nanjing |
|  2 | lisi     |   17 | 341223199608212346 | 18365265124 | nanjing |
|  3 | wangwu   |   22 | 341223199008212347 | 18365265125 | nanjing |
+----+----------+------+--------------------+-------------+---------+
3 rows in set (0.00 sec)

mysql> show profiles;
+----------+------------+--------------------+
| Query_ID | Duration   | Query              |
+----------+------------+--------------------+
|        1 | 0.00030400 | select * from user |
+----------+------------+--------------------+
1 row in set, 1 warning (0.00 sec)

mysql> show profile for query 1;
+--------------------------------+----------+
| Status                         | Duration |
+--------------------------------+----------+
| starting                       | 0.000065 |
| Executing hook on transaction  | 0.000004 |
| starting                       | 0.000006 |
| checking permissions           | 0.000003 |
| Opening tables                 | 0.000025 |
| init                           | 0.000003 |
| System lock                    | 0.000006 |
| optimizing                     | 0.000003 |
| statistics                     | 0.000017 |
| preparing                      | 0.000019 |
| executing                      | 0.000064 |
| end                            | 0.000003 |
| query end                      | 0.000002 |
| waiting for handler commit     | 0.000026 |
| closing tables                 | 0.000008 |
| freeing items                  | 0.000046 |
| cleaning up                    | 0.000007 |
+--------------------------------+----------+
17 rows in set, 1 warning (0.00 sec)

SHOW PROFILE 的输出中,常见的阶段包括:

  • starting:SQL 语句开始执行。
  • checking permissions:检查权限。
  • Opening tables:打开表。
  • init:初始化。
  • System lock:系统锁。
  • optimizing:优化查询。
  • statistics:统计信息。
  • preparing:准备执行。
  • executing:执行查询。
  • Sending data:发送数据。
  • end:结束。
  • query end:查询结束。
  • closing tables:关闭表。
  • freeing items:释放资源。
  • cleaning up:清理。

在 MySQL 8.0 中,推荐使用 Performance Schema 进行更全面的性能分析。

mysql> UPDATE performance_schema.setup_instruments SET ENABLED = 'YES', TIMED = 'YES';
Query OK, 984 rows affected (0.01 sec)
Rows matched: 1253  Changed: 984  Warnings: 0

mysql> UPDATE performance_schema.setup_consumers SET ENABLED = 'YES';
Query OK, 9 rows affected (0.00 sec)
Rows matched: 16  Changed: 9  Warnings: 0

mysql> select * from user;
+----+----------+------+--------------------+-------------+---------+
| id | name     | age  | id_no              | phone       | address |
+----+----------+------+--------------------+-------------+---------+
|  1 | zhangsan |   19 | 341223199008212345 | 18365265123 | nanjing |
|  2 | lisi     |   17 | 341223199608212346 | 18365265124 | nanjing |
|  3 | wangwu   |   22 | 341223199008212347 | 18365265125 | nanjing |
+----+----------+------+--------------------+-------------+---------+
3 rows in set (0.00 sec)


mysql> SELECT * FROM performance_schema.events_statements_history_long order by TIMER_START desc limit 1\G;
*************************** 1. row ***************************
              THREAD_ID: 50
               EVENT_ID: 337
           END_EVENT_ID: 399
             EVENT_NAME: statement/sql/select
                 SOURCE: init_net_server_extension.cc:105
            TIMER_START: 887238175800000
              TIMER_END: 887238426600000
             TIMER_WAIT: 250800000
              LOCK_TIME: 2000000
               SQL_TEXT: select * from user
                 DIGEST: 4681397545a73eb8e3b6bb1e918323c06d906d4114619ef72013aa442fced2ba
            DIGEST_TEXT: SELECT * FROM SYSTEM_USER
         CURRENT_SCHEMA: mcc
            OBJECT_TYPE: NULL
          OBJECT_SCHEMA: NULL
            OBJECT_NAME: NULL
  OBJECT_INSTANCE_BEGIN: NULL
            MYSQL_ERRNO: 0
      RETURNED_SQLSTATE: NULL
           MESSAGE_TEXT: NULL
                 ERRORS: 0
               WARNINGS: 0
          ROWS_AFFECTED: 0
              ROWS_SENT: 3
          ROWS_EXAMINED: 3
CREATED_TMP_DISK_TABLES: 0
     CREATED_TMP_TABLES: 0
       SELECT_FULL_JOIN: 0
 SELECT_FULL_RANGE_JOIN: 0
           SELECT_RANGE: 0
     SELECT_RANGE_CHECK: 0
            SELECT_SCAN: 1
      SORT_MERGE_PASSES: 0
             SORT_RANGE: 0
              SORT_ROWS: 0
              SORT_SCAN: 0
          NO_INDEX_USED: 1
     NO_GOOD_INDEX_USED: 0
       NESTING_EVENT_ID: NULL
     NESTING_EVENT_TYPE: NULL
    NESTING_EVENT_LEVEL: 0
           STATEMENT_ID: 26
               CPU_TIME: 0
  MAX_CONTROLLED_MEMORY: 1094784
       MAX_TOTAL_MEMORY: 1692963
       EXECUTION_ENGINE: PRIMARY
1 row in set (0.00 sec)

ERROR:
No query specified
  • 启用 profiling:在分析 SQL 性能时启用 profiling。
  • 分析耗时阶段:重点关注耗时较长的阶段,优化相关操作。
  • 在 MySQL 8.0 中,使用 Performance Schema 替代 SHOW PROFILE。

Trace分析

:使用TRACE工具进一步分析SQL执行的详细过程。

SET optimizer_trace="enabled=on";

select * from user;

SELECT * FROM INFORMATION_SCHEMA.OPTIMIZER_TRACE;

SET optimizer_trace="enabled=off";

mysql> SET optimizer_trace="enabled=on";
Query OK, 0 rows affected (0.00 sec)

mysql> select * from user;
+----+----------+------+--------------------+-------------+---------+
| id | name     | age  | id_no              | phone       | address |
+----+----------+------+--------------------+-------------+---------+
|  1 | zhangsan |   19 | 341223199008212345 | 18365265123 | nanjing |
|  2 | lisi     |   17 | 341223199608212346 | 18365265124 | nanjing |
|  3 | wangwu   |   22 | 341223199008212347 | 18365265125 | nanjing |
+----+----------+------+--------------------+-------------+---------+
3 rows in set (0.00 sec)

mysql>
mysql> SELECT * FROM INFORMATION_SCHEMA.OPTIMIZER_TRACE\G;
*************************** 1. row ***************************
                            QUERY: select * from user
                            TRACE: {
  "steps": [
    {
      "join_preparation": {
        "select#": 1,
        "steps": [
          {
            "expanded_query": "/* select#1 */ select `user`.`id` AS `id`,`user`.`name` AS `name`,`user`.`age` AS `age`,`user`.`id_no` AS `id_no`,`user`.`phone` AS `phone`,`user`.`address` AS `address` from `user`"
          }
        ]
      }
    },
    {
      "join_optimization": {
        "select#": 1,
        "steps": [
          {
            "table_dependencies": [
              {
                "table": "`user`",
                "row_may_be_null": false,
                "map_bit": 0,
                "depends_on_map_bits": [
                ]
              }
            ]
          },
          {
            "rows_estimation": [
              {
                "table": "`user`",
                "table_scan": {
                  "rows": 2,
                  "cost": 0.25
                }
              }
            ]
          },
          {
            "considered_execution_plans": [
              {
                "plan_prefix": [
                ],
                "table": "`user`",
                "best_access_path": {
                  "considered_access_paths": [
                    {
                      "rows_to_scan": 2,
                      "access_type": "scan",
                      "resulting_rows": 2,
                      "cost": 0.45,
                      "chosen": true
                    }
                  ]
                },
                "condition_filtering_pct": 100,
                "rows_for_plan": 2,
                "cost_for_plan": 0.45,
                "chosen": true
              }
            ]
          },
          {
            "attaching_conditions_to_tables": {
              "original_condition": null,
              "attached_conditions_computation": [
              ],
              "attached_conditions_summary": [
                {
                  "table": "`user`",
                  "attached": null
                }
              ]
            }
          },
          {
            "finalizing_table_conditions": [
            ]
          },
          {
            "refine_plan": [
              {
                "table": "`user`"
              }
            ]
          }
        ]
      }
    },
    {
      "join_execution": {
        "select#": 1,
        "steps": [
        ]
      }
    }
  ]
}
MISSING_BYTES_BEYOND_MAX_MEM_SIZE: 0
          INSUFFICIENT_PRIVILEGES: 0
1 row in set (0.00 sec)

ERROR:
No query specified

总结

确定问题并采取措施:根据以上分析确定问题所在,并采取相应的优化措施。

### SQL优化十大案例

  1. 最左匹配原则:在复合索引中,MySQL会从左到右匹配条件,未携带最左字段的条件会导致索引失效。
  2. 隐式转换问题:字符与数字的隐式转换可能导致索引失效,应确保类型一致。
  3. 大分页问题:对于大分页查询,可以通过延迟关联或携带上次查询的最后一条记录的值来优化。
  4. IN + ORDER BY问题:当IN条件与ORDER BY结合时,需要确保索引能够覆盖查询条件。
  5. 范围查询阻断:范围查询会阻断后续字段使用索引,应避免在复合索引中使用范围查询。
  6. 不等于和不包含:这些条件可能导致无法使用索引,可以考虑使用ICP(Index Condition Pushdown)。
  7. 优化器不使用索引:有时候优化器可能不选择使用索引,可以通过调整查询或强制索引来优化。
  8. 复杂查询优化:复杂查询往往涉及多表关联和子查询,优化这类查询需要综合考虑索引和查询逻辑。
  9. ASC和DESC混用:在同一个查询中混用ASC和DESC可能会导致索引失效,应避免这种情况。
  10. 大数据量处理:对于大数据量的表,优化索引和查询逻辑尤为重要,可以考虑分区表和物化视图等技术。

SQL性能提升秘籍:5步优化法与10个实战案例-阿里云开发者社区