diff --git a/pkg/executor/testdata/prepare_suite_out.json b/pkg/executor/testdata/prepare_suite_out.json index 3d4018e0656c4..1e69f9f051b67 100644 --- a/pkg/executor/testdata/prepare_suite_out.json +++ b/pkg/executor/testdata/prepare_suite_out.json @@ -1211,9 +1211,9 @@ ], "Plan": [ "TopN_7 1.00 root test.t.b, offset:0, count:1", - "└─TableReader_16 1.00 root data:TopN_15", - " └─TopN_15 1.00 cop[tikv] test.t.b, offset:0, count:1", - " └─TableFullScan_14 10000.00 cop[tikv] table:t keep order:false, stats:pseudo" + "└─TableReader_17 1.00 root data:TopN_16", + " └─TopN_16 1.00 cop[tikv] test.t.b, offset:0, count:1", + " └─TableFullScan_15 10000.00 cop[tikv] table:t keep order:false, stats:pseudo" ], "FromCache": "0" }, @@ -1227,9 +1227,9 @@ ], "Plan": [ "TopN_7 5.00 root test.t.b, offset:0, count:5", - "└─TableReader_16 5.00 root data:TopN_15", - " └─TopN_15 5.00 cop[tikv] test.t.b, offset:0, count:5", - " └─TableFullScan_14 10000.00 cop[tikv] table:t keep order:false, stats:pseudo" + "└─TableReader_17 5.00 root data:TopN_16", + " └─TopN_16 5.00 cop[tikv] test.t.b, offset:0, count:5", + " └─TableFullScan_15 10000.00 cop[tikv] table:t keep order:false, stats:pseudo" ], "FromCache": "0" }, diff --git a/pkg/planner/cardinality/testdata/cardinality_suite_out.json b/pkg/planner/cardinality/testdata/cardinality_suite_out.json index 42d9ea09269d6..d7afee3258f45 100644 --- a/pkg/planner/cardinality/testdata/cardinality_suite_out.json +++ b/pkg/planner/cardinality/testdata/cardinality_suite_out.json @@ -1177,7 +1177,8 @@ "TopN 1.00 root test.t.c, offset:0, count:1", "└─IndexMerge 1.00 root type: union", " ├─IndexRangeScan(Build) 510.00 cop[tikv] table:t, index:ib(b) range:[0,50], keep order:false, stats:pseudo", - " ├─IndexRangeScan(Build) 510.00 cop[tikv] table:t, index:ic(c) range:[0,50], keep order:false, stats:pseudo", + " ├─Limit(Build) 1.00 cop[tikv] offset:0, count:1", + " │ └─IndexRangeScan 510.00 cop[tikv] table:t, index:ic(c) range:[0,50], keep order:true, stats:pseudo", " └─TopN(Probe) 1.00 cop[tikv] test.t.c, offset:0, count:1", " └─TableRowIDScan 1017.40 cop[tikv] table:t keep order:false, stats:pseudo" ] diff --git a/pkg/planner/core/casetest/cbotest/testdata/analyze_suite_out.json b/pkg/planner/core/casetest/cbotest/testdata/analyze_suite_out.json index e470de63d2b43..ce1b52a0d0d0d 100644 --- a/pkg/planner/core/casetest/cbotest/testdata/analyze_suite_out.json +++ b/pkg/planner/core/casetest/cbotest/testdata/analyze_suite_out.json @@ -583,23 +583,23 @@ { "SQL": "explain format = 'cost_trace' \nSELECT\n /*+ QB_NAME(`listObjectsWithPrefix_FilterByCreated_Since_Securable`) */\n path,\n updated_ms,\n size,\n etag,\n seq,\n last_seen_ms\nFROM\n objects FORCE INDEX(idx_metastore_securable_seq)\nWHERE\n metastore_uuid = 0x3CBCC26CA7E740A48DD164D74757DEE2\n AND securable_id = 2238365063123291\n AND (\n seq > 17299834\n AND TRUE\n )\nORDER BY\n seq\nLIMIT\n 5001;", "Plan": [ - "Projection_27 5001.00 9909305.39 (((((((net(5001*rowsize(54.67)*tidb_kv_net_factor(3.96))) + (((scan(5001*logrowsize(71.47926389640116)*tikv_scan_factor(40.7)))*1.00)))/15.00) + (((((net(5001*rowsize(273.515)*tidb_kv_net_factor(3.96))) + ((scan(5001*logrowsize(313.69926389640113)*tikv_scan_factor(40.7)))*1.00))/15.00) + ((double-read-cpu(5001*tidb_cpu_factor(49.9))) + (doubleRead(tasks(8.0016)*tidb_request_factor(6e+06)))))/5.00))*1.00)*1.00) + ((cpu(5001*filters(0.08)*tidb_cpu_factor(49.9)))/5.00)) + ((cpu(5001*filters(0.060000000000000005)*tidb_cpu_factor(49.9)))/5.00) root test.objects.path, test.objects.updated_ms, test.objects.size, test.objects.etag, test.objects.seq, test.objects.last_seen_ms", - "└─Projection_26 5001.00 9906310.79 ((((((net(5001*rowsize(54.67)*tidb_kv_net_factor(3.96))) + (((scan(5001*logrowsize(71.47926389640116)*tikv_scan_factor(40.7)))*1.00)))/15.00) + (((((net(5001*rowsize(273.515)*tidb_kv_net_factor(3.96))) + ((scan(5001*logrowsize(313.69926389640113)*tikv_scan_factor(40.7)))*1.00))/15.00) + ((double-read-cpu(5001*tidb_cpu_factor(49.9))) + (doubleRead(tasks(8.0016)*tidb_request_factor(6e+06)))))/5.00))*1.00)*1.00) + ((cpu(5001*filters(0.08)*tidb_cpu_factor(49.9)))/5.00) root test.objects.path, test.objects.updated_ms, test.objects.size, test.objects.etag, test.objects.seq, test.objects.last_seen_ms, test.objects.metastore_uuid, test.objects.securable_id", - " └─IndexLookUp_25 5001.00 9902317.99 (((((net(5001*rowsize(54.67)*tidb_kv_net_factor(3.96))) + (((scan(5001*logrowsize(71.47926389640116)*tikv_scan_factor(40.7)))*1.00)))/15.00) + (((((net(5001*rowsize(273.515)*tidb_kv_net_factor(3.96))) + ((scan(5001*logrowsize(313.69926389640113)*tikv_scan_factor(40.7)))*1.00))/15.00) + ((double-read-cpu(5001*tidb_cpu_factor(49.9))) + (doubleRead(tasks(8.0016)*tidb_request_factor(6e+06)))))/5.00))*1.00)*1.00 root limit embedded(offset:0, count:5001)", - " ├─Limit_24(Build) 5001.00 1253699.35 ((scan(5001*logrowsize(71.47926389640116)*tikv_scan_factor(40.7)))*1.00) cop[tikv] offset:0, count:5001", - " │ └─IndexRangeScan_22 5001.00 1253699.35 (scan(5001*logrowsize(71.47926389640116)*tikv_scan_factor(40.7)))*1.00 cop[tikv] table:objects, index:idx_metastore_securable_seq(metastore_uuid, securable_id, seq) range:(\"<\\xbc\\xc2l\\xa7\\xe7@\\xa4\\x8d\\xd1d\\xd7GW\\xde\\xe2\" 2238365063123291 17299834,\"<\\xbc\\xc2l\\xa7\\xe7@\\xa4\\x8d\\xd1d\\xd7GW\\xde\\xe2\" 2238365063123291 +inf], keep order:true", - " └─TableRowIDScan_23(Probe) 5001.00 1688011.54 (scan(5001*logrowsize(313.69926389640113)*tikv_scan_factor(40.7)))*1.00 cop[tikv] table:objects keep order:false" + "Projection_32 5001.00 9909305.39 (((((((net(5001*rowsize(54.67)*tidb_kv_net_factor(3.96))) + (((scan(5001*logrowsize(71.47926389640116)*tikv_scan_factor(40.7)))*1.00)))/15.00) + (((((net(5001*rowsize(273.515)*tidb_kv_net_factor(3.96))) + ((scan(5001*logrowsize(313.69926389640113)*tikv_scan_factor(40.7)))*1.00))/15.00) + ((double-read-cpu(5001*tidb_cpu_factor(49.9))) + (doubleRead(tasks(8.0016)*tidb_request_factor(6e+06)))))/5.00))*1.00)*1.00) + ((cpu(5001*filters(0.08)*tidb_cpu_factor(49.9)))/5.00)) + ((cpu(5001*filters(0.060000000000000005)*tidb_cpu_factor(49.9)))/5.00) root test.objects.path, test.objects.updated_ms, test.objects.size, test.objects.etag, test.objects.seq, test.objects.last_seen_ms", + "└─Projection_31 5001.00 9906310.79 ((((((net(5001*rowsize(54.67)*tidb_kv_net_factor(3.96))) + (((scan(5001*logrowsize(71.47926389640116)*tikv_scan_factor(40.7)))*1.00)))/15.00) + (((((net(5001*rowsize(273.515)*tidb_kv_net_factor(3.96))) + ((scan(5001*logrowsize(313.69926389640113)*tikv_scan_factor(40.7)))*1.00))/15.00) + ((double-read-cpu(5001*tidb_cpu_factor(49.9))) + (doubleRead(tasks(8.0016)*tidb_request_factor(6e+06)))))/5.00))*1.00)*1.00) + ((cpu(5001*filters(0.08)*tidb_cpu_factor(49.9)))/5.00) root test.objects.path, test.objects.updated_ms, test.objects.size, test.objects.etag, test.objects.seq, test.objects.last_seen_ms, test.objects.metastore_uuid, test.objects.securable_id", + " └─IndexLookUp_30 5001.00 9902317.99 (((((net(5001*rowsize(54.67)*tidb_kv_net_factor(3.96))) + (((scan(5001*logrowsize(71.47926389640116)*tikv_scan_factor(40.7)))*1.00)))/15.00) + (((((net(5001*rowsize(273.515)*tidb_kv_net_factor(3.96))) + ((scan(5001*logrowsize(313.69926389640113)*tikv_scan_factor(40.7)))*1.00))/15.00) + ((double-read-cpu(5001*tidb_cpu_factor(49.9))) + (doubleRead(tasks(8.0016)*tidb_request_factor(6e+06)))))/5.00))*1.00)*1.00 root limit embedded(offset:0, count:5001)", + " ├─Limit_29(Build) 5001.00 1253699.35 ((scan(5001*logrowsize(71.47926389640116)*tikv_scan_factor(40.7)))*1.00) cop[tikv] offset:0, count:5001", + " │ └─IndexRangeScan_27 5001.00 1253699.35 (scan(5001*logrowsize(71.47926389640116)*tikv_scan_factor(40.7)))*1.00 cop[tikv] table:objects, index:idx_metastore_securable_seq(metastore_uuid, securable_id, seq) range:(\"<\\xbc\\xc2l\\xa7\\xe7@\\xa4\\x8d\\xd1d\\xd7GW\\xde\\xe2\" 2238365063123291 17299834,\"<\\xbc\\xc2l\\xa7\\xe7@\\xa4\\x8d\\xd1d\\xd7GW\\xde\\xe2\" 2238365063123291 +inf], keep order:true", + " └─TableRowIDScan_28(Probe) 5001.00 1688011.54 (scan(5001*logrowsize(313.69926389640113)*tikv_scan_factor(40.7)))*1.00 cop[tikv] table:objects keep order:false" ] }, { "SQL": "explain format = 'cost_trace' \nSELECT\n /*+ limit_to_cop(), QB_NAME(`listObjectsWithPrefix_FilterByCreated_Since_Securable`) */\n path,\n updated_ms,\n size,\n etag,\n seq,\n last_seen_ms\nFROM\n objects FORCE INDEX(idx_metastore_securable_seq)\nWHERE\n metastore_uuid = 0x3CBCC26CA7E740A48DD164D74757DEE2\n AND securable_id = 2238365063123291\n AND (\n seq > 17299834\n AND TRUE\n )\nORDER BY\n seq\nLIMIT\n 5001;", "Plan": [ - "Projection_27 5001.00 9909305.39 (((((((net(5001*rowsize(54.67)*tidb_kv_net_factor(3.96))) + (((scan(5001*logrowsize(71.47926389640116)*tikv_scan_factor(40.7)))*1.00)))/15.00) + (((((net(5001*rowsize(273.515)*tidb_kv_net_factor(3.96))) + ((scan(5001*logrowsize(313.69926389640113)*tikv_scan_factor(40.7)))*1.00))/15.00) + ((double-read-cpu(5001*tidb_cpu_factor(49.9))) + (doubleRead(tasks(8.0016)*tidb_request_factor(6e+06)))))/5.00))*1.00)*1.00) + ((cpu(5001*filters(0.08)*tidb_cpu_factor(49.9)))/5.00)) + ((cpu(5001*filters(0.060000000000000005)*tidb_cpu_factor(49.9)))/5.00) root test.objects.path, test.objects.updated_ms, test.objects.size, test.objects.etag, test.objects.seq, test.objects.last_seen_ms", - "└─Projection_26 5001.00 9906310.79 ((((((net(5001*rowsize(54.67)*tidb_kv_net_factor(3.96))) + (((scan(5001*logrowsize(71.47926389640116)*tikv_scan_factor(40.7)))*1.00)))/15.00) + (((((net(5001*rowsize(273.515)*tidb_kv_net_factor(3.96))) + ((scan(5001*logrowsize(313.69926389640113)*tikv_scan_factor(40.7)))*1.00))/15.00) + ((double-read-cpu(5001*tidb_cpu_factor(49.9))) + (doubleRead(tasks(8.0016)*tidb_request_factor(6e+06)))))/5.00))*1.00)*1.00) + ((cpu(5001*filters(0.08)*tidb_cpu_factor(49.9)))/5.00) root test.objects.path, test.objects.updated_ms, test.objects.size, test.objects.etag, test.objects.seq, test.objects.last_seen_ms, test.objects.metastore_uuid, test.objects.securable_id", - " └─IndexLookUp_25 5001.00 9902317.99 (((((net(5001*rowsize(54.67)*tidb_kv_net_factor(3.96))) + (((scan(5001*logrowsize(71.47926389640116)*tikv_scan_factor(40.7)))*1.00)))/15.00) + (((((net(5001*rowsize(273.515)*tidb_kv_net_factor(3.96))) + ((scan(5001*logrowsize(313.69926389640113)*tikv_scan_factor(40.7)))*1.00))/15.00) + ((double-read-cpu(5001*tidb_cpu_factor(49.9))) + (doubleRead(tasks(8.0016)*tidb_request_factor(6e+06)))))/5.00))*1.00)*1.00 root limit embedded(offset:0, count:5001)", - " ├─Limit_24(Build) 5001.00 1253699.35 ((scan(5001*logrowsize(71.47926389640116)*tikv_scan_factor(40.7)))*1.00) cop[tikv] offset:0, count:5001", - " │ └─IndexRangeScan_22 5001.00 1253699.35 (scan(5001*logrowsize(71.47926389640116)*tikv_scan_factor(40.7)))*1.00 cop[tikv] table:objects, index:idx_metastore_securable_seq(metastore_uuid, securable_id, seq) range:(\"<\\xbc\\xc2l\\xa7\\xe7@\\xa4\\x8d\\xd1d\\xd7GW\\xde\\xe2\" 2238365063123291 17299834,\"<\\xbc\\xc2l\\xa7\\xe7@\\xa4\\x8d\\xd1d\\xd7GW\\xde\\xe2\" 2238365063123291 +inf], keep order:true", - " └─TableRowIDScan_23(Probe) 5001.00 1688011.54 (scan(5001*logrowsize(313.69926389640113)*tikv_scan_factor(40.7)))*1.00 cop[tikv] table:objects keep order:false" + "Projection_32 5001.00 9909305.39 (((((((net(5001*rowsize(54.67)*tidb_kv_net_factor(3.96))) + (((scan(5001*logrowsize(71.47926389640116)*tikv_scan_factor(40.7)))*1.00)))/15.00) + (((((net(5001*rowsize(273.515)*tidb_kv_net_factor(3.96))) + ((scan(5001*logrowsize(313.69926389640113)*tikv_scan_factor(40.7)))*1.00))/15.00) + ((double-read-cpu(5001*tidb_cpu_factor(49.9))) + (doubleRead(tasks(8.0016)*tidb_request_factor(6e+06)))))/5.00))*1.00)*1.00) + ((cpu(5001*filters(0.08)*tidb_cpu_factor(49.9)))/5.00)) + ((cpu(5001*filters(0.060000000000000005)*tidb_cpu_factor(49.9)))/5.00) root test.objects.path, test.objects.updated_ms, test.objects.size, test.objects.etag, test.objects.seq, test.objects.last_seen_ms", + "└─Projection_31 5001.00 9906310.79 ((((((net(5001*rowsize(54.67)*tidb_kv_net_factor(3.96))) + (((scan(5001*logrowsize(71.47926389640116)*tikv_scan_factor(40.7)))*1.00)))/15.00) + (((((net(5001*rowsize(273.515)*tidb_kv_net_factor(3.96))) + ((scan(5001*logrowsize(313.69926389640113)*tikv_scan_factor(40.7)))*1.00))/15.00) + ((double-read-cpu(5001*tidb_cpu_factor(49.9))) + (doubleRead(tasks(8.0016)*tidb_request_factor(6e+06)))))/5.00))*1.00)*1.00) + ((cpu(5001*filters(0.08)*tidb_cpu_factor(49.9)))/5.00) root test.objects.path, test.objects.updated_ms, test.objects.size, test.objects.etag, test.objects.seq, test.objects.last_seen_ms, test.objects.metastore_uuid, test.objects.securable_id", + " └─IndexLookUp_30 5001.00 9902317.99 (((((net(5001*rowsize(54.67)*tidb_kv_net_factor(3.96))) + (((scan(5001*logrowsize(71.47926389640116)*tikv_scan_factor(40.7)))*1.00)))/15.00) + (((((net(5001*rowsize(273.515)*tidb_kv_net_factor(3.96))) + ((scan(5001*logrowsize(313.69926389640113)*tikv_scan_factor(40.7)))*1.00))/15.00) + ((double-read-cpu(5001*tidb_cpu_factor(49.9))) + (doubleRead(tasks(8.0016)*tidb_request_factor(6e+06)))))/5.00))*1.00)*1.00 root limit embedded(offset:0, count:5001)", + " ├─Limit_29(Build) 5001.00 1253699.35 ((scan(5001*logrowsize(71.47926389640116)*tikv_scan_factor(40.7)))*1.00) cop[tikv] offset:0, count:5001", + " │ └─IndexRangeScan_27 5001.00 1253699.35 (scan(5001*logrowsize(71.47926389640116)*tikv_scan_factor(40.7)))*1.00 cop[tikv] table:objects, index:idx_metastore_securable_seq(metastore_uuid, securable_id, seq) range:(\"<\\xbc\\xc2l\\xa7\\xe7@\\xa4\\x8d\\xd1d\\xd7GW\\xde\\xe2\" 2238365063123291 17299834,\"<\\xbc\\xc2l\\xa7\\xe7@\\xa4\\x8d\\xd1d\\xd7GW\\xde\\xe2\" 2238365063123291 +inf], keep order:true", + " └─TableRowIDScan_28(Probe) 5001.00 1688011.54 (scan(5001*logrowsize(313.69926389640113)*tikv_scan_factor(40.7)))*1.00 cop[tikv] table:objects keep order:false" ] } ] diff --git a/pkg/planner/core/casetest/cbotest/testdata/analyze_suite_xut.json b/pkg/planner/core/casetest/cbotest/testdata/analyze_suite_xut.json index e470de63d2b43..ce1b52a0d0d0d 100644 --- a/pkg/planner/core/casetest/cbotest/testdata/analyze_suite_xut.json +++ b/pkg/planner/core/casetest/cbotest/testdata/analyze_suite_xut.json @@ -583,23 +583,23 @@ { "SQL": "explain format = 'cost_trace' \nSELECT\n /*+ QB_NAME(`listObjectsWithPrefix_FilterByCreated_Since_Securable`) */\n path,\n updated_ms,\n size,\n etag,\n seq,\n last_seen_ms\nFROM\n objects FORCE INDEX(idx_metastore_securable_seq)\nWHERE\n metastore_uuid = 0x3CBCC26CA7E740A48DD164D74757DEE2\n AND securable_id = 2238365063123291\n AND (\n seq > 17299834\n AND TRUE\n )\nORDER BY\n seq\nLIMIT\n 5001;", "Plan": [ - "Projection_27 5001.00 9909305.39 (((((((net(5001*rowsize(54.67)*tidb_kv_net_factor(3.96))) + (((scan(5001*logrowsize(71.47926389640116)*tikv_scan_factor(40.7)))*1.00)))/15.00) + (((((net(5001*rowsize(273.515)*tidb_kv_net_factor(3.96))) + ((scan(5001*logrowsize(313.69926389640113)*tikv_scan_factor(40.7)))*1.00))/15.00) + ((double-read-cpu(5001*tidb_cpu_factor(49.9))) + (doubleRead(tasks(8.0016)*tidb_request_factor(6e+06)))))/5.00))*1.00)*1.00) + ((cpu(5001*filters(0.08)*tidb_cpu_factor(49.9)))/5.00)) + ((cpu(5001*filters(0.060000000000000005)*tidb_cpu_factor(49.9)))/5.00) root test.objects.path, test.objects.updated_ms, test.objects.size, test.objects.etag, test.objects.seq, test.objects.last_seen_ms", - "└─Projection_26 5001.00 9906310.79 ((((((net(5001*rowsize(54.67)*tidb_kv_net_factor(3.96))) + (((scan(5001*logrowsize(71.47926389640116)*tikv_scan_factor(40.7)))*1.00)))/15.00) + (((((net(5001*rowsize(273.515)*tidb_kv_net_factor(3.96))) + ((scan(5001*logrowsize(313.69926389640113)*tikv_scan_factor(40.7)))*1.00))/15.00) + ((double-read-cpu(5001*tidb_cpu_factor(49.9))) + (doubleRead(tasks(8.0016)*tidb_request_factor(6e+06)))))/5.00))*1.00)*1.00) + ((cpu(5001*filters(0.08)*tidb_cpu_factor(49.9)))/5.00) root test.objects.path, test.objects.updated_ms, test.objects.size, test.objects.etag, test.objects.seq, test.objects.last_seen_ms, test.objects.metastore_uuid, test.objects.securable_id", - " └─IndexLookUp_25 5001.00 9902317.99 (((((net(5001*rowsize(54.67)*tidb_kv_net_factor(3.96))) + (((scan(5001*logrowsize(71.47926389640116)*tikv_scan_factor(40.7)))*1.00)))/15.00) + (((((net(5001*rowsize(273.515)*tidb_kv_net_factor(3.96))) + ((scan(5001*logrowsize(313.69926389640113)*tikv_scan_factor(40.7)))*1.00))/15.00) + ((double-read-cpu(5001*tidb_cpu_factor(49.9))) + (doubleRead(tasks(8.0016)*tidb_request_factor(6e+06)))))/5.00))*1.00)*1.00 root limit embedded(offset:0, count:5001)", - " ├─Limit_24(Build) 5001.00 1253699.35 ((scan(5001*logrowsize(71.47926389640116)*tikv_scan_factor(40.7)))*1.00) cop[tikv] offset:0, count:5001", - " │ └─IndexRangeScan_22 5001.00 1253699.35 (scan(5001*logrowsize(71.47926389640116)*tikv_scan_factor(40.7)))*1.00 cop[tikv] table:objects, index:idx_metastore_securable_seq(metastore_uuid, securable_id, seq) range:(\"<\\xbc\\xc2l\\xa7\\xe7@\\xa4\\x8d\\xd1d\\xd7GW\\xde\\xe2\" 2238365063123291 17299834,\"<\\xbc\\xc2l\\xa7\\xe7@\\xa4\\x8d\\xd1d\\xd7GW\\xde\\xe2\" 2238365063123291 +inf], keep order:true", - " └─TableRowIDScan_23(Probe) 5001.00 1688011.54 (scan(5001*logrowsize(313.69926389640113)*tikv_scan_factor(40.7)))*1.00 cop[tikv] table:objects keep order:false" + "Projection_32 5001.00 9909305.39 (((((((net(5001*rowsize(54.67)*tidb_kv_net_factor(3.96))) + (((scan(5001*logrowsize(71.47926389640116)*tikv_scan_factor(40.7)))*1.00)))/15.00) + (((((net(5001*rowsize(273.515)*tidb_kv_net_factor(3.96))) + ((scan(5001*logrowsize(313.69926389640113)*tikv_scan_factor(40.7)))*1.00))/15.00) + ((double-read-cpu(5001*tidb_cpu_factor(49.9))) + (doubleRead(tasks(8.0016)*tidb_request_factor(6e+06)))))/5.00))*1.00)*1.00) + ((cpu(5001*filters(0.08)*tidb_cpu_factor(49.9)))/5.00)) + ((cpu(5001*filters(0.060000000000000005)*tidb_cpu_factor(49.9)))/5.00) root test.objects.path, test.objects.updated_ms, test.objects.size, test.objects.etag, test.objects.seq, test.objects.last_seen_ms", + "└─Projection_31 5001.00 9906310.79 ((((((net(5001*rowsize(54.67)*tidb_kv_net_factor(3.96))) + (((scan(5001*logrowsize(71.47926389640116)*tikv_scan_factor(40.7)))*1.00)))/15.00) + (((((net(5001*rowsize(273.515)*tidb_kv_net_factor(3.96))) + ((scan(5001*logrowsize(313.69926389640113)*tikv_scan_factor(40.7)))*1.00))/15.00) + ((double-read-cpu(5001*tidb_cpu_factor(49.9))) + (doubleRead(tasks(8.0016)*tidb_request_factor(6e+06)))))/5.00))*1.00)*1.00) + ((cpu(5001*filters(0.08)*tidb_cpu_factor(49.9)))/5.00) root test.objects.path, test.objects.updated_ms, test.objects.size, test.objects.etag, test.objects.seq, test.objects.last_seen_ms, test.objects.metastore_uuid, test.objects.securable_id", + " └─IndexLookUp_30 5001.00 9902317.99 (((((net(5001*rowsize(54.67)*tidb_kv_net_factor(3.96))) + (((scan(5001*logrowsize(71.47926389640116)*tikv_scan_factor(40.7)))*1.00)))/15.00) + (((((net(5001*rowsize(273.515)*tidb_kv_net_factor(3.96))) + ((scan(5001*logrowsize(313.69926389640113)*tikv_scan_factor(40.7)))*1.00))/15.00) + ((double-read-cpu(5001*tidb_cpu_factor(49.9))) + (doubleRead(tasks(8.0016)*tidb_request_factor(6e+06)))))/5.00))*1.00)*1.00 root limit embedded(offset:0, count:5001)", + " ├─Limit_29(Build) 5001.00 1253699.35 ((scan(5001*logrowsize(71.47926389640116)*tikv_scan_factor(40.7)))*1.00) cop[tikv] offset:0, count:5001", + " │ └─IndexRangeScan_27 5001.00 1253699.35 (scan(5001*logrowsize(71.47926389640116)*tikv_scan_factor(40.7)))*1.00 cop[tikv] table:objects, index:idx_metastore_securable_seq(metastore_uuid, securable_id, seq) range:(\"<\\xbc\\xc2l\\xa7\\xe7@\\xa4\\x8d\\xd1d\\xd7GW\\xde\\xe2\" 2238365063123291 17299834,\"<\\xbc\\xc2l\\xa7\\xe7@\\xa4\\x8d\\xd1d\\xd7GW\\xde\\xe2\" 2238365063123291 +inf], keep order:true", + " └─TableRowIDScan_28(Probe) 5001.00 1688011.54 (scan(5001*logrowsize(313.69926389640113)*tikv_scan_factor(40.7)))*1.00 cop[tikv] table:objects keep order:false" ] }, { "SQL": "explain format = 'cost_trace' \nSELECT\n /*+ limit_to_cop(), QB_NAME(`listObjectsWithPrefix_FilterByCreated_Since_Securable`) */\n path,\n updated_ms,\n size,\n etag,\n seq,\n last_seen_ms\nFROM\n objects FORCE INDEX(idx_metastore_securable_seq)\nWHERE\n metastore_uuid = 0x3CBCC26CA7E740A48DD164D74757DEE2\n AND securable_id = 2238365063123291\n AND (\n seq > 17299834\n AND TRUE\n )\nORDER BY\n seq\nLIMIT\n 5001;", "Plan": [ - "Projection_27 5001.00 9909305.39 (((((((net(5001*rowsize(54.67)*tidb_kv_net_factor(3.96))) + (((scan(5001*logrowsize(71.47926389640116)*tikv_scan_factor(40.7)))*1.00)))/15.00) + (((((net(5001*rowsize(273.515)*tidb_kv_net_factor(3.96))) + ((scan(5001*logrowsize(313.69926389640113)*tikv_scan_factor(40.7)))*1.00))/15.00) + ((double-read-cpu(5001*tidb_cpu_factor(49.9))) + (doubleRead(tasks(8.0016)*tidb_request_factor(6e+06)))))/5.00))*1.00)*1.00) + ((cpu(5001*filters(0.08)*tidb_cpu_factor(49.9)))/5.00)) + ((cpu(5001*filters(0.060000000000000005)*tidb_cpu_factor(49.9)))/5.00) root test.objects.path, test.objects.updated_ms, test.objects.size, test.objects.etag, test.objects.seq, test.objects.last_seen_ms", - "└─Projection_26 5001.00 9906310.79 ((((((net(5001*rowsize(54.67)*tidb_kv_net_factor(3.96))) + (((scan(5001*logrowsize(71.47926389640116)*tikv_scan_factor(40.7)))*1.00)))/15.00) + (((((net(5001*rowsize(273.515)*tidb_kv_net_factor(3.96))) + ((scan(5001*logrowsize(313.69926389640113)*tikv_scan_factor(40.7)))*1.00))/15.00) + ((double-read-cpu(5001*tidb_cpu_factor(49.9))) + (doubleRead(tasks(8.0016)*tidb_request_factor(6e+06)))))/5.00))*1.00)*1.00) + ((cpu(5001*filters(0.08)*tidb_cpu_factor(49.9)))/5.00) root test.objects.path, test.objects.updated_ms, test.objects.size, test.objects.etag, test.objects.seq, test.objects.last_seen_ms, test.objects.metastore_uuid, test.objects.securable_id", - " └─IndexLookUp_25 5001.00 9902317.99 (((((net(5001*rowsize(54.67)*tidb_kv_net_factor(3.96))) + (((scan(5001*logrowsize(71.47926389640116)*tikv_scan_factor(40.7)))*1.00)))/15.00) + (((((net(5001*rowsize(273.515)*tidb_kv_net_factor(3.96))) + ((scan(5001*logrowsize(313.69926389640113)*tikv_scan_factor(40.7)))*1.00))/15.00) + ((double-read-cpu(5001*tidb_cpu_factor(49.9))) + (doubleRead(tasks(8.0016)*tidb_request_factor(6e+06)))))/5.00))*1.00)*1.00 root limit embedded(offset:0, count:5001)", - " ├─Limit_24(Build) 5001.00 1253699.35 ((scan(5001*logrowsize(71.47926389640116)*tikv_scan_factor(40.7)))*1.00) cop[tikv] offset:0, count:5001", - " │ └─IndexRangeScan_22 5001.00 1253699.35 (scan(5001*logrowsize(71.47926389640116)*tikv_scan_factor(40.7)))*1.00 cop[tikv] table:objects, index:idx_metastore_securable_seq(metastore_uuid, securable_id, seq) range:(\"<\\xbc\\xc2l\\xa7\\xe7@\\xa4\\x8d\\xd1d\\xd7GW\\xde\\xe2\" 2238365063123291 17299834,\"<\\xbc\\xc2l\\xa7\\xe7@\\xa4\\x8d\\xd1d\\xd7GW\\xde\\xe2\" 2238365063123291 +inf], keep order:true", - " └─TableRowIDScan_23(Probe) 5001.00 1688011.54 (scan(5001*logrowsize(313.69926389640113)*tikv_scan_factor(40.7)))*1.00 cop[tikv] table:objects keep order:false" + "Projection_32 5001.00 9909305.39 (((((((net(5001*rowsize(54.67)*tidb_kv_net_factor(3.96))) + (((scan(5001*logrowsize(71.47926389640116)*tikv_scan_factor(40.7)))*1.00)))/15.00) + (((((net(5001*rowsize(273.515)*tidb_kv_net_factor(3.96))) + ((scan(5001*logrowsize(313.69926389640113)*tikv_scan_factor(40.7)))*1.00))/15.00) + ((double-read-cpu(5001*tidb_cpu_factor(49.9))) + (doubleRead(tasks(8.0016)*tidb_request_factor(6e+06)))))/5.00))*1.00)*1.00) + ((cpu(5001*filters(0.08)*tidb_cpu_factor(49.9)))/5.00)) + ((cpu(5001*filters(0.060000000000000005)*tidb_cpu_factor(49.9)))/5.00) root test.objects.path, test.objects.updated_ms, test.objects.size, test.objects.etag, test.objects.seq, test.objects.last_seen_ms", + "└─Projection_31 5001.00 9906310.79 ((((((net(5001*rowsize(54.67)*tidb_kv_net_factor(3.96))) + (((scan(5001*logrowsize(71.47926389640116)*tikv_scan_factor(40.7)))*1.00)))/15.00) + (((((net(5001*rowsize(273.515)*tidb_kv_net_factor(3.96))) + ((scan(5001*logrowsize(313.69926389640113)*tikv_scan_factor(40.7)))*1.00))/15.00) + ((double-read-cpu(5001*tidb_cpu_factor(49.9))) + (doubleRead(tasks(8.0016)*tidb_request_factor(6e+06)))))/5.00))*1.00)*1.00) + ((cpu(5001*filters(0.08)*tidb_cpu_factor(49.9)))/5.00) root test.objects.path, test.objects.updated_ms, test.objects.size, test.objects.etag, test.objects.seq, test.objects.last_seen_ms, test.objects.metastore_uuid, test.objects.securable_id", + " └─IndexLookUp_30 5001.00 9902317.99 (((((net(5001*rowsize(54.67)*tidb_kv_net_factor(3.96))) + (((scan(5001*logrowsize(71.47926389640116)*tikv_scan_factor(40.7)))*1.00)))/15.00) + (((((net(5001*rowsize(273.515)*tidb_kv_net_factor(3.96))) + ((scan(5001*logrowsize(313.69926389640113)*tikv_scan_factor(40.7)))*1.00))/15.00) + ((double-read-cpu(5001*tidb_cpu_factor(49.9))) + (doubleRead(tasks(8.0016)*tidb_request_factor(6e+06)))))/5.00))*1.00)*1.00 root limit embedded(offset:0, count:5001)", + " ├─Limit_29(Build) 5001.00 1253699.35 ((scan(5001*logrowsize(71.47926389640116)*tikv_scan_factor(40.7)))*1.00) cop[tikv] offset:0, count:5001", + " │ └─IndexRangeScan_27 5001.00 1253699.35 (scan(5001*logrowsize(71.47926389640116)*tikv_scan_factor(40.7)))*1.00 cop[tikv] table:objects, index:idx_metastore_securable_seq(metastore_uuid, securable_id, seq) range:(\"<\\xbc\\xc2l\\xa7\\xe7@\\xa4\\x8d\\xd1d\\xd7GW\\xde\\xe2\" 2238365063123291 17299834,\"<\\xbc\\xc2l\\xa7\\xe7@\\xa4\\x8d\\xd1d\\xd7GW\\xde\\xe2\" 2238365063123291 +inf], keep order:true", + " └─TableRowIDScan_28(Probe) 5001.00 1688011.54 (scan(5001*logrowsize(313.69926389640113)*tikv_scan_factor(40.7)))*1.00 cop[tikv] table:objects keep order:false" ] } ] diff --git a/pkg/planner/core/casetest/testdata/integration_suite_out.json b/pkg/planner/core/casetest/testdata/integration_suite_out.json index 85a8b343c97f2..fb6561bd583b4 100644 --- a/pkg/planner/core/casetest/testdata/integration_suite_out.json +++ b/pkg/planner/core/casetest/testdata/integration_suite_out.json @@ -40,18 +40,18 @@ "SQL": "explain format = 'verbose' select * from t3 order by a limit 1", "Plan": [ "TopN_7 1.00 60.74 root test.t3.a, offset:0, count:1", - "└─TableReader_16 1.00 54.34 root data:TopN_15", - " └─TopN_15 1.00 688.32 cop[tikv] test.t3.a, offset:0, count:1", - " └─TableFullScan_14 3.00 681.92 cop[tikv] table:t3 keep order:false" + "└─TableReader_17 1.00 54.34 root data:TopN_16", + " └─TopN_16 1.00 688.32 cop[tikv] test.t3.a, offset:0, count:1", + " └─TableFullScan_15 3.00 681.92 cop[tikv] table:t3 keep order:false" ] }, { "SQL": "explain format = 'verbose' select * from t3 order by b limit 1", "Plan": [ "TopN_7 1.00 60.74 root test.t3.b, offset:0, count:1", - "└─TableReader_16 1.00 54.34 root data:TopN_15", - " └─TopN_15 1.00 688.32 cop[tikv] test.t3.b, offset:0, count:1", - " └─TableFullScan_14 3.00 681.92 cop[tikv] table:t3 keep order:false" + "└─TableReader_17 1.00 54.34 root data:TopN_16", + " └─TopN_16 1.00 688.32 cop[tikv] test.t3.b, offset:0, count:1", + " └─TableFullScan_15 3.00 681.92 cop[tikv] table:t3 keep order:false" ] }, { diff --git a/pkg/planner/core/casetest/testdata/integration_suite_xut.json b/pkg/planner/core/casetest/testdata/integration_suite_xut.json index 85a8b343c97f2..fb6561bd583b4 100644 --- a/pkg/planner/core/casetest/testdata/integration_suite_xut.json +++ b/pkg/planner/core/casetest/testdata/integration_suite_xut.json @@ -40,18 +40,18 @@ "SQL": "explain format = 'verbose' select * from t3 order by a limit 1", "Plan": [ "TopN_7 1.00 60.74 root test.t3.a, offset:0, count:1", - "└─TableReader_16 1.00 54.34 root data:TopN_15", - " └─TopN_15 1.00 688.32 cop[tikv] test.t3.a, offset:0, count:1", - " └─TableFullScan_14 3.00 681.92 cop[tikv] table:t3 keep order:false" + "└─TableReader_17 1.00 54.34 root data:TopN_16", + " └─TopN_16 1.00 688.32 cop[tikv] test.t3.a, offset:0, count:1", + " └─TableFullScan_15 3.00 681.92 cop[tikv] table:t3 keep order:false" ] }, { "SQL": "explain format = 'verbose' select * from t3 order by b limit 1", "Plan": [ "TopN_7 1.00 60.74 root test.t3.b, offset:0, count:1", - "└─TableReader_16 1.00 54.34 root data:TopN_15", - " └─TopN_15 1.00 688.32 cop[tikv] test.t3.b, offset:0, count:1", - " └─TableFullScan_14 3.00 681.92 cop[tikv] table:t3 keep order:false" + "└─TableReader_17 1.00 54.34 root data:TopN_16", + " └─TopN_16 1.00 688.32 cop[tikv] test.t3.b, offset:0, count:1", + " └─TableFullScan_15 3.00 681.92 cop[tikv] table:t3 keep order:false" ] }, { diff --git a/pkg/planner/core/find_best_task.go b/pkg/planner/core/find_best_task.go index 8ec9f11644c84..3c27d9a251567 100644 --- a/pkg/planner/core/find_best_task.go +++ b/pkg/planner/core/find_best_task.go @@ -773,9 +773,15 @@ type candidatePath struct { // partialOrderMatch records the partial order match result for TopN optimization. // When the matched is true, it means this path can provide partial order using prefix index. partialOrderMatchResult property.PartialOrderMatchResult // Result of matching partial order property - indexJoinCols int // how many index columns are used in access conditions in this IndexJoin. - isFullRange bool // cached result of whether this path covers the full scan range. - eqOrInCount int // cached result of equalPredicateCount(). + // matchWithAdvisorySortItems indicates the property matching used SortItemsHints + // (i.e. noSortItem && len(prop.SortItemsHints) > 0). Only relevant for IndexMerge. + matchWithAdvisorySortItems bool + // partialPathMatchResults stores each partial path's matchProperty result. + // Length equals len(path.PartialIndexPaths). Only set for IndexMerge paths. + partialPathMatchResults []property.PhysicalPropMatchResult + indexJoinCols int // how many index columns are used in access conditions in this IndexJoin. + isFullRange bool // cached result of whether this path covers the full scan range. + eqOrInCount int // cached result of equalPredicateCount(). } func compareBool(l, r bool) int { @@ -1410,49 +1416,97 @@ func GroupRangesByCols(ranges []*ranger.Range, groupByColIdxs []int) ([][]*range // // at last, according to determinedIndexPartialPaths to rewrite their real countAfterAccess, this part is move from deriveStats to // here. -func matchPropForIndexMergeAlternatives(ds *logicalop.DataSource, path *util.AccessPath, prop *property.PhysicalProperty) (*util.AccessPath, property.PhysicalPropMatchResult) { +func matchPropForIndexMergeAlternatives(ds *logicalop.DataSource, path *util.AccessPath, prop *property.PhysicalProperty) (*util.AccessPath, []property.PhysicalPropMatchResult, bool, property.PhysicalPropMatchResult) { // target: // 1: index merge case, try to match the every alternative partial path to the order property as long as // possible, and generate that property-matched index merge path out if any. - // 2: If the prop is empty (means no sort requirement), we will generate a random index partial combination - // path from all alternatives in case that no index merge path comes out. + // 2: If the prop is empty (means no sort requirement) but AdvisorySortItems is set, prefer alternatives + // that can satisfy the SoftSortItems for potential Limit pushdown. + // 3: If neither, generate a random index partial combination path from all alternatives. // Execution part doesn't support the merge operation for intersection case yet. if path.IndexMergeIsIntersection { - return nil, property.PropNotMatched + return nil, nil, false, property.PropNotMatched } noSortItem := prop.IsSortItemEmpty() allSame, _ := prop.AllSameOrder() if !allSame { - return nil, property.PropNotMatched + return nil, nil, false, property.PropNotMatched + } + + // When SortItems is empty and AdvisorySortItems is set, use hints as soft sort + // requirements. Alternatives that satisfy AdvisorySortItems can benefit from + // Limit pushdown. + useAdvisorySortItems := noSortItem && len(prop.AdvisorySortItems) > 0 + var hintsProp *property.PhysicalProperty + if useAdvisorySortItems { + hintsProp = prop.CloneEssentialFields() + hintsProp.SortItems = hintsProp.AdvisorySortItems } + // step1: match the property from all the index partial alternative paths. determinedIndexPartialPaths := make([]*util.AccessPath, 0, len(path.PartialAlternativeIndexPaths)) + partialPathMatchResults := make([]property.PhysicalPropMatchResult, 0, len(path.PartialAlternativeIndexPaths)) usedIndexMap := make(map[int64]struct{}, 1) useMVIndex := false for _, oneORBranch := range path.PartialAlternativeIndexPaths { matchIdxes := make([]int, 0, 1) - for i, oneAlternative := range oneORBranch { - // if there is some sort items and this path doesn't match this prop, continue. - match := true - for _, oneAccessPath := range oneAlternative { - if !noSortItem && !matchProperty(ds, oneAccessPath, prop).Matched() { - match = false + // altMatchResults[i] stores matchProperty results for each access path + // in oneORBranch[i]. Populated during hints or fallback matching. + var altMatchResults [][]property.PhysicalPropMatchResult + + if useAdvisorySortItems { + // First pass: prefer alternatives that satisfy SortItemsHints. + altMatchResults = make([][]property.PhysicalPropMatchResult, len(oneORBranch)) + for i, oneAlternative := range oneORBranch { + match := true + results := make([]property.PhysicalPropMatchResult, 0, len(oneAlternative)) + for _, oneAccessPath := range oneAlternative { + result := matchProperty(ds, oneAccessPath, hintsProp) + results = append(results, result) + if !result.Matched() { + match = false + } + } + altMatchResults[i] = results + if match { + matchIdxes = append(matchIdxes, i) } } - if !match { - continue + } + + // If no matches found with hints, fall back to default matching logic. + if len(matchIdxes) == 0 { + altMatchResults = make([][]property.PhysicalPropMatchResult, len(oneORBranch)) + for i, oneAlternative := range oneORBranch { + // if there is some sort items and this path doesn't match this prop, continue. + match := true + results := make([]property.PhysicalPropMatchResult, 0, len(oneAlternative)) + for _, oneAccessPath := range oneAlternative { + var result property.PhysicalPropMatchResult + if !noSortItem { + result = matchProperty(ds, oneAccessPath, prop) + } + results = append(results, result) + if !noSortItem && !result.Matched() { + match = false + } + } + altMatchResults[i] = results + if !match { + continue + } + // two possibility here: + // 1. no sort items requirement. + // 2. matched with sorted items. + matchIdxes = append(matchIdxes, i) } - // two possibility here: - // 1. no sort items requirement. - // 2. matched with sorted items. - matchIdxes = append(matchIdxes, i) } if len(matchIdxes) == 0 { // if all index alternative of one of the cnf item's couldn't match the sort property, // the entire index merge union path can be ignored for this sort property, return false. - return nil, property.PropNotMatched + return nil, nil, false, property.PropNotMatched } if len(matchIdxes) > 1 { // if matchIdxes greater than 1, we should sort this match alternative path by its CountAfterAccess. @@ -1475,6 +1529,7 @@ func matchPropForIndexMergeAlternatives(ds *logicalop.DataSource, path *util.Acc } lowestCountAfterAccessIdx := matchIdxes[0] determinedIndexPartialPaths = append(determinedIndexPartialPaths, sliceutil.DeepClone(oneORBranch[lowestCountAfterAccessIdx])...) + partialPathMatchResults = append(partialPathMatchResults, altMatchResults[lowestCountAfterAccessIdx]...) // record the index usage info to avoid choosing a single index for all partial paths var indexID int64 if oneORBranch[lowestCountAfterAccessIdx][0].IsTablePath() { @@ -1493,7 +1548,7 @@ func matchPropForIndexMergeAlternatives(ds *logicalop.DataSource, path *util.Acc // since ds index merge hints will prune other path ahead, lift the all single index limitation here. if len(usedIndexMap) == 1 && !useMVIndex && len(ds.IndexMergeHints) <= 0 { // if all partial path are using a same index, meaningless and fail over. - return nil, property.PropNotMatched + return nil, nil, false, property.PropNotMatched } // check if any of the partial paths is not cacheable. @@ -1532,26 +1587,29 @@ func matchPropForIndexMergeAlternatives(ds *logicalop.DataSource, path *util.Acc if noSortItem { // since there is no sort property, index merge case is generated by random combination, each alternative with the lower/lowest // countAfterAccess, here the returned matchProperty should be PropNotMatched. - return indexMergePath, property.PropNotMatched + return indexMergePath, partialPathMatchResults, useAdvisorySortItems, property.PropNotMatched } - return indexMergePath, property.PropMatched + return indexMergePath, partialPathMatchResults, useAdvisorySortItems, property.PropMatched } -func isMatchPropForIndexMerge(ds *logicalop.DataSource, path *util.AccessPath, prop *property.PhysicalProperty) property.PhysicalPropMatchResult { +func isMatchPropForIndexMerge(ds *logicalop.DataSource, path *util.AccessPath, prop *property.PhysicalProperty) ([]property.PhysicalPropMatchResult, property.PhysicalPropMatchResult) { // Execution part doesn't support the merge operation for intersection case yet. if path.IndexMergeIsIntersection { - return property.PropNotMatched + return nil, property.PropNotMatched } allSame, _ := prop.AllSameOrder() if !allSame { - return property.PropNotMatched + return nil, property.PropNotMatched } + results := make([]property.PhysicalPropMatchResult, 0, len(path.PartialIndexPaths)) for _, partialPath := range path.PartialIndexPaths { - if !matchProperty(ds, partialPath, prop).Matched() { - return property.PropNotMatched + result := matchProperty(ds, partialPath, prop) + results = append(results, result) + if !result.Matched() { + return nil, property.PropNotMatched } } - return property.PropMatched + return results, property.PropMatched } func getTableCandidate(ds *logicalop.DataSource, path *util.AccessPath, prop *property.PhysicalProperty) *candidatePath { @@ -1598,12 +1656,16 @@ func getIndexCandidateForIndexJoin(sctx planctx.PlanContext, path *util.AccessPa } func convergeIndexMergeCandidate(ds *logicalop.DataSource, path *util.AccessPath, prop *property.PhysicalProperty) *candidatePath { - // since the all index path alternative paths is collected and undetermined, and we should determine a possible and concrete path for this prop. - possiblePath, match := matchPropForIndexMergeAlternatives(ds, path, prop) + possiblePath, partialMatchResults, matchWithAdvisory, match := matchPropForIndexMergeAlternatives(ds, path, prop) if possiblePath == nil { return nil } - candidate := &candidatePath{path: possiblePath, matchPropResult: match} + candidate := &candidatePath{ + path: possiblePath, + matchPropResult: match, + matchWithAdvisorySortItems: matchWithAdvisory, + partialPathMatchResults: partialMatchResults, + } candidate.isFullRange = possiblePath.IsFullScanRange(ds.TableInfo) candidate.eqOrInCount = candidate.equalPredicateCount() return candidate @@ -1611,7 +1673,22 @@ func convergeIndexMergeCandidate(ds *logicalop.DataSource, path *util.AccessPath func getIndexMergeCandidate(ds *logicalop.DataSource, path *util.AccessPath, prop *property.PhysicalProperty) *candidatePath { candidate := &candidatePath{path: path} - candidate.matchPropResult = isMatchPropForIndexMerge(ds, path, prop) + + allSameOrder, _ := prop.AllSameOrder() + // When SortItems is empty and SortItemsHints is set, check which partial + // paths satisfy the hints (for Limit pushdown). + if prop.IsSortItemEmpty() && len(prop.AdvisorySortItems) > 0 && !path.IndexMergeIsIntersection && allSameOrder { + hintsProp := prop.CloneEssentialFields() + hintsProp.SortItems = hintsProp.AdvisorySortItems + candidate.matchWithAdvisorySortItems = true + candidate.partialPathMatchResults, _ = isMatchPropForIndexMerge(ds, path, hintsProp) + // When using hints, there are no real sort items, so the overall match + // against the original prop is PropNotMatched. + candidate.matchPropResult = property.PropNotMatched + } else { + candidate.partialPathMatchResults, candidate.matchPropResult = isMatchPropForIndexMerge(ds, path, prop) + } + candidate.isFullRange = path.IsFullScanRange(ds.TableInfo) candidate.eqOrInCount = candidate.equalPredicateCount() return candidate @@ -2214,6 +2291,13 @@ func convertToIndexMergeScan(ds *logicalop.DataSource, prop *property.PhysicalPr TblColHists: ds.TblColHists, } cop.PhysPlanPartInfo = buildPhysPlanPartInfo(ds) + + advisoryProp := prop + if candidate.matchWithAdvisorySortItems { + advisoryProp = prop.CloneEssentialFields() + advisoryProp.SortItems = advisoryProp.AdvisorySortItems + } + // Add sort items for index scan for merge-sort operation between partitions. byItems := make([]*util.ByItems, 0, len(prop.SortItems)) for _, si := range prop.SortItems { @@ -2223,13 +2307,23 @@ func convertToIndexMergeScan(ds *logicalop.DataSource, prop *property.PhysicalPr }) } globalRemainingFilters := make([]expression.Expression, 0, 3) - for _, partPath := range path.PartialIndexPaths { + for i, partPath := range path.PartialIndexPaths { var scan base.PhysicalPlan + partMatchPropResult := property.PropNotMatched + if len(candidate.partialPathMatchResults) > 0 { + partMatchPropResult = candidate.partialPathMatchResults[i] + } + effectiveProp := prop + // If matchWithAdvisorySortItems is true and this partial path can match the property, + // it means it actually matches the advisory property. + if candidate.matchWithAdvisorySortItems && partMatchPropResult.Matched() { + effectiveProp = advisoryProp + } if partPath.IsTablePath() { - scan = convertToPartialTableScan(ds, prop, partPath, candidate.matchPropResult, byItems) + scan = convertToPartialTableScan(ds, effectiveProp, partPath, partMatchPropResult, byItems) } else { var remainingFilters []expression.Expression - scan, remainingFilters, err = physicalop.ConvertToPartialIndexScan(ds, cop.PhysPlanPartInfo, prop, partPath, candidate.matchPropResult, byItems) + scan, remainingFilters, err = physicalop.ConvertToPartialIndexScan(ds, cop.PhysPlanPartInfo, effectiveProp, partPath, partMatchPropResult, byItems) if err != nil { return base.InvalidTask, err } @@ -2271,6 +2365,8 @@ func convertToIndexMergeScan(ds *logicalop.DataSource, prop *property.PhysicalPr cop.IdxMergePartPlans = scans cop.IdxMergeIsIntersection = path.IndexMergeIsIntersection cop.IdxMergeAccessMVIndex = path.IndexMergeAccessMVIndex + cop.IdxMergePartPlansMatchResults = candidate.partialPathMatchResults + cop.IdxMergeMatchWithAdvisorySortItems = candidate.matchWithAdvisorySortItems if len(globalRemainingFilters) != 0 { cop.RootTaskConds = globalRemainingFilters } diff --git a/pkg/planner/core/operator/physicalop/physical_topn.go b/pkg/planner/core/operator/physicalop/physical_topn.go index 3b72d7591bcba..8532f0da25ff5 100644 --- a/pkg/planner/core/operator/physicalop/physical_topn.go +++ b/pkg/planner/core/operator/physicalop/physical_topn.go @@ -279,6 +279,25 @@ func getPhysTopN(lt *logicalop.LogicalTopN, prop *property.PhysicalProperty) []b } ret := make([]base.PhysicalPlan, 0, len(allTaskTypes)+1) + // When TopN is directly above a DataSource, set AdvisorySortItems so that + // IndexMerge can prefer partial paths that satisfy the ORDER BY. + // This enables pushing Limit to ordered partial paths. + var advisorySortItems []property.SortItem + if _, ok := lt.Children()[0].(*logicalop.DataSource); ok && len(lt.ByItems) > 0 { + advisorySortItems = make([]property.SortItem, 0, len(lt.ByItems)) + for _, byItem := range lt.ByItems { + col, ok := byItem.Expr.(*expression.Column) + if !ok { + advisorySortItems = nil + break + } + advisorySortItems = append(advisorySortItems, property.SortItem{ + Col: col, + Desc: byItem.Desc, + }) + } + } + // Generate candidate plans for partial order optimization using prefix index FIRST. // This is important because when use_index hint is used with a prefix index, // we need to set ForcePartialOrder flag before other candidates are evaluated. @@ -299,6 +318,22 @@ func getPhysTopN(lt *logicalop.LogicalTopN, prop *property.PhysicalProperty) []b }.Init(lt.SCtx(), lt.StatsInfo(), lt.QueryBlockOffset(), resultProp) topN.SetSchema(lt.Schema()) ret = append(ret, topN) + + // The AdvisorySortItems optimization may not fully succeed (e.g. the global filter blocks the LIMIT pushdown), + // in this case, it may generate a plan with unnecessary `keep order: true`. So we add this plan as an extra + // candidate instead of replacing the original plan. + if tp == property.CopMultiReadTaskType && len(advisorySortItems) > 0 { + resultProp = resultProp.CloneEssentialFields() + resultProp.AdvisorySortItems = advisorySortItems + topN := PhysicalTopN{ + ByItems: lt.ByItems, + PartitionBy: lt.PartitionBy, + Count: lt.Count, + Offset: lt.Offset, + }.Init(lt.SCtx(), lt.StatsInfo(), lt.QueryBlockOffset(), resultProp) + topN.SetSchema(lt.Schema()) + ret = append(ret, topN) + } } // If we can generate MPP task and there's vector distance function in the order by column. diff --git a/pkg/planner/core/operator/physicalop/task_base.go b/pkg/planner/core/operator/physicalop/task_base.go index 459aaa2605f12..3704a7988fbd8 100644 --- a/pkg/planner/core/operator/physicalop/task_base.go +++ b/pkg/planner/core/operator/physicalop/task_base.go @@ -392,6 +392,14 @@ type CopTask struct { IdxMergePartPlans []base.PhysicalPlan IdxMergeIsIntersection bool IdxMergeAccessMVIndex bool + // IdxMergeMatchWithAdvisorySortItems indicates the IndexMerge property matching + // used advisory sort items (i.e. no SortItems but SortItemsHints was set). + IdxMergeMatchWithAdvisorySortItems bool + // IdxMergePartPlansMatchResults stores each partial path's matchProperty result. + // Set by convertToIndexMergeScan. Length equals len(IdxMergePartPlans) or 0. + // 0 length may be caused by cases like Intersection type IndexMerge, which can't satisfy any order property. When + // its length is 0, it should be considered as all property.PropNotMatched. + IdxMergePartPlansMatchResults []property.PhysicalPropMatchResult // RootTaskConds stores select conditions containing virtual columns. // These conditions can't push to TiKV, so we have to add a selection for rootTask diff --git a/pkg/planner/core/task.go b/pkg/planner/core/task.go index 83b5adcc270ca..0085835514973 100644 --- a/pkg/planner/core/task.go +++ b/pkg/planner/core/task.go @@ -1269,6 +1269,23 @@ func attach2Task4PhysicalTopN(pp base.PhysicalPlan, tasks ...base.Task) base.Tas } } if copTask, ok := t.(*physicalop.CopTask); ok && needPushDown && canPushDownToTiKV(p, copTask) && len(copTask.RootTaskConds) == 0 { + // Handle IndexMerge with advisory sort items when some (but not all) + // partial paths satisfy the sort order. When all paths satisfy, the + // existing Limit pushdown via attach2Task4PhysicalLimit gives a better plan. + if len(copTask.IdxMergePartPlans) > 0 && !copTask.IndexPlanFinished && !copTask.IdxMergeIsIntersection && + copTask.IdxMergeMatchWithAdvisorySortItems { + intest.Assert(len(copTask.IdxMergePartPlans) == len(copTask.IdxMergePartPlansMatchResults)) + allSatisfy := true + for _, result := range copTask.IdxMergePartPlansMatchResults { + if !result.Matched() { + allSatisfy = false + break + } + } + if !allSatisfy { + return handleAdvisorySortItemsForIndexMerge(p, copTask) + } + } // If all columns in topN are from index plan, we push it to index plan, otherwise we finish the index plan and // push it to table plan. var pushedDownTopN *physicalop.PhysicalTopN @@ -1435,6 +1452,55 @@ func estimateMaxXForPartialOrder() uint64 { return 0 } +// handleAdvisorySortItemsForIndexMerge handles TopN pushdown when IndexMerge +// has advisory sort items satisfaction info. It pushes Limit to partial paths +// that satisfy the sort order and TopN to those that don't, then keeps a root +// TopN for final merge. +func handleAdvisorySortItemsForIndexMerge(p *physicalop.PhysicalTopN, copTask *physicalop.CopTask) base.Task { + newCount := p.Offset + p.Count + + cols := make([]*expression.Column, 0, len(p.ByItems)) + for _, item := range p.ByItems { + cols = append(cols, expression.ExtractColumns(item.Expr)...) + } + newPartitionBy := make([]property.SortItem, 0, len(p.GetPartitionBy())) + for _, expr := range p.GetPartitionBy() { + newPartitionBy = append(newPartitionBy, expr.Clone()) + } + + for i, partialPlan := range copTask.IdxMergePartPlans { + if copTask.IdxMergePartPlansMatchResults[i].Matched() { + // This partial path satisfies the sort order, push Limit. + childProfile := partialPlan.StatsInfo() + stats := property.DeriveLimitStats(childProfile, float64(newCount)) + pushedDownLimit := physicalop.PhysicalLimit{ + Count: newCount, + PartitionBy: newPartitionBy, + }.Init(p.SCtx(), stats, p.QueryBlockOffset()) + pushedDownLimit.SetChildren(partialPlan) + pushedDownLimit.SetSchema(partialPlan.Schema()) + copTask.IdxMergePartPlans[i] = pushedDownLimit + } else if canPushToIndexPlan(partialPlan, cols) { + // This partial path does not satisfy the sort order, push TopN. + pushedDownTopN, _ := getPushedDownTopN(p, partialPlan, copTask.GetStoreType()) + copTask.IdxMergePartPlans[i] = pushedDownTopN + } + } + + // Push TopN to the table plan side if it exists. + if copTask.TablePlan != nil { + pushedDownTopN, _ := getPushedDownTopN(p, copTask.TablePlan, copTask.GetStoreType()) + copTask.TablePlan = pushedDownTopN + } + + // Keep the root TopN as the final merge layer. + rootTask := copTask.ConvertToRootTask(p.SCtx()) + if len(p.GetPartitionBy()) > 0 { + return rootTask + } + return attachPlan2Task(p, rootTask) +} + // attach2Task4PhysicalProjection implements PhysicalPlan interface. func attach2Task4PhysicalProjection(pp base.PhysicalPlan, tasks ...base.Task) base.Task { p := pp.(*physicalop.PhysicalProjection) diff --git a/pkg/planner/property/physical_property.go b/pkg/planner/property/physical_property.go index 035f8a36bb7b2..9a5c62396a1e9 100644 --- a/pkg/planner/property/physical_property.go +++ b/pkg/planner/property/physical_property.go @@ -323,6 +323,13 @@ type PhysicalProperty struct { // The partialOrderInfo property will pass through to the datasource and try to matchPartialOrderProperty such as: // index: (a, b(10) ) PartialOrderInfo *PartialOrderInfo + + // AdvisorySortItems contains sort items that are preferred but not required. + // When SortItems is empty and AdvisorySortItems is not, DataSource can try to + // generate paths that satisfy these sort items, enabling Limit pushdown to + // partial paths of IndexMerge. + // Currently only set when TopN is directly above a DataSource. + AdvisorySortItems []SortItem } // PartialOrderInfo records information needed for partial order optimization. @@ -579,7 +586,7 @@ func (p *PhysicalProperty) HashCode() []byte { if p.hashcode != nil { return p.hashcode } - hashcodeSize := 8 + 8 + 8 + (16+8)*len(p.SortItems) + 8 + hashcodeSize := 8 + 8 + 8 + (16+8)*len(p.SortItems) + 8 + (16+8)*len(p.AdvisorySortItems) + 8 if p.PartialOrderInfo != nil { hashcodeSize += (16 + 8) * len(p.PartialOrderInfo.SortItems) } else { @@ -652,6 +659,15 @@ func (p *PhysicalProperty) HashCode() []byte { } else { p.hashcode = codec.EncodeInt(p.hashcode, 0) } + // encode SortItemsHints into physical prop's hashcode. + for _, item := range p.AdvisorySortItems { + p.hashcode = append(p.hashcode, item.Col.HashCode()...) + if item.Desc { + p.hashcode = codec.EncodeInt(p.hashcode, 1) + } else { + p.hashcode = codec.EncodeInt(p.hashcode, 0) + } + } return p.hashcode } @@ -673,6 +689,7 @@ func (p *PhysicalProperty) CloneEssentialFields() *PhysicalProperty { CTEProducerStatus: p.CTEProducerStatus, NoCopPushDown: p.NoCopPushDown, PartialOrderInfo: p.PartialOrderInfo, // Copy PartialOrderInfo for TopN partial order optimization + AdvisorySortItems: p.AdvisorySortItems, // we default not to clone basic indexJoinProp by default. // and only call admitIndexJoinProp to inherit the indexJoinProp for special pattern operators. } @@ -710,6 +727,9 @@ func (p *PhysicalProperty) MemoryUsage() (sum int64) { for _, mppCol := range p.MPPPartitionCols { sum += mppCol.MemoryUsage() } + for _, sortItem := range p.AdvisorySortItems { + sum += sortItem.MemoryUsage() + } return } diff --git a/tests/integrationtest/r/executor/index_lookup_pushdown.result b/tests/integrationtest/r/executor/index_lookup_pushdown.result index ea702d22a87fb..fd5c33fca9498 100644 --- a/tests/integrationtest/r/executor/index_lookup_pushdown.result +++ b/tests/integrationtest/r/executor/index_lookup_pushdown.result @@ -363,13 +363,13 @@ B 2 5050 500 50 explain select /*+ index_lookup_pushdown(t2, i) */ * from t2 where b != 3 order by a desc, b desc limit 4; id estRows task access object operator info TopN_9 4.00 root executor__index_lookup_pushdown.t2.a:desc, executor__index_lookup_pushdown.t2.b:desc, offset:0, count:4 -└─IndexLookUp_19 4.00 root - ├─LocalIndexLookUp_21(Build) 4.00 cop[tikv] index handle offsets:[] - │ ├─TopN_18(Build) 4.00 cop[tikv] executor__index_lookup_pushdown.t2.a:desc, executor__index_lookup_pushdown.t2.b:desc, offset:0, count:4 - │ │ └─Selection_17 6656.67 cop[tikv] ne(executor__index_lookup_pushdown.t2.b, 3) - │ │ └─IndexFullScan_15 10000.00 cop[tikv] table:t2, index:i(c) keep order:false, stats:pseudo - │ └─TableRowIDScan_20(Probe) 4.00 cop[tikv] table:t2 keep order:false, stats:pseudo - └─TableRowIDScan_16(Probe) 0.00 cop[tikv] table:t2 keep order:false, stats:pseudo +└─IndexLookUp_20 4.00 root + ├─LocalIndexLookUp_22(Build) 4.00 cop[tikv] index handle offsets:[] + │ ├─TopN_19(Build) 4.00 cop[tikv] executor__index_lookup_pushdown.t2.a:desc, executor__index_lookup_pushdown.t2.b:desc, offset:0, count:4 + │ │ └─Selection_18 6656.67 cop[tikv] ne(executor__index_lookup_pushdown.t2.b, 3) + │ │ └─IndexFullScan_16 10000.00 cop[tikv] table:t2, index:i(c) keep order:false, stats:pseudo + │ └─TableRowIDScan_21(Probe) 4.00 cop[tikv] table:t2 keep order:false, stats:pseudo + └─TableRowIDScan_17(Probe) 0.00 cop[tikv] table:t2 keep order:false, stats:pseudo select /*+ index_lookup_pushdown(t2, i) */ * from t2 where b != 3 order by a desc, b desc limit 4; a b c d e C 2 2080 800 20 diff --git a/tests/integrationtest/r/executor/index_lookup_pushdown_partition.result b/tests/integrationtest/r/executor/index_lookup_pushdown_partition.result index 2e7476b56a5ea..43913527062be 100644 --- a/tests/integrationtest/r/executor/index_lookup_pushdown_partition.result +++ b/tests/integrationtest/r/executor/index_lookup_pushdown_partition.result @@ -23,12 +23,12 @@ a b c explain select /*+ index_lookup_pushdown(tp1, b) */ * from tp1 where b < 10 order by a limit 3; id estRows task access object operator info TopN_9 3.00 root executor__index_lookup_pushdown_partition.tp1.a, offset:0, count:3 -└─IndexLookUp_18 3.00 root partition:all - ├─LocalIndexLookUp_20(Build) 3.00 cop[tikv] index handle offsets:[1] - │ ├─TopN_17(Build) 3.00 cop[tikv] executor__index_lookup_pushdown_partition.tp1.a, offset:0, count:3 - │ │ └─IndexRangeScan_15 3323.33 cop[tikv] table:tp1, index:b(b) range:[-inf,10), keep order:false, stats:pseudo - │ └─TableRowIDScan_19(Probe) 3.00 cop[tikv] table:tp1 keep order:false, stats:pseudo - └─TableRowIDScan_16(Probe) 0.00 cop[tikv] table:tp1 keep order:false, stats:pseudo +└─IndexLookUp_19 3.00 root partition:all + ├─LocalIndexLookUp_21(Build) 3.00 cop[tikv] index handle offsets:[1] + │ ├─TopN_18(Build) 3.00 cop[tikv] executor__index_lookup_pushdown_partition.tp1.a, offset:0, count:3 + │ │ └─IndexRangeScan_16 3323.33 cop[tikv] table:tp1, index:b(b) range:[-inf,10), keep order:false, stats:pseudo + │ └─TableRowIDScan_20(Probe) 3.00 cop[tikv] table:tp1 keep order:false, stats:pseudo + └─TableRowIDScan_17(Probe) 0.00 cop[tikv] table:tp1 keep order:false, stats:pseudo select /*+ index_lookup_pushdown(tp1, b) */ * from tp1 where b < 10 order by a limit 3; a b c 2 9 20 @@ -207,37 +207,37 @@ id estRows task access object operator info TopN_19 5.00 root executor__index_lookup_pushdown_partition.tp1.c, offset:0, count:5 └─PartitionUnion_24 20.00 root ├─TopN_27 5.00 root executor__index_lookup_pushdown_partition.tp1.c, offset:0, count:5 - │ └─IndexLookUp_36 5.00 root - │ ├─TopN_37(Build) 5.00 cop[tikv] executor__index_lookup_pushdown_partition.tp1.c, offset:0, count:5 - │ │ └─LocalIndexLookUp_39 10000.00 cop[tikv] index handle offsets:[1] - │ │ ├─IndexFullScan_33(Build) 10000.00 cop[tikv] table:tp1, partition:p0, index:b(b) keep order:false, stats:pseudo - │ │ └─TableRowIDScan_38(Probe) 10000.00 cop[tikv] table:tp1, partition:p0 keep order:false, stats:pseudo - │ └─TopN_35(Probe) 0.00 cop[tikv] executor__index_lookup_pushdown_partition.tp1.c, offset:0, count:5 - │ └─TableRowIDScan_34 0.00 cop[tikv] table:tp1, partition:p0 keep order:false, stats:pseudo - ├─TopN_46 5.00 root executor__index_lookup_pushdown_partition.tp1.c, offset:0, count:5 - │ └─IndexLookUp_55 5.00 root - │ ├─TopN_56(Build) 5.00 cop[tikv] executor__index_lookup_pushdown_partition.tp1.c, offset:0, count:5 - │ │ └─LocalIndexLookUp_58 10000.00 cop[tikv] index handle offsets:[1] - │ │ ├─IndexFullScan_52(Build) 10000.00 cop[tikv] table:tp1, partition:p1, index:b(b) keep order:false, stats:pseudo - │ │ └─TableRowIDScan_57(Probe) 10000.00 cop[tikv] table:tp1, partition:p1 keep order:false, stats:pseudo - │ └─TopN_54(Probe) 0.00 cop[tikv] executor__index_lookup_pushdown_partition.tp1.c, offset:0, count:5 - │ └─TableRowIDScan_53 0.00 cop[tikv] table:tp1, partition:p1 keep order:false, stats:pseudo - ├─TopN_65 5.00 root executor__index_lookup_pushdown_partition.tp1.c, offset:0, count:5 - │ └─IndexLookUp_74 5.00 root - │ ├─TopN_75(Build) 5.00 cop[tikv] executor__index_lookup_pushdown_partition.tp1.c, offset:0, count:5 - │ │ └─LocalIndexLookUp_77 10000.00 cop[tikv] index handle offsets:[1] - │ │ ├─IndexFullScan_71(Build) 10000.00 cop[tikv] table:tp1, partition:p2, index:b(b) keep order:false, stats:pseudo - │ │ └─TableRowIDScan_76(Probe) 10000.00 cop[tikv] table:tp1, partition:p2 keep order:false, stats:pseudo - │ └─TopN_73(Probe) 0.00 cop[tikv] executor__index_lookup_pushdown_partition.tp1.c, offset:0, count:5 - │ └─TableRowIDScan_72 0.00 cop[tikv] table:tp1, partition:p2 keep order:false, stats:pseudo - └─TopN_84 5.00 root executor__index_lookup_pushdown_partition.tp1.c, offset:0, count:5 - └─IndexLookUp_93 5.00 root - ├─TopN_94(Build) 5.00 cop[tikv] executor__index_lookup_pushdown_partition.tp1.c, offset:0, count:5 - │ └─LocalIndexLookUp_96 10000.00 cop[tikv] index handle offsets:[1] - │ ├─IndexFullScan_90(Build) 10000.00 cop[tikv] table:tp1, partition:p3, index:b(b) keep order:false, stats:pseudo - │ └─TableRowIDScan_95(Probe) 10000.00 cop[tikv] table:tp1, partition:p3 keep order:false, stats:pseudo - └─TopN_92(Probe) 0.00 cop[tikv] executor__index_lookup_pushdown_partition.tp1.c, offset:0, count:5 - └─TableRowIDScan_91 0.00 cop[tikv] table:tp1, partition:p3 keep order:false, stats:pseudo + │ └─IndexLookUp_37 5.00 root + │ ├─TopN_38(Build) 5.00 cop[tikv] executor__index_lookup_pushdown_partition.tp1.c, offset:0, count:5 + │ │ └─LocalIndexLookUp_40 10000.00 cop[tikv] index handle offsets:[1] + │ │ ├─IndexFullScan_34(Build) 10000.00 cop[tikv] table:tp1, partition:p0, index:b(b) keep order:false, stats:pseudo + │ │ └─TableRowIDScan_39(Probe) 10000.00 cop[tikv] table:tp1, partition:p0 keep order:false, stats:pseudo + │ └─TopN_36(Probe) 0.00 cop[tikv] executor__index_lookup_pushdown_partition.tp1.c, offset:0, count:5 + │ └─TableRowIDScan_35 0.00 cop[tikv] table:tp1, partition:p0 keep order:false, stats:pseudo + ├─TopN_54 5.00 root executor__index_lookup_pushdown_partition.tp1.c, offset:0, count:5 + │ └─IndexLookUp_64 5.00 root + │ ├─TopN_65(Build) 5.00 cop[tikv] executor__index_lookup_pushdown_partition.tp1.c, offset:0, count:5 + │ │ └─LocalIndexLookUp_67 10000.00 cop[tikv] index handle offsets:[1] + │ │ ├─IndexFullScan_61(Build) 10000.00 cop[tikv] table:tp1, partition:p1, index:b(b) keep order:false, stats:pseudo + │ │ └─TableRowIDScan_66(Probe) 10000.00 cop[tikv] table:tp1, partition:p1 keep order:false, stats:pseudo + │ └─TopN_63(Probe) 0.00 cop[tikv] executor__index_lookup_pushdown_partition.tp1.c, offset:0, count:5 + │ └─TableRowIDScan_62 0.00 cop[tikv] table:tp1, partition:p1 keep order:false, stats:pseudo + ├─TopN_81 5.00 root executor__index_lookup_pushdown_partition.tp1.c, offset:0, count:5 + │ └─IndexLookUp_91 5.00 root + │ ├─TopN_92(Build) 5.00 cop[tikv] executor__index_lookup_pushdown_partition.tp1.c, offset:0, count:5 + │ │ └─LocalIndexLookUp_94 10000.00 cop[tikv] index handle offsets:[1] + │ │ ├─IndexFullScan_88(Build) 10000.00 cop[tikv] table:tp1, partition:p2, index:b(b) keep order:false, stats:pseudo + │ │ └─TableRowIDScan_93(Probe) 10000.00 cop[tikv] table:tp1, partition:p2 keep order:false, stats:pseudo + │ └─TopN_90(Probe) 0.00 cop[tikv] executor__index_lookup_pushdown_partition.tp1.c, offset:0, count:5 + │ └─TableRowIDScan_89 0.00 cop[tikv] table:tp1, partition:p2 keep order:false, stats:pseudo + └─TopN_108 5.00 root executor__index_lookup_pushdown_partition.tp1.c, offset:0, count:5 + └─IndexLookUp_118 5.00 root + ├─TopN_119(Build) 5.00 cop[tikv] executor__index_lookup_pushdown_partition.tp1.c, offset:0, count:5 + │ └─LocalIndexLookUp_121 10000.00 cop[tikv] index handle offsets:[1] + │ ├─IndexFullScan_115(Build) 10000.00 cop[tikv] table:tp1, partition:p3, index:b(b) keep order:false, stats:pseudo + │ └─TableRowIDScan_120(Probe) 10000.00 cop[tikv] table:tp1, partition:p3 keep order:false, stats:pseudo + └─TopN_117(Probe) 0.00 cop[tikv] executor__index_lookup_pushdown_partition.tp1.c, offset:0, count:5 + └─TableRowIDScan_116 0.00 cop[tikv] table:tp1, partition:p3 keep order:false, stats:pseudo select /*+ index_lookup_pushdown(tp1, b) */ * from tp1 order by c limit 5; a b c 1 10 10 diff --git a/tests/integrationtest/r/executor/issues.result b/tests/integrationtest/r/executor/issues.result index edca126fcea0d..9e5a8966ff4fb 100644 --- a/tests/integrationtest/r/executor/issues.result +++ b/tests/integrationtest/r/executor/issues.result @@ -929,23 +929,23 @@ Limit_8 64.00 root NULL NULL offset:0, count:100000 └─TableFullScan_11 256.00 cop[tikv] table:t NULL keep order:false explain analyze select * from t order by id limit 100; id estRows actRows task access object execution info operator info memory disk -Limit_11 100.00 root NULL NULL offset:0, count:100 -└─TableReader_21 100.00 root NULL max_distsql_concurrency: 1 NULL - └─Limit_20 100.00 cop[tikv] NULL NULL offset:0, count:100 - └─TableFullScan_19 101.56 cop[tikv] table:t NULL keep order:true +Limit_12 100.00 root NULL NULL offset:0, count:100 +└─TableReader_22 100.00 root NULL max_distsql_concurrency: 1 NULL + └─Limit_21 100.00 cop[tikv] NULL NULL offset:0, count:100 + └─TableFullScan_20 101.56 cop[tikv] table:t NULL keep order:true explain analyze select * from t order by id limit 100000; id estRows actRows task access object execution info operator info memory disk -Limit_11 256.00 root NULL NULL offset:0, count:100000 -└─TableReader_21 256.00 root NULL max_distsql_concurrency: 15 NULL - └─Limit_20 256.00 cop[tikv] NULL NULL offset:0, count:100000 - └─TableFullScan_19 256.00 cop[tikv] table:t NULL keep order:true +Limit_12 256.00 root NULL NULL offset:0, count:100000 +└─TableReader_22 256.00 root NULL max_distsql_concurrency: 15 NULL + └─Limit_21 256.00 cop[tikv] NULL NULL offset:0, count:100000 + └─TableFullScan_20 256.00 cop[tikv] table:t NULL keep order:true explain analyze select * from t where c = 'abd' order by id limit 100; id estRows actRows task access object execution info operator info memory disk -Limit_12 1.00 root NULL NULL offset:0, count:100 -└─TableReader_25 1.00 root NULL max_distsql_concurrency: 15 NULL - └─Limit_24 1.00 cop[tikv] NULL NULL offset:0, count:100 - └─Selection_23 1.00 cop[tikv] NULL NULL eq(executor__issues.t.c, "abd") - └─TableFullScan_22 256.00 cop[tikv] table:t NULL keep order:true +Limit_13 1.00 root NULL NULL offset:0, count:100 +└─TableReader_26 1.00 root NULL max_distsql_concurrency: 15 NULL + └─Limit_25 1.00 cop[tikv] NULL NULL offset:0, count:100 + └─Selection_24 1.00 cop[tikv] NULL NULL eq(executor__issues.t.c, "abd") + └─TableFullScan_23 256.00 cop[tikv] table:t NULL keep order:true select @@tidb_partition_prune_mode; @@tidb_partition_prune_mode dynamic @@ -981,23 +981,23 @@ Limit_8 1.00 root NULL NULL offset:0, count:100000 └─TableFullScan_11 256.00 cop[tikv] table:pt NULL keep order:false explain analyze select * from pt order by id limit 100; id estRows actRows task access object execution info operator info memory disk -Limit_11 100.00 root NULL NULL offset:0, count:100 -└─TableReader_21 100.00 root partition:all max_distsql_concurrency: 1 NULL - └─Limit_20 100.00 cop[tikv] NULL NULL offset:0, count:100 - └─TableFullScan_19 101.56 cop[tikv] table:pt NULL keep order:true +Limit_12 100.00 root NULL NULL offset:0, count:100 +└─TableReader_22 100.00 root partition:all max_distsql_concurrency: 1 NULL + └─Limit_21 100.00 cop[tikv] NULL NULL offset:0, count:100 + └─TableFullScan_20 101.56 cop[tikv] table:pt NULL keep order:true explain analyze select * from pt order by id limit 100000; id estRows actRows task access object execution info operator info memory disk -Limit_11 256.00 root NULL NULL offset:0, count:100000 -└─TableReader_21 256.00 root partition:all max_distsql_concurrency: 15 NULL - └─Limit_20 256.00 cop[tikv] NULL NULL offset:0, count:100000 - └─TableFullScan_19 256.00 cop[tikv] table:pt NULL keep order:true +Limit_12 256.00 root NULL NULL offset:0, count:100000 +└─TableReader_22 256.00 root partition:all max_distsql_concurrency: 15 NULL + └─Limit_21 256.00 cop[tikv] NULL NULL offset:0, count:100000 + └─TableFullScan_20 256.00 cop[tikv] table:pt NULL keep order:true explain analyze select * from pt where val = 126 order by id limit 100; id estRows actRows task access object execution info operator info memory disk -Limit_12 1.00 root NULL NULL offset:0, count:100 -└─TableReader_25 1.00 root partition:all max_distsql_concurrency: 15 NULL - └─Limit_24 1.00 cop[tikv] NULL NULL offset:0, count:100 - └─Selection_23 1.00 cop[tikv] NULL NULL eq(executor__issues.pt.val, 126) - └─TableFullScan_22 256.00 cop[tikv] table:pt NULL keep order:true +Limit_13 1.00 root NULL NULL offset:0, count:100 +└─TableReader_26 1.00 root partition:all max_distsql_concurrency: 15 NULL + └─Limit_25 1.00 cop[tikv] NULL NULL offset:0, count:100 + └─Selection_24 1.00 cop[tikv] NULL NULL eq(executor__issues.pt.val, 126) + └─TableFullScan_23 256.00 cop[tikv] table:pt NULL keep order:true explain analyze select /*+ set_var(tidb_distsql_scan_concurrency=5)*/ * from t order by id; id estRows actRows task access object execution info operator info memory disk TableReader_11 256.00 root NULL max_distsql_concurrency: 5 NULL diff --git a/tests/integrationtest/r/index_merge.result b/tests/integrationtest/r/index_merge.result index a59042e73e90f..84bd048b181e6 100644 --- a/tests/integrationtest/r/index_merge.result +++ b/tests/integrationtest/r/index_merge.result @@ -906,12 +906,12 @@ analyze table t_im_in; # Case 1: a=1 keeps order directly, b IN (1,2) needs merge sort explain select /*+ use_index_merge(t_im_in) */ * from t_im_in where a = 1 or b in (1, 2) order by c limit 5; id estRows task access object operator info -IndexMerge_29 5.00 root type: union, limit embedded(offset:0, count:5) -├─Limit_27(Build) 2.44 cop[tikv] offset:0, count:5 -│ └─IndexRangeScan_24 2.44 cop[tikv] table:t_im_in, index:idx_a_c(a, c) range:[1,1], keep order:true -├─Limit_28(Build) 4.27 cop[tikv] offset:0, count:5 -│ └─IndexRangeScan_25 4.27 cop[tikv] table:t_im_in, index:idx_b_c(b, c) range:[1,1], [2,2], keep order:true -└─TableRowIDScan_26(Probe) 5.00 cop[tikv] table:t_im_in keep order:false +IndexMerge_35 5.00 root type: union, limit embedded(offset:0, count:5) +├─Limit_33(Build) 2.44 cop[tikv] offset:0, count:5 +│ └─IndexRangeScan_30 2.44 cop[tikv] table:t_im_in, index:idx_a_c(a, c) range:[1,1], keep order:true +├─Limit_34(Build) 4.27 cop[tikv] offset:0, count:5 +│ └─IndexRangeScan_31 4.27 cop[tikv] table:t_im_in, index:idx_b_c(b, c) range:[1,1], [2,2], keep order:true +└─TableRowIDScan_32(Probe) 5.00 cop[tikv] table:t_im_in keep order:false select /*+ use_index_merge(t_im_in) */ * from t_im_in where a = 1 or b in (1, 2) order by c limit 5; id a b c padding 8 1 1 5 row8 @@ -922,12 +922,12 @@ id a b c padding # Case 2: Both partial paths need merge sort explain select /*+ use_index_merge(t_im_in) */ * from t_im_in where a in (1, 2) or b in (1, 2) order by c limit 5; id estRows task access object operator info -IndexMerge_29 5.00 root type: union, limit embedded(offset:0, count:5) -├─Limit_27(Build) 3.85 cop[tikv] offset:0, count:5 -│ └─IndexRangeScan_24 3.85 cop[tikv] table:t_im_in, index:idx_a_c(a, c) range:[1,1], [2,2], keep order:true -├─Limit_28(Build) 3.85 cop[tikv] offset:0, count:5 -│ └─IndexRangeScan_25 3.85 cop[tikv] table:t_im_in, index:idx_b_c(b, c) range:[1,1], [2,2], keep order:true -└─TableRowIDScan_26(Probe) 5.00 cop[tikv] table:t_im_in keep order:false +IndexMerge_35 5.00 root type: union, limit embedded(offset:0, count:5) +├─Limit_33(Build) 3.85 cop[tikv] offset:0, count:5 +│ └─IndexRangeScan_30 3.85 cop[tikv] table:t_im_in, index:idx_a_c(a, c) range:[1,1], [2,2], keep order:true +├─Limit_34(Build) 3.85 cop[tikv] offset:0, count:5 +│ └─IndexRangeScan_31 3.85 cop[tikv] table:t_im_in, index:idx_b_c(b, c) range:[1,1], [2,2], keep order:true +└─TableRowIDScan_32(Probe) 5.00 cop[tikv] table:t_im_in keep order:false select /*+ use_index_merge(t_im_in) */ * from t_im_in where a in (1, 2) or b in (1, 2) order by c limit 5; id a b c padding 8 1 1 5 row8 @@ -938,12 +938,12 @@ id a b c padding # Case 3: DESC ordering with IN condition explain select /*+ use_index_merge(t_im_in) */ * from t_im_in where a = 1 or b in (1, 2) order by c desc limit 5; id estRows task access object operator info -IndexMerge_29 5.00 root type: union, limit embedded(offset:0, count:5) -├─Limit_27(Build) 2.44 cop[tikv] offset:0, count:5 -│ └─IndexRangeScan_24 2.44 cop[tikv] table:t_im_in, index:idx_a_c(a, c) range:[1,1], keep order:true, desc -├─Limit_28(Build) 4.27 cop[tikv] offset:0, count:5 -│ └─IndexRangeScan_25 4.27 cop[tikv] table:t_im_in, index:idx_b_c(b, c) range:[1,1], [2,2], keep order:true, desc -└─TableRowIDScan_26(Probe) 5.00 cop[tikv] table:t_im_in keep order:false +IndexMerge_35 5.00 root type: union, limit embedded(offset:0, count:5) +├─Limit_33(Build) 2.44 cop[tikv] offset:0, count:5 +│ └─IndexRangeScan_30 2.44 cop[tikv] table:t_im_in, index:idx_a_c(a, c) range:[1,1], keep order:true, desc +├─Limit_34(Build) 4.27 cop[tikv] offset:0, count:5 +│ └─IndexRangeScan_31 4.27 cop[tikv] table:t_im_in, index:idx_b_c(b, c) range:[1,1], [2,2], keep order:true, desc +└─TableRowIDScan_32(Probe) 5.00 cop[tikv] table:t_im_in keep order:false select /*+ use_index_merge(t_im_in) */ * from t_im_in where a = 1 or b in (1, 2) order by c desc limit 5; id a b c padding 7 3 2 70 row7 diff --git a/tests/integrationtest/r/planner/core/indexmerge_path.result b/tests/integrationtest/r/planner/core/indexmerge_path.result index 58fc1fe94e996..8c596d77f3ea6 100644 --- a/tests/integrationtest/r/planner/core/indexmerge_path.result +++ b/tests/integrationtest/r/planner/core/indexmerge_path.result @@ -1340,3 +1340,36 @@ Limit root offset:0, count:10 ├─IndexRangeScan(Build) cop[tikv] table:t, index:idx2(d, e, c) range:[10,10], keep order:true, desc, stats:pseudo └─Selection(Probe) cop[tikv] eq(planner__core__indexmerge_path.t.a, 5) └─TableRowIDScan cop[tikv] table:t keep order:false, stats:pseudo +drop table if exists t; +create table t(a int, b int, c int, index idx_ac(a, c), index idx_bc(b, c)); +insert into t values(1, 1, 100), (1, 2, 50), (1, 3, 150), (6, 6, 10), (7, 7, 20), (8, 8, 30), (9, 9, 5); +explain format='plan_tree' select /*+ use_index_merge(t, idx_ac, idx_bc) */ * from t where a = 1 or b > 5 order by c limit 3; +id task access object operator info +TopN root planner__core__indexmerge_path.t.c, offset:0, count:3 +└─IndexMerge root type: union + ├─Limit(Build) cop[tikv] offset:0, count:3 + │ └─IndexRangeScan cop[tikv] table:t, index:idx_ac(a, c) range:[1,1], keep order:true, stats:pseudo + ├─TopN(Build) cop[tikv] planner__core__indexmerge_path.t.c, offset:0, count:3 + │ └─IndexRangeScan cop[tikv] table:t, index:idx_bc(b, c) range:(5,+inf], keep order:false, stats:pseudo + └─TopN(Probe) cop[tikv] planner__core__indexmerge_path.t.c, offset:0, count:3 + └─TableRowIDScan cop[tikv] table:t keep order:false, stats:pseudo +select /*+ use_index_merge(t, idx_ac, idx_bc) */ * from t where a = 1 or b > 5 order by c limit 3; +a b c +9 9 5 +6 6 10 +7 7 20 +explain format='plan_tree' select /*+ use_index_merge(t, idx_ac, idx_bc) */ * from t where a = 1 or b > 5 order by c limit 3 offset 1; +id task access object operator info +TopN root planner__core__indexmerge_path.t.c, offset:1, count:3 +└─IndexMerge root type: union + ├─Limit(Build) cop[tikv] offset:0, count:4 + │ └─IndexRangeScan cop[tikv] table:t, index:idx_ac(a, c) range:[1,1], keep order:true, stats:pseudo + ├─TopN(Build) cop[tikv] planner__core__indexmerge_path.t.c, offset:0, count:4 + │ └─IndexRangeScan cop[tikv] table:t, index:idx_bc(b, c) range:(5,+inf], keep order:false, stats:pseudo + └─TopN(Probe) cop[tikv] planner__core__indexmerge_path.t.c, offset:0, count:4 + └─TableRowIDScan cop[tikv] table:t keep order:false, stats:pseudo +select /*+ use_index_merge(t, idx_ac, idx_bc) */ * from t where a = 1 or b > 5 order by c limit 3 offset 1; +a b c +6 6 10 +7 7 20 +8 8 30 diff --git a/tests/integrationtest/r/planner/core/plan_cost_ver2.result b/tests/integrationtest/r/planner/core/plan_cost_ver2.result index cefd94a2f9338..66a076af8169c 100644 --- a/tests/integrationtest/r/planner/core/plan_cost_ver2.result +++ b/tests/integrationtest/r/planner/core/plan_cost_ver2.result @@ -5,17 +5,17 @@ analyze table t all columns; explain format='verbose' select /*+ limit_to_cop() */ * from t where a=1 order by a limit 1; id estRows estCost task access object operator info TopN_8 1.00 24.53 root planner__core__plan_cost_ver2.t.a, offset:0, count:1 -└─TableReader_18 1.00 21.33 root data:TopN_17 - └─TopN_17 1.00 256.60 cop[tikv] planner__core__plan_cost_ver2.t.a, offset:0, count:1 - └─Selection_16 1.00 253.40 cop[tikv] eq(planner__core__plan_cost_ver2.t.a, 1) - └─TableFullScan_15 1.00 203.50 cop[tikv] table:t keep order:false +└─TableReader_19 1.00 21.33 root data:TopN_18 + └─TopN_18 1.00 256.60 cop[tikv] planner__core__plan_cost_ver2.t.a, offset:0, count:1 + └─Selection_17 1.00 253.40 cop[tikv] eq(planner__core__plan_cost_ver2.t.a, 1) + └─TableFullScan_16 1.00 203.50 cop[tikv] table:t keep order:false explain format='verbose' select /*+ limit_to_cop() */ * from t where a=1 order by a limit 1000000000; id estRows estCost task access object operator info TopN_8 1.00 716.08 root planner__core__plan_cost_ver2.t.a, offset:0, count:1000000000 -└─TableReader_18 1.00 64.55 root data:TopN_17 - └─TopN_17 1.00 904.93 cop[tikv] planner__core__plan_cost_ver2.t.a, offset:0, count:1000000000 - └─Selection_16 1.00 253.40 cop[tikv] eq(planner__core__plan_cost_ver2.t.a, 1) - └─TableFullScan_15 1.00 203.50 cop[tikv] table:t keep order:false +└─TableReader_19 1.00 64.55 root data:TopN_18 + └─TopN_18 1.00 904.93 cop[tikv] planner__core__plan_cost_ver2.t.a, offset:0, count:1000000000 + └─Selection_17 1.00 253.40 cop[tikv] eq(planner__core__plan_cost_ver2.t.a, 1) + └─TableFullScan_16 1.00 203.50 cop[tikv] table:t keep order:false drop table if exists t; create table t (a int primary key, b int, c int, key(b)); insert into t values (0, 0, 0), (1, 1, 1), (2, 2, 2), (3, 3, 3), (4, 4, 4), (5, 5, 5), (6, 6, 6), (7, 7, 7), (8, 8, 8), (9, 9, 9), (10, 10, 10), (11, 11, 11), (12, 12, 12), (13, 13, 13), (14, 14, 14), (15, 15, 15), (16, 16, 16), (17, 17, 17), (18, 18, 18), (19, 19, 19), (20, 20, 20), (21, 21, 21), (22, 22, 22), (23, 23, 23), (24, 24, 24), (25, 25, 25), (26, 26, 26), (27, 27, 27), (28, 28, 28), (29, 29, 29), (30, 30, 30), (31, 31, 31), (32, 32, 32), (33, 33, 33), (34, 34, 34), (35, 35, 35), (36, 36, 36), (37, 37, 37), (38, 38, 38), (39, 39, 39), (40, 40, 40), (41, 41, 41), (42, 42, 42), (43, 43, 43), (44, 44, 44), (45, 45, 45), (46, 46, 46), (47, 47, 47), (48, 48, 48), (49, 49, 49), (50, 50, 50), (51, 51, 51), (52, 52, 52), (53, 53, 53), (54, 54, 54), (55, 55, 55), (56, 56, 56), (57, 57, 57), (58, 58, 58), (59, 59, 59), (60, 60, 60), (61, 61, 61), (62, 62, 62), (63, 63, 63), (64, 64, 64), (65, 65, 65), (66, 66, 66), (67, 67, 67), (68, 68, 68), (69, 69, 69), (70, 70, 70), (71, 71, 71), (72, 72, 72), (73, 73, 73), (74, 74, 74), (75, 75, 75), (76, 76, 76), (77, 77, 77), (78, 78, 78), (79, 79, 79), (80, 80, 80), (81, 81, 81), (82, 82, 82), (83, 83, 83), (84, 84, 84), (85, 85, 85), (86, 86, 86), (87, 87, 87), (88, 88, 88), (89, 89, 89), (90, 90, 90), (91, 91, 91), (92, 92, 92), (93, 93, 93), (94, 94, 94), (95, 95, 95), (96, 96, 96), (97, 97, 97), (98, 98, 98), (99, 99, 99); diff --git a/tests/integrationtest/t/planner/core/indexmerge_path.test b/tests/integrationtest/t/planner/core/indexmerge_path.test index aec21ca3e914a..a38713d63c694 100644 --- a/tests/integrationtest/t/planner/core/indexmerge_path.test +++ b/tests/integrationtest/t/planner/core/indexmerge_path.test @@ -578,3 +578,14 @@ explain format='plan_tree' select /*+ use_index_merge(t) */ * from t where a = 5 and (1 member of (j1) or b = 3 or b = 4 and json_overlaps(j2, '[3,4,5]') or d = 10) order by e desc limit 10; + +# TestIndexMergeSortItemsHints +# When one partial path can keep order (a=1 on idx_ac) and another cannot (b>5 on idx_bc), +# Limit should be pushed to the ordered partial path and TopN to the unordered one. +drop table if exists t; +create table t(a int, b int, c int, index idx_ac(a, c), index idx_bc(b, c)); +insert into t values(1, 1, 100), (1, 2, 50), (1, 3, 150), (6, 6, 10), (7, 7, 20), (8, 8, 30), (9, 9, 5); +explain format='plan_tree' select /*+ use_index_merge(t, idx_ac, idx_bc) */ * from t where a = 1 or b > 5 order by c limit 3; +select /*+ use_index_merge(t, idx_ac, idx_bc) */ * from t where a = 1 or b > 5 order by c limit 3; +explain format='plan_tree' select /*+ use_index_merge(t, idx_ac, idx_bc) */ * from t where a = 1 or b > 5 order by c limit 3 offset 1; +select /*+ use_index_merge(t, idx_ac, idx_bc) */ * from t where a = 1 or b > 5 order by c limit 3 offset 1;