TiDB 关联子查询及半连接的优化实践

导读

TiDB 针对子查询语句会执行多种子查询相关的优化 ( https://docs.pingcap.com/zh/tidb/stable/subquery-optimization ),以提升子查询的执行性能。半连接语句和关联子查询语句是常用的两类子查询,TiDB 优化器默认包含一些自动优化策略,同时 TiDB 也提供额外的 HINT 用于影响优化器在特定场景下可以选择更高效的执行计划。本文针对半连接及关联子查询语句在 TiDB 中的用法及优化技巧进行说明。

在这里插入图片描述

关于半连接(Semi Join)

半连接(Semi Join)是数据库中一种特殊的连接操作,它只关心一个表(通常称为外表或左表)中的记录是否在另一个表(通常称为内表或右表)中有匹配的记录,而不关心匹配记录的具体数量或内容。半连接的结果集通常只包含外表中的记录,并且这些记录在内表中至少有一个匹配项。

在 SQL 中,半连接可以通过多种方式实现,包括但不限于:

  1. 使用 EXISTS 子查询:最常见的实现方式之一。通过在外表的主查询中使用 EXISTS 关键字,并嵌套一个在内表上进行查找的子查询,可以判断外表中的记录是否在内表中有匹配项。
  1. 使用 IN 子查询:虽然 IN 子查询通常用于返回匹配项的具体内容,但也可以用于实现半连接的效果。需要注意的是,当内表中的匹配项很多时,IN 子查询的性能可能会下降。
  1. 直接转换为 JOIN 操作:在某些数据库系统中,优化器可能会自动将半连接转换为更高效的 JOIN 操作。这通常取决于数据库系统的具体实现和查询的复杂性。

下面是一个典型的半连接 SQL 语句:


select * from t1 WHERE EXISTS (SELECT 1 FROM t2 WHERE t2.a = t1.a);

优化器针对 Semi Join 优化的不足

优化器针对 Semi Join 语句中表之间的关联方式可能会转换为不同的 Join 方式,如 Hash Join 或 Index Join。然而,不论是哪种 Join 方式,在某些特定的场景下都存在一定的不足之处。

  • 选择 Hash Join 时:Semi Join 被优化为 Hash Join 的执行方式时,只能够使用子查询构建哈希表,因此在子查询比外查询结果集大时,执行速度可能会不及预期。
  • 选择 Index Join 时:Semi Join 被优化为 Index Join 的执行方式时,只能够使用外查询作为驱动表,因此在子查询比外查询结果集小时,执行速度可能会不及预期。

TiDB semi_join_rewrite() 优化的效果

针对上述默认优化器存在的不足,TiDB 提供 semi_join_rewrite() HINT。使用 SEMI_JOIN_REWRITE() 进行改写后,优化器便可以扩大选择范围,选择更好的执行方式。(目前该 Hint 只作用于 EXISTS 子查询)

  • Hash Join 的优化效果

根据上述描述,执行计划默认选择走 Hash Join 的情况下,只能够使用子查询构建哈希表,因此在子查询的结果集比外查询大时,执行速度可能不及预期。我们模拟 2 张表 t1 和 t2 分别为 100 万 和 1000 万,通过执行计划可以发现,子查询中的大表 t2 被用来构建哈希表,造成语句的执行耗时约为 7 秒

注:为了确保执行计划选择 Hash Join,添加 /+ hash_join(t1,t2@sel_2)/ HINT 。


mysql> explain analyze select /*+ hash_join(t1,t2@sel_2)*/ * from t1 WHERE EXISTS (SELECT 1 FROM t2 WHERE t2.a = t1.a);

+-----------------------------+-------------+----------+-----------+-----------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+---------------------------------------------+----------+---------+

| id                          | estRows     | actRows  | task      | access object               | execution info                                                                                                                                                                                                                                                                                                                                                                                                                                           | operator info                               | memory   | disk    |

+-----------------------------+-------------+----------+-----------+-----------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+---------------------------------------------+----------+---------+

| HashJoin_21                 | 800000.00   | 1000000  | root      |                             | time:7.13s, loops:979, RU:9602.429239, build_hash_table:{total:6.85s, fetch:172ms, build:6.68s}, probe:{concurrency:5, total:35.7s, max:7.13s, probe:1.35s, fetch and wait:34.3s}                                                                                                                                                                                                                                                                        | semi join, equal:[eq(test.t1.a, test.t2.a)] | 801.3 MB | 0 Bytes |

| ├─IndexReader_29(Build)     | 10000000.00 | 10000000 | root      |                             | time:93ms, loops:9783, cop_task: {num: 328, max: 50.2ms, min: 523.3µs, avg: 23.7ms, p95: 41.4ms, max_proc_keys: 50144, p95_proc_keys: 50144, tot_proc: 7.26s, tot_wait: 55.2ms, copr_cache_hit_ratio: 0.02, build_task_duration: 46.6µs, max_distsql_concurrency: 15}, rpc_info:{Cop:{num_rpc:328, total_time:7.75s}}                                                                                                                                    | index:IndexFullScan_28                      | 6.10 MB  | N/A     |

| │ └─IndexFullScan_28        | 10000000.00 | 10000000 | cop[tikv] | table:t2, index:idx_t2_a(a) | tikv_task:{proc max:50ms, min:0s, avg: 21.3ms, p80:40ms, p95:40ms, iters:11061, tasks:328}, scan_detail: {total_process_keys: 9987904, total_process_keys_size: 459443584, total_keys: 9988226, get_snapshot_time: 28.7ms, rocksdb: {key_skipped_count: 9987904, block: {cache_hit_count: 16496}}}, time_detail: {total_process_time: 7.26s, total_suspend_time: 20.5ms, total_wait_time: 55.2ms, total_kv_read_wall_time: 6.95s, tikv_wall_time: 7.43s} | keep order:false                            | N/A      | N/A     |

| └─TableReader_24(Probe)     | 1000000.00  | 1000000  | root      |                             | time:14.3ms, loops:979, cop_task: {num: 28, max: 1.68ms, min: 596.2µs, avg: 1.13ms, p95: 1.5ms, max_proc_keys: 992, p95_proc_keys: 480, tot_proc: 2.22ms, tot_wait: 3.62ms, copr_cache_hit_ratio: 0.89, build_task_duration: 21.4µs, max_distsql_concurrency: 1}, rpc_info:{Cop:{num_rpc:28, total_time:30.2ms}}                                                                                                                                         | data:Selection_23                           | 1.53 MB  | N/A     |

|   └─Selection_23            | 1000000.00  | 1000000  | cop[tikv] |                             | tikv_task:{proc max:50ms, min:0s, avg: 32.5ms, p80:50ms, p95:50ms, iters:1088, tasks:28}, scan_detail: {total_process_keys: 1696, total_process_keys_size: 79129, total_keys: 1699, get_snapshot_time: 1.17ms, rocksdb: {key_skipped_count: 1696, block: {cache_hit_count: 15}}}, time_detail: {total_process_time: 2.22ms, total_suspend_time: 15.4µs, total_wait_time: 3.62ms, tikv_wall_time: 11.8ms}                                                 | not(isnull(test.t1.a))                      | N/A      | N/A     |

|     └─TableFullScan_22      | 1000000.00  | 1000000  | cop[tikv] | table:t1                    | tikv_task:{proc max:50ms, min:0s, avg: 31.4ms, p80:50ms, p95:50ms, iters:1088, tasks:28}                                                                                                                                                                                                                                                                                                                                                                 | keep order:false                            | N/A      | N/A     |

+-----------------------------+-------------+----------+-----------+-----------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+---------------------------------------------+----------+---------+

6 rows in set (7.13 sec)

而当我们添加了 /*+ semi_join_rewrite() */ HINT之后,执行计划虽然还是走 Hash Join,但是被哈希的表变成了小表 t1, 同时大表 t2 也做了一个分组聚合。执行耗时也是优化到 3.6 秒,提升了 1 倍


mysql> explain analyze select /*+ hash_join(t1,t2@sel_2)*/ * from t1 WHERE EXISTS (SELECT /*+ semi_join_rewrite() */1 FROM t2 WHERE t2.a = t1.a);

+------------------------------+-------------+----------+-----------+-----------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+----------------------------------------------------------+----------+---------+

| id                           | estRows     | actRows  | task      | access object               | execution info                                                                                                                                                                                                                                                                                                                                                                                                                                          | operator info                                            | memory   | disk    |

+------------------------------+-------------+----------+-----------+-----------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+----------------------------------------------------------+----------+---------+

| HashJoin_28                  | 1000000.00  | 1000000  | root      |                             | time:3.6s, loops:980, RU:9856.929542, build_hash_table:{total:568.5ms, fetch:19.1ms, build:549.5ms}, probe:{concurrency:5, total:18s, max:3.6s, probe:4.96s, fetch and wait:13s}                                                                                                                                                                                                                                                                        | inner join, equal:[eq(test.t1.a, test.t2.a)]             | 102.3 MB | 0 Bytes |

| ├─TableReader_31(Build)      | 1000000.00  | 1000000  | root      |                             | time:13.2ms, loops:979, cop_task: {num: 28, max: 16ms, min: 570.1µs, avg: 1.55ms, p95: 2.28ms, max_proc_keys: 992, p95_proc_keys: 480, tot_proc: 2.43ms, tot_wait: 2.98ms, copr_cache_hit_ratio: 0.89, build_task_duration: 21µs, max_distsql_concurrency: 1}, rpc_info:{Cop:{num_rpc:28, total_time:42.4ms}}                                                                                                                                           | data:Selection_30                                        | 1.53 MB  | N/A     |

| │ └─Selection_30             | 1000000.00  | 1000000  | cop[tikv] |                             | tikv_task:{proc max:50ms, min:0s, avg: 32.5ms, p80:50ms, p95:50ms, iters:1088, tasks:28}, scan_detail: {total_process_keys: 1696, total_process_keys_size: 79129, total_keys: 1699, get_snapshot_time: 876.9µs, rocksdb: {key_skipped_count: 1696, block: {cache_hit_count: 15}}}, time_detail: {total_process_time: 2.43ms, total_suspend_time: 23.3µs, total_wait_time: 2.98ms, tikv_wall_time: 20.4ms}                                               | not(isnull(test.t1.a))                                   | N/A      | N/A     |

| │   └─TableFullScan_29       | 1000000.00  | 1000000  | cop[tikv] | table:t1                    | tikv_task:{proc max:50ms, min:0s, avg: 31.4ms, p80:50ms, p95:50ms, iters:1088, tasks:28}                                                                                                                                                                                                                                                                                                                                                                | keep order:false                                         | N/A      | N/A     |

| └─StreamAgg_46(Probe)        | 9965568.00  | 10000000 | root      |                             | time:2.56s, loops:9767                                                                                                                                                                                                                                                                                                                                                                                                                                  | group by:test.t2.a, funcs:firstrow(test.t2.a)->test.t2.a | 8.39 KB  | N/A     |

|   └─IndexReader_47           | 9965568.00  | 10000000 | root      |                             | time:1.62s, loops:9774, cop_task: {num: 329, max: 61ms, min: 1.47ms, avg: 26.1ms, p95: 45.5ms, max_proc_keys: 50176, p95_proc_keys: 50176, tot_proc: 8s, tot_wait: 55.6ms, copr_cache_hit_ratio: 0.00, build_task_duration: 61.3µs, max_distsql_concurrency: 15}, rpc_info:{Cop:{num_rpc:329, total_time:8.59s}}                                                                                                                                        | index:StreamAgg_38                                       | 52.8 MB  | N/A     |

|     └─StreamAgg_38           | 9965568.00  | 10000000 | cop[tikv] |                             | tikv_task:{proc max:50ms, min:0s, avg: 23.8ms, p80:40ms, p95:40ms, iters:9774, tasks:329}, scan_detail: {total_process_keys: 10000000, total_process_keys_size: 460000000, total_keys: 10000329, get_snapshot_time: 26.3ms, rocksdb: {key_skipped_count: 10000000, block: {cache_hit_count: 16548}}}, time_detail: {total_process_time: 8s, total_suspend_time: 24.4ms, total_wait_time: 55.6ms, total_kv_read_wall_time: 7.28s, tikv_wall_time: 8.24s} | group by:test.t2.a,                                      | N/A      | N/A     |

|       └─IndexFullScan_45     | 10000000.00 | 10000000 | cop[tikv] | table:t2, index:idx_t2_a(a) | tikv_task:{proc max:50ms, min:0s, avg: 22.1ms, p80:40ms, p95:40ms, iters:9774, tasks:329}                                                                                                                                                                                                                                                                                                                                                               | keep order:true                                          | N/A      | N/A     |

+------------------------------+-------------+----------+-----------+-----------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+----------------------------------------------------------+----------+---------+

8 rows in set (3.60 sec)

  • Index Join 的优化效果

根据上述描述,执行计划默认选择走 Index Join 的情况下,只能够使用外查询作为驱动表,因此在子查询比外查询结果集小时,执行速度可能会不及预期。t1 和 t2 仍然是 100 万 和 1000 万,通过执行计划可以发现,1000 万的 t2 (外表) 被作为驱动表进行 Build,语句执行耗时达到 1 分 31 秒

注:为了确保执行计划选择 Index Join,添加 /+ no_hash_join(t2,t1@sel_2)/ HINT。


mysql> explain analyze select /*+ no_hash_join(t2, t1@sel_2) */ * from t2 WHERE EXISTS (SELECT 1 FROM t1 WHERE t2.a = t1.a);

+------------------------------+-------------+----------+-----------+---------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+----------------------------------------------------------------------------------------------------------------+---------+------+

| id                           | estRows     | actRows  | task      | access object             | execution info                                                                                                                                                                                                                                                                                                                                                                                                                                                 | operator info                                                                                                  | memory  | disk |

+------------------------------+-------------+----------+-----------+---------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+----------------------------------------------------------------------------------------------------------------+---------+------+

| IndexJoin_14                 | 8000000.00  | 1000000  | root      |                           | time:1m31.2s, loops:978, RU:142580.352894, inner:{total:7m32.5s, concurrency:5, task:394, construct:11.5s, fetch:7m20.7s, build:303.2ms}, probe:1.46s                                                                                                                                                                                                                                                                                                          | semi join, inner:IndexReader_13, outer key:test.t2.a, inner key:test.t1.a, equal cond:eq(test.t2.a, test.t1.a) | 31.2 MB | N/A  |

| ├─TableReader_37(Build)      | 10000000.00 | 10000000 | root      |                           | time:50.7ms, loops:9779, cop_task: {num: 285, max: 72.9ms, min: 493.3µs, avg: 33.8ms, p95: 51.1ms, max_proc_keys: 50144, p95_proc_keys: 50144, tot_proc: 8.93s, tot_wait: 63.9ms, copr_cache_hit_ratio: 0.09, build_task_duration: 49.1µs, max_distsql_concurrency: 11}, rpc_info:{Cop:{num_rpc:285, total_time:9.61s}}                                                                                                                                        | data:Selection_36                                                                                              | 9.20 MB | N/A  |

| │ └─Selection_36             | 10000000.00 | 10000000 | cop[tikv] |                           | tikv_task:{proc max:50ms, min:0s, avg: 30ms, p80:50ms, p95:50ms, iters:10898, tasks:285}, scan_detail: {total_process_keys: 9929120, total_process_keys_size: 466539933, total_keys: 9929380, get_snapshot_time: 39.6ms, rocksdb: {key_skipped_count: 9929120, block: {cache_hit_count: 19121}}}, time_detail: {total_process_time: 8.93s, total_suspend_time: 25.6ms, total_wait_time: 63.9ms, total_kv_read_wall_time: 8.13s, tikv_wall_time: 9.1s}          | not(isnull(test.t2.a))                                                                                         | N/A     | N/A  |

| │   └─TableFullScan_35       | 10000000.00 | 10000000 | cop[tikv] | table:t2                  | tikv_task:{proc max:50ms, min:0s, avg: 28.8ms, p80:40ms, p95:50ms, iters:10898, tasks:285}                                                                                                                                                                                                                                                                                                                                                                     | keep order:false                                                                                               | N/A     | N/A  |

| └─IndexReader_13(Probe)      | 1003455.10  | 1000000  | root      |                           | time:7m3.2s, loops:1436, cop_task: {num: 1202, max: 1.29s, min: 748.3µs, avg: 360.2ms, p95: 1.13s, max_proc_keys: 9184, p95_proc_keys: 5088, tot_proc: 6m33.2s, tot_wait: 209.3ms, copr_cache_hit_ratio: 0.03, build_task_duration: 580.2ms, max_distsql_concurrency: 2}, rpc_info:{Cop:{num_rpc:1202, total_time:7m12.9s}}                                                                                                                                    | index:Selection_12                                                                                             | 6.10 KB | N/A  |

|   └─Selection_12             | 1003455.10  | 1000000  | cop[tikv] |                           | tikv_task:{proc max:0s, min:0s, avg: 339.8ms, p80:901ms, p95:1.1s, iters:3594, tasks:1202}, scan_detail: {total_process_keys: 996040, total_process_keys_size: 45817840, total_keys: 10990023, get_snapshot_time: 57.4ms, rocksdb: {key_skipped_count: 996040, block: {cache_hit_count: 39977432}}}, time_detail: {total_process_time: 6m33.2s, total_suspend_time: 15.5s, total_wait_time: 209.3ms, total_kv_read_wall_time: 6m48.2s, tikv_wall_time: 7m6.6s} | not(isnull(test.t1.a))                                                                                         | N/A     | N/A  |

|     └─IndexRangeScan_11      | 1003455.10  | 1000000  | cop[tikv] | table:t1, index:idx_t1(a) | tikv_task:{proc max:0s, min:0s, avg: 339.8ms, p80:901ms, p95:1.1s, iters:3594, tasks:1202}                                                                                                                                                                                                                                                                                                                                                                     | range: decided by [eq(test.t1.a, test.t2.a)], keep order:false                                                 | N/A     | N/A  |

+------------------------------+-------------+----------+-----------+---------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+----------------------------------------------------------------------------------------------------------------+---------+------+

7 rows in set (1 min 31.21 sec)

而当我们添加了 /*+ semi_join_rewrite() */ HINT之后,执行计划发生了显著的变化。首先,驱动表变成 100 万的小表 t1,其次表的关联方式从 IndexJoin 变成了 IndexHashJoin。添加 HINT 后的执行耗时也是有了很大的改善,直接优化到约 11 秒,提升了 9 倍


mysql> explain analyze select /*+ no_hash_join(t2, t1@sel_2) */ * from t2 WHERE EXISTS (SELECT /*+ semi_join_rewrite() */ 1 FROM t1 WHERE t2.a = t1.a);

+----------------------------------+------------+---------+-----------+-----------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-----------------------------------------------------------------------------------------------------------------+----------+------+

| id                               | estRows    | actRows | task      | access object               | execution info                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             | operator info                                                                                                   | memory   | disk |

+----------------------------------+------------+---------+-----------+-----------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-----------------------------------------------------------------------------------------------------------------+----------+------+

| IndexHashJoin_20                 | 1003390.88 | 1000000 | root      |                             | time:10.8s, loops:978, RU:13383.173606, inner:{total:52.9s, concurrency:5, task:42, construct:935.8ms, fetch:51s, build:246.5ms, join:884.6ms}                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                             | inner join, inner:IndexLookUp_17, outer key:test.t1.a, inner key:test.t2.a, equal cond:eq(test.t1.a, test.t2.a) | 30.5 MB  | N/A  |

| ├─StreamAgg_72(Build)            | 999936.00  | 1000000 | root      |                             | time:98.2ms, loops:979                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     | group by:test.t1.a, funcs:firstrow(test.t1.a)->test.t1.a                                                        | 8.39 KB  | N/A  |

| │ └─IndexReader_73               | 999936.00  | 1000000 | root      |                             | time:12.4ms, loops:978, cop_task: {num: 28, max: 3.2ms, min: 470µs, avg: 856µs, p95: 2.16ms, max_proc_keys: 1024, p95_proc_keys: 1024, tot_proc: 4.38ms, tot_wait: 3.47ms, copr_cache_hit_ratio: 0.86, build_task_duration: 22.3µs, max_distsql_concurrency: 1}, rpc_info:{Cop:{num_rpc:28, total_time:23.3ms}}                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            | index:StreamAgg_68                                                                                              | 6.36 MB  | N/A  |

| │   └─StreamAgg_68               | 999936.00  | 1000000 | cop[tikv] |                             | tikv_task:{proc max:50ms, min:0s, avg: 30.7ms, p80:40ms, p95:50ms, iters:977, tasks:28}, scan_detail: {total_process_keys: 4096, total_process_keys_size: 188416, total_keys: 4100, get_snapshot_time: 1.86ms, rocksdb: {key_skipped_count: 4096, block: {cache_hit_count: 22}}}, time_detail: {total_process_time: 4.38ms, total_suspend_time: 48.7µs, total_wait_time: 3.47ms, total_kv_read_wall_time: 10ms, tikv_wall_time: 12.5ms}                                                                                                                                                                                                                                                                                                                                                                    | group by:test.t1.a,                                                                                             | N/A      | N/A  |

| │     └─IndexFullScan_36         | 1000000.00 | 1000000 | cop[tikv] | table:t1, index:idx_t1(a)   | tikv_task:{proc max:50ms, min:0s, avg: 29.3ms, p80:40ms, p95:50ms, iters:977, tasks:28}                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    | keep order:true                                                                                                 | N/A      | N/A  |

| └─IndexLookUp_17(Probe)          | 1003390.88 | 1000000 | root      |                             | time:49.5s, loops:1019, index_task: {total_time: 49.3s, fetch_handle: 49.3s, build: 619.8µs, wait: 3.04ms}, table_task: {total_time: 915ms, num: 201, concurrency: 5}, next: {wait_index: 48.6s, wait_table_lookup_build: 99.4ms, wait_table_lookup_resp: 791.6ms}                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         |                                                                                                                 | 158.1 KB | N/A  |

|   ├─Selection_16(Build)          | 1003390.88 | 1000000 | cop[tikv] |                             | time:49.2s, loops:1142, cop_task: {num: 253, max: 666.6ms, min: 1.15ms, avg: 211.1ms, p95: 501.8ms, max_proc_keys: 9184, p95_proc_keys: 9184, tot_proc: 36.1s, tot_wait: 53.1ms, copr_cache_hit_ratio: 0.00, build_task_duration: 65.1ms, max_distsql_concurrency: 3}, rpc_info:{Cop:{num_rpc:253, total_time:53.4s}}, tikv_task:{proc max:650ms, min:0s, avg: 190.8ms, p80:300ms, p95:480ms, iters:2026, tasks:253}, scan_detail: {total_process_keys: 999840, total_process_keys_size: 45992640, total_keys: 1999853, get_snapshot_time: 29.8ms, rocksdb: {key_skipped_count: 999840, block: {cache_hit_count: 4787484}}}, time_detail: {total_process_time: 36.1s, total_suspend_time: 12.3s, total_wait_time: 53.1ms, total_kv_read_wall_time: 48.2s, tikv_wall_time: 52s}                             | not(isnull(test.t2.a))                                                                                          | N/A      | N/A  |

|   │ └─IndexRangeScan_14          | 1003390.88 | 1000000 | cop[tikv] | table:t2, index:idx_t2_a(a) | tikv_task:{proc max:650ms, min:0s, avg: 190.7ms, p80:300ms, p95:480ms, iters:2026, tasks:253}                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              | range: decided by [eq(test.t2.a, test.t1.a)], keep order:false                                                  | N/A      | N/A  |

|   └─TableRowIDScan_15(Probe)     | 1003390.88 | 1000000 | cop[tikv] | table:t2                    | time:775.3ms, loops:1258, cop_task: {num: 217, max: 26.9ms, min: 634.9µs, avg: 3.52ms, p95: 7.6ms, max_proc_keys: 8544, p95_proc_keys: 5024, tot_proc: 370.8ms, tot_wait: 32.1ms, copr_cache_hit_ratio: 0.33, build_task_duration: 10.7ms, max_distsql_concurrency: 2, max_extra_concurrency: 1}, rpc_info:{Cop:{num_rpc:217, total_time:756.1ms}}, tikv_task:{proc max:10ms, min:0s, avg: 3.92ms, p80:10ms, p95:10ms, iters:1992, tasks:217}, scan_detail: {total_process_keys: 381888, total_process_keys_size: 17893063, total_keys: 382135, get_snapshot_time: 12.6ms, rocksdb: {key_skipped_count: 381888, block: {cache_hit_count: 1681}}}, time_detail: {total_process_time: 370.8ms, total_suspend_time: 4.6ms, total_wait_time: 32.1ms, total_kv_read_wall_time: 370ms, tikv_wall_time: 475.2ms}  | keep order:false                                                                                                | N/A      | N/A  |

+----------------------------------+------------+---------+-----------+-----------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-----------------------------------------------------------------------------------------------------------------+----------+------+

9 rows in set (10.83 sec)

在这里插入图片描述

关联子查询定义

关联子查询是指和外部查询有关联的子查询,即在子查询中使用了外部查询包含的列。在关联子查询中,对于外部查询返回的每一行数据,内部查询都要执行一次。

关联子查询具有以下几方面的特点:

  1. 信息流双向:关联子查询中的信息流是双向的。外部查询的每行数据传递一个值给子查询,子查询为每一行数据执行一次并返回记录,然后外部查询根据返回的记录做出决策。
  1. 灵活性:关联子查询可以使用关联列的灵活性,将 SQL 查询写成子查询的形式,这往往可以极大地简化 SQL 查询,并使 SQL 查询的语义更加方便理解。
  1. 执行挑战:为了计算关联结果的值(子查询的输出),关联子查询需要采用迭代(iterative)的执行方式。

以下是一个典型的关联子查询示例:


select * from t1 where t1.a < (select sum(t2.a) from t2 where t2.b = t1.b);

TiDB 关联子查询优化-“子查询去关联”

关联子查询每次子查询执行时都是要和它的外部查询结果绑定的,因此,如果上述语句中 t1.a 有一千万个值,那这个子查询就要被重复执行一千万次,因为 t2.b=t1.b 这个条件会随着 t1.a 值的不同而发生变化。

如果能将关联依赖解除的话,这个子查询就只需要被执行一次。默认情况下,TiDB 会尝试进行子查询去关联,以达到更高的执行效率。以下是 TiDB 中针对这条语句生成的执行计划,可以看出 TiDB 的优化器将语句的执行计划生成了一个 Hash Join 关联方式。模拟 2 个表的数据量为 1 千万行进行测试时,语句的实际执行耗时约为 14 秒


mysql> explain select * from t1 where t1.a < (select sum(t2.a) from t2 where t2.b = t1.b);

+--------------------------------+-----------+-----------+---------------+--------------------------------------------------------------------------------------------------------------+

| id                             | estRows   | task      | access object | operator info                                                                                                |

+--------------------------------+-----------+-----------+---------------+--------------------------------------------------------------------------------------------------------------+

| HashJoin_12                    | 452412.14 | root      |               | inner join, equal:[eq(test.t1.b, test.t2.b)], other cond:lt(cast(test.t1.a, decimal(10,0) BINARY), Column#7) |

| ├─TableReader_15(Build)        | 452412.14 | root      |               | data:Selection_14                                                                                            |

| │ └─Selection_14               | 452412.14 | cop[tikv] |               | not(isnull(test.t1.b))                                                                                       |

| │   └─TableFullScan_13         | 452865.00 | cop[tikv] | table:t1      | keep order:false, stats:pseudo                                                                               |

| └─HashAgg_21(Probe)            | 472411.12 | root      |               | group by:test.t2.b, funcs:sum(Column#8)->Column#7, funcs:firstrow(test.t2.b)->test.t2.b                      |

|   └─TableReader_22             | 472411.12 | root      |               | data:HashAgg_16                                                                                              |

|     └─HashAgg_16               | 472411.12 | cop[tikv] |               | group by:test.t2.b, funcs:sum(test.t2.a)->Column#8                                                           |

|       └─Selection_20           | 590513.90 | cop[tikv] |               | not(isnull(test.t2.b))                                                                                       |

|         └─TableFullScan_19     | 591105.00 | cop[tikv] | table:t2      | keep order:false, stats:pseudo                                                                               |

+--------------------------------+-----------+-----------+---------------+--------------------------------------------------------------------------------------------------------------+

9 rows in set (0.00 sec)

假如 TiDB 没有做子查询去关联的优化,实际执行情况又是怎么样呢?我们可以用 HINT/*+ NO_DECORRELATE() */来关闭子查询去关联,模拟未优化前的情况。此时得到如下的执行计划,其中 operator info 为range: decided by [eq(test.t2.b, test.t1.b)]的 IndexRangeScan_42算子表示 TiDB 使用关联条件进行索引范围查询。同样的语句执行耗时超过 1 小时


mysql> explain select * from t1 where t1.a < (select /*+ NO_DECORRELATE() */ sum(t2.a) from t2 where t2.b = t1.b);

+------------------------------------------+-----------+-----------+---------------------------+--------------------------------------------------------------------------------------+

| id                                       | estRows   | task      | access object             | operator info                                                                        |

+------------------------------------------+-----------+-----------+---------------------------+--------------------------------------------------------------------------------------+

| Projection_10                            | 452865.00 | root      |                           | test.t1.a, test.t1.b                                                                 |

| └─Apply_12                               | 452865.00 | root      |                           | CARTESIAN inner join, other cond:lt(cast(test.t1.a, decimal(10,0) BINARY), Column#7) |

|   ├─TableReader_14(Build)                | 452865.00 | root      |                           | data:TableFullScan_13                                                                |

|   │ └─TableFullScan_13                   | 452865.00 | cop[tikv] | table:t1                  | keep order:false, stats:pseudo                                                       |

|   └─MaxOneRow_15(Probe)                  | 452865.00 | root      |                           |                                                                                      |

|     └─StreamAgg_20                       | 452865.00 | root      |                           | funcs:sum(Column#14)->Column#7                                                       |

|       └─Projection_45                    | 857574.13 | root      |                           | cast(test.t2.a, decimal(10,0) BINARY)->Column#14                                     |

|         └─IndexLookUp_44                 | 857574.13 | root      |                           |                                                                                      |

|           ├─IndexRangeScan_42(Build)     | 857574.13 | cop[tikv] | table:t2, index:idx_t2(b) | range: decided by [eq(test.t2.b, test.t1.b)], keep order:false                       |

|           └─TableRowIDScan_43(Probe)     | 857574.13 | cop[tikv] | table:t2                  | keep order:false                                                                     |

+------------------------------------------+-----------+-----------+---------------------------+--------------------------------------------------------------------------------------+

10 rows in set (0.01 sec)

何时需要关闭 “子查询去关联”

那么,是不是所有的关联子查询语句在关联依赖解除优化后性能都能有较大的提升呢?并不是这样。仍然用上面的示例来说,在外部的值比较少的情况下,不解除关联依赖反而可能对执行性能更有帮助。例如,如果 t3 表只有 100 条记录时,以下语句执行耗时仅 0.28 秒


mysql> explain analyze select * from t3 where t3.a = (select /*+ NO_DECORRELATE() */ sum(t2.a) from t2 where t2.b = t3.b);

+------------------------------------------+---------+---------+-----------+---------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-----------------------------------------------------------------------+-----------+------+

| id                                       | estRows | actRows | task      | access object             | execution info                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            | operator info                                                         | memory    | disk |

+------------------------------------------+---------+---------+-----------+---------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-----------------------------------------------------------------------+-----------+------+

| Projection_11                            | 100.00  | 0       | root      |                           | time:277.9ms, loops:1, RU:117.173904, Concurrency:OFF                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     | test.t3.a, test.t3.b                                                  | 3.55 KB   | N/A  |

| └─Apply_13                               | 100.00  | 0       | root      |                           | time:277.9ms, loops:1, concurrency:OFF, cache:ON, cacheHitRatio:0.000%                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    | inner join, equal:[eq(Column#8, Column#7)]                            | 900 Bytes | N/A  |

|   ├─Projection_14(Build)                 | 100.00  | 100     | root      |                           | time:1.61ms, loops:2, Concurrency:OFF                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     | test.t3.a, test.t3.b, cast(test.t3.a, decimal(10,0) BINARY)->Column#8 | 1.82 KB   | N/A  |

|   │ └─TableReader_16                     | 100.00  | 100     | root      |                           | time:1.56ms, loops:2, cop_task: {num: 1, max: 1.36ms, proc_keys: 100, tot_proc: 333.9µs, tot_wait: 146.6µs, copr_cache_hit_ratio: 0.00, build_task_duration: 18.3µs, max_distsql_concurrency: 1}, rpc_info:{Cop:{num_rpc:1, total_time:1.32ms}}                                                                                                                                                                                                                                                                                                                                                                                                                                                                                           | data:TableFullScan_15                                                 | 1.86 KB   | N/A  |

|   │   └─TableFullScan_15                 | 100.00  | 100     | cop[tikv] | table:t3                  | tikv_task:{time:0s, loops:3}, scan_detail: {total_process_keys: 100, total_process_keys_size: 4400, total_keys: 101, get_snapshot_time: 46.4µs, rocksdb: {delete_skipped_count: 100, key_skipped_count: 200, block: {cache_hit_count: 4}}}, time_detail: {total_process_time: 333.9µs, total_wait_time: 146.6µs, tikv_wall_time: 730.8µs}                                                                                                                                                                                                                                                                                                                                                                                                 | keep order:false, stats:pseudo                                        | N/A       | N/A  |

|   └─MaxOneRow_17(Probe)                  | 100.00  | 100     | root      |                           | time:265.2ms, loops:200                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   |                                                                       | N/A       | N/A  |

|     └─StreamAgg_22                       | 100.00  | 100     | root      |                           | time:264.7ms, loops:200                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                   | funcs:sum(Column#16)->Column#7                                        | 8.75 KB   | N/A  |

|       └─Projection_47                    | 189.37  | 178     | root      |                           | time:264ms, loops:176, Concurrency:OFF                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    | cast(test.t2.a, decimal(10,0) BINARY)->Column#16                      | 8.61 KB   | N/A  |

|         └─IndexLookUp_46                 | 189.37  | 178     | root      |                           | time:263.1ms, loops:176, index_task: {total_time: 78.3ms, fetch_handle: 77.5ms, build: 124.7µs, wait: 629µs}, table_task: {total_time: 144.4ms, num: 76, concurrency: 5}, next: {wait_index: 90ms, wait_table_lookup_build: 7.42ms, wait_table_lookup_resp: 136.1ms}                                                                                                                                                                                                                                                                                                                                                                                                                                                                      |                                                                       | 26.4 KB   | N/A  |

|           ├─IndexRangeScan_44(Build)     | 189.37  | 178     | cop[tikv] | table:t2, index:idx_t2(b) | time:100ms, loops:252, cop_task: {num: 100, max: 1.38ms, min: 596.8µs, avg: 880.7µs, p95: 1.2ms, max_proc_keys: 8, p95_proc_keys: 5, tot_proc: 13.5ms, tot_wait: 9.62ms, copr_cache_hit_ratio: 0.00, build_task_duration: 2.07ms, max_distsql_concurrency: 1}, rpc_info:{Cop:{num_rpc:100, total_time:85.1ms}}, tikv_task:{proc max:10ms, min:0s, avg: 300µs, p80:0s, p95:0s, iters:100, tasks:100}, scan_detail: {total_process_keys: 178, total_process_keys_size: 8188, total_keys: 278, get_snapshot_time: 3.17ms, rocksdb: {key_skipped_count: 178, block: {cache_hit_count: 465}}}, time_detail: {total_process_time: 13.5ms, total_wait_time: 9.62ms, total_kv_read_wall_time: 30ms, tikv_wall_time: 41.8ms}                       | range: decided by [eq(test.t2.b, test.t3.b)], keep order:false        | N/A       | N/A  |

|           └─TableRowIDScan_45(Probe)     | 189.37  | 178     | cop[tikv] | table:t2                  | time:133.3ms, loops:152, cop_task: {num: 159, max: 9.38ms, min: 0s, avg: 753.7µs, p95: 1.13ms, max_proc_keys: 2, p95_proc_keys: 2, tot_proc: 18.6ms, tot_wait: 24.3ms, copr_cache_hit_ratio: 0.00, build_task_duration: 3.5ms, max_distsql_concurrency: 1, max_extra_concurrency: 1, store_batch_num: 34}, rpc_info:{Cop:{num_rpc:125, total_time:117ms}}, tikv_task:{proc max:10ms, min:0s, avg: 62.9µs, p80:0s, p95:0s, iters:159, tasks:159}, scan_detail: {total_process_keys: 178, total_process_keys_size: 8366, total_keys: 178, get_snapshot_time: 5.14ms, rocksdb: {block: {cache_hit_count: 798}}}, time_detail: {total_process_time: 18.6ms, total_wait_time: 24.3ms, total_kv_read_wall_time: 10ms, tikv_wall_time: 63.3ms}   | keep order:false                                                      | N/A       | N/A  |

+------------------------------------------+---------+---------+-----------+---------------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+-----------------------------------------------------------------------+-----------+------+

11 rows in set (0.28 sec)

而如果走 TiDB 默认的解除关联依赖时,耗时约 5 秒,且存在 OOM 的风险。


mysql> explain analyze select * from t3 where t3.a = (select sum(t2.a) from t2 where t2.b = t3.b);

ERROR 8175 (HY000): Your query has been cancelled due to exceeding the allowed memory limit for a single SQL query. Please try narrowing your query scope or increase the tidb_mem_quota_query limit and try again.[conn=480325804]

mysql> set tidb_mem_quota_query=10737418240;

Query OK, 0 rows affected (0.00 sec)

  


mysql> explain analyze select * from t3 where t3.a = (select sum(t2.a) from t2 where t2.b = t3.b);

+--------------------------------+-------------+----------+-----------+---------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+------------------------------------------------------------------------------------------+---------+---------+

| id                             | estRows     | actRows  | task      | access object | execution info                                                                                                                                                                                                                                                                                                                                                                                                                                                                   | operator info                                                                            | memory  | disk    |

+--------------------------------+-------------+----------+-----------+---------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+------------------------------------------------------------------------------------------+---------+---------+

| HashJoin_13                    | 99.90       | 0        | root      |               | time:5.11s, loops:1, RU:11889.809066, build_hash_table:{total:1.48ms, fetch:1.36ms, build:122.2µs}, probe:{concurrency:5, total:25.5s, max:5.11s, probe:1.44s, fetch and wait:24.1s}                                                                                                                                                                                                                                                                                             | inner join, equal:[eq(test.t3.b, test.t2.b) eq(Column#8, Column#7)]                      | 46.0 KB | 0 Bytes |

| ├─Projection_14(Build)         | 99.90       | 100      | root      |               | time:1.26ms, loops:2, Concurrency:OFF                                                                                                                                                                                                                                                                                                                                                                                                                                            | test.t3.a, test.t3.b, cast(test.t3.a, decimal(10,0) BINARY)->Column#8                    | 2.52 KB | N/A     |

| │ └─TableReader_17             | 99.90       | 100      | root      |               | time:1.22ms, loops:2, cop_task: {num: 1, max: 1.38ms, proc_keys: 100, tot_proc: 353µs, tot_wait: 129.8µs, copr_cache_hit_ratio: 0.00, build_task_duration: 24.3µs, max_distsql_concurrency: 1}, rpc_info:{Cop:{num_rpc:1, total_time:1.33ms}}                                                                                                                                                                                                                                    | data:Selection_16                                                                        | 1.87 KB | N/A     |

| │   └─Selection_16             | 99.90       | 100      | cop[tikv] |               | tikv_task:{time:0s, loops:3}, scan_detail: {total_process_keys: 100, total_process_keys_size: 4400, total_keys: 101, get_snapshot_time: 49.9µs, rocksdb: {delete_skipped_count: 100, key_skipped_count: 200, block: {cache_hit_count: 4}}}, time_detail: {total_process_time: 353µs, total_wait_time: 129.8µs, tikv_wall_time: 716.5µs}                                                                                                                                          | not(isnull(test.t3.b))                                                                   | N/A     | N/A     |

| │     └─TableFullScan_15       | 100.00      | 100      | cop[tikv] | table:t3      | tikv_task:{time:0s, loops:3}                                                                                                                                                                                                                                                                                                                                                                                                                                                     | keep order:false, stats:pseudo                                                           | N/A     | N/A     |

| └─HashAgg_28(Probe)            | 5280768.00  | 5343857  | root      |               | time:5.09s, loops:5222, partial_worker:{wall_time:2.494534938s, concurrency:5, task_num:285, tot_wait:215.654904ms, tot_exec:12.25579073s, tot_time:12.47193031s, max:2.494394166s, p95:2.494394166s}, final_worker:{wall_time:5.107395873s, concurrency:5, task_num:25, tot_wait:70.452µs, tot_exec:8.250691502s, tot_time:24.968242542s, max:5.107349993s, p95:5.107349993s}                                                                                                   | group by:test.t2.b, funcs:sum(Column#11)->Column#7, funcs:firstrow(test.t2.b)->test.t2.b | 1.95 GB | 0 Bytes |

|   └─TableReader_29             | 5280768.00  | 9972967  | root      |               | time:225.2ms, loops:286, cop_task: {num: 285, max: 157.9ms, min: 470.7µs, avg: 55.1ms, p95: 83.7ms, max_proc_keys: 50176, p95_proc_keys: 50176, tot_proc: 13.8s, tot_wait: 51ms, copr_cache_hit_ratio: 0.04, build_task_duration: 41.8µs, max_distsql_concurrency: 11}, rpc_info:{Cop:{num_rpc:285, total_time:15.7s}}                                                                                                                                                           | data:HashAgg_21                                                                          | 25.2 MB | N/A     |

|     └─HashAgg_21               | 5280768.00  | 9972967  | cop[tikv] |               | tikv_task:{proc max:90ms, min:0s, avg: 46.7ms, p80:70ms, p95:70ms, iters:9769, tasks:285}, scan_detail: {total_process_keys: 9982592, total_process_keys_size: 469050751, total_keys: 9982867, get_snapshot_time: 19.6ms, rocksdb: {delete_skipped_count: 874, key_skipped_count: 9983466, block: {cache_hit_count: 18229}}}, time_detail: {total_process_time: 13.8s, total_suspend_time: 37.8ms, total_wait_time: 51ms, total_kv_read_wall_time: 9.13s, tikv_wall_time: 14.3s} | group by:test.t2.b, funcs:sum(test.t2.a)->Column#11                                      | N/A     | N/A     |

|       └─Selection_27           | 10000000.00 | 10000000 | cop[tikv] |               | tikv_task:{proc max:70ms, min:0s, avg: 33.2ms, p80:50ms, p95:60ms, iters:9769, tasks:285}                                                                                                                                                                                                                                                                                                                                                                                        | not(isnull(test.t2.b))                                                                   | N/A     | N/A     |

|         └─TableFullScan_26     | 10000000.00 | 10000000 | cop[tikv] | table:t2      | tikv_task:{proc max:70ms, min:0s, avg: 32ms, p80:50ms, p95:60ms, iters:9769, tasks:285}                                                                                                                                                                                                                                                                                                                                                                                          | keep order:false                                                                         | N/A     | N/A     |

+--------------------------------+-------------+----------+-----------+---------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+------------------------------------------------------------------------------------------+---------+---------+

10 rows in set (5.12 sec)

全局关闭 “子查询去关联”

/*+ NO_DECORRELATE() */HINT 是一种针对具体 SQL 级别来关闭子查询去关联的方式,TiDB 也提供另外一种可以全局关闭的方式,它是直接将子查询去关联的规则加入到黑名单中。

关于优化规则黑名单的使用,可以参考 TiDB 官网文档 https://docs.pingcap.com/zh/tidb/v7.5/blocklist-control-plan。如果希望全局关闭子查询去关联的功能,需要执行以下命令,此后不需要使用/*+ NO_DECORRELATE() */这个 HINT 我们也同样可以获得具有子查询关联的执行计划。`


insert into mysql.opt_rule_blacklist values("decorrelate");

admin reload opt_rule_blacklist;

  


在这里插入图片描述

TiDB 优化器针对 semi join 半连接默认会转换为 HashJoin 或 IndexJoin 以获得更好的性能,然而在某些特殊的场景下默认选择的 HashJoin 或 IndexJoin 可能仍然达不到最佳的性能。TiDB 提供的SEMI_JOIN_REWRITE()可以扩大选择范围,让优化器选择更好的执行方式,从而在性能上达到几倍的性能。

TiDB 默认针对关联子查询有自动解除关联依赖的优化操作,当涉及查询条件的数据量较大时,将重复执行无数次的子查询转化为只需执行一次,从而大大缩减了执行耗时。然而,如果满足查询条件的数据量很小的话,这种优化方式可能会造成相反的效果,此时我们则需要借助NO_DECORRELATE()来关闭 “子查询去关联” 的优化,也可以通过全局添加规则黑名单的方式实现全局控制。

相关推荐

TiDB 可观测性解读(二)丨算子执行信息性能诊断案例分享

通常我们可以用 explain analyze 语句获得算子执行信息。explain analyze 会实际执行对应的 SQL 语句,同时记录其运行时信息,和执行计划一并返回出来,记录的信息包括: actRows 、 execution info 、 memory 、 disk。不同算子的 execution info 可以通过 TiDB 文档 ( https://docs.pingcap.com/zh/tidb/stable/sql-statement-explain-analyze )了解。

TiDB_PingCAP 的博客 1012

TiDB × AI :DeepSeek 时代你需要什么样的数据基座

AutoFlow 是一套 GraphRAG 框架,不仅提供了类似于 LlamaIndex 的能力,而且还内置语义化的知识图谱构建和召回,以及我们在 AutoFlow 上实践得出的一系列行之有效的领先的 RAG 能力(这些接下来会介绍)。不过需要强调的是,Dify 是一个开箱即用的非常易用的界面,而 AutoFlow 虽然功能更强却则具有比较高的使用门槛,所以这两个选择其实面向了不同的群体,用户需要依据自己的实际需求进行选择。它并非传统的基于规则的优化工具,而是利用大模型的知识来优化不同类型的数据库。

TiDB_PingCAP 的博客 1408

架构师必看!现代应用架构发展趋势与数据库选型建议丨TiDB vs MySQL 专题(一)

随着业务系统数据量的增长,早起只能无奈采取分库分表方案以及架构,这不单为运维带 来了极高的复杂度,同时对业务开发也带来的极大的入侵,SQL 只能限定按照 shard key 维度进行编写,无法任意维度的进行 SQL 查询,开发不得不牺牲业务需求,业务的发展也不得不受限,同时又需投入大量的高精尖人才进行开发维护。这对数据库的承载能力提出了极高的挑战,不但需要承载大数据量,又需要保障业务读写性能的稳定性,而在数据的承载能力上,MySQL 的极限,是 TiDB 的起点。一行命令完成扩展,无需任何的人工干预。

TiDB_PingCAP 的博客 1224

TiDB 观测性解读(一)丨索引观测:快速识别无用索引与低效索

通过识别并优化未使用或低效的索引,可以减少资源浪费,并提高系统的响应速度和稳定性。在 TiDB 中,TIDB_INDEX_USAGE 系统表提供了相对丰富的索引使用统计数据,帮助 DBA 快速发现低效索引,并通过优化或删除它们来提升数据库效率。尽管删除索引的操作相对简单,但在实施时仍需注意潜在的限制和风险,尤其是在大数据量和高并发环境下。定期检查索引使用情况,尤其是对于大规模数据库。确保用于决策的统计数据涵盖足够长的业务周期,避免误判。

TiDB_PingCAP 的博客 966

海量数据融合互通丨TiDB 在安徽省住房公积金监管服务平台的应用实践

目前安徽省住房公积金监管服务平台已具备一系列功能模块,包括首页、运营分析、统计报表、智慧大屏、数据治理、风险检查和系统管理。其中,运营分析主要用于从不同维度分析公积金业务指标,统计报表则负责生成、填报和查询住建部规定的报表,同时也支持省级用户的报表导入、核对和更新。智慧大屏提供了综合和业务两大类可视化展示,而数据治理模块则涵盖了传数统计、数据检核和人工数据核对等功能,以确保数据的质量。风险检查方面,平台不仅支持公积金中心的自我检查,也支持省厅的检查,并可以根据需要添加新的检查模型。

TiDB_PingCAP 的博客 1124

TiDB 2024 年度报告:增长的故事

TiDB 2024 年度报告:增长的故事

TiDB_PingCAP 的博客 322

TiDB Chat2Query 深度解析:我们如何打造一款更高效、准确的智能 SQL 生成工具?

上个季度的销售额是多少?“哪个产品类别表现最佳?“本月客户投诉的趋势如何?相比其他工具,Chat2Query 能够对用户上传的大规模数据集进行理解和分析, 摒弃繁杂的专业术语和查询语句,Chat2Query 使得用户能够通过自然语言直接向数据库提问,并即时获得答案。

TiDB_PingCAP 的博客 1332

4.98 亿月活背后的国产数据库:咪咕视讯携手 TiDB 攻克内容分发核心系统挑战

咪咕大概是 2018 年左右正式开始对分布式数据库进行研究的,到现在为止我们看到和测试过太多的国内产品。但是在当时,敢用 LSM 树而不是 B+ 树做存储引擎,敢做分布式存算引擎分离的,能够行列副本共存、优化器路由分流做 MPP shuffle 的,市面上真的真的非常非常地不多见。比较多的是精致的分库分表外挂,或某些知名国外产品的模仿版。TiDB 的产品给我的印象是极具冲击性的,那么大胆、不随大流。验证和使用下来,效果也是切实的。

TiDB_PingCAP 的博客 1187

53 倍性能提升!TiDB 全局索引如何优化分区表查询?

在 TiDB 中,全局索引是一种定义在分区表上的索引类型,它允许索引分区与表分区之间建立一对多的映射关系,即一个索引分区可以对应多个表分区。这与 TiDB 早期版本中的本地索引(Local Index)不同,本地索引的索引分区与表分区之间是一对一的映射关系,即一个分区对应一个局部的索引块。全局索引能覆盖整个表的数据,使得主键和唯一键在不包含分区键的情况下仍能保持全局唯一性。此外,全局索引可以在一次操作中访问多个分区的索引数据,而无需对每个分区的本地索引逐一查找,显著提升了针对非分区键的查询性能。

TiDB_PingCAP 的博客 1346

一行代码不用写,用 Autoflow + Gitee AI 搭建本地知识库问答机器人

本文详解 AutoFlow 从部署到配置的完整流程,包括数据库连接、模型设置、知识库创建及聊天引擎配置,实现了一行代码不用写的问答机器人快速搭建。轻松上手,助力开发者探索智能问答解决方案。

TiDB_PingCAP 的博客 1043

TiDB 分布式数据库多业务资源隔离应用实践

本文将分享某客户在 TiDB 分布式数据库中实现多业务资源隔离的实践案例。

TiDB_PingCAP 的博客 1309

攻克多版本运维难题:爱奇艺百套 TiDB 集群升级至 v7.1.5 实战宝典来袭!

本文将深入探讨爱奇艺如何通过升级,成功将百套 TiDB 集群从多个旧版本升级至 v7.1.5,攻克多版本运维挑战,获得更稳更快的 TiDB 使用体验。

TiDB_PingCAP 的博客 1150

百亿大表的实时分析:华安基金 HTAP 数据库的选型历程与 TiDB 使用体验

明确需求:首先评估业务对 TP(事务处理)和 AP(分析处理)的需求比重,确定数据量、查询速度和响应时间,确保数据库能满足业务对实时性的要求。技术特性评估:考虑数据库的实时分析能力、可扩展性、高性能、安全性和灵活性,以支持业务人员实施的场景需求,特别是后台营销人员对数据实时性的需求。集成与兼容性:评估数据库与现有数据库、应用程序和其他关键系统的集成能力,确保数据同步策略的无缝实施。安全性与可靠性:重视数据库的安全性措施、容灾备份机制、数据恢复能力和错误处理机制,保障业务连续性和数据安全。

TiDB_PingCAP 的博客 1012

TiDB 的高可用实践:一文了解代理组件 TiProxy 的原理与应用

TiDB 是一款典型的分布式存算分离架构的数据库,其中计算层由多个无状态的 TiDB Server 组成,这些 TiDB Server 同时对外承担连接请求。为了可以将连接分发到多个 TiDB Server 节点上,一般需要借助外部负载均衡组件如硬件负载均衡 F5、软件负载均衡 HAProxy 等。为了实现全链路的高可用架构,我们经常也需要考虑负载均衡组件本身的高可用性,比如通过 KeepAlived 来保证 HAProxy 的高可用。

TiDB_PingCAP 的博客 1250

你需要什么样的资源隔离?丨TiDB 资源隔离最佳实践

通过本文的学习,相信大家对 TiDB 的资源隔离能力有了更全面的理解;大家可以根据不同的场景需求,选择合适的资源隔离方案。如果您有新的资源隔离需求或场景,欢迎与我们联系。

TiDB_PingCAP 的博客 1240

狂飙 50 倍丨TiDB DDL 框架优化深度解析

前面我们介绍了 TiDB DDL 任务的整体执行流程。接下来,让我们聚焦到在线 Schema 变更的细节上。执行单步变更:Job Worker 会根据任务定义,执行一次在线 Schema 的变更。每一次变更都代表着 Schema 向目标状态迈进了一步,即进入下一个状态,可能的状态包括 write-only 和 delete-only 等。状态更新:完成单步变更后,Job Worker 会将当前的 Schema 状态更新到元数据中。

TiDB_PingCAP 的博客 1197

唐刘:TiDB 的 2024 - Cloud、SaaS 与 AI

最后再说下产品,在今年,我们发布了TiDB 8.1和8.5两个版本。在 2025 年,我们会有一个重大的改变,就是会持续的投入到 8.5 版本的质量加固,同时收敛新功能的开发,只会在 2025 年发布一个有更高质量的 LTS 版本。关于 TiDB 8 系列,我后面再写一篇文章详细的介绍一下吧。在 2024 年,我们交付了非常不错的成果,当然,能取得这样的成绩,来自于我们不断地交付优异的产品,满足客户的需求,赢得客户的信任。

TiDB_PingCAP 的博客 1050

PingCAP 连续两年入选 Gartner 云数据库管理系统魔力象限“荣誉提及”

TiDB 凭借领先的 HTAP 架构设计,支持用户在云上的数据库中同时运行关键业务交易和实时分析任务,充分享受云的弹性优势和业务连续性优势,助力企业实现数据敏捷。PingCAP 是业界领先的企业级开源分布式数据库企业,提供包括开源分布式数据库产品、解决方案与咨询、技术支持与培训认证服务,致力于为全球行业用户提供稳定高效、安全可靠、开放兼容的新型数据服务平台,解放企业生产力,加速企业数字化转型升级。

TiDB_PingCAP 的博客 692

TiDB 8.5 LTS 发版——支持无限扩展,开启 AI 就绪新时代

TiDB 8.5 扩展了Runaway Queries 的功能,新增“处理行数”和“用量(RU)”作为识别标准,实现更精确的识别,并允许将这些 Runaway Queries 放入一个资源可控的组中,确保在高负载环境下集群的稳定性。TiDB 8.5 通过实例级执行计划缓存功能,使得同一 TiDB 实例内的所有会话共享执行计划缓存,减少SQL编译时间,从而降低整体 SQL 运行时间,提高 OLTP 的性能和吞吐量,并有效地控制内存使用,提升数据库的稳定性。

TiDB_PingCAP 的博客 770

基于时间维度水平拆分的多 TiDB 集群统一数据路由/联邦查询技术的实践

通过该组件与 TiDB 分布式数据库的有效结合,可以实现近乎无容量上限的超大规模数据管理,尤其是对于重要程度高、吞吐量大、业务敏捷性强、数据冷热特征明显的业务系统。不仅能够支撑面向内外部客户业务无损的多维度、不受分片键制约的灵活高效访问,还可以有效控制和平衡单集群的负载、容量、资源利用率、稳定性等关键指标,在不增加过多复杂性的前提下实现更强的整体扩展能力。当然,组件的现有功能更多是聚焦在当前的客户场景,未来可以按需在功能性、易用性、高性能等方面进一步优化和提升。

TiDB_PingCAP 的博客 908

Rakuten 乐天积分系统从 Cassandra 到 TiDB 的选型与实战

Rakuten 乐天是一家成立于 1997 年的日本公司,总部位于东京,员工总数超过 30,000 人,业务遍及 100 多个国家和地区。除了电商业务外,乐天还涉及电信、金融等多个行业。乐天的积分系统在日本非常普及,用户通过使用乐天的服务,如银行卡、手机卡等,可以获得积分,并在乐天商城中使用这些积分进行购物或抵扣,我们每个季度还会举办促销活动、会有大量用户高频访问积分系统,因此,乐天积分服务平台的性能、延迟和可用性要求极高。

TiDB_PingCAP 的博客 1348

平凯星辰亮相开放原子开发者大会,TiDB 荣获年度活跃开源项目奖项

12 月 20 - 21 日,以“一切为了开发者”为主题的 2024 开放原子开发者大会暨首届开源技术学术大会在武汉成功举办。平凯星辰亮相本次大会,出品 AI 时代的数据库技术发展论坛,获评“校源行”优质开源课程合作单位。由平凯星辰创立的开源分布式数据库 TiDB 获评“2024 年度数据库领域国内活跃开源项目”,7 位 TiDB 开发者获评“2024 年度数据库领域国内活跃开源开发者”,彰显了 TiDB 在开源数据库领域的卓越影响力和社区活力。

TiDB_PingCAP 的博客 602

B 站数据库负责人赵月顺:助力海内外业务增长,百套 TiDB 的选型与运维实战

B 站的 TiDB 集群规模已达到 100 多套,计算节点超过 2000 个,存储节点超过 800 个。TiDB 的应用场景非常广泛,包括视频观看、一键三连、发送弹幕、撰写评论、阅读漫画以及视频后端的存储等。B 站的 TiDB 部署采取了存算分离的策略,计算节点被部署在容器中,这种做法的优势在于能够充分发挥容器化管理无状态服务达到水平弹性拓展能力,允许根据需求随时对计算节点进行扩展或缩减,整个过程仅需一次服务发版,有效提升了效率与灵活性。存储节点则继续部署在物理机上。

TiDB_PingCAP 的博客 1224

微众银行携手平凯星辰荣膺金融科技创新奖,共同打造纳管千台服务器的大规模数据库运维平台

在过去的十年中,平凯数据库在金融行业积累了丰富的实践经验,已经在国有大型银行的 PB 级别数据服务平台、头部商业银行的核心交易系统、头部保险公司的核心保单系统、头部证券公司的核心交易系统等领域,成功完成了对国外商业数据库和 MySQL 数据库的替换升级,部署了超过 1,000 套关键业务系统,集群节点总数超过了 10,000 个。为了应对这些挑战,同时满足金融行业对高可用、高可靠、高性能数据库的需求,微众银行开发了一套完整的运维体系,打造了大规模信创原生分布式数据库智能运维平台。

TiDB_PingCAP 的博客 799

知乎 PB 级别 TiDB 数据库集群管控实践

知乎的数据库团队以“致力于提供稳定、高效和易用的数据库服务”为目标,为公司业务团队提供更好的 TiDB 存储服务来应对高并发、复杂查询和大数据存储的需求。本文详细介绍了 TiDB 的生态架构,包括核心组件、数据迁移与同步、运维与监控平台、备份与恢复、生态集成、K8s 支持、工具集和安全与审计等方面。同时探讨了知乎如何在云上和云下环境中管控 TiDB 集群,以及如何通过自研的天穹平台实现数据库平台化建设,提升业务研发团队数据库变更和 DBA 团队的资源管控效率。

TiDB_PingCAP 的博客 1173

TiDB 优化器 | 执行计划管理及实践

本文深入解析了 TiDB 优化器的执行计划生成过程及其局限性,介绍了如何通过 Hint、SQL Binding、执行计划缓存等技术手段进行执行计划管理,确保查询性能的稳定性和高效性。

TiDB_PingCAP 的博客 1307

商业银行基于容器云的分布式数据库架构设计与创新实践

本文介绍了某商业银行基于 TiDB 和 Kubernetes(简称 K8s) 构建的云化分布式数据库平台,重点解决了传统私有部署模式下的高成本、低资源利用率及运维复杂等问题。通过引入 TiDB Operator 自动化管理与容器化技术,银行能够实现多个业务系统的高可用、弹性扩展与自动化运维,极大提高了运营效率与资源利用率。本文还详细阐述了平台架构设计、面临的技术挑战及创新解决方案,展示了 TiDB 在金融行业数字化转型中的应用前景。

TiDB_PingCAP 的博客 1353

PingCAP 荣膺 2024 亚马逊云科技合作伙伴两项殊荣

近日,在 2024 亚马逊云科技 re:Invent 全球大会上,PingCAP 荣膺亚马逊云科技年度技术合作伙伴和年度亚马逊云科技 Marketplace 合作伙伴两项殊荣。这是 PingCAP 连续第二年获得亚马逊云科技年度合作伙伴奖项,彰显了 PingCAP 在与亚马逊云科技合作服务客户的过程中所展现的卓越技术实力和专业服务能力,共同推动全球用户业务取得成功。

TiDB_PingCAP 的博客 561

基于 AutoFlow 快速搭建基于 TiDB 向量搜索的本地知识库问答机器人

通过本篇文章介绍,相信大家对使用 PingCAP 开源项目 AutoFlow 实现快速搭建基于 TiDB 的本地知识库问答机器人会有一个完整的了解。如果提前准备好 Docker、TiDB 环境,整个搭建过程估计在 10 分钟左右即可完成,无须开发任何代码。文中使用一篇 TiDB 文档作为本地数据源作为示例,在实际情况中,您可以基于自己的企业环境用同样的方法快速构造企业内部知识库问答机器人。

TiDB_PingCAP 的博客 1278

实战丨证券 HTAP 混合业务场景的难点问题应对

某领先的全国性大型综合证券公司,坚持以核心业务为发展重心,并积极投身于前沿科技的应用创新。本文将分享该证券公司债权开放信息平台的构建经验,深入探讨如何利用 TiDB 分布式数据库成功应对 HTAP 场景下的挑战,满足数据实时性、可靠性、资源隔离、可维护性等要求。通过这一实践案例,我们可以看到 TiDB 如何在金融服务领域发挥关键作用,以及它如何帮助企业在激烈的市场竞争中保持领先地位。

TiDB_PingCAP 的博客 854
上一篇: 实战丨证券 HTAP 混合业务场景的难点问题应对
下一篇: 基于 AutoFlow 快速搭建基于 TiDB 向量搜索的本地知识库问答机器人
TiDB_PingCAP
TiDB_PingCAP 企业官方账号 企业官方账号
博客等级 码龄10年 2724粉丝 780原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值