You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: src/current/_includes/v25.4/misc/force-index-selection.md
+2-4Lines changed: 2 additions & 4 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -1,9 +1,7 @@
1
-
By using the explicit index annotation, you can override [CockroachDB's index selection](https://www.cockroachlabs.com/blog/index-selection-cockroachdb-2/) and use a specific [index]({% link {{ page.version.version }}/indexes.md %}) when reading from a named table. This is called an *index hint*.
1
+
By using the explicit index annotation, you can override [CockroachDB's index selection](https://www.cockroachlabs.com/blog/index-selection-cockroachdb-2/) and use a specific [index]({% link {{ page.version.version }}/indexes.md %}) when reading from a named table.
2
2
3
+
{{site.data.alerts.callout_info}}
3
4
Index selection can impact [performance]({% link {{ page.version.version }}/performance-best-practices-overview.md %}), but does not change the result of a query.
4
-
5
-
{{site.data.alerts.callout_success}}
6
-
You can apply index hints without modifying the original query text. Refer to [Hint injection]({% link {{ page.version.version }}/cost-based-optimizer.md %}#hint-injection).
Copy file name to clipboardExpand all lines: src/current/_includes/v26.1/misc/force-index-selection.md
+4-2Lines changed: 4 additions & 2 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -1,7 +1,9 @@
1
-
By using the explicit index annotation, you can override [CockroachDB's index selection](https://www.cockroachlabs.com/blog/index-selection-cockroachdb-2/) and use a specific [index]({% link {{ page.version.version }}/indexes.md %}) when reading from a named table.
1
+
By using the explicit index annotation, you can override [CockroachDB's index selection](https://www.cockroachlabs.com/blog/index-selection-cockroachdb-2/) and use a specific [index]({% link {{ page.version.version }}/indexes.md %}) when reading from a named table. This is called an *index hint*.
2
2
3
-
{{site.data.alerts.callout_info}}
4
3
Index selection can impact [performance]({% link {{ page.version.version }}/performance-best-practices-overview.md %}), but does not change the result of a query.
4
+
5
+
{{site.data.alerts.callout_success}}
6
+
{% include_cached new-in.html version="v26.1" %} You can use [hint injection]({% link {{ page.version.version }}/cost-based-optimizer.md %}#hint-injection) to apply index hints without modifying the original query text.
Copy file name to clipboardExpand all lines: src/current/v25.4/cost-based-optimizer.md
-103Lines changed: 0 additions & 103 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -419,10 +419,6 @@ Due to SQL's implicit `AS` syntax, you cannot specify a join hint with only the
419
419
420
420
For a join hint example, see [Use the right join type]({% link {{ page.version.version }}/apply-statement-performance-rules.md %}#rule-3-use-the-right-join-type).
421
421
422
-
{{site.data.alerts.callout_success}}
423
-
You can apply join hints without modifying the original query text. Refer to [Hint injection](#hint-injection).
424
-
{{site.data.alerts.end}}
425
-
426
422
### Supported join algorithms
427
423
428
424
-`HASH`: Forces a hash join; in other words, it disables merge and lookup joins. A hash join is always possible, even if there are no equality columns: CockroachDB treats a nested loop join without an index as a special case of a hash join, where the hash table effectively has one bucket.
@@ -461,105 +457,6 @@ To make the optimizer prefer lookup joins to merge joins when performing foreign
461
457
462
458
- You should reconsider hint usage with each new release of CockroachDB. Due to improvements in the optimizer, hints specified to work with an older version may cause decreased performance in a newer version.
463
459
464
-
## Hint injection
465
-
466
-
<span class="version-tag">New inv25.4.1:</span>*Hint injection* allows you to apply [index hints]({% link {{ page.version.version }}/table-expressions.md %}#force-index-selection) and [join hints](#join-hints) without modifying the original query text. This is useful when you cannot modify application code, need to optimize queries from ORMs or third-party applications, or want to test different hints without changing production queries.
467
-
468
-
Instead of relying on inline hints (such as`SELECT * FROM table@index_name`or`INNER HASH JOIN`), hint injection stores hints in the `system.statement_hints` table and automatically applies them to statements that match a [fingerprint]({% link {{ page.version.version }}/ui-statements-page.md %}#sql-statement-fingerprints). To inject a hint, invoke the `crdb_internal.inject_hint()` function with a SQL statement fingerprint and a matching statement with hints applied:
469
-
470
-
{% include_cached copy-clipboard.html %}
471
-
~~~ sql
472
-
SELECTcrdb_internal.inject_hint(
473
-
'{statement fingerprint}',
474
-
'{statement with hints}'
475
-
);
476
-
~~~
477
-
478
-
For example, the following invocation stores the `users_email_idx` hint in the `system.statement_hints` table:
479
-
480
-
{% include_cached copy-clipboard.html %}
481
-
~~~sql
482
-
SELECTcrdb_internal.inject_hint(
483
-
'SELECT * FROM users WHERE email = _',
484
-
'SELECT * FROM users@users_email_idx WHERE email = _'
485
-
);
486
-
~~~
487
-
488
-
{{site.data.alerts.callout_info}}
489
-
The statement with hints must have the same syntactic structure as the statement fingerprint. Constants in the second parameter are ignored, and only hints are extracted and applied.
490
-
{{site.data.alerts.end}}
491
-
492
-
The function returns a unique hint ID:
493
-
494
-
~~~
495
-
crdb_internal.inject_hint
496
-
-----------------------------
497
-
1119388019656228865
498
-
~~~
499
-
500
-
Afterward, any query matching the `SELECT * FROM users WHERE email = _` fingerprint will use the `users_email_idx` index, regardless of the `email` value.
501
-
502
-
Similarly, to force a specific join algorithm:
503
-
504
-
{% include_cached copy-clipboard.html %}
505
-
~~~sql
506
-
SELECTcrdb_internal.inject_hint(
507
-
'SELECT * FROM orders AS o INNER JOIN customers AS c ON o.customer_id = c.id',
508
-
'SELECT * FROM orders AS o INNER HASH JOIN customers AS c ON o.customer_id = c.id'
509
-
);
510
-
~~~
511
-
512
-
You can inject both index and join hints in a single statement:
513
-
514
-
{% include_cached copy-clipboard.html %}
515
-
~~~sql
516
-
SELECTcrdb_internal.inject_hint(
517
-
'SELECT * FROM orders AS o INNER JOIN customers AS c ON o.customer_id = c.id WHERE o.status = _',
518
-
'SELECT * FROM orders@orders_status_idx AS o INNER LOOKUP JOIN customers@primary AS c ON o.customer_id = c.id WHERE o.status = _'
519
-
);
520
-
~~~
521
-
522
-
Hint injection supports all inline hint types, including:
The optimizer may plan a zigzag join when there are at least **two secondary indexes on the same table**and the table is filtered in a query with at least two filters constraining different attributes to a constant. A zigzag join works by "zigzagging" back and forth between two indexes and returning only rows with matching primary keys within a specified range. For example:
Copy file name to clipboardExpand all lines: src/current/v26.1/cost-based-optimizer.md
+137Lines changed: 137 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -419,6 +419,10 @@ Due to SQL's implicit `AS` syntax, you cannot specify a join hint with only the
419
419
420
420
For a join hint example, see [Use the right join type]({% link {{ page.version.version }}/apply-statement-performance-rules.md %}#rule-3-use-the-right-join-type).
421
421
422
+
{{site.data.alerts.callout_success}}
423
+
{% include_cached new-in.html version="v26.1" %} You can use [hint injection](#hint-injection) to apply join hints without modifying the original query text.
424
+
{{site.data.alerts.end}}
425
+
422
426
### Supported join algorithms
423
427
424
428
-`HASH`: Forces a hash join; in other words, it disables merge and lookup joins. A hash join is always possible, even if there are no equality columns: CockroachDB treats a nested loop join without an index as a special case of a hash join, where the hash table effectively has one bucket.
@@ -457,6 +461,139 @@ To make the optimizer prefer lookup joins to merge joins when performing foreign
457
461
458
462
- You should reconsider hint usage with each new release of CockroachDB. Due to improvements in the optimizer, hints specified to work with an older version may cause decreased performance in a newer version.
459
463
464
+
## Hint injection
465
+
466
+
{% include_cached new-in.html version="v26.1" %} *Hint injection* allows you to apply [index hints]({% link {{ page.version.version }}/table-expressions.md %}#force-index-selection) and [join hints](#join-hints) without modifying the original query text. This is useful when you cannot modify application code, need to optimize queries from ORMs or third-party applications, or want to test different hints without changing queries in production.
467
+
468
+
Hint injection supports all inline hint types. Instead of relying on inline hints (such as`SELECT * FROM table@index_name`or`INNER HASH JOIN`), hint injection stores hints in the `system.statement_hints` table and automatically applies them to statements that match a [fingerprint]({% link {{ page.version.version }}/ui-statements-page.md %}#sql-statement-fingerprints).
469
+
470
+
### Inject hints
471
+
472
+
{{site.data.alerts.callout_info}}
473
+
To inject hints using `crdb_internal.inject_hint()`, users must have the [`REPAIRCLUSTER`]({% link {{ page.version.version }}/security-reference/authorization.md %}#supported-privileges) privilege.
474
+
{{site.data.alerts.end}}
475
+
476
+
To inject a hint, invoke the `crdb_internal.inject_hint()` built-in function with two matching SQL statement fingerprints: a fingerprint identifying which queries to optimize, and a fingerprint with hints that must be applied:
477
+
478
+
{% include_cached copy-clipboard.html %}
479
+
~~~ sql
480
+
SELECTcrdb_internal.inject_hint(
481
+
'{fingerprint}',
482
+
'{fingerprint with hints}'
483
+
);
484
+
~~~
485
+
486
+
Both fingerprints in the function invocation **must** have the same syntactic structure and use underscores (`_`) as placeholders for constants. For example, use `SELECT * FROM users WHERE city = _` rather than `SELECT * FROM users WHERE city = 'new york'`.
487
+
488
+
For example, the following invocation stores the `users_city_idx` index hint in the `system.statement_hints` table, returning a unique hint ID:
489
+
490
+
{% include_cached copy-clipboard.html %}
491
+
~~~sql
492
+
SELECTcrdb_internal.inject_hint(
493
+
'SELECT * FROM users WHERE city = _',
494
+
'SELECT * FROM users@users_city_idx WHERE city = _'
495
+
);
496
+
~~~
497
+
498
+
~~~
499
+
crdb_internal.inject_hint
500
+
-----------------------------
501
+
1143498239153340417
502
+
~~~
503
+
504
+
Afterward, any executed statement that matches the `SELECT * FROM users WHERE city = _` fingerprint will first be rewritten to use the `users_city_idx` index, regardless of the `city` value.
505
+
506
+
{{site.data.alerts.callout_info}}
507
+
If a query already contains explicit inline hints (such as `SELECT * FROM users@primary WHERE city = 'new york'`), those inline hints will take precedence over the injected hints.
508
+
{{site.data.alerts.end}}
509
+
510
+
To inject a specific [join algorithm](#supported-join-algorithms):
511
+
512
+
{% include_cached copy-clipboard.html %}
513
+
~~~sql
514
+
SELECTcrdb_internal.inject_hint(
515
+
'SELECT * FROM users AS u INNER JOIN rides AS r ON u.id = r.rider_id',
516
+
'SELECT * FROM users AS u INNER MERGE JOIN rides AS r ON u.id = r.rider_id'
517
+
);
518
+
~~~
519
+
520
+
{{site.data.alerts.callout_success}}
521
+
If multiple hints exist for the same statement fingerprint, only the **most recently created hint** is applied. To replace an existing hint, you can create a new one for the same fingerprint, which will automatically take precedence.
522
+
{{site.data.alerts.end}}
523
+
524
+
### View injected hints
525
+
526
+
{{site.data.alerts.callout_info}}
527
+
To view hints using [`SHOW STATEMENT HINTS`]({% link {{ page.version.version }}/show-statement-hints.md %}), users must have the [`VIEWCLUSTERMETADATA`]({% link {{ page.version.version }}/security-reference/authorization.md %}#supported-privileges) privilege.
528
+
{{site.data.alerts.end}}
529
+
530
+
Use the [`SHOW STATEMENT HINTS`]({% link {{ page.version.version }}/show-statement-hints.md %}) statement to view hints for a specific statement fingerprint:
531
+
532
+
{% include_cached copy-clipboard.html %}
533
+
~~~sql
534
+
SHOW STATEMENT HINTS FOR 'SELECT * FROM users WHERE city = _' WITH DETAILS;
1143470380756697089 | SELECT * FROM users WHERE city = _ | rewrite_inline_hints | 2026-01-21 21:11:06.782818+00 | {"donorSql": "SELECT * FROM users@users_city_idx WHERE city = _"}
541
+
~~~
542
+
543
+
You can also query the `system.statement_hints` table directly to view all injected hints:
1143498239153340417 | SELECT * FROM users WHERE city = _ | 2026-01-21 23:32:48.490786+00
554
+
1143498276700913665 | SELECT * FROM users AS u INNER JOIN rides AS r ON u.id = r.rider_id | 2026-01-21 23:32:59.949608+00
555
+
(2 rows)
556
+
~~~
557
+
558
+
To verify that injected hints are being applied to your queries, use [`EXPLAIN`]({% link {{ page.version.version }}/explain.md %}) or [`EXPLAIN ANALYZE`]({% link {{ page.version.version }}/explain-analyze.md %}). When hints from `system.statement_hints` are applied, the output includes a `statement hints count` field showing the number of hints applied. For example:
estimated row count: 6 (12% of the table; stats collected 22 minutes ago)
573
+
table: users@users_pkey
574
+
spans: [/'new york' - /'new york']
575
+
~~~
576
+
577
+
{{site.data.alerts.callout_info}}
578
+
The `statement hints count` field shows the number of individual hints applied. A single row in `system.statement_hints` can contain multiple hints (such as an index hint and a join hint).
The optimizer may plan a zigzag join when there are at least **two secondary indexes on the same table** and the table is filtered in a query with at least two filters constraining different attributes to a constant. A zigzag join works by "zigzagging" back and forth between two indexes and returning only rows with matching primary keys within a specified range. For example:
Copy file name to clipboardExpand all lines: src/current/v26.1/explain-analyze.md
+1Lines changed: 1 addition & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -68,6 +68,7 @@ Property | Description
68
68
`execution time` | The time it took for the final statement plan to complete.
69
69
`distribution` | Whether the statement was distributed or local. If `distribution` is `full`, execution of the statement is performed by multiple nodes in parallel, then the results are returned by the gateway node. If `local`, the execution plan is performed only on the gateway node. Even if the execution plan is `local`, row data may be fetched from remote nodes, but the processing of the data is performed by the local node.
70
70
`plan type` | The plan type used by the query: `generic, re-optimized`, `generic, reused`, or `custom`. For details, refer to [Query plan type]({% link {{ page.version.version }}/cost-based-optimizer.md %}#query-plan-type).
71
+
`statement hints count` | <spanclass="version-tag">New in v26.1:</span> The number of [injected hints]({% link {{ page.version.version }}/cost-based-optimizer.md %}#hint-injection) from `system.statement_hints` applied to the statement. This field is only displayed when hints are applied.
71
72
`vectorized` | Whether the [vectorized execution engine]({% link {{ page.version.version }}/vectorized-execution.md %}) was used in this statement.
72
73
`rows decoded from KV` | The number of rows read from the [storage layer]({% link {{ page.version.version }}/architecture/storage-layer.md %}).
73
74
`cumulative time spent in KV` | The total amount of time spent in the storage layer.
|`distribution`| Whether the statement was distributed or local. If `distribution` is `full`, execution of the statement is performed by multiple nodes in parallel, then the results are returned by the gateway node. If `local`, the execution plan is performed only on the gateway node. Even if the execution plan is `local`, row data may be fetched from remote nodes, but the processing of the data is performed by the local node. |
69
69
|`vectorized`| Whether the [vectorized execution engine]({% link {{ page.version.version }}/vectorized-execution.md %}) was used in this statement. |
70
+
|`statement hints count`| <spanclass="version-tag">New in v26.1:</span> The number of [injected hints]({% link {{ page.version.version }}/cost-based-optimizer.md %}#hint-injection) from `system.statement_hints` applied to the statement. This field is only displayed when hints are applied. |
0 commit comments