dbaplus社群 10月31日 08:52
MySQL的optimizer_switch参数:开启或关闭的考量
index_new5.html
../../../zaker_core/zaker_tpl_static/wap/tpl_guoji1.html

 

MySQL的`optimizer_switch`参数中的`prefer_ordering_index`选项,旨在优化`ORDER BY`和`GROUP BY`结合`LIMIT`的查询,优先使用已排序索引。然而,在数据分布不均的情况下,此选项可能导致性能问题。当查询命中了绝大部分数据行(如99%),且该列(如性别、状态)选择性极低时,优化器选择有序索引会带来巨大的回表IO开销,可能比全表扫描后的Filesort开销更大。因此,在实际应用中,许多DBA倾向于关闭此选项,以避免在处理低选择性列的`GROUP BY`或`ORDER BY`查询时出现性能瓶颈。

💡 **`prefer_ordering_index`参数的初衷与挑战**:MySQL的`prefer_ordering_index`参数本意是为了优化`ORDER BY`和`GROUP BY`结合`LIMIT`的查询,通过优先选择已有排序规则的索引来替代`filesort`。然而,当索引列的数据分布极不均匀时,例如在一个包含性别或状态的索引列中,绝大多数数据属于同一类别,优化器选择该索引反而可能导致大量的索引扫描和回表操作,降低查询效率。

📉 **低选择性列与索引的性能陷阱**:当查询条件的选择性极低(如命中了99%的数据行),且优化器选择了有序索引时,逐行扫描索引并回表读取数据的IO开销会非常巨大,甚至高于全表扫描后进行`filesort`的开销。这是因为大量的随机IO(回表)比顺序IO(全表扫描)更耗时,尤其是在数据量庞大的情况下。

⚙️ **实践中的权衡与选择**:鉴于上述潜在问题,许多数据库管理员(DBA)在实际操作中倾向于关闭`prefer_ordering_index`选项。这是为了规避在数据分布不均且遇到`GROUP BY`或`ORDER BY`查询时,优化器错误地使用索引而导致性能下降的情况,从而确保查询的稳定性和效率。

🧪 **实验验证**:通过`EXPLAIN`命令对开启和关闭`prefer_ordering_index`参数时的查询计划进行对比,可以直观地看到优化器在`ORDER BY gender LIMIT 10`和`GROUP BY gender`等场景下的行为差异。当参数关闭时,查询可能会转为使用`Using filesort`,而在某些情况下,即使`GROUP BY`,关闭该参数也可能不会导致性能下降,甚至在数据分布不均时表现更好。

2025-10-31 07:15 广东

MySQL推出这个参数本来是好意,但容易好心办坏事……

MySQL在SQL执行中有一个一直没有明晰,且没有定论的问题,这个问题就是SET optimizer_switch = 'prefer_ordering_index=off'; 可他默认的设置是ON。我到底应该是ON 还是OFF。

数据库配置选项

这个优化器的主要作用针对order by 和 group by中的搭配limit的查询,决定优化器是否优先选择已经建立排序规则的有序索引问题,来替代在查询中的 filesort等优化数据提取后,在排序的问题。

那么问题来了,这不是一个好的功能吗? 为什么有一种说法,要关掉这个配置建议off处理。

这个问题主要发生在以下情况中:

你的数据分布的不均匀,优化器在选择这个索引进行数据排序的时候,这个索引中包含,如性别,状态,等被包含在group by order by 中,而这些数据根本不具备索引扫描的优势,属于通篇都是女,然后就导致 index scan ,然后排序毫无用处,还需要回表。

具体总结为:

查询命中了绝大部分(例如 99%)的数据行。

优化器如果选择了这个有序索引:它会逐行扫描索引并回表读取数据。

由于需要读取几乎所有的数据行,使用索引带来的回表 IO 开销(随机 IO)变得巨大,远高于全表扫描的顺序 IO。

同时,由于 WHERE 子句的选择性极低,即使使用全表扫描后进行排序(Filesort),排序操作的开销也可能比大量的随机回表 IO 低得多.

