使用Explain判断SQL是否可以使用列存索引

本文将介绍如何使用Explain来判断SQL语句是否能够使用列存索引来加速查询。

分析示例

PolarDB MySQL集群中,列存执行计划以横向树的形式输出,该格式与行存执行计划的输出格式存在明显区别。您可以通过使用Explain查看SQL的执行计划,来判断某条SQL语句是否可以使用列存索引加速功能。

SQL示例:

EXPLAIN SELECT l_orderkey, SUM(l_extendedprice * (1 - l_discount)) AS revenue, o_orderdate, o_shippriority
FROM customer, orders, lineitem
WHERE c_mktsegment = 'BUILDING'
AND c_custkey = o_custkey
AND l_orderkey = o_orderkey
AND o_orderdate < date '1995-03-24'
AND l_shipdate > date '1995-03-24'
GROUP BY l_orderkey, o_orderdate, o_shippriority
ORDER BY revenue DESC, o_orderdate;

行存执行计划显示结果:

+----+-------------+----------+------------+------+--------------------+------------+---------+-----------------------------+--------+----------+----------------------------------------------+
| id | select_type | table    | partitions | type | possible_keys      | key        | key_len | ref                         | rows   | filtered | Extra                                        |
+----+-------------+----------+------------+------+--------------------+------------+---------+-----------------------------+--------+----------+----------------------------------------------+
|  1 | SIMPLE      | customer | NULL       | ALL  | PRIMARY            | NULL       | NULL    | NULL                        | 147630 |    10.00 | Using where; Using temporary; Using filesort |
|  1 | SIMPLE      | orders   | NULL       | ref  | PRIMARY,ORDERS_FK1 | ORDERS_FK1 | 4       | tpch100g.customer.C_CUSTKEY |     14 |    33.33 | Using where                                  |
|  1 | SIMPLE      | lineitem | NULL       | ref  | PRIMARY            | PRIMARY    | 4       | tpch100g.orders.O_ORDERKEY  |      4 |    33.33 | Using where                                  |
+----+-------------+----------+------------+------+--------------------+------------+---------+-----------------------------+--------+----------+----------------------------------------------+
3 rows in set, 1 warning (0.00 sec)

列存执行计划显示结果(横向树形式):

+----+----------------------------+----------+--------+--------+-----------------------------------------------------------------------------+
| ID | Operator                   | Name     | E-Rows | E-Cost | Extra Info                                                                  |
+----+----------------------------+----------+--------+--------+-----------------------------------------------------------------------------+
|  1 | Select Statement           |          |        |        | IMCI Execution Plan (max_dop = 4, max_query_mem = 858993459)                |
|  2 | └─Sort                     |          |        |        | Sort Key: revenue DESC,o_orderdate ASC                                      |
|  3 |   └─Hash Groupby           |          |        |        | Group Key: (lineitem.L_ORDERKEY, orders.O_ORDERDATE, orders.O_SHIPPRIORITY) |
|  4 |     └─Hash Join            |          |        |        | Join Cond: orders.O_ORDERKEY = lineitem.L_ORDERKEY                          |
|  5 |       ├─Hash Join          |          |        |        | Join Cond: customer.C_CUSTKEY = orders.O_CUSTKEY                            |
|  6 |       │ ├─Table Scan       | customer |        |        | Cond: (C_MKTSEGMENT = "BUILDING")                                           |
|  7 |       │ └─Table Scan       | orders   |        |        | Cond: (O_ORDERDATE < 03/24/1995)                                            |
|  8 |       └─Table Scan         | lineitem |        |        | Cond: (L_SHIPDATE > 03/24/1995)                                             |
+----+----------------------------+----------+--------+--------+-----------------------------------------------------------------------------+
8 rows in set (0.01 sec)

相关文档

列存索引只能访问被其所覆盖的列。因此,当您的SQL语句中所需的表或列未被列存索引完全覆盖时,列存索引将无法生效。

  • 检查SQL语句中的表是否已创建列索引:存储过程dbms_imci.check_columnar_index()能够解析输入的SQL语句,提取该语句中使用的所有列,并验证这些列是否被列索引所覆盖。

  • 获取创建列存索引的DDL语句:存储过程dbms_imci.columnar_advise()可以针对输入的SQL语句,生成相应的用于添加列存索引的DDL语句。执行后,可以确保该SQL语句中涉及的所有列均被列索引覆盖。

常见问题

为什么SQL语句并没有走列存索引?

