Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions src/current/_includes/v26.1/misc/force-index-selection.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
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.
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*.

{{site.data.alerts.callout_info}}
Index selection can impact [performance]({% link {{ page.version.version }}/performance-best-practices-overview.md %}), but does not change the result of a query.

{{site.data.alerts.callout_success}}
{% 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.
{{site.data.alerts.end}}

##### Force index scan
Expand Down
6 changes: 6 additions & 0 deletions src/current/_includes/v26.1/sidebar-data/sql.json
Original file line number Diff line number Diff line change
Expand Up @@ -808,6 +808,12 @@
"/${VERSION}/show-sessions.html"
]
},
{
"title": "<code>SHOW STATEMENT HINTS</code>",
"urls": [
"/${VERSION}/show-statement-hints.html"
]
},
{
"title": "<code>SHOW STATEMENTS</code>",
"urls": [
Expand Down
165 changes: 165 additions & 0 deletions src/current/v26.1/cost-based-optimizer.md
Original file line number Diff line number Diff line change
Expand Up @@ -419,6 +419,10 @@ Due to SQL's implicit `AS` syntax, you cannot specify a join hint with only the

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

{{site.data.alerts.callout_success}}
{% 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.
{{site.data.alerts.end}}

### Supported join algorithms

- `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.
Expand Down Expand Up @@ -457,6 +461,167 @@ To make the optimizer prefer lookup joins to merge joins when performing foreign

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

## Hint injection

{{site.data.alerts.callout_info}}
{% include feature-phases/preview.md %}
{{site.data.alerts.end}}

{% include_cached new-in.html version="v26.1" %} *Hint injection* allows you to apply inline hints, such as [index hints]({% link {{ page.version.version }}/table-expressions.md %}#force-index-selection) and [join hints](#join-hints), without modifying the original statement. 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.

Hint injection supports all inline hint types, and automatically applies them to statements that match a [fingerprint]({% link {{ page.version.version }}/ui-statements-page.md %}#sql-statement-fingerprints).

### Inject hints

{{site.data.alerts.callout_info}}
To inject hints using `information_schema.crdb_rewrite_inline_hints()`, users must have the [`REPAIRCLUSTER`]({% link {{ page.version.version }}/security-reference/authorization.md %}#supported-privileges) privilege.
{{site.data.alerts.end}}

To inject hints, invoke the `information_schema.crdb_rewrite_inline_hints()` built-in function with two matching SQL statement fingerprints: a fingerprint identifying which statements to optimize, and a *donor* fingerprint with inline hints that must be applied to those statements:

{% include_cached copy-clipboard.html %}
~~~ sql
SELECT information_schema.crdb_rewrite_inline_hints(
'{fingerprint}',
'{donor fingerprint}'
);
~~~

Both fingerprints in the function invocation **must** have the same syntactic structure and use underscores (`_`) as placeholders for constants (for example, `SELECT * FROM users WHERE city = _`). If you use a regular SQL statement in either argument, it is automatically converted into the matching fingerprint.
Comment thread
taroface marked this conversation as resolved.
Outdated

{{site.data.alerts.callout_success}}
To find the fingerprint for a statement, use the [**Statements** page of the DB Console]({% link {{ page.version.version }}/ui-statements-page.md %}#statement-fingerprints-view) or [`EXPLAIN (FINGERPRINT) {statement}`]({% link {{ page.version.version }}/explain.md %}#fingerprint-option).
{{site.data.alerts.end}}

The `information_schema.crdb_rewrite_inline_hints()` function does the following:

1. Stores the injected hint in the `system.statement_hints` table.
1. Matches SQL statements with the fingerprint in the first argument.
1. Removes any existing inline hints from the matching SQL statements.
1. Rewrites the matching SQL statements with the inline hints from the donor fingerprint.

For example, the following call injects the `users_city_idx` index hint (provided an [index was created]({% link {{ page.version.version }}/create-index.md %}#create-standard-indexes) on the `city` column) into SQL statements matching `SELECT * FROM users WHERE city = _`:

{% include_cached copy-clipboard.html %}
~~~ sql
SELECT information_schema.crdb_rewrite_inline_hints(
'SELECT * FROM users WHERE city = _',
'SELECT * FROM users@users_city_idx WHERE city = _'
);
~~~

A unique ID is returned:

~~~
information_schema.crdb_rewrite_inline_hints
------------------------------------------------
1143727380739620865
~~~

Afterward, any executed statement that matches the `SELECT * FROM users WHERE city = _` fingerprint will first be rewritten with the `users_city_idx` index hint, regardless of the `city` value.

The following call injects the `MERGE` [join algorithm](#supported-join-algorithms):

{% include_cached copy-clipboard.html %}
~~~ sql
SELECT information_schema.crdb_rewrite_inline_hints(
'SELECT * FROM users AS u INNER JOIN rides AS r ON u.id = r.rider_id',
'SELECT * FROM users AS u INNER MERGE JOIN rides AS r ON u.id = r.rider_id'
);
~~~

Fingerprints distinguish between queries with different inline hints. For example, the fingerprint `SELECT * FROM users WHERE city = _` does **not** match the statement `SELECT * FROM users@users_pkey WHERE city = 'new york'` (which matches the fingerprint `SELECT * FROM users@users_pkey WHERE city = _`). As a result, you can use `information_schema.crdb_rewrite_inline_hints()` to remove inline hints:
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is great! Thank you!


{% include_cached copy-clipboard.html %}
~~~ sql
SELECT information_schema.crdb_rewrite_inline_hints(
'SELECT * FROM users@users_pkey WHERE city = _',
'SELECT * FROM users WHERE city = _'
);
~~~

{{site.data.alerts.callout_success}}
If multiple injected hints exist for the same statement fingerprint, only the **most recently created hint** with `information_schema.crdb_rewrite_inline_hints()` is applied. To replace an existing hint, you can create a new one for the same fingerprint, which will take precedence.
{{site.data.alerts.end}}

### View injected hints

{{site.data.alerts.callout_info}}
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.
{{site.data.alerts.end}}

Use the [`SHOW STATEMENT HINTS`]({% link {{ page.version.version }}/show-statement-hints.md %}) statement to view injected hints for a specific statement fingerprint:

{% include_cached copy-clipboard.html %}
~~~ sql
SHOW STATEMENT HINTS FOR 'SELECT * FROM users WHERE city = _' WITH DETAILS;
~~~

~~~
row_id | fingerprint | hint_type | created_at | details
----------------------+------------------------------------+----------------------+-------------------------------+--------------------------------------------------------------------
1143727380739620865 | SELECT * FROM users WHERE city = _ | rewrite_inline_hints | 2026-01-22 18:58:16.952689+00 | {"donorSql": "SELECT * FROM users@users_city_idx WHERE city = _"}
~~~

You can also query the `system.statement_hints` table directly to view all fingerprints with injected hints:

{% include_cached copy-clipboard.html %}
~~~ sql
SELECT row_id, fingerprint, created_at FROM system.statement_hints;
~~~

~~~
row_id | fingerprint | created_at
----------------------+---------------------------------------------------------------------+--------------------------------
1143727380739620865 | SELECT * FROM users WHERE city = _ | 2026-01-22 18:58:16.952689+00
1143727427097886721 | SELECT * FROM users AS u INNER JOIN rides AS r ON u.id = r.rider_id | 2026-01-22 18:58:31.100944+00
1143727443278921729 | SELECT * FROM users@users_pkey WHERE city = _ | 2026-01-22 18:58:36.039125+00
(3 rows)
~~~

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:

{% include_cached copy-clipboard.html %}
~~~ sql
EXPLAIN SELECT * FROM users WHERE city = 'new york';
~~~

~~~
info
----------------------------------------------------------------------------------
distribution: local
statement hints count: 1

• index join
│ estimated row count: 6
│ table: users@users_pkey
└── • scan
estimated row count: 6 (12% of the table; stats collected 4 minutes ago)
table: users@users_city_idx
spans: [/'new york' - /'new york']
~~~

{{site.data.alerts.callout_info}}
The `statement hints count` field shows the number of individual inline hints applied. A single row in `system.statement_hints` can contain multiple inline hints.
Comment thread
taroface marked this conversation as resolved.
Outdated
{{site.data.alerts.end}}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As of cockroachdb/cockroach#161043 there's also a new metric, sql.query.with_statement_hints.count, which counts the number of statement executions that used statement hints.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see the issue to document this metric. However, apparently metrics.yaml now has a visibility field that (separately from the presence of a release note) determines whether these metrics are supposed to be documented. Since this metric doesn't appear to have that field, and I'm not clear on this metrics policy right now, I'll have to defer this for now. Let me know if it's meant to have visibility though.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see. Let's skip documenting it for now.

### Remove injected hints

To remove an injected hint by its ID:

{% include_cached copy-clipboard.html %}
~~~ sql
DELETE FROM system.statement_hints WHERE row_id = {id};
~~~

To remove all injected hints for a specific query fingerprint:

{% include_cached copy-clipboard.html %}
~~~ sql
DELETE FROM system.statement_hints WHERE fingerprint = '{statement_fingerprint}';
~~~

## Zigzag joins

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:
Expand Down
1 change: 1 addition & 0 deletions src/current/v26.1/explain-analyze.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ Property | Description
`execution time` | The time it took for the final statement plan to complete.
`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.
`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).
`statement hints count` | <span class="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.
`vectorized` | Whether the [vectorized execution engine]({% link {{ page.version.version }}/vectorized-execution.md %}) was used in this statement.
`rows decoded from KV` | The number of rows read from the [storage layer]({% link {{ page.version.version }}/architecture/storage-layer.md %}).
`cumulative time spent in KV` | The total amount of time spent in the storage layer.
Expand Down
39 changes: 28 additions & 11 deletions src/current/v26.1/explain.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,15 +39,16 @@ The user requires the appropriate [privileges]({% link {{ page.version.version }

Parameter | Description
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure how helpful this is, but just noting here that there are two "levels" to EXPLAIN parameters: "modes" and "flags". There has to be 1 mode, and then 0 or more flags for that mode.

Modes:

  • PLAN (the default if no modes are provided)
  • OPT
  • DISTSQL
  • DDL
  • VEC
  • DEBUG
  • GIST
  • FINGERPRINT (new)

Flags:

  • VERBOSE
  • TYPES
  • ENV
  • CATALOG
  • JSON
  • MEMO
  • SHAPE
  • VIZ
  • REDACT

It confuses things a little bit to document them all together, because they work a bit differently.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you! I will file this into a separate ticket.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

-------------------+------------
`VERBOSE` | Show as much information as possible about the statement plan. See [`VERBOSE` option](#verbose-option).
`TYPES` | Include the intermediate [data types]({% link {{ page.version.version }}/data-types.md %}) CockroachDB chooses to evaluate intermediate SQL expressions. See [`TYPES` option](#types-option).
`OPT` | Display the statement plan tree generated by the [cost-based optimizer]({% link {{ page.version.version }}/cost-based-optimizer.md %}). See [`OPT` option](#opt-option).
`ENV` | Include all details used by the optimizer, including statistics. See [`ENV` suboption](#opt-env-option).
`MEMO` | Print a representation of the optimizer memo with the best plan. See [`MEMO` suboption](#opt-memo-option).
`REDACT` | Redact constants, literal values, parameter values, and personally identifiable information (PII) from the output. See [`REDACT` option](#redact-option).
`VEC` | Show detailed information about the [vectorized execution]({% link {{ page.version.version }}/vectorized-execution.md %}) plan for a query. See [`VEC` option](#vec-option).
`DISTSQL` | Generate a URL to a [distributed SQL physical statement plan diagram]({% link {{ page.version.version }}/explain-analyze.md %}#distsql-plan-diagram). See [`DISTSQL` option](#distsql-option).
`preparable_stmt` | The [statement]({% link {{ page.version.version }}/sql-grammar.md %}#preparable_stmt) you want details about. All preparable statements are explainable.
`VERBOSE` | Show as much information as possible about the statement plan. Refer to [`VERBOSE` option](#verbose-option).
`TYPES` | Include the intermediate [data types]({% link {{ page.version.version }}/data-types.md %}) CockroachDB chooses to evaluate intermediate SQL expressions. Refer to [`TYPES` option](#types-option).
`OPT` | Display the statement plan tree generated by the [cost-based optimizer]({% link {{ page.version.version }}/cost-based-optimizer.md %}). Refer to [`OPT` option](#opt-option).
`ENV` | Include all details used by the optimizer, including statistics. Refer to [`ENV` suboption](#opt-env-option).
`MEMO` | Print a representation of the optimizer memo with the best plan. Refer to [`MEMO` suboption](#opt-memo-option).
`REDACT` | Redact constants, literal values, parameter values, and personally identifiable information (PII) from the output. Refer to [`REDACT` option](#redact-option).
`VEC` | Show detailed information about the [vectorized execution]({% link {{ page.version.version }}/vectorized-execution.md %}) plan for a query. Refer to [`VEC` option](#vec-option).
`DISTSQL` | Generate a URL to a [distributed SQL physical statement plan diagram]({% link {{ page.version.version }}/explain-analyze.md %}#distsql-plan-diagram). Refer to [`DISTSQL` option](#distsql-option).
`FINGERPRINT` | Show the matching fingerprint for the statement. Refer to [`FINGERPRINT` option](#fingerprint-option).
`explainable_stmt` | The [statement]({% link {{ page.version.version }}/sql-grammar.md %}#preparable_stmt) you want details about. All preparable statements are explainable.

## Success responses

Expand All @@ -67,6 +68,7 @@ Node details | The properties, columns, and ordering details for the current sta
|----------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `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. |
| `vectorized` | Whether the [vectorized execution engine]({% link {{ page.version.version }}/vectorized-execution.md %}) was used in this statement. |
| `statement hints count` | <span class="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. |


### Statement plan tree properties
Expand Down Expand Up @@ -886,7 +888,7 @@ The output of `EXPLAIN (DISTSQL)` is a URL for a graphical diagram that displays

To view the [DistSQL plan diagram]({% link {{ page.version.version }}/explain-analyze.md %}#distsql-plan-diagram), open the URL. You should see the following:

<img src="{{ 'images/v24.2/explain-distsql-plan.png' | relative_url }}" alt="EXPLAIN (DISTSQL)" style="border:1px solid #eee;max-width:100%" />
<img src="{{ 'images/v26.1/explain-distsql-plan.png' | relative_url }}" alt="EXPLAIN (DISTSQL)" style="border:1px solid #eee;max-width:100%" />

To include the data types of the input columns in the physical plan, use `EXPLAIN(DISTSQL, TYPES)`:

Expand All @@ -903,7 +905,22 @@ EXPLAIN (DISTSQL, TYPES) SELECT l_shipmode, AVG(l_extendedprice) FROM lineitem G

Open the URL. You should see the following:

<img src="{{ 'images/v24.2/explain-distsql-types-plan.png' | relative_url }}" alt="EXPLAIN (DISTSQL)" style="border:1px solid #eee;max-width:100%" />
<img src="{{ 'images/v26.1/explain-distsql-types-plan.png' | relative_url }}" alt="EXPLAIN (DISTSQL)" style="border:1px solid #eee;max-width:100%" />

#### `FINGERPRINT` option

To view the [statement fingerprint]({% link {{ page.version.version }}/ui-statements-page.md %}#sql-statement-fingerprints) for a query, use the `FINGERPRINT` option. The fingerprint shows the query structure with constants replaced by underscores.

{% include_cached copy-clipboard.html %}
~~~ sql
EXPLAIN (FINGERPRINT) SELECT * FROM users WHERE city = 'new york';
~~~

~~~
info
--------------------------------------
SELECT * FROM users WHERE city = _
~~~

### Find the indexes and key ranges a query uses

Expand Down
Loading
Loading