我们做一个简单的练习

    DROP TABLE IF EXISTS users;
    CREATE TABLE users (
        id BIGINT AUTO_INCREMENT PRIMARY KEY,
        name VARCHAR(50),
        gender CHAR(1),  -- 'M' 或 'F'
        age INT,
        INDEX idx_gender (gender)
    );

      DELIMITER //


      CREATE PROCEDURE load_users(IN total INT)
      BEGIN
          DECLARE i INT DEFAULT 1;
          WHILE i <= total DO
              INSERT INTO users(name, gender, age)
              VALUES (
                  CONCAT('user_', i),
                  IF(RAND() > 0.5'M''F'),
                  FLOOR(20 + (RAND() * 30))
              );
              SET i = i + 1;
          END WHILE;
      END //


      DELIMITER ;

        -- 开启 prefer_ordering_index
        SET optimizer_switch = 'prefer_ordering_index=on';
        EXPLAIN SELECT * FROM users ORDER BY gender LIMIT 10;


        -- 关闭 prefer_ordering_index
        SET optimizer_switch = 'prefer_ordering_index=off';
        EXPLAIN SELECT * FROM users ORDER BY gender LIMIT 10;

          mysql> SET optimizer_switch = 'prefer_ordering_index=on';
          Query OK, 0 rows affected (0.00 sec)


          mysql> EXPLAIN SELECT * FROM users ORDER BY gender LIMIT 10;
          +----+-------------+-------+------------+-------+---------------+------------+---------+------+------+----------+-------+
          | id | select_type | table | partitions | type  | possible_keys | key        | key_len | ref  | rows | filtered | Extra |
          +----+-------------+-------+------------+-------+---------------+------------+---------+------+------+----------+-------+
          |  1 | SIMPLE      | users | NULL       | index | NULL          | idx_gender | 5       | NULL |   10 |   100.00 | NULL  |
          +----+-------------+-------+------------+-------+---------------+------------+---------+------+------+----------+-------+
          1 row inset, 1 warning (0.00 sec)


          mysql> SET optimizer_switch = 'prefer_ordering_index=off';
          Query OK, 0 rows affected (0.00 sec)


          mysql> EXPLAIN SELECT * FROM users ORDER BY gender LIMIT 10;
          +----+-------------+-------+------------+------+---------------+------+---------+------+--------+----------+----------------+
          | id | select_type | table | partitions | type | possible_keys | key  | key_len | ref  | rows   | filtered | Extra          |
          +----+-------------+-------+------------+------+---------------+------+---------+------+--------+----------+----------------+
          |  1 | SIMPLE      | users | NULL       | ALL  | NULL          | NULL | NULL    | NULL | 997227 |   100.00 | Using filesort |
          +----+-------------+-------+------------+------+---------------+------+---------+------+--------+----------+----------------+
          1 row inset, 1 warning (0.00 sec)

          -- 开启 prefer_ordering_index SET optimizer_switch = 'prefer_ordering_index=on'; EXPLAIN SELECT gender, COUNT(*) FROM users GROUP BY gender;

          -- 关闭 prefer_ordering_index SET optimizer_switch = 'prefer_ordering_index=off'; EXPLAIN SELECT gender, COUNT(*) FROM users GROUP BY gender;

            mysql> SET optimizer_switch = 'prefer_ordering_index=on';
            Query OK, 0 rows affected (0.00 sec)


            mysql> EXPLAIN SELECT gender, COUNT(*FROM users GROUP BY gender;
            +----+-------------+-------+------------+-------+---------------+------------+---------+------+--------+----------+-------------+
            | id | select_type | table | partitions | type  | possible_keys | key        | key_len | ref  | rows   | filtered | Extra       |
            +----+-------------+-------+------------+-------+---------------+------------+---------+------+--------+----------+-------------+
            |  1 | SIMPLE      | users | NULL       | index | idx_gender    | idx_gender | 5       | NULL | 997227 |   100.00 | Using index |
            +----+-------------+-------+------------+-------+---------------+------------+---------+------+--------+----------+-------------+
            1 row inset, 1 warning (0.04 sec)


            mysql> 
            mysql> -- 关闭 prefer_ordering_index
            mysql> SET optimizer_switch = 'prefer_ordering_index=off';
            Query OK, 0 rows affected (0.00 sec)


            mysql> EXPLAIN SELECT gender, COUNT(*FROM users GROUP BY gender;
            +----+-------------+-------+------------+-------+---------------+------------+---------+------+--------+----------+-------------+
            | id | select_type | table | partitions | type  | possible_keys | key        | key_len | ref  | rows   | filtered | Extra       |
            +----+-------------+-------+------------+-------+---------------+------------+---------+------+--------+----------+-------------+
            |  1 | SIMPLE      | users | NULL       | index | idx_gender    | idx_gender | 5       | NULL | 997227 |   100.00 | Using index |
            +----+-------------+-------+------------+-------+---------------+------------+---------+------+--------+----------+-------------+
            1 row inset, 1 warning (0.00 sec)

            写到这里,MySQL推出这个参数本来是好意,但是截至到目前,尚未得到在任何版本,智能化此问题的方案,对于业务和DBA来说,大部分情况选择的是关闭此选项,因为我们不能完全杜绝无可选择性下的数据和索引建立后,在遇到GROUP BY  ORDER BY 情况下的错误索引的在这类语句上得使用。

            作者介绍

            刘华阳,20年经历风霜雨打的 DBA,5年的 DBA 架构和团队管理经验,只要是数据库都喜欢学习。PostgreSQL ACE,MongoDB 狂热者,10年的 MYSQL 工作经验,现在在玩 POLARDB 与时俱进。

            来源丨公众号:AustinDatabases(ID:AustinDatabases)

            dbaplus社群欢迎广大技术人员投稿,投稿邮箱:editor@dbaplus.cn

            阅读原文

            跳转微信打开

            Fish AI Reader

            Fish AI Reader

            AI辅助创作,多种专业模板,深度分析,高质量内容生成。从观点提取到深度思考,FishAI为您提供全方位的创作支持。新版本引入自定义参数,让您的创作更加个性化和精准。

            FishAI

            FishAI

            鱼阅,AI 时代的下一个智能信息助手,助你摆脱信息焦虑

            联系邮箱 441953276@qq.com

            相关标签

            MySQL optimizer_switch prefer_ordering_index SQL优化 数据库性能 ORDER BY GROUP BY filesort 索引优化 DBA
            相关文章