Skip to content

Commit 0ac2669

Browse files
committed
[wip] hint injection docs
1 parent 285f444 commit 0ac2669

File tree

2 files changed

+107
-2
lines changed

2 files changed

+107
-2
lines changed

src/current/_includes/v25.4/misc/force-index-selection.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff 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*.
22

3-
{{site.data.alerts.callout_info}}
43
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).
57
{{site.data.alerts.end}}
68

79
##### Force index scan

src/current/v25.4/cost-based-optimizer.md

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -419,6 +419,10 @@ Due to SQL's implicit `AS` syntax, you cannot specify a join hint with only the
419419

420420
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).
421421

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+
422426
### Supported join algorithms
423427

424428
- `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,105 @@ To make the optimizer prefer lookup joins to merge joins when performing foreign
457461

458462
- 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.
459463

464+
## Hint injection
465+
466+
<span class="version-tag">New in v25.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+
SELECT crdb_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+
SELECT crdb_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+
SELECT crdb_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+
SELECT crdb_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:
523+
524+
- [Index hints]({% link {{ page.version.version }}/table-expressions.md %}#force-index-selection): `@index_name`, `@{FORCE_INDEX=idx}`, `@{FORCE_INDEX=idx,DESC}`, `@{NO_FULL_SCAN}`, `@{AVOID_FULL_SCAN}`, `@{NO_ZIGZAG_JOIN}`, `@{FORCE_ZIGZAG}`
525+
- [Join hints](#join-hints): `HASH`, `MERGE`, `LOOKUP`, `INVERTED`, `STRAIGHT`
526+
527+
### Manage injected hints
528+
529+
To view all injected hints, query the `system.statement_hints` table:
530+
531+
{% include_cached copy-clipboard.html %}
532+
~~~ sql
533+
SELECT row_id, fingerprint, created_at FROM system.statement_hints;
534+
~~~
535+
536+
~~~
537+
row_id | fingerprint | created_at
538+
----------------------+------------------------------------------------------------------------------------------------+--------------------------------
539+
1119388019656228865 | SELECT * FROM users WHERE email = _ | 2025-10-28 19:41:53.416787+00
540+
1119394099211304961 | SELECT * FROM orders AS o INNER JOIN customers AS c ON o.customer_id = c.id | 2025-10-28 20:12:48.75263+00
541+
1119397773284343809 | SELECT * FROM orders AS o INNER JOIN customers AS c ON o.customer_id = c.id WHERE o.status = _ | 2025-10-28 20:31:29.990728+00
542+
(3 rows)
543+
~~~
544+
545+
{{site.data.alerts.callout_success}}
546+
The `hint` column is stored in a format that is not human-readable. Use the `fingerprint` column to identify which hints are stored.
547+
{{site.data.alerts.end}}
548+
549+
To remove a hint by its hint ID:
550+
551+
{% include_cached copy-clipboard.html %}
552+
~~~ sql
553+
DELETE FROM system.statement_hints WHERE row_id = {hint_id};
554+
~~~
555+
556+
To remove all hints for a specific query fingerprint:
557+
558+
{% include_cached copy-clipboard.html %}
559+
~~~ sql
560+
DELETE FROM system.statement_hints WHERE fingerprint = '{statement_fingerprint}';
561+
~~~
562+
460563
## Zigzag joins
461564

462565
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:

0 commit comments

Comments
 (0)