新增只读列存节点后,需要为SQL语句中所查询的表都增加列存索引,且SQL语句的预估执行代价超过一定阈值,该SQL语句才会使用列索引进行查询。另外,SQL语句需要被转发到只读列存节点,才可以使用列存索引进行查询加速。一般来说,如果一条SQL语句无法使用列存索引进行查询,可以按照以下步骤进行问题排查:

  1. 确认SQL是否被转发至只读列存节点。

    • 使用的数据库连接地址中的服务节点是否包含只读列存节点。

    • 通过SQL洞察功能,可以确认SQL是否被转发到了只读列存节点。具体信息,请参见SQL洞察

    如果使用集群地址并开启行存/列存自动引流,同时SQL预估执行代价高于设定的阈值loose_imci_ap_thresholdloose_cost_threshold_for_imci,则数据库代理将会将SQL转发至只读列存节点。此外,您可以在SQL语句的SELECT关键字前添加HINT语法/*FORCE_IMCI_NODES*/以强制指定将SQL转发到只读列存节点。具体信息,请参见配置自动引流阈值。示例如下:

    内核版本8.0.1.1.398.0.2.2.23及之后的版本,参数loose_imci_ap_threshold被弃用,统一使用参数loose_cost_threshold_for_imci
    /*FORCE_IMCI_NODES*/EXPLAIN SELECT COUNT(*) FROM t1 WHERE t1.a > 1;
    创建一个新的数据库连接地址,可以保证SQL语句一定会被转发到只读列存节点上执行。具体信息,请参见创建一个新的数据库连接地址
  2. SQL预估执行代价是否高于设定的阈值。

    在只读列存节点上,优化器会对SQL进行执行代价预估,如果预估执行代价高于设定的阈值loose_imci_ap_thresholdloose_cost_threshold_for_imci,则使用列存索引进行查询,否则将使用原有的行索引进行查询。

    在确认SQL被转发到只读列存节点后,如果通过EXPLAIN查看执行计划依然没有使用列存索引,可以通过比较预估执行代价与预设的阈值,判断是否是预估执行代价过小而没有使用列存索引。您可以通过查询Last_query_cost变量获取“上一条SQL预估执行代价”:

    -- 使用EXPLAIN查看SQL的执行计划
    EXPLAIN SELECT * FROM t1;
    -- 获取上一条SQL的预估执行代价
    SHOW STATUS LIKE 'Last_query_cost';
    如果使用集群地址连接数据库,建议您在SHOW STATUS LIKE 'Last_query_cost'前添加HINT语法/*ROUTE_TO_LAST_USED*/,以确保能够在正确的节点上查询到上一条语句的预估执行代价。例如,/*ROUTE_TO_LAST_USED*/SHOW STATUS LIKE 'Last_query_cost';

    SQL预估执行代价小于预设的阈值,可以考虑调整loose_imci_ap_thresholdloose_cost_threshold_for_imci的值。例如,使用HINT语法调整单条SQL的预设阈值:

    /*FORCE_IMCI_NODES*/EXPLAIN SELECT /*+ SET_VAR(cost_threshold_for_imci=0) */ COUNT(*) FROM t1 WHERE t1.a > 1;
  3. SQL中所涉及的表或列是否被列存索引完全覆盖。

    可以通过内置的存储过程dbms_imci.check_columnar_index('<query_string>'),检查SQL语句中的表或列是否已创建列存索引。具体信息,请参见检查SQL语句中的表或列是否已创建列存索引。示例如下:

    CALL dbms_imci.check_columnar_index('SELECT COUNT(*) FROM t1 WHERE t1.a > 1');

    如果SQL未被列存索引完全覆盖,调用该存储过程将返回未被覆盖的表和列,您需对返回的表和列逐一创建列存索引。如果已经完全被列存索引覆盖,则调用该存储过程将返回空结果集。

  4. 是否有不支持的SQL特性。

    通过查看列存索引语法使用限制,确认某个SQL特性是否支持列存索引。具体信息,请参见列存索引语法使用限制

如按上述步骤排查后,SQL语句仍然没有走列存索引,可通过专家面对面进行咨询或联系我们

如何为SQL增加合适的列存索引?

建议您为SQL中所需的列添加列存索引。具体信息,请参见检查SQL语句中的表或列是否已创建列存索引

当一条SQL语句所需的列被列存索引完全覆盖时,该SQL语句才能使用列存索引进行查询。如果SQL语句中所需的列未被列存索引完全覆盖,可以通过ALTER TABLE语句来增加列存索引。PolarDB为您提供了一系列内置存储过程以辅助此操作。

说明
  • 使用dbms_imci.columnar_advise()存储过程可以获得某个SQL语句所需的创建列存索引DDL语句。按照这个DDL语句构建列存索引,可以保证该SQL语句完全被列存索引覆盖。具体信息,请参见获取创建列存索引的DDL语句

    dbms_imci.columnar_advise('<query_string>');
  • 使用dbms_imci.columnar_advise_begin()dbms_imci.columnar_advise_end()以及dbms_imci.columnar_advise()存储过程,可以获得一批SQL语句所需的创建列存索引DDL语句。具体信息,请参见批量获取创建列存索引的DDL语句