一般情况下,优化器会为用户查询选择最佳的执行计划,不需要用户使用 HINT 指定,但在某些场景下,优化器生成的执行计划可能不满足用户的要求,这时就需要用户使用 HINT 来指定生成某种执行计划。

HINT 从语法上看是一种特殊的 SQL 注释,所不同的是在注释的左标记后(’/*‘ 符号)增加了一个“+”。 既然是注释,如果服务器端无法识别 SQL 语句中的 HINT,优化器会选择忽略用户 HINT 而使用默认的计划生成逻辑结构。另外,HINT 只影响优化器生成计划的逻辑结构,而不影响 SQL 语句的语义。

注意

如果使用 MySQL 的 C 客户端执行带 HINT 的 SQL 语句,需要使用 -c 选项登陆,否则 MySQL 客户端会将 HINT 作为注释从用户 SQL 中去除,导致系统无法收到用户 HINT。

HINT 参数

HINT 相关参数名称、语义和语法如下表:

  • qb_name 语法是:

  • TBL_NAME 语法是: [db_name.]relation_name [qb_name]

在 DML 语句中,每一个 query_block 都会有一个 QB_NAME(query block name),可以用户指定,也可以系统自动生成。在用户没有用 HINT 指定的 QB_NAME 的时候,系统会按照 SEL$1、SEL$2,UPD$1,DEL$1 方式从左到右(实际也是 Resolver 的解析顺序)依次生成。

通过 QB_NAME 可以精确定位每一个 table,也可以在一处地方指定任意 query block 的行为。在 TBL_NAME 中的 QB_NAME 用于定位 table,在 HINT 中最前面的 qb_name 用于定位 HINT 作用于哪一个 query_block。

如下例所示,按照默认规则,会为 SEL$1 中的 t 选择 t_c1 路径,为 SEL$2 中的 t 选择 PRIMARY(主表)访问。如果 SQL 通过 HINT 来指定 SEL$1 的 t 走主表,则 SEL$2 的 t 走索引。

  1. obclient>CREATE TABLE t(c1 INT, c2 INT, KEY t_c1(c1));
  2. Query OK, 0 rows affected (0.31 sec)
  3. obclient>EXPLAIN SELECT * FROM t , (SELECT * FROM t WHERE c2 = 1) ta
  4. WHERE t.c1 = 1\G
  5. *************************** 1. row ***************************
  6. Query Plan:
  7. ============================================================
  8. |ID|OPERATOR |NAME |EST. ROWS|COST|
  9. ------------------------------------------------------------
  10. |0 |NESTED-LOOP INNER JOIN CARTESIAN| |1 |1895|
  11. |1 | TABLE SCAN |t(t_c1)|1 |472 |
  12. |2 | TABLE SCAN |t |1 |1397|
  13. ============================================================
  14. Outputs & filters:
  15. -------------------------------------
  16. 0 - output([t.c1], [t.c2], [t.c1], [t.c2]), filter(nil),
  17. conds(nil), nl_params_(nil)
  18. 1 - output([t.c1], [t.c2]), filter(nil),
  19. access([t.c1], [t.c2]), partitions(p0)
  20. 2 - output([t.c2], [t.c1]), filter([t.c2 = 1]),
  21. access([t.c2], [t.c1]), partitions(p0)

注意

因为改写后,SEL$2 被提升到 SEL$1 所以这里不用指定 HINT 作用的 query block。

  1. obclient>EXPLAIN SELECT/*+INDEX(t@SEL$1 PRIMARY) INDEX(t@SEL$2 t_c1)*/ *
  2. FROM t , (SELECT * FROM t WHERE c2 = 1) ta WHERE t.c1 = 1\G
  3. *************************** 1. row ***************************
  4. Query Plan:
  5. =============================================================
  6. |ID|OPERATOR |NAME |EST. ROWS|COST |
  7. -------------------------------------------------------------
  8. |0 |NESTED-LOOP INNER JOIN CARTESIAN| |1 |16166|
  9. |1 | TABLE SCAN |t |1 |1397 |
  10. |2 | TABLE SCAN |t(t_c1)|1 |14743|
  11. =============================================================
  12. Outputs & filters:
  13. -------------------------------------
  14. 0 - output([t.c1], [t.c2], [t.c1], [t.c2]), filter(nil),
  15. conds(nil), nl_params_(nil)
  16. 1 - output([t.c1], [t.c2]), filter([t.c1 = 1]),
  17. access([t.c1], [t.c2]), partitions(p0)
  18. 2 - output([t.c2], [t.c1]), filter([t.c2 = 1]),
  19. access([t.c2], [t.c1]), partitions(p0)

此例中 SQL 也可以写成如下方式:

  1. (SELECT * FROM t WHERE c2 = 1) ta WHERE t.c1 = 1\G
  2. <==>
  3. obclient>SELECT/*+INDEX(t@SEL$1 PRIMARY)*/ * from t , (SELECT/*+INDEX(t@SEL$2 t_c1)*/ * from t
  4. WHERE c2 = 1) ta WHERE t.c1 = 1\G
  5. <==>
  6. obclient>SELECT/*+INDEX(@SEL$1 t@SEL$1 PRIMARY) INDEX(@SEL$2 t@SEL$2 t_c1)*/ * from t ,
  7. (SELECT * FROM t WHERE c2 = 1) ta WHERE t.c1 = 1\G

对于 HINT 可以通过 EXPLAIN EXTENDED 查看 Outline Data 来学习。

  • 对于没有指定 query block 的 HINT 代表作用在本 query block。如下例所示,由于 t1 在 query block 2,同时无法改写提升到 query block 1,所以 HINT 无法生效。

    1. obclient>CREATE TABLE t1(c1 INT, c2 INT, INDEX t1_c1(c1), INDEX
    2. t1_c2(c2));
    3. obclient>EXPLAIN SELECT/*+INDEX(t1 t1_c2)*/ * FROM t,
    4. (SELECT * FROM t1 GROUP BY c1) ta WHERE t.c1 = 1\G;
    5. *************************** 1. row ***************************
    6. Query Plan:
    7. ============================================================
    8. |ID|OPERATOR |NAME |EST. ROWS|COST|
    9. ------------------------------------------------------------
    10. |0 |NESTED-LOOP INNER JOIN CARTESIAN| |666 |5906|
    11. |1 | TABLE SCAN |t(t_c1)|1 |472 |
    12. |2 | SUBPLAN SCAN |ta |666 |5120|
    13. |3 | HASH GROUP BY | |666 |4454|
    14. |4 | TABLE SCAN |t1 |1000 |1397|
    15. ============================================================
    16. Outputs & filters:
    17. -------------------------------------
    18. 0 - output([t.c1], [t.c2], [ta.c1], [ta.c2]), filter(nil),
    19. conds(nil), nl_params_(nil)
    20. 1 - output([t.c1], [t.c2]), filter(nil),
    21. access([t.c1], [t.c2]), partitions(p0)
    22. 2 -
    23. output([ta.c1], [ta.c2]), filter(nil),
    24. access([ta.c1], [ta.c2])
    25. 4 - output([t1.c1], [t1.c2]), filter(nil),
    26. group([t1.c1]), agg_func(nil)
    27. 5 - output([t1.c1], [t1.c2]), filter(nil),
    28. access([t1.c1], [t1.c2]), partitions(p0)

    如下例所示,SQL 可以发生改写,t1 提升到 SEL$1,则 HINT 生效。

    1. obclient>EXPLAIN SELECT/*+INDEX(t1 t1_c2)*/ * FROM t,
    2. (SELECT * FROM t1) ta WHERE t.c1 = 1\G;
    3. *************************** 1. row ***************************
    4. Query Plan:
    5. ===============================================================
    6. |ID|OPERATOR |NAME |EST. ROWS|COST |
    7. ---------------------------------------------------------------
    8. |0 |NESTED-LOOP INNER JOIN CARTESIAN| |1000 |15674|
    9. |1 | TABLE SCAN |t(t_c1) |1 |472 |
    10. |2 | TABLE SCAN |t1(t1_c2)|1000 |14743|
    11. ===============================================================
    12. Outputs & filters:
    13. -------------------------------------
    14. 0 - output([t.c1], [t.c2], [t1.c1], [t1.c2]), filter(nil),
    15. conds(nil), nl_params_(nil)
    16. 1 - output([t1.c1], [t1.c2]), filter(nil),
    17. access([t1.c1], [t1.c2]), partitions(p0)
    18. 2 -
    19. output([t.c1], [t.c2]), filter(nil),
    20. access([t.c1], [t.c2]), partitions(p0)
  • 联接方法的 HINT 中指定的 table 如果找不到,忽略该 table,其他的指定依然生效;如果优化器不能生成指定的联接方法,就会选择其他方法,HINT 无效。

  • 联接顺序的 HINT 中如果存在 table 无法找到,则该 HINT 完全失效。

与其他数据库的行为相比,OceanBase 数据库优化器是动态规划的,已经考虑了所有可能的最优路径,HINT 主要作用是指定优化器的行为,并按照 HINT 执行。

INDEX HINT

INDEX HINT 的语法同时支持 MySQL 和 Oracle 方式。

  • INDEX HINT 的 Oracle 语法如下:
  • INDEX HINT 的 MySQL 语法如下:
  1. tbl_name [[AS] alias] [index_hint_list]
  2. index_hint_list:
  3. index_hint [, index_hint] ...
  4. index_hint:
  5. USE {INDEX|KEY}
  6. [FOR {JOIN|ORDER BY|GROUP BY}] ([index_list])
  7. | IGNORE {INDEX|KEY}
  8. [FOR {JOIN|ORDER BY|GROUP BY}] (index_list)
  9. | FORCE {INDEX|KEY}
  10. [FOR {JOIN|ORDER BY|GROUP BY}] (index_list)
  11. index_list:
  12. index_name [, index_name] ...

Oracle 语法中一个表只能指定一个 INDEX,MySQL 语法可以指定多个。但是 OceanBase 数据库中 MySQL 语法虽然支持指定多个,但是对于 USE 和 FORCE,只会用第一个 INDEX 生成 PATH,即使 SQL 语句中没有该 INDEX 的 filter 而导致全部扫描同时回表(即 OceanBase 数据库当前设计是认为写 HINT 的人比程序更明白那条路径是更好的)。IGNORE 类型会忽略所有指定的 INDEX。USE、 FORCE 和 Oracle HINT 方式实际是一样的,该方式的 INDEX 不存在或者处于 invalid 状态,则 HINT 无效。对于 IGNORE 方式,如果将包括主表 (primary) 在内的所有 INDEX 忽略,则 HINT 无效。

FULL HINT

FULL HINT 的语法是用于指定表使用主表扫描,语法如下:

/*+ FULL(table_name)*/

FULL HINT 用于指定表选择主表扫描等价于 INDEX HINT /*+ INDEX(table_name PRIMARY)*/

ORDERED HINT

ORDERED HINT 可以指定按照 from 后面的表的顺序作为联接顺序,语法如下:

/*+ ORDERED*/

如果指定该 HINT 后发生改写,那么就按照改写后的 stmt 中 from items 的顺序联接,因为改写时候 sub_query 会在 from items 中对应位置填放新的 table item。

LEADING HINT

LEADING HINT 可以指定表的联接顺序,语法如下:

/*+ LEADING(table_name_list)*/

table_name_list 中 table_name 比较特殊,其他 table_name 语法如下:

  1. db_name . relation_name
  2. relation_name
  3. .relation_name

在 table_name_list 中 table_name 语法如下:

  1. db_name . relation_name

table_name_list 语法如下:

LEADING HINT 为确保按照用户指定的顺序联接检查比较严格,如果发现 HINT 指定的 table_name 不存在,LEADING HINT 失效;如果发现 HINT 中存在重复 table,LEADING HINT 失效。如果在 optimizer 联接期间,按 table_id 无法在 from items 中找到对应的,即可能发生改写,那么该 table 及后面的 table 指定的 JOIN 序失效,前面的依然有效。

Use_merge

可以指定表在 JOIN 时候使用 merge-join 算法,语法为:/*+ USE_MERGE(table_name_list) */

使用 merge-join 将 use_merge 指定的表作为右表。

注意

OceanBase 数据库中 merge-join 必须有等值条件的 join-condition,因此无等值条件的两个表联接,use_merge 会无效。

关于 merge-join 是否认为 A merge-join B 等效于 B merge-join A 当前并没有最后结论。按照代价模型,merge-join 计算代价时是区分左右表的。同时考虑到区分左右表可以增加 HINT 灵活性,当前 merge-join 区分左右表,即 use_merge 仅对表作为右表的时候生效。

Use_nl

指定表作为右表在联接的时候使用 NESTED LOOP JOIN 算法,语法如下:

/*+ USE_NL(table_name_list) */

Use_hash

指定表作为右表在联接的时候使用 HASH JOIN 算法,语法如下:

/*+ USE_HASH(table_name_list) */

Parallel

/*+ PARALLEL(4) */