> ## Documentation Index
> Fetch the complete documentation index at: https://www.cockroachlabs.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Known Limitations in CockroachDB v23.1

export const InternalLink = ({version, path = "", children, ...props}) => {
  let detectedVersion = version || "stable";
  if (typeof window !== 'undefined' && !version) {
    const match = window.location.pathname.match(/\/docs\/([^/]+)/);
    if (match) {
      detectedVersion = match[1];
    }
  }
  const normalizedPath = path.startsWith("/") ? path.slice(1) : path;
  return <a href={`/docs/${detectedVersion}/${normalizedPath}`} {...props}>
      {children}
    </a>;
};

This page describes newly identified limitations in the CockroachDB  release as well as unresolved limitations identified in earlier releases.

## New limitations

### Limitations for `EXPLAIN ANALYZE`

* <InternalLink path="explain-analyze">`EXPLAIN ANALYZE`</InternalLink> does not collect inverted statistics on columns that are indexed with both forward and inverted indexes; only forward statistics are collected for those columns.

* <InternalLink path="explain-analyze">`EXPLAIN ANALYZE`</InternalLink> does not support the `AS OF SYSTEM TIME` syntax. Use <InternalLink path="create-statistics#create-statistics-as-of-a-given-time">`CREATE STATISTICS ... AS OF SYSTEM TIME`</InternalLink> instead.

### Limitations for index recommendations

* <InternalLink path="indexes">Index</InternalLink> recommendations are not aware of <InternalLink path="hash-sharded-indexes">hash sharding</InternalLink>.

* CockroachDB does not support <InternalLink path="indexes">index</InternalLink> recommendations on <InternalLink path="table-localities#regional-by-row-tables">`REGIONAL BY ROW` tables</InternalLink>.

### Limitations for `SELECT FOR UPDATE`

* <InternalLink path="select-for-update">`SELECT FOR UPDATE`</InternalLink> places locks on each key scanned by the base index scan. This means that even if some of those keys are later filtered out by a predicate which could not be pushed into the scan, they will still be locked.

* <InternalLink path="select-for-update">`SELECT FOR UPDATE`</InternalLink> only places an unreplicated lock on the index being scanned by the query. This diverges from PostgreSQL, which aquires a lock on all indexes.

### Limitations for composite types

* Changefeed types are not fully integrated with <InternalLink path="create-type">user-defined composite types</InternalLink>. Running changefeeds with user-defined composite types is in <InternalLink version="releases" path="cockroachdb-feature-availability#feature-availability-phases">Preview</InternalLink>. Certain changefeed types do not support user-defined composite types. Refer to the change data capture <InternalLink path="create-and-configure-changefeeds#known-limitations">Known Limitations</InternalLink> for more detail. The following limitations apply:
  * A changefeed in <InternalLink path="changefeed-messages#avro">Avro format</InternalLink> will not be able to serialize <InternalLink path="create-type">user-defined composite (tuple) types</InternalLink>.
  * A changefeed emitting <InternalLink path="changefeed-messages#csv">CSV</InternalLink> will include `AS` labels in the message format when the changefeed serializes a <InternalLink path="create-type">user-defined composite type</InternalLink>.

### Common table expressions are not supported in user-defined functions

<InternalLink path="common-table-expressions">Common table expressions</InternalLink> (CTE), recursive or non-recursive, are not supported in <InternalLink path="user-defined-functions">user-defined functions</InternalLink> (UDF). That is, you cannot use a `WITH` clause in the body of a UDF.

### Low estimated Request Units are rounded to zero

The <InternalLink version="cockroachcloud" path="plan-your-cluster-basic#request-units">Request Units</InternalLink> (RUs) estimate surfaced in <InternalLink path="explain-analyze">`EXPLAIN ANALYZE`</InternalLink> is displayed as an integer value. Because of this, fractional RU estimates, which represent very inexpensive queries, are rounded down to zero.

### `AS OF SYSTEM TIME` does not support placeholders

CockroachDB does not support placeholders in <InternalLink path="as-of-system-time">`AS OF SYSTEM TIME`</InternalLink>. The time value must be embedded in the SQL string.

### `null_ordered_last` does not produce correct results with tuples

By default, CockroachDB orders `NULL`s before all other values. For compatibility with PostgreSQL, the `null_ordered_last` <InternalLink path="set-vars">session variable</InternalLink> was added, which changes the default to order `NULL`s after all other values. This works in most cases, due to some transformations CockroachDB makes in the optimizer to add extra ordering columns. However, it is broken when the ordering column is a tuple.

### Inverted join for `tsvector` and `tsquery` types is not supported

CockroachDB cannot index-accelerate queries with `@@` predicates when both sides of the operator are variables.

### No guaranteed state switch from `DECOMMISSIONING` to `DECOMMISSIONED` if `node decommission` is interrupted

There is no guaranteed state switch from `DECOMMISSIONING` to `DECOMMISSIONED` if <InternalLink path="cockroach-node">`node decommission`</InternalLink> is interrupted in one of the following ways:

* The `cockroach node decommission --wait-all` command was run and then interrupted
* The `cockroach node decommission --wait=none` command was run

This is because the state flip is effected by the CLI program at the end. Only the CLI (or its underlying API call) is able to finalize the "decommissioned" state. If the command is interrupted, or `--wait=none` is used, the state will only flip to "decommissioned" when the CLI program is run again after decommissioning has done all its work.

### Execution locality in changefeeds

* Changefeeds that use the <InternalLink path="changefeeds-in-multi-region-deployments#run-a-changefeed-job-by-locality">`execution_locality` option</InternalLink> set to a <InternalLink path="multiregion-overview#secondary-regions">secondary region</InternalLink> could create a plan that assigns most of the ranges to an <InternalLink path="how-does-an-enterprise-changefeed-work">aggregator</InternalLink> on the coordinator node. This leads to an unbalanced plan and slow changefeed progress, particularly when the table is large and has many ranges.

## Unresolved limitations

### Limitations for user-defined functions (UDFs)

#### Limitations on use of UDFs

<InternalLink path="user-defined-functions">User-defined functions</InternalLink> are not currently supported in:

* Expressions (column, index, constraint) in tables.

* Views.

* Other user-defined functions.

#### Limitations on expressions allowed within UDFs

The following are not currently allowed within the body of a <InternalLink path="user-defined-functions">UDF</InternalLink>:

* Mutation statements such as `INSERT`, `UPDATE`, `DELETE`, and `UPSERT`.

* Common table expressions (CTEs).

* References to other user-defined functions.

### Table-level restore will not restore user-defined functions

`RESTORE` will not restore a table that references a <InternalLink path="user-defined-functions">UDF</InternalLink>, unless you skip restoring the function with the  [`skip_missing_udfs`](#skip-missing-udfs)  <InternalLink path="restore#skip-missing-udfs">`skip_missing_udfs`</InternalLink>  option. Alternatively, take a <InternalLink path="backup#back-up-a-database">database-level backup</InternalLink> to include everything needed to restore the table.

### Incorrect query plans for partitions with `NULL` values

In cases where the partition definition includes a comparison with `NULL` and a query constraint, incorrect query plans are returned. However, this case uses non-standard partitioning which defines partitions which could never hold values, so it is not likely to occur in production environments.

### Limitations for `DROP OWNED BY`

<InternalLink path="drop-owned-by">`DROP OWNED BY`</InternalLink> drops all owned objects as well as any <InternalLink path="grant">grants</InternalLink> on objects not owned by the <InternalLink path="security-reference/authorization#roles">role</InternalLink>.

#### `DROP OWNED BY` is not supported where role has system-level privileges

* If the <InternalLink path="security-reference/authorization#roles">role</InternalLink> for which you are trying to `DROP OWNED BY` was granted a <InternalLink path="security-reference/authorization#supported-privileges">system-level privilege</InternalLink> (i.e., using the <InternalLink path="grant#grant-system-level-privileges-on-the-entire-cluster">`GRANT SYSTEM ...`</InternalLink> statement), the error shown below will be signalled.  The workaround is to use <InternalLink path="show-system-grants">`SHOW SYSTEM GRANTS FOR {role}`</InternalLink> and then use <InternalLink path="revoke#revoke-system-level-privileges-on-the-entire-cluster">`REVOKE SYSTEM ...`</InternalLink> for each privilege in the result.

  ```
  ERROR: cannot perform drop owned by if role has synthetic privileges; foo has entries in system.privileges
  SQLSTATE: 0A000
  HINT: perform REVOKE SYSTEM ... for the relevant privileges foo has in system.privileges
  ```

Note that the phrase "synthetic privileges" in the above error message refers to <InternalLink path="security-reference/authorization#supported-privileges">system-level privileges</InternalLink>.

### Spatial features disabled for ARM Macs

<InternalLink path="spatial-data-overview">Spatial features</InternalLink> are disabled due to an issue with macOS code signing for the [GEOS](https://libgeos.org/) libraries. Users needing spatial features on an ARM Mac may instead [use Rosetta](https://developer.apple.com/documentation/virtualization/running_intel_binaries_in_linux_vms_with_rosetta) to <InternalLink path="install-cockroachdb-mac">run the Intel binary</InternalLink> or use the <InternalLink path="install-cockroachdb-mac#use-docker">Docker image</InternalLink> distribution. This is expected to be resolved in an upcoming 22.2 patch release.

### Limited SQL cursor support

CockroachDB implements SQL  <InternalLink path="cursors">cursor</InternalLink>  cursor  support with the following limitations:

* `DECLARE` only supports forward cursors. Reverse cursors created with `DECLARE SCROLL` are not supported.
* `FETCH` supports forward, relative, and absolute variants, but only for forward cursors.
* `BINARY CURSOR`, which returns data in the Postgres binary format, is not supported.
* `WITH HOLD`, which allows keeping a cursor open for longer than a transaction by writing its results into a buffer, is accepted as valid syntax within a single transaction but is not supported. It acts as a no-op and does not actually perform the function of `WITH HOLD`, which is to make the cursor live outside its parent transaction. Instead, if you are using `WITH HOLD`, you will be forced to close that cursor within the transaction it was created in.
  * This syntax is accepted (but does not have any effect):

    ```sql theme={"theme":{"light":"catppuccin-mocha","dark":"catppuccin-mocha"}}
    BEGIN;
    DECLARE test_cur CURSOR WITH HOLD FOR SELECT * FROM foo ORDER BY bar;
    CLOSE test_cur;
    COMMIT;
    ```
  * This syntax is not accepted, and will result in an error:

    ```sql theme={"theme":{"light":"catppuccin-mocha","dark":"catppuccin-mocha"}}
    BEGIN;
    DECLARE test_cur CURSOR WITH HOLD FOR SELECT * FROM foo ORDER BY bar;
    COMMIT; -- This will fail with an error because CLOSE test_cur was not called inside the transaction.
    ```
* Scrollable cursor (also known as reverse `FETCH`) is not supported.
* <InternalLink path="select-for-update">`SELECT ... FOR UPDATE`</InternalLink> with a cursor is not supported.
* Respect for <InternalLink path="savepoint">`SAVEPOINT`s</InternalLink> is not supported. Cursor definitions do not disappear properly if rolled back to a `SAVEPOINT` from before they were created.

### `SELECT FOR UPDATE` locks are dropped on lease transfers  and range splits/merges

Locks acquired using  `SELECT ... FOR UPDATE`  <InternalLink path="select-for-update">`SELECT ... FOR UPDATE`</InternalLink>  are dropped on <InternalLink path="architecture/replication-layer#epoch-based-leases-table-data">lease transfers</InternalLink> and <InternalLink path="architecture/distribution-layer#range-merges">range splits and merges</InternalLink>. `SELECT ... FOR UPDATE` locks should be thought of as best-effort, and should not be relied upon for correctness, as they are implemented as fast, in-memory <InternalLink path="architecture/transaction-layer">unreplicated locks</InternalLink>.

If a lease transfer or range split/merge occurs on a range held by an unreplicated lock, the lock is dropped, and the following behaviors can occur:

* The desired ordering of concurrent accesses to one or more rows of a table expressed by your use of `SELECT ... FOR UPDATE` may not be preserved (that is, a transaction *B* against some table *T* that was supposed to wait behind another transaction *A* operating on *T* may not wait for transaction *A*).
* The transaction that acquired the (now dropped) unreplicated lock may fail to commit, leading to <InternalLink path="common-errors#restart-transaction">transaction retry errors with code `40001` and the `restart transaction` error message</InternalLink>.

Note that <InternalLink path="transactions#serializable-isolation">serializable isolation</InternalLink> is preserved despite this limitation.

### Unsupported trigram syntax

The following PostgreSQL syntax and features are currently unsupported for <InternalLink path="trigram-indexes">trigrams</InternalLink>:

* `word_similarity()` built-in function.
* `strict_word_similarity()` built-in function.
* `%>` and `<%` comparisons and acceleration.
* `<<%` and `%>>` comparisons and acceleration.
* `<->`, `<<->`, `<->>`, `<<<->`, and `<->>>` comparisons.
* Acceleration on <InternalLink path="scalar-expressions#string-matching-using-posix-regular-expressions">regex string matching</InternalLink>.
* `%` comparisons, `show_trgm`, and trigram index creation on <InternalLink path="collate">collated strings</InternalLink>.

### A multi-region table cannot be restored into a non-multi-region table

You cannot <InternalLink path="restore#skip-missing-udfs">restore</InternalLink> a multi-region table into a non-multi-region table.

### Statements containing multiple modification subqueries of the same table are disallowed

Statements containing multiple modification subqueries mutating the same row could cause corruption. These statements are disallowed by default, but you can enable multiple modification subqueries with one the following:

* Set the `sql.multiple_modifications_of_table.enabled` <InternalLink path="cluster-settings">cluster setting</InternalLink> to `true`.
* Use the `enable_multiple_modifications_of_table` <InternalLink path="set-vars">session variable</InternalLink>.

Note that if multiple mutations inside the same statement affect different tables with <InternalLink path="foreign-key">`FOREIGN KEY`</InternalLink> relations and `ON CASCADE` clauses between them, the results will be different from what is expected in PostgreSQL.

### `transaction_rows_read_err` and `transaction_rows_written_err` do not halt query execution

The `transaction_rows_read_err` and `transaction_rows_written_err` <InternalLink path="set-vars">session settings</InternalLink> limit the number of rows read or written by a single <InternalLink path="transactions#limit-the-number-of-rows-written-or-read-in-a-transaction">transaction</InternalLink>. These session settings will fail the transaction with an error, but not until the current query finishes executing and the results have been returned to the client.

### `sql.guardrails.max_row_size_err` misses indexed virtual computed columns

The `sql.guardrails.max_row_size_err` <InternalLink path="cluster-settings">cluster setting</InternalLink> misses large rows caused by indexed virtual computed columns. This is because the guardrail only checks the size of primary key rows, not secondary index rows.

### CockroachDB does not allow inverted indexes with `STORING`

CockroachDB does not allow inverted indexes with a <InternalLink path="create-index#store-columns">`STORING` column</InternalLink>.

### CockroachDB does not properly optimize some left and anti joins with GIN indexes

<InternalLink path="joins#left-outer-joins">Left joins</InternalLink> and anti joins involving <InternalLink path="jsonb">`JSONB`</InternalLink>, <InternalLink path="array">`ARRAY`</InternalLink>, or <InternalLink path="export-spatial-data">spatial-typed</InternalLink> columns with a multi-column or <InternalLink path="alter-index#partition-by">partitioned</InternalLink> <InternalLink path="inverted-indexes">GIN index</InternalLink> will not take advantage of the index if the prefix columns of the index are unconstrained, or if they are constrained to multiple, constant values.

To work around this limitation, make sure that the prefix columns of the index are either constrained to single constant values, or are part of an equality condition with an input column (e.g., `col1 = col2`, where `col1` is a prefix column and `col2` is an input column).

For example, suppose you have the following <InternalLink path="multiregion-overview">multi-region database</InternalLink> and tables:

```sql theme={"theme":{"light":"catppuccin-mocha","dark":"catppuccin-mocha"}}
CREATE DATABASE multi_region_test_db PRIMARY REGION "europe-west1" REGIONS "us-west1", "us-east1" SURVIVE REGION FAILURE;
USE multi_region_test_db;

CREATE TABLE t1 (
  k INT PRIMARY KEY,
  geom GEOMETRY
);

CREATE TABLE t2 (
  k INT PRIMARY KEY,
  geom GEOMETRY,
  INVERTED INDEX geom_idx (geom)
) LOCALITY REGIONAL BY ROW;
```

And you <InternalLink path="insert">insert</InternalLink> some data into the tables:

```sql theme={"theme":{"light":"catppuccin-mocha","dark":"catppuccin-mocha"}}
INSERT INTO t1 SELECT generate_series(1, 1000), 'POINT(1.0 1.0)';
INSERT INTO t2 (crdb_region, k, geom) SELECT 'us-east1', generate_series(1, 1000), 'POINT(1.0 1.0)';
INSERT INTO t2 (crdb_region, k, geom) SELECT 'us-west1', generate_series(1001, 2000), 'POINT(2.0 2.0)';
INSERT INTO t2 (crdb_region, k, geom) SELECT 'europe-west1', generate_series(2001, 3000), 'POINT(3.0 3.0)';
```

If you attempt a left join between `t1` and `t2` on only the geometry columns, CockroachDB will not be able to plan an <InternalLink path="joins#inverted-joins">inverted join</InternalLink>:

```
> EXPLAIN SELECT * FROM t1 LEFT JOIN t2 ON st_contains(t1.geom, t2.geom);
                info
------------------------------------
  distribution: full
  vectorized: true

  • cross join (right outer)
  │ pred: st_contains(geom, geom)
  │
  ├── • scan
  │     estimated row count: 3,000
  │     table: t2@primary
  │     spans: FULL SCAN
  │
  └── • scan
        estimated row count: 1,000
        table: t1@primary
        spans: FULL SCAN
(15 rows)
```

However, if you constrain the `crdb_region` column to a single value, CockroachDB can plan an inverted join:

```
> EXPLAIN SELECT * FROM t1 LEFT JOIN t2 ON st_contains(t1.geom, t2.geom) AND t2.crdb_region = 'us-east1';
                       info
--------------------------------------------------
  distribution: full
  vectorized: true

  • lookup join (left outer)
  │ table: t2@primary
  │ equality: (crdb_region, k) = (crdb_region,k)
  │ equality cols are key
  │ pred: st_contains(geom, geom)
  │
  └── • inverted join (left outer)
      │ table: t2@geom_idx
      │
      └── • render
          │
          └── • scan
                estimated row count: 1,000
                table: t1@primary
                spans: FULL SCAN
(18 rows)
```

If you do not know which region to use, you can combine queries with <InternalLink path="selection-queries#union-combine-two-queries">`UNION ALL`</InternalLink>:

```
> EXPLAIN SELECT * FROM t1 LEFT JOIN t2 ON st_contains(t1.geom, t2.geom) AND t2.crdb_region = 'us-east1'
UNION ALL SELECT * FROM t1 LEFT JOIN t2 ON st_contains(t1.geom, t2.geom) AND t2.crdb_region = 'us-west1'
UNION ALL SELECT * FROM t1 LEFT JOIN t2 ON st_contains(t1.geom, t2.geom) AND t2.crdb_region = 'europe-west1';
                           info
----------------------------------------------------------
  distribution: full
  vectorized: true

  • union all
  │
  ├── • union all
  │   │
  │   ├── • lookup join (left outer)
  │   │   │ table: t2@primary
  │   │   │ equality: (crdb_region, k) = (crdb_region,k)
  │   │   │ equality cols are key
  │   │   │ pred: st_contains(geom, geom)
  │   │   │
  │   │   └── • inverted join (left outer)
  │   │       │ table: t2@geom_idx
  │   │       │
  │   │       └── • render
  │   │           │
  │   │           └── • scan
  │   │                 estimated row count: 1,000
  │   │                 table: t1@primary
  │   │                 spans: FULL SCAN
  │   │
  │   └── • lookup join (left outer)
  │       │ table: t2@primary
  │       │ equality: (crdb_region, k) = (crdb_region,k)
  │       │ equality cols are key
  │       │ pred: st_contains(geom, geom)
  │       │
  │       └── • inverted join (left outer)
  │           │ table: t2@geom_idx
  │           │
  │           └── • render
  │               │
  │               └── • scan
  │                     estimated row count: 1,000
  │                     table: t1@primary
  │                     spans: FULL SCAN
  │
  └── • lookup join (left outer)
      │ table: t2@primary
      │ equality: (crdb_region, k) = (crdb_region,k)
      │ equality cols are key
      │ pred: st_contains(geom, geom)
      │
      └── • inverted join (left outer)
          │ table: t2@geom_idx
          │
          └── • render
              │
              └── • scan
                    estimated row count: 1,000
                    table: t1@primary
                    spans: FULL SCAN
(54 rows)
```

### Using `RESTORE` with multi-region table localities

* Restoring <InternalLink path="table-localities#global-tables">`GLOBAL`</InternalLink> and <InternalLink path="table-localities#regional-tables">`REGIONAL BY TABLE`</InternalLink> tables into a **non**-multi-region database is not supported.

* <InternalLink path="table-localities#regional-tables">`REGIONAL BY TABLE`</InternalLink> and <InternalLink path="table-localities#regional-by-row-tables">`REGIONAL BY ROW`</InternalLink> tables can be restored **only** if the regions of the backed-up table match those of the target database. All of the following must be true for `RESTORE` to be successful:

  * The <InternalLink path="multiregion-overview#database-regions">regions</InternalLink> of the source database and the regions of the destination database have the same set of regions.
  * The regions were added to each of the databases in the same order.
  * The databases have the same <InternalLink path="alter-database#set-primary-region">primary region</InternalLink>.

  The following example would be considered as having **mismatched** regions because the database regions were not added in the same order and the primary regions do not match.

  Running on the source database:

  ```sql theme={"theme":{"light":"catppuccin-mocha","dark":"catppuccin-mocha"}}
  ALTER DATABASE source_database SET PRIMARY REGION "us-east1";
  ```

  ```sql theme={"theme":{"light":"catppuccin-mocha","dark":"catppuccin-mocha"}}
  ALTER DATABASE source_database ADD region "us-west1";
  ```

  Running on the destination database:

  ```sql theme={"theme":{"light":"catppuccin-mocha","dark":"catppuccin-mocha"}}
  ALTER DATABASE destination_database SET PRIMARY REGION "us-west1";
  ```

  ```sql theme={"theme":{"light":"catppuccin-mocha","dark":"catppuccin-mocha"}}
  ALTER DATABASE destination_database ADD region "us-east1";
  ```

  In addition, the following scenario has mismatched regions between the databases since the regions were not added to the database in the same order.

  Running on the source database:

  ```sql theme={"theme":{"light":"catppuccin-mocha","dark":"catppuccin-mocha"}}
  ALTER DATABASE source_database SET PRIMARY REGION "us-east1";
  ```

  ```sql theme={"theme":{"light":"catppuccin-mocha","dark":"catppuccin-mocha"}}
  ALTER DATABASE source_database ADD region "us-west1";
  ```

  Running on the destination database:

  ```sql theme={"theme":{"light":"catppuccin-mocha","dark":"catppuccin-mocha"}}
  ALTER DATABASE destination_database SET PRIMARY REGION "us-west1";
  ```

  ```sql theme={"theme":{"light":"catppuccin-mocha","dark":"catppuccin-mocha"}}
  ALTER DATABASE destination_database ADD region "us-east1";
  ```

  ```sql theme={"theme":{"light":"catppuccin-mocha","dark":"catppuccin-mocha"}}
  ALTER DATABASE destination_database SET PRIMARY REGION "us-east1";
  ```

### `SET` does not `ROLLBACK` in a transaction

`SET`  <InternalLink path="set-vars">`SET`</InternalLink>  does not properly apply <InternalLink path="rollback-transaction">`ROLLBACK`</InternalLink> within a transaction. For example, in the following transaction, showing the `TIME ZONE` <InternalLink path="set-vars#supported-variables">variable</InternalLink> does not return `2` as expected after the rollback:

```sql theme={"theme":{"light":"catppuccin-mocha","dark":"catppuccin-mocha"}}
SET TIME ZONE +2;
BEGIN;
SET TIME ZONE +3;
ROLLBACK;
SHOW TIME ZONE;
```

```sql theme={"theme":{"light":"catppuccin-mocha","dark":"catppuccin-mocha"}}
timezone
------------
3
```

### `JSONB`/`JSON` comparison operators are not implemented

You cannot use comparison operators (such as `<` or `>`) on <InternalLink path="jsonb">`JSONB`</InternalLink> elements. For example, the following query does not work and returns an error:

```sql theme={"theme":{"light":"catppuccin-mocha","dark":"catppuccin-mocha"}}
SELECT '{"a": 1}'::JSONB -> 'a' < '{"b": 2}'::JSONB -> 'b';
```

```
ERROR: unsupported comparison operator: <jsonb> < <jsonb>
SQLSTATE: 22023
```

### Locality-optimized search works only for queries selecting a limited number of records

* Locality optimized search  <InternalLink path="cost-based-optimizer#locality-optimized-search-in-multi-region-clusters">Locality optimized search</InternalLink>  works only for queries selecting a limited number of records (up to 100,000 unique keys).

### Expression indexes cannot reference computed columns

CockroachDB does not allow  expression indexes  <InternalLink path="expression-indexes">expression indexes</InternalLink>  to reference <InternalLink path="computed-columns">computed columns</InternalLink>.

### Materialized view limitations

* The optimizer may not select the most optimal query plan when querying materialized views because CockroachDB does not <InternalLink path="cost-based-optimizer#table-statistics">collect statistics</InternalLink> on materialized views.

* CockroachDB cannot refresh  materialized views  <InternalLink path="views#materialized-views">materialized views</InternalLink>  inside <InternalLink path="begin-transaction">explicit transactions</InternalLink>. Trying to refresh a materialized view inside an explicit transaction will result in an error.
  1. Start <InternalLink path="cockroach-demo">`cockroach demo`</InternalLink> with the sample `bank` data set:

     ```shell theme={"theme":{"light":"catppuccin-mocha","dark":"catppuccin-mocha"}}
     cockroach demo bank
     ```
  2. Create the materialized view described in <InternalLink path="views#usage">Usage</InternalLink>.
  3. Start a new multi-statement transaction with <InternalLink path="begin-transaction">`BEGIN TRANSACTION`</InternalLink>:

     ```sql theme={"theme":{"light":"catppuccin-mocha","dark":"catppuccin-mocha"}}
     BEGIN TRANSACTION;
     ```
  4. Inside the open transaction, attempt to <InternalLink path="refresh">refresh the view</InternalLink>. This will result in an error.

     ```sql theme={"theme":{"light":"catppuccin-mocha","dark":"catppuccin-mocha"}}
     REFRESH MATERIALIZED VIEW overdrawn_accounts;
     ```

     ```
     ERROR: cannot refresh view in an explicit transaction
     SQLSTATE: 25000
     ```

### CockroachDB cannot plan locality optimized searches that use partitioned unique indexes on virtual computed columns

* Locality optimized search  <InternalLink path="cost-based-optimizer#locality-optimized-search-in-multi-region-clusters">Locality optimized search</InternalLink>  does not work for queries that use <InternalLink path="partitioning#partition-using-a-secondary-index">partitioned unique indexes</InternalLink> on <InternalLink path="computed-columns">virtual computed columns</InternalLink>. A workaround for computed columns is to make the virtual computed column a <InternalLink path="computed-columns">stored computed column</InternalLink>. Locality optimized search does not work for queries that use partitioned unique <InternalLink path="expression-indexes">expression indexes</InternalLink>.

### Expressions as `ON CONFLICT` targets are not supported

CockroachDB does not support expressions as `ON CONFLICT` targets. This means that unique  expression indexes  <InternalLink path="expression-indexes">expression indexes</InternalLink>  cannot be selected as arbiters for <InternalLink path="insert#on-conflict-clause">`INSERT .. ON CONFLICT`</InternalLink> statements. For example:

```sql theme={"theme":{"light":"catppuccin-mocha","dark":"catppuccin-mocha"}}
CREATE TABLE t (a INT, b INT, UNIQUE INDEX ((a + b)));
```

```
CREATE TABLE
```

```sql theme={"theme":{"light":"catppuccin-mocha","dark":"catppuccin-mocha"}}
INSERT INTO t VALUES (1, 2) ON CONFLICT ((a + b)) DO NOTHING;
```

```
invalid syntax: statement ignored: at or near "(": syntax error
SQLSTATE: 42601
DETAIL: source SQL:
INSERT INTO t VALUES (1, 2) ON CONFLICT ((a + b)) DO NOTHING
                                    ^
HINT: try \h INSERT
```

```sql theme={"theme":{"light":"catppuccin-mocha","dark":"catppuccin-mocha"}}
INSERT INTO t VALUES (1, 2) ON CONFLICT ((a + b)) DO UPDATE SET a = 10;
```

```
invalid syntax: statement ignored: at or near "(": syntax error
SQLSTATE: 42601
DETAIL: source SQL:
INSERT INTO t VALUES (1, 2) ON CONFLICT ((a + b)) DO UPDATE SET a = 10
                                    ^
HINT: try \h INSERT
```

### Statistics limitations

* The <InternalLink path="cost-based-optimizer#control-statistics-refresh-rate">automatic statistics refresher</InternalLink> automatically checks whether it needs to refresh statistics for every table in the database upon startup of each node in the cluster. If statistics for a table have not been refreshed in a while, this will trigger collection of statistics for that table. If statistics have been refreshed recently, it will not force a refresh. As a result, the automatic statistics refresher does not necessarily perform a refresh of statistics after an <InternalLink path="upgrade-cockroach-version">upgrade</InternalLink>. This could cause a problem, for example, if the upgrade moves from a version without <InternalLink path="cost-based-optimizer#control-histogram-collection">histograms</InternalLink> to a version with histograms. To refresh statistics manually, use <InternalLink path="create-statistics">`CREATE STATISTICS`</InternalLink>.
* The following <InternalLink path="cluster-settings">cluster settings</InternalLink> do not immediately take effect, and instead only take effect when new statistics are collected for a table.

  * <InternalLink path="cluster-settings">`sql.stats.forecasts.max_decrease`</InternalLink>
  * <InternalLink path="cluster-settings">`sql.stats.forecasts.min_goodness_of_fit`</InternalLink>
  * <InternalLink path="cluster-settings">`sql.stats.forecasts.min_observations`</InternalLink>

  Although <InternalLink path="show-statistics#display-forecasted-statistics">`SHOW STATISTICS WITH FORECAST`</InternalLink> shows the settings taking effect immediately, they do not actually take effect until new statistics are collected (as can be verified with <InternalLink path="explain">`EXPLAIN`</InternalLink>).

  As a workaround, disable and enable forecasting at the <InternalLink path="cost-based-optimizer#enable-and-disable-automatic-statistics-collection-for-clusters">cluster</InternalLink> or <InternalLink path="cost-based-optimizer#enable-and-disable-automatic-statistics-collection-for-tables">table</InternalLink> level. This will invalidate the statistics cache and cause these settings to take effect immediately.
* When a table is dropped, the related rows in `system.table_statistics` are not deleted. CockroachDB does not delete historical statistics.
* CockroachDB does not collect statistics for <InternalLink path="computed-columns">virtual computed columns</InternalLink>. This can prevent the <InternalLink path="cost-based-optimizer">optimizer</InternalLink> from accurately calculating the cost of scanning an index on a virtual column, and, transitively, the cost of scanning an <InternalLink path="expression-indexes">expression index</InternalLink>.

### Differences in syntax and behavior between CockroachDB and PostgreSQL

CockroachDB supports the [PostgreSQL wire protocol](https://www.postgresql.org/docs/current/protocol.html) and the majority of its syntax. However, CockroachDB does not support some of the PostgreSQL features or behaves differently from PostgreSQL because not all features can be easily implemented in a distributed system.

For a list of known differences in syntax and behavior between CockroachDB and PostgreSQL, see <InternalLink path="postgresql-compatibility#features-that-differ-from-postgresql">Features that differ from PostgreSQL</InternalLink>.

### Multiple arbiter indexes for `INSERT ON CONFLICT DO UPDATE`

CockroachDB does not currently support multiple arbiter indexes for <InternalLink path="insert#on-conflict-clause">`INSERT ON CONFLICT DO UPDATE`</InternalLink>, and will return an error if there are multiple unique or exclusion constraints matching the `ON CONFLICT DO UPDATE` specification.

### Spatial support limitations

CockroachDB supports efficiently storing and querying <InternalLink path="export-spatial-data">spatial data</InternalLink>, with the following limitations:

* Not all [PostGIS spatial functions](https://postgis.net/docs/reference.html) are supported.

* The `AddGeometryColumn` <InternalLink path="functions-and-operators">spatial function</InternalLink> only allows constant arguments.

* The `AddGeometryColumn` spatial function only allows the `true` value for its `use_typmod` parameter.

* CockroachDB does not support the `@` operator. Instead of using `@` in spatial expressions, we recommend using the inverse, with `~`. For example, instead of `a @ b`, use `b ~ a`.

* CockroachDB does not yet support <InternalLink path="insert">`INSERT`</InternalLink>s into the <InternalLink path="architecture/glossary#spatial-system-tables">`spatial_ref_sys` table</InternalLink>. This limitation also blocks the [`ogr2ogr -f PostgreSQL` file conversion command](https://gdal.org/programs/ogr2ogr.html#cmdoption-ogr2ogr-f).

* CockroachDB does not yet support Triangle or [`TIN`](https://wikipedia.org/wiki/Triangulated_irregular_network) spatial shapes.

* CockroachDB does not yet support Curve, MultiCurve, or CircularString spatial shapes.

* CockroachDB does not yet support [k-nearest neighbors](https://wikipedia.org/wiki/K-nearest_neighbors_algorithm).

* CockroachDB does not support using <InternalLink path="sql-name-resolution#how-name-resolution-works">schema name prefixes</InternalLink> to refer to <InternalLink path="data-types">data types</InternalLink> with type modifiers (e.g., `public.geometry(linestring, 4326)`). Instead, use fully-unqualified names to refer to data types with type modifiers (e.g., `geometry(linestring,4326)`).

  Note that, in <InternalLink version="molt" path="migrate-to-cockroachdb">`IMPORT PGDUMP`</InternalLink> output, <InternalLink path="export-spatial-data">`GEOMETRY` and `GEOGRAPHY`</InternalLink> data type names are prefixed by `public.`. If the type has a type modifier, you must remove the `public.` from the type name in order for the statements to work in CockroachDB.

### Enterprise `BACKUP` does not capture database/table/column comments

The <InternalLink path="comment-on">`COMMENT ON`</InternalLink> statement associates comments to databases, tables, or columns. However, the internal table (`system.comments`) in which these comments are stored is not captured by a <InternalLink path="backup">`BACKUP`</InternalLink> of a table or database.

As a workaround, take a cluster backup instead, as the `system.comments` table is included in cluster backups.

### `SHOW BACKUP` does not support symlinks for nodelocal

<InternalLink path="show-backup">`SHOW BACKUP`</InternalLink> does not support listing backups if the <InternalLink path="cockroach-nodelocal-upload">`nodelocal`</InternalLink> storage location is a symlink.

### DB Console may become inaccessible for secure clusters

Accessing the DB Console for a secure cluster now requires login information (i.e., username and password). This login information is stored in a system table that is replicated like other data in the cluster. If a majority of the nodes with the replicas of the system table data go down, users will be locked out of the DB Console.

### Large index keys can impair performance

The use of tables with very large primary or secondary index keys (>32KB) can result in excessive memory usage. Specifically, if the primary or secondary index key is larger than 32KB the default indexing scheme for <InternalLink path="cockroach-start#storage-engine">storage engine</InternalLink> SSTables breaks down and causes the index to be excessively large. The index is pinned in memory by default for performance.

To work around this issue, we recommend limiting the size of primary and secondary keys to 4KB, which you must account for manually. Note that most columns are 8B (exceptions being `STRING` and `JSON`), which still allows for very complex key structures.

### Using `LIKE...ESCAPE` in `WHERE` and `HAVING` constraints

CockroachDB tries to optimize most comparisons operators in `WHERE` and `HAVING` clauses into constraints on SQL indexes by only accessing selected rows. This is done for `LIKE` clauses when a common prefix for all selected rows can be determined in the search pattern (e.g., `... LIKE 'Joe%'`). However, this optimization is not yet available if the `ESCAPE` keyword is also used.

### Current sequence value not checked when updating min/max value

Altering the minimum or maximum value of a series does not check the current value of a series. This means that it is possible to silently set the maximum to a value less than, or a minimum value greater than, the current value.

### Using `default_int_size` session variable in batch of statements

When setting the `default_int_size` <InternalLink path="set-vars">session variable</InternalLink> in a batch of statements such as `SET default_int_size='int4'; SELECT 1::IN`, the `default_int_size` variable will not take affect until the next statement. This happens because statement parsing takes place asynchronously from statement execution.

As a workaround, set `default_int_size` via your database driver, or ensure that `SET default_int_size` is in its own statement.

### `COPY` syntax not supported by CockroachDB

CockroachDB does not yet support the following `COPY` syntax:

* Various `COPY` options (`FORMAT`, `FREEZE`, `QUOTE`, etc.).
* `COPY ... FROM ... WHERE <expr>`.

### Import with a high amount of disk contention

<InternalLink path="import">`IMPORT`</InternalLink> can sometimes fail with a "context canceled" error, or can restart itself many times without ever finishing. If this is happening, it is likely due to a high amount of disk contention. This can be mitigated by setting the `kv.bulk_io_write.max_rate` <InternalLink path="cluster-settings">cluster setting</InternalLink> to a value below your max disk write speed. For example, to set it to 10MB/s, execute:

```sql theme={"theme":{"light":"catppuccin-mocha","dark":"catppuccin-mocha"}}
> SET CLUSTER SETTING kv.bulk_io_write.max_rate = '10MB';
```

### Placeholders in `PARTITION BY`

When defining a <InternalLink path="partitioning">table partition</InternalLink>, either during table creation or table alteration, it is not possible to use placeholders in the `PARTITION BY` clause.

### Dropping a single partition

CockroachDB does not currently support dropping a single partition from a table. In order to remove partitions, you can <InternalLink path="partitioning#repartition-a-table">repartition</InternalLink> the table.

### Adding a column with sequence-based `DEFAULT` values

It is currently not possible to <InternalLink path="alter-table#add-column">add a column</InternalLink> to a table when the column uses a <InternalLink path="create-sequence">sequence</InternalLink> as the <InternalLink path="default-value">`DEFAULT`</InternalLink> value, for example:

```sql theme={"theme":{"light":"catppuccin-mocha","dark":"catppuccin-mocha"}}
> CREATE TABLE t (x INT);
```

```sql theme={"theme":{"light":"catppuccin-mocha","dark":"catppuccin-mocha"}}
> INSERT INTO t(x) VALUES (1), (2), (3);
```

```sql theme={"theme":{"light":"catppuccin-mocha","dark":"catppuccin-mocha"}}
> CREATE SEQUENCE s;
```

```sql theme={"theme":{"light":"catppuccin-mocha","dark":"catppuccin-mocha"}}
> ALTER TABLE t ADD COLUMN y INT DEFAULT nextval('s');
```

```
ERROR: nextval(): unimplemented: cannot evaluate scalar expressions containing sequence operations in this context
SQLSTATE: 0A000
```

### Available capacity metric in the DB Console

If you are testing your deployment locally with multiple CockroachDB nodes running on a single machine (this is <InternalLink path="recommended-production-settings#topology">not recommended in production</InternalLink>), you must explicitly <InternalLink path="cockroach-start#store">set the store size</InternalLink> per node in order to display the correct capacity. Otherwise, the machine's actual disk capacity will be counted as a separate store for each node, thus inflating the computed capacity.

### Schema changes within transactions

Within a single <InternalLink path="transactions">transaction</InternalLink>:

* You can run schema changes inside the same transaction as a <InternalLink path="create-table">`CREATE TABLE`</InternalLink> statement. For more information, see <InternalLink path="online-schema-changes">Run schema changes inside a transaction with `CREATE TABLE`</InternalLink>. However, a `CREATE TABLE` statement containing <InternalLink path="foreign-key">`FOREIGN KEY`</InternalLink> clauses cannot be followed by statements that reference the new table.
* [Schema change DDL statements inside a multi-statement transaction can fail while other statements succeed](#schema-change-ddl-statements-inside-a-multi-statement-transaction-can-fail-while-other-statements-succeed).
* <InternalLink path="alter-table#drop-column">`DROP COLUMN`</InternalLink> can result in data loss if one of the other schema changes in the transaction fails or is canceled. To work around this, move the `DROP COLUMN` statement to its own explicit transaction or run it in a single statement outside the existing transaction.

<Note>
  If a schema change within a transaction fails, manual intervention may be needed to determine which statement has failed. After determining which schema change(s) failed, you can then retry the schema change.
</Note>

### Schema change DDL statements inside a multi-statement transaction can fail while other statements succeed

Most schema change [DDL](https://wikipedia.org/wiki/Data_definition_language#ALTER_statement) statements that run inside a multi-statement transaction with non-DDL statements can fail at <InternalLink path="commit-transaction">`COMMIT`</InternalLink> time, even if other statements in the transaction succeed. This leaves such transactions in a "partially committed, partially aborted" state that may require manual intervention to determine whether the DDL statements succeeded.

Some DDL statements do not have this limitation. `CREATE TABLE` and `CREATE INDEX` statements have the same atomicity guarantees as other statements within a transaction.

If such a failure occurs, CockroachDB will emit a CockroachDB-specific error code, `XXA00`, and the following error message:

```
transaction committed but schema change aborted with error: <description of error>
HINT: Some of the non-DDL statements may have committed successfully, but some of the DDL statement(s) failed.
Manual inspection may be required to determine the actual state of the database.
```

<Danger>
  If you must execute schema change DDL statements inside a multi-statement transaction, we **strongly recommend** checking for this error code and handling it appropriately every time you execute such transactions.
</Danger>

This error will occur in various scenarios, including but not limited to:

* Creating a unique index fails because values aren't unique.
* The evaluation of a computed value fails.
* Adding a constraint (or a column with a constraint) fails because the constraint is violated for the default/computed values in the column.

To see an example of this error, start by creating the following table.

```sql theme={"theme":{"light":"catppuccin-mocha","dark":"catppuccin-mocha"}}
CREATE TABLE T(x INT);
INSERT INTO T(x) VALUES (1), (2), (3);
```

Then, enter the following multi-statement transaction, which will trigger the error.

```sql theme={"theme":{"light":"catppuccin-mocha","dark":"catppuccin-mocha"}}
BEGIN;
ALTER TABLE t ADD CONSTRAINT unique_x UNIQUE(x);
INSERT INTO T(x) VALUES (3);
COMMIT;
```

```
pq: transaction committed but schema change aborted with error: (23505): duplicate key value (x)=(3) violates unique constraint "unique_x"
HINT: Some of the non-DDL statements may have committed successfully, but some of the DDL statement(s) failed.
Manual inspection may be required to determine the actual state of the database.
```

In this example, the <InternalLink path="insert">`INSERT`</InternalLink> statement committed, but the <InternalLink path="alter-table">`ALTER TABLE`</InternalLink> statement adding a <InternalLink path="unique">`UNIQUE` constraint</InternalLink> failed.  We can verify this by looking at the data in table `t` and seeing that the additional non-unique value `3` was successfully inserted.

```sql theme={"theme":{"light":"catppuccin-mocha","dark":"catppuccin-mocha"}}
SELECT * FROM t;
```

```
  x
+---+
  1
  2
  3
  3
(4 rows)
```

### Schema changes between executions of prepared statements

When the schema of a table targeted by a prepared statement changes after the prepared statement is created, future executions of the prepared statement could result in an error. For example, adding a column to a table referenced in a prepared statement with a `SELECT *` clause will result in an error:

```sql theme={"theme":{"light":"catppuccin-mocha","dark":"catppuccin-mocha"}}
CREATE TABLE users (id INT PRIMARY KEY);
```

```sql theme={"theme":{"light":"catppuccin-mocha","dark":"catppuccin-mocha"}}
PREPARE prep1 AS SELECT * FROM users;
```

```sql theme={"theme":{"light":"catppuccin-mocha","dark":"catppuccin-mocha"}}
ALTER TABLE users ADD COLUMN name STRING;
```

```sql theme={"theme":{"light":"catppuccin-mocha","dark":"catppuccin-mocha"}}
INSERT INTO users VALUES (1, 'Max Roach');
```

```sql theme={"theme":{"light":"catppuccin-mocha","dark":"catppuccin-mocha"}}
EXECUTE prep1;
```

```
ERROR: cached plan must not change result type
SQLSTATE: 0A000
```

It's therefore recommended to explicitly list result columns instead of using `SELECT *` in prepared statements, when possible.

### Size limits on statement input from SQL clients

CockroachDB imposes a hard limit of 16MiB on the data input for a single statement passed to CockroachDB from a client (including the SQL shell). We do not recommend attempting to execute statements from clients with large input.

### Using `\|` to perform a large input in the SQL shell

In the <InternalLink path="cockroach-sql">built-in SQL shell</InternalLink>, using the <InternalLink path="cockroach-sql#commands">`\|`</InternalLink> operator to perform a large number of inputs from a file can cause the server to close the connection. This is because `\|` sends the entire file as a single query to the server, which can exceed the upper bound on the size of a packet the server can accept from any client (16MB).

As a workaround, <InternalLink path="cockroach-sql#execute-sql-statements-from-a-file">execute the file from the command line</InternalLink> with `cat data.sql | cockroach sql` instead of from within the interactive shell.

### New values generated by `DEFAULT` expressions during `ALTER TABLE ADD COLUMN`

When executing an <InternalLink path="alter-table#add-column">`ALTER TABLE ADD COLUMN`</InternalLink> statement with a <InternalLink path="default-value">`DEFAULT`</InternalLink> expression, new values generated:

* use the default <InternalLink path="sql-name-resolution#search-path">search path</InternalLink> regardless of the search path configured in the current session via `SET SEARCH_PATH`.
* use the UTC time zone regardless of the time zone configured in the current session via <InternalLink path="set-vars">`SET TIME ZONE`</InternalLink>.
* have no default database regardless of the default database configured in the current session via <InternalLink path="set-vars">`SET DATABASE`</InternalLink>, so you must specify the database of any tables they reference.
* use the transaction timestamp for the `statement_timestamp()` function regardless of the time at which the `ALTER` statement was issued.

### Load-based lease rebalancing in uneven latency deployments

When nodes are started with the <InternalLink path="cockroach-start#flags">`--locality`</InternalLink> flag, CockroachDB attempts to place the replica lease holder (the replica that client requests are forwarded to) on the node closest to the source of the request. This means as client requests move geographically, so too does the replica lease holder.

However, you might see increased latency caused by a consistently high rate of lease transfers between datacenters in the following case:

* Your cluster runs in datacenters which are very different distances away from each other.
* Each node was started with a single tier of `--locality`, e.g., `--locality=datacenter=a`.
* Most client requests get sent to a single datacenter because that's where all your application traffic is.

To detect if this is happening, open the <InternalLink path="ui-overview">DB Console</InternalLink>, select the **Queues** dashboard, hover over the **Replication Queue** graph, and check the **Leases Transferred / second** data point. If the value is consistently larger than 0, you should consider stopping and restarting each node with additional tiers of locality to improve request latency.

For example, let's say that latency is 10ms from nodes in datacenter A to nodes in datacenter B but is 100ms from nodes in datacenter A to nodes in datacenter C. To ensure A's and B's relative proximity is factored into lease holder rebalancing, you could restart the nodes in datacenter A and B with a common region, `--locality=region=foo,datacenter=a` and `--locality=region=foo,datacenter=b`, while restarting nodes in datacenter C with a different region, `--locality=region=bar,datacenter=c`.

### Overload resolution for collated strings

Many string operations are not properly overloaded for <InternalLink path="collate">collated strings</InternalLink>, for example:

```sql theme={"theme":{"light":"catppuccin-mocha","dark":"catppuccin-mocha"}}
> SELECT 'string1' || 'string2';
```

```
     ?column?
------------------
  string1string2
(1 row)
```

```sql theme={"theme":{"light":"catppuccin-mocha","dark":"catppuccin-mocha"}}
> SELECT ('string1' collate en) || ('string2' collate en);
```

```
pq: unsupported binary operator: <collatedstring{en} || <collatedstring{en}
```

### Max size of a single column family

When creating or updating a row, if the combined size of all values in a single <InternalLink path="column-families">column family</InternalLink> exceeds the <InternalLink path="configure-replication-zones#range-max-bytes">max range size</InternalLink> for the table, the operation may fail, or cluster performance may suffer.

As a workaround, you can either <InternalLink path="column-families#manual-override">manually split a table's columns into multiple column families</InternalLink>, or you can <InternalLink path="configure-replication-zones#create-a-replication-zone-for-a-table">create a table-specific zone configuration</InternalLink> with an increased max range size.

### Simultaneous client connections and running queries on a single node

When a node has both a high number of client connections and running queries, the node may crash due to memory exhaustion. This is due to CockroachDB not accurately limiting the number of clients and queries based on the amount of available RAM on the node.

To prevent memory exhaustion, monitor each node's memory usage and ensure there is some margin between maximum CockroachDB memory usage and available system RAM. For more details about memory usage in CockroachDB, see [this blog post](https://www.cockroachlabs.com/blog/memory-usage-cockroachdb/).

To control the maximum number of non-superuser (<InternalLink path="security-reference/authorization">`root`</InternalLink> user or other <InternalLink path="security-reference/authorization#admin-role">`admin` role</InternalLink>) connections a <InternalLink path="architecture/sql-layer">gateway node</InternalLink> can have open at one time, use the `server.max_connections_per_gateway` <InternalLink path="cluster-settings">cluster setting</InternalLink>.  If a new non-superuser connection would exceed this limit, the error message `"sorry, too many clients already"` is returned, along with error code `53300`. This may be useful in addition to your memory monitoring.

### Privileges for `DELETE` and `UPDATE`

Every <InternalLink path="delete">`DELETE`</InternalLink> or <InternalLink path="update">`UPDATE`</InternalLink> statement constructs a `SELECT` statement, even when no `WHERE` clause is involved. As a result, the user executing `DELETE` or `UPDATE` requires both the `DELETE` and `SELECT` or `UPDATE` and `SELECT` <InternalLink path="security-reference/authorization#managing-privileges">privileges</InternalLink> on the table.

### `ROLLBACK TO SAVEPOINT` in high-priority transactions containing DDL

Transactions with <InternalLink path="transactions#transaction-priorities">priority `HIGH`</InternalLink> that contain DDL and `ROLLBACK TO SAVEPOINT` are not supported, as they could result in a deadlock. For example:

```sql theme={"theme":{"light":"catppuccin-mocha","dark":"catppuccin-mocha"}}
> BEGIN PRIORITY HIGH; SAVEPOINT s; CREATE TABLE t(x INT); ROLLBACK TO SAVEPOINT s;
```

```
ERROR: unimplemented: cannot use ROLLBACK TO SAVEPOINT in a HIGH PRIORITY transaction containing DDL
SQLSTATE: 0A000
HINT: You have attempted to use a feature that is not yet implemented.
```

### CockroachDB does not test for all connection failure scenarios

CockroachDB servers rely on the network to report when a TCP connection fails. In most scenarios when a connection fails, the network immediately reports a connection failure, resulting in a `Connection refused` error.

However, if there is no host at the target IP address, or if a firewall rule blocks traffic to the target address and port, a TCP handshake can linger while the client network stack waits for a TCP packet in response to network requests. To work around this kind of scenario, we recommend the following:

* When migrating a node to a new machine, keep the server listening at the previous IP address until the cluster has completed the migration.
* Configure any active network firewalls to allow node-to-node traffic.
* Verify that orchestration tools (e.g., Kubernetes) are configured to use the correct network connection information.

### Some column-dropping schema changes do not roll back properly

Some <InternalLink path="online-schema-changes">schema changes</InternalLink> that <InternalLink path="alter-table#drop-column">drop columns</InternalLink> cannot be <InternalLink path="rollback-transaction">rolled back</InternalLink> properly.

In some cases, the rollback will succeed, but the column data might be partially or totally missing, or stale due to the asynchronous nature of the schema change.

In other cases, the rollback will fail in such a way that will never be cleaned up properly, leaving the table descriptor in a state where no other schema changes can be run successfully.

To reduce the chance that a column drop will roll back incorrectly:

* Perform column drops in transactions separate from other schema changes. This ensures that other schema change failures will not cause the column drop to be rolled back.

* Drop all <InternalLink path="constraints">constraints</InternalLink> (including <InternalLink path="unique">unique indexes</InternalLink>) on the column in a separate transaction, before dropping the column.

* Drop any <InternalLink path="default-value">default values</InternalLink> or <InternalLink path="computed-columns">computed expressions</InternalLink> on a column before attempting to drop the column. This prevents conflicts between constraints and default/computed values during a column drop rollback.

If you think a rollback of a column-dropping schema change has occurred, check the <InternalLink path="show-jobs">jobs table</InternalLink>. Schema changes with an error prefaced by `cannot be reverted, manual cleanup may be required` might require manual intervention.

### Remove a `UNIQUE` index created as part of `CREATE TABLE`

<InternalLink path="create-index">`UNIQUE` indexes</InternalLink> created as part of a <InternalLink path="create-table">`CREATE TABLE`</InternalLink> statement cannot be removed without using <InternalLink path="drop-index#remove-an-index-and-dependent-objects-with-cascade">         </InternalLink>. Unique indexes created with <InternalLink path="create-index">`CREATE INDEX`</InternalLink> do not have this limitation.

### Secondary regions and regional by row tables

<InternalLink path="multiregion-overview#secondary-regions">Secondary regions</InternalLink> are not compatible with databases containing <InternalLink path="table-localities#regional-by-row-tables">`REGIONAL BY ROW`</InternalLink> tables. CockroachDB does not prevent you from defining secondary regions on databases with regional by row tables, but the interaction of these features is not supported.

Therefore, Cockroach Labs recommends that you avoid defining secondary regions on databases that use regional by row table configurations.

### Row-Level TTL limitations

* Any queries you run against tables with Row-Level TTL enabled do not filter out expired rows from the result set (this includes <InternalLink path="update">`UPDATE`s</InternalLink> and <InternalLink path="delete">`DELETE`s</InternalLink>). This feature may be added in a future release. For now, follow the instructions in <InternalLink path="row-level-ttl#filter-out-expired-rows-from-a-selection-query">Filter out expired rows from a selection query</InternalLink>.
* Enabling Row-Level TTL on a table with multiple <InternalLink path="indexes">secondary indexes</InternalLink> can have negative performance impacts on a cluster, including increased <InternalLink path="common-issues-to-monitor#service-latency">latency</InternalLink> and <InternalLink path="performance-best-practices-overview#transaction-contention">contention</InternalLink>. This is particularly true for large tables with terabytes of data and billions of rows that are split up into multiple ranges across separate nodes.
  * Increased latency may occur because secondary indexes aren't necessarily stored on the same underlying <InternalLink path="architecture/overview">ranges</InternalLink> as a table's <InternalLink path="indexes">primary indexes</InternalLink>. Further, the secondary indexes' ranges may have <InternalLink path="architecture/overview">leaseholders</InternalLink> located on different nodes than the primary index.
  * Increased contention may occur because <InternalLink path="architecture/transaction-layer#write-intents">intents</InternalLink> must be written as part of performing the deletions.
  * Finally, secondary indexes can also have a negative impact on the overall performance of <InternalLink path="row-level-ttl#view-running-ttl-jobs">TTL jobs</InternalLink>. According to internal testing, the <InternalLink path="ui-ttl-dashboard#processing-rate">TTL job processing rate</InternalLink> is worse on tables with secondary indexes. If you encounter this situation, decreasing the <InternalLink path="row-level-ttl">`ttl_delete_batch_size` storage parameter</InternalLink> may help by decreasing the number of ranges that need to be accessed by the job.

### Change data capture limitations

Change data capture (CDC) provides efficient, distributed, row-level changefeeds into Apache Kafka for downstream processing such as reporting, caching, or full-text indexing. It has the following known limitations:

* Changefeed target options are limited to tables and <InternalLink path="changefeeds-on-tables-with-column-families">column families</InternalLink>.
* <InternalLink version="cockroachcloud" path="network-authorization">VPC Peering</InternalLink> and <InternalLink version="cockroachcloud" path="network-authorization">AWS PrivateLink</InternalLink> in CockroachDB Advanced clusters do **not** support connecting to a <InternalLink path="changefeed-sinks#kafka">Kafka</InternalLink> sink's internal IP addresses for <InternalLink path="change-data-capture-overview">changefeeds</InternalLink>. To connect to a Kafka sink from CockroachDB Advanced, it is necessary to expose the Kafka cluster's external IP address and open ports with firewall rules to allow access from a CockroachDB Advanced cluster.
* Webhook sinks only support HTTPS. Use the <InternalLink path="create-changefeed">`insecure_tls_skip_verify`</InternalLink> parameter when testing to disable certificate verification; however, this still requires HTTPS and certificates.
* Formats for changefeed messages are not supported by all changefeed sinks. Refer to the <InternalLink path="changefeed-sinks">Changefeed Sinks</InternalLink> page for details on compatible formats with each sink and the <InternalLink path="create-changefeed">`format`</InternalLink> option to specify a changefeed message format.
* Using the <InternalLink path="create-changefeed">`split_column_families`</InternalLink> and <InternalLink path="create-changefeed">`resolved`</InternalLink> options on the same changefeed will cause an error when using the following <InternalLink path="changefeed-sinks">sinks</InternalLink>: Kafka and Google Cloud Pub/Sub. Instead, use the individual `FAMILY` keyword to specify column families when creating a changefeed.
* Changefeed types are not fully integrated with <InternalLink path="create-type">user-defined composite types</InternalLink>. Running changefeeds with user-defined composite types is in <InternalLink version="releases" path="cockroachdb-feature-availability#feature-availability-phases">Preview</InternalLink>. Certain changefeed types do not support user-defined composite types. Refer to the change data capture <InternalLink path="create-and-configure-changefeeds#known-limitations">Known Limitations</InternalLink> for more detail. The following limitations apply:
  * A changefeed in <InternalLink path="changefeed-messages#avro">Avro format</InternalLink> will not be able to serialize <InternalLink path="create-type">user-defined composite (tuple) types</InternalLink>.
  * A changefeed emitting <InternalLink path="changefeed-messages#csv">CSV</InternalLink> will include `AS` labels in the message format when the changefeed serializes a <InternalLink path="create-type">user-defined composite type</InternalLink>.
* You can only apply CDC queries on a single table in each statement.
* Some <InternalLink path="functions-and-operators#built-in-functions">stable functions</InternalLink>, notably functions that return MVCC timestamps, are overridden to return the MVCC timestamp of the event, e.g., `transaction_timestamp` or `statement_timestamp`. Additionally, some <InternalLink path="functions-and-operators">time-based functions</InternalLink>, such as `now()` are not supported. We recommend using the `transaction_timestamp()` function or the  `crdb_internal_mvcc_timestamp` <InternalLink path="cdc-queries#crdb-internal-mvcc-timestamp">`crdb_internal_mvcc_timestamp`</InternalLink>  column instead.
* The following are not permitted in CDC queries:
  * <InternalLink path="functions-and-operators#function-volatility">Volatile functions</InternalLink>.
  * Sub-select queries.
  * <InternalLink path="functions-and-operators#aggregate-functions">Aggregate</InternalLink> and <InternalLink path="window-functions">window functions</InternalLink> (i.e., functions operating over many rows).
* `delete` changefeed events will only contain the <InternalLink path="primary-key">primary key</InternalLink>. All other columns will emit as `NULL`. See <InternalLink path="cdc-queries#capture-delete-messages">Capture delete messages</InternalLink> for detail on running a CDC query that emits the deleted values.
* `ALTER CHANGEFEED`  <InternalLink path="alter-changefeed">`ALTER CHANGEFEED`</InternalLink>  is not fully supported with changefeeds that use  CDC queries.  <InternalLink path="cdc-queries#crdb-internal-mvcc-timestamp">CDC queries</InternalLink>.  You can alter the options that a changefeed uses, but you cannot alter the changefeed target tables.
* Creating a changefeed with  CDC queries  <InternalLink path="cdc-queries#crdb-internal-mvcc-timestamp">CDC queries</InternalLink>  on tables with more than one  column family  <InternalLink path="changefeeds-on-tables-with-column-families">column family</InternalLink>  is not supported.
* When you create a changefeed on a table with more than one  column family  <InternalLink path="changefeeds-on-tables-with-column-families">column family</InternalLink> , the changefeed will emit messages per column family in separate streams. As a result, <InternalLink path="changefeed-messages">changefeed messages</InternalLink> for different column families will arrive at the <InternalLink path="changefeed-sinks">sink</InternalLink> under separate topics.
