๐Ÿชณ

CockroachDB

CockroachDB: SQL, transactions, multi-region, changefeeds, cluster operations, and tuning

Connection & CLI

Connect with cockroach sql and manage the cluster CLI

bashยทConnect with cockroach sql
# Connect to a local insecure node
cockroach sql --insecure

# Connect to a local secure node
cockroach sql --certs-dir=certs

# Connect to a remote cluster
cockroach sql --url "postgresql://user:pass@host:26257/defaultdb?sslmode=verify-full"

# Connect with individual flags
cockroach sql \
  --host=10.0.0.1 \
  --port=26257 \
  --user=myuser \
  --database=mydb \
  --certs-dir=certs

# Execute a SQL statement directly
cockroach sql --insecure -e "SHOW DATABASES"
cockroach sql --url "$COCKROACH_URL" -e "SELECT version()"

# Execute a SQL file
cockroach sql --insecure -f schema.sql
cockroach sql --url "$COCKROACH_URL" -f migration.sql

# Connect via standard PostgreSQL drivers (CockroachDB is wire-compatible)
psql "postgresql://user:pass@host:26257/mydb?sslmode=verify-full"
bashยทcockroach CLI essentials
# Start a single-node dev cluster (insecure)
cockroach start-single-node --insecure --listen-addr=localhost:26257 --http-addr=localhost:8080

# Start a secure single-node cluster
cockroach start-single-node --certs-dir=certs --listen-addr=localhost:26257

# Start a multi-node cluster node
cockroach start \
  --insecure \
  --store=node1 \
  --listen-addr=localhost:26257 \
  --http-addr=localhost:8080 \
  --join=localhost:26257,localhost:26258,localhost:26259

# Init a new cluster (run once after all nodes start)
cockroach init --insecure --host=localhost:26257

# Create a user
cockroach sql --insecure -e "CREATE USER alice WITH PASSWORD 'secret'"

# Create TLS certs (secure mode)
cockroach cert create-ca --certs-dir=certs --ca-key=certs/ca.key
cockroach cert create-node localhost $(hostname) --certs-dir=certs --ca-key=certs/ca.key
cockroach cert create-client root --certs-dir=certs --ca-key=certs/ca.key

Databases & Schemas

Create and manage databases, schemas, and users

sqlยทDatabases & schemas
-- List databases
SHOW DATABASES;

-- Create a database
CREATE DATABASE mydb;
CREATE DATABASE IF NOT EXISTS mydb;

-- Switch database
USE mydb;
SET database = mydb;

-- Drop database
DROP DATABASE mydb;
DROP DATABASE IF EXISTS mydb CASCADE;  -- also drops all tables

-- Schemas (CockroachDB supports PostgreSQL-style schemas)
CREATE SCHEMA mydb.analytics;
SHOW SCHEMAS FROM mydb;
SET search_path = analytics, public;

-- Current database / schema
SELECT current_database(), current_schema();
sqlยทUsers, roles & grants
-- Create users
CREATE USER alice WITH PASSWORD 'secret';
CREATE USER bob;   -- no password (cert-based auth)

-- Create a role
CREATE ROLE readonly;
CREATE ROLE readwrite;

-- Grant privileges
GRANT SELECT ON DATABASE mydb TO readonly;
GRANT SELECT, INSERT, UPDATE, DELETE ON TABLE mydb.public.orders TO readwrite;
GRANT ALL ON DATABASE mydb TO alice;
GRANT readonly TO alice;          -- assign role to user

-- Grant schema usage
GRANT USAGE ON SCHEMA analytics TO alice;

-- Show grants
SHOW GRANTS ON DATABASE mydb;
SHOW GRANTS ON TABLE orders;
SHOW GRANTS FOR alice;

-- Revoke
REVOKE SELECT ON TABLE orders FROM readonly;

-- Drop user / role
DROP USER alice;
DROP ROLE readonly;

DDL โ€” Tables & Indexes

Create, alter, and drop tables and indexes

sqlยทTables
-- Basic table (UUID primary key โ€” recommended)
CREATE TABLE users (
  id         UUID        DEFAULT gen_random_uuid() PRIMARY KEY,
  email      STRING      NOT NULL UNIQUE,
  name       STRING      NOT NULL,
  age        INT,
  active     BOOL        DEFAULT true,
  created_at TIMESTAMPTZ DEFAULT now()
);

-- Composite primary key
CREATE TABLE order_items (
  order_id   UUID NOT NULL,
  product_id UUID NOT NULL,
  quantity   INT  NOT NULL,
  price      DECIMAL(10,2),
  PRIMARY KEY (order_id, product_id)
);

-- With foreign key
CREATE TABLE orders (
  id          UUID        DEFAULT gen_random_uuid() PRIMARY KEY,
  user_id     UUID        NOT NULL REFERENCES users(id) ON DELETE CASCADE,
  total       DECIMAL(10,2),
  status      STRING      DEFAULT 'pending',
  created_at  TIMESTAMPTZ DEFAULT now()
);

-- Show table schema
SHOW CREATE TABLE users;

-- List tables
SHOW TABLES FROM mydb;

-- Drop table
DROP TABLE users;
DROP TABLE IF EXISTS users CASCADE;
sqlยทALTER TABLE
-- Add column
ALTER TABLE users ADD COLUMN phone STRING;
ALTER TABLE users ADD COLUMN score FLOAT DEFAULT 0.0 NOT NULL;

-- Drop column
ALTER TABLE users DROP COLUMN phone;

-- Rename column
ALTER TABLE users RENAME COLUMN name TO full_name;

-- Rename table
ALTER TABLE users RENAME TO app_users;

-- Add / drop constraint
ALTER TABLE orders ADD CONSTRAINT fk_user FOREIGN KEY (user_id) REFERENCES users(id);
ALTER TABLE orders DROP CONSTRAINT fk_user;

-- Set / drop default
ALTER TABLE users ALTER COLUMN active SET DEFAULT false;
ALTER TABLE users ALTER COLUMN active DROP DEFAULT;

-- Set NOT NULL
ALTER TABLE users ALTER COLUMN email SET NOT NULL;
ALTER TABLE users ALTER COLUMN email DROP NOT NULL;

-- Change column type (CockroachDB is cautious โ€” compatible types only)
ALTER TABLE users ALTER COLUMN age TYPE BIGINT;
sqlยทIndexes
-- Single-column index
CREATE INDEX ON users (email);
CREATE INDEX users_email_idx ON users (email);

-- Multi-column index
CREATE INDEX ON orders (user_id, created_at DESC);

-- Unique index
CREATE UNIQUE INDEX ON users (email);

-- Partial index (only index rows matching condition)
CREATE INDEX ON orders (user_id) WHERE status = 'pending';

-- Inverted index (for JSONB / arrays)
CREATE INVERTED INDEX ON products (attributes);

-- Storing index (include extra columns to avoid table lookup)
CREATE INDEX ON orders (user_id) STORING (total, status);

-- Show indexes on a table
SHOW INDEXES FROM orders;

-- Drop index
DROP INDEX users_email_idx;
DROP INDEX IF EXISTS orders@orders_user_id_idx;

DML โ€” CRUD

Insert, select, update, upsert, and delete

sqlยทINSERT & UPSERT
-- Basic insert
INSERT INTO users (email, name, age) VALUES ('alice@example.com', 'Alice', 30);

-- Multiple rows
INSERT INTO users (email, name) VALUES
  ('bob@example.com', 'Bob'),
  ('carol@example.com', 'Carol');

-- Insert with auto-generated UUID
INSERT INTO users (id, email, name)
VALUES (gen_random_uuid(), 'dave@example.com', 'Dave');

-- Return inserted row
INSERT INTO users (email, name) VALUES ('eve@example.com', 'Eve')
RETURNING id, created_at;

-- UPSERT (insert or replace on PK/unique conflict)
UPSERT INTO users (id, email, name) VALUES ('some-uuid', 'alice@example.com', 'Alice Updated');

-- INSERT ON CONFLICT (PostgreSQL-style)
INSERT INTO users (email, name) VALUES ('alice@example.com', 'Alice')
ON CONFLICT (email) DO UPDATE SET name = excluded.name;

-- Insert and do nothing on conflict
INSERT INTO users (email, name) VALUES ('alice@example.com', 'Alice')
ON CONFLICT (email) DO NOTHING;
sqlยทSELECT
-- Basic select
SELECT * FROM users WHERE active = true;
SELECT id, name, email FROM users WHERE age > 25 ORDER BY name LIMIT 10;

-- Keyset pagination (efficient โ€” avoids OFFSET on large tables)
SELECT id, name FROM users
WHERE id > 'last-seen-uuid'
ORDER BY id
LIMIT 20;

-- Aggregation
SELECT status, COUNT(*), SUM(total), AVG(total)
FROM orders
GROUP BY status
HAVING COUNT(*) > 5;

-- JOIN
SELECT u.name, o.id, o.total
FROM users u
JOIN orders o ON u.id = o.user_id
WHERE o.status = 'shipped'
ORDER BY o.created_at DESC;

-- CTE
WITH recent_orders AS (
  SELECT user_id, COUNT(*) AS cnt
  FROM orders
  WHERE created_at > now() - INTERVAL '30 days'
  GROUP BY user_id
)
SELECT u.name, r.cnt
FROM users u
JOIN recent_orders r ON u.id = r.user_id
ORDER BY r.cnt DESC;

-- JSONB
SELECT id, attributes->>'color' AS color
FROM products
WHERE attributes @> '{"in_stock": true}';
sqlยทUPDATE & DELETE
-- Update rows
UPDATE users SET active = false WHERE last_login < now() - INTERVAL '90 days';

-- Update with RETURNING
UPDATE orders SET status = 'shipped' WHERE id = 'some-uuid' RETURNING id, status;

-- Update with JOIN (using FROM)
UPDATE orders o
SET status = 'vip_shipped'
FROM users u
WHERE o.user_id = u.id AND u.tier = 'vip' AND o.status = 'pending';

-- Delete rows
DELETE FROM sessions WHERE expires_at < now();

-- Delete with RETURNING
DELETE FROM orders WHERE id = 'some-uuid' RETURNING *;

-- Truncate (fast delete all rows)
TRUNCATE TABLE sessions;
TRUNCATE TABLE sessions, cache;  -- multiple tables

Transactions

ACID transactions, savepoints, and retry handling

sqlยทTransaction basics
-- Basic transaction
BEGIN;
  UPDATE accounts SET balance = balance - 100 WHERE id = 'a1';
  UPDATE accounts SET balance = balance + 100 WHERE id = 'a2';
COMMIT;

-- Rollback on error
BEGIN;
  INSERT INTO orders (id, user_id, total) VALUES (gen_random_uuid(), 'u1', 99.99);
  -- something fails...
ROLLBACK;

-- Isolation levels (CockroachDB default: SERIALIZABLE)
BEGIN ISOLATION LEVEL SERIALIZABLE;
BEGIN ISOLATION LEVEL READ COMMITTED;  -- available in v23.1+

-- Read-only transaction (optimized โ€” no write intents)
BEGIN READ ONLY;
  SELECT * FROM products WHERE category = 'electronics';
COMMIT;

-- Savepoints
BEGIN;
  INSERT INTO users (email, name) VALUES ('x@example.com', 'X');
  SAVEPOINT my_savepoint;
  INSERT INTO orders (user_id, total) VALUES ('some-uuid', 50);
  -- roll back only to savepoint
  ROLLBACK TO SAVEPOINT my_savepoint;
  -- users insert is still active
COMMIT;
sqlยทClient-side retry (recommended pattern)
-- CockroachDB may return a 40001 serialization error.
-- The recommended pattern is to retry the entire transaction.

-- Cockroach-specific: use SAVEPOINT cockroach_restart for automatic retries
BEGIN;
  SAVEPOINT cockroach_restart;
  -- execute statements...
  UPDATE accounts SET balance = balance - 100 WHERE id = 'a1';
  UPDATE accounts SET balance = balance + 100 WHERE id = 'a2';
  RELEASE SAVEPOINT cockroach_restart;
COMMIT;

-- If a 40001 error occurs, retry with:
ROLLBACK TO SAVEPOINT cockroach_restart;
-- then re-execute statements and RELEASE + COMMIT again

-- Show transaction priority (to reduce contention)
BEGIN PRIORITY HIGH;
BEGIN PRIORITY LOW;
BEGIN PRIORITY NORMAL;  -- default

Multi-Region

Survive zone/region failures and optimise data locality

sqlยทCluster regions & database locality
-- Show regions available in the cluster
SHOW REGIONS;
SHOW REGIONS FROM CLUSTER;

-- Show regions for a database
SHOW REGIONS FROM DATABASE mydb;

-- Add regions to a database
ALTER DATABASE mydb ADD REGION "us-east1";
ALTER DATABASE mydb ADD REGION "eu-west1";
ALTER DATABASE mydb ADD REGION "ap-southeast1";

-- Set the primary region
ALTER DATABASE mydb SET PRIMARY REGION "us-east1";

-- Drop a region
ALTER DATABASE mydb DROP REGION "ap-southeast1";

-- Survive zone failure (default)
ALTER DATABASE mydb SURVIVE ZONE FAILURE;

-- Survive region failure (requires 3+ regions)
ALTER DATABASE mydb SURVIVE REGION FAILURE;
sqlยทTable locality
-- REGIONAL BY TABLE โ€” whole table pinned to primary region (default)
ALTER TABLE orders SET LOCALITY REGIONAL BY TABLE IN PRIMARY REGION;
ALTER TABLE orders SET LOCALITY REGIONAL BY TABLE IN "us-east1";

-- REGIONAL BY ROW โ€” each row stored near its home region
--   requires a hidden or explicit crdb_region column
ALTER TABLE users SET LOCALITY REGIONAL BY ROW;

-- With explicit region column
ALTER TABLE users ADD COLUMN region crdb_internal_region
  NOT VISIBLE DEFAULT default_to_database_primary_region(gateway_region())::crdb_internal_region;
ALTER TABLE users SET LOCALITY REGIONAL BY ROW AS region;

-- Insert into a specific region
INSERT INTO users (email, name, region)
VALUES ('alice@example.com', 'Alice', 'us-east1');

-- GLOBAL โ€” low-latency reads everywhere (replicated to all regions)
-- Best for reference tables (currencies, countries, config)
ALTER TABLE currencies SET LOCALITY GLOBAL;

-- Show table locality
SHOW CREATE TABLE users;
SHOW LOCALITY OF TABLE users;

Changefeeds (CDC)

Stream row-level changes to Kafka, GCS, S3, and webhooks

sqlยทCreate & manage changefeeds
-- Core changefeed (prints to SQL client โ€” for testing)
CREATE CHANGEFEED FOR TABLE orders;

-- Enterprise changefeed to Kafka
CREATE CHANGEFEED FOR TABLE orders, users
INTO 'kafka://broker:9092'
WITH updated, resolved = '10s', key_in_value;

-- Changefeed to GCS
CREATE CHANGEFEED FOR TABLE orders
INTO 'gs://my-bucket/crdb-feed?AUTH=implicit'
WITH updated, resolved = '30s', format = csv;

-- Changefeed to S3
CREATE CHANGEFEED FOR TABLE orders
INTO 's3://my-bucket/feed?AWS_ACCESS_KEY_ID=xxx&AWS_SECRET_ACCESS_KEY=yyy'
WITH updated, resolved, format = json;

-- Changefeed to webhook
CREATE CHANGEFEED FOR TABLE orders
INTO 'webhook-https://my-service.example.com/events'
WITH updated, format = json;

-- Show all changefeeds
SHOW CHANGEFEED JOBS;
SHOW JOBS (SELECT job_id FROM [SHOW CHANGEFEED JOBS]);

-- Pause / resume / cancel
PAUSE JOB <job-id>;
RESUME JOB <job-id>;
CANCEL JOB <job-id>;

Backup & Restore

Full, incremental, and point-in-time backup and restore

sqlยทBackup
-- Full cluster backup to S3
BACKUP INTO 's3://my-bucket/backups?AWS_ACCESS_KEY_ID=xxx&AWS_SECRET_ACCESS_KEY=yyy';

-- Full database backup
BACKUP DATABASE mydb INTO 's3://my-bucket/mydb-backup';

-- Full table backup
BACKUP TABLE mydb.orders INTO 's3://my-bucket/orders-backup';

-- Incremental backup (appended to an existing full backup)
BACKUP INTO LATEST IN 's3://my-bucket/backups';

-- Scheduled backup (full weekly, incremental daily)
CREATE SCHEDULE daily_backup
FOR BACKUP INTO 's3://my-bucket/backups'
  RECURRING '@daily'
  FULL BACKUP '@weekly'
  WITH SCHEDULE OPTIONS first_run = 'now';

-- Show backup schedules
SHOW SCHEDULES;
SHOW SCHEDULE <schedule-id>;

-- Pause / resume a schedule
PAUSE SCHEDULE <schedule-id>;
RESUME SCHEDULE <schedule-id>;
sqlยทRestore
-- Restore full cluster
RESTORE FROM LATEST IN 's3://my-bucket/backups';

-- Restore a database
RESTORE DATABASE mydb FROM LATEST IN 's3://my-bucket/backups';

-- Restore a table (into existing database)
RESTORE TABLE mydb.orders FROM LATEST IN 's3://my-bucket/backups';

-- Restore to a new table name
RESTORE TABLE mydb.orders FROM LATEST IN 's3://my-bucket/backups'
WITH into_db = 'mydb_restore';

-- Point-in-time restore (as-of-system-time)
RESTORE DATABASE mydb FROM 's3://my-bucket/backups'
AS OF SYSTEM TIME '2024-06-15 12:00:00';

-- Show restore jobs
SHOW JOBS WHERE job_type = 'RESTORE';

Query Tuning

EXPLAIN, optimizer hints, and session variables

sqlยทEXPLAIN & EXPLAIN ANALYZE
-- Show query plan (logical)
EXPLAIN SELECT * FROM orders WHERE user_id = 'u1';

-- Verbose plan with estimated row counts and costs
EXPLAIN (VERBOSE) SELECT * FROM orders WHERE user_id = 'u1';

-- Show plan as JSON
EXPLAIN (FORMAT JSON) SELECT * FROM orders o JOIN users u ON o.user_id = u.id;

-- EXPLAIN ANALYZE โ€” run query and show actual stats
EXPLAIN ANALYZE SELECT * FROM orders WHERE user_id = 'u1';

-- EXPLAIN ANALYZE with full plan details
EXPLAIN ANALYZE (VERBOSE) SELECT * FROM orders WHERE user_id = 'u1';

-- Debug plan (most detail, slowest)
EXPLAIN ANALYZE (DEBUG) SELECT * FROM orders WHERE user_id = 'u1';

-- Show table statistics (used by optimizer)
SHOW STATISTICS FOR TABLE orders;

-- Manually update table statistics
ANALYZE orders;
ANALYZE;   -- all tables
sqlยทOptimizer hints & session settings
-- Force an index
SELECT * FROM orders@orders_user_id_idx WHERE user_id = 'u1';

-- Disable index (force table scan)
SELECT * FROM orders@{NO_INDEX_JOIN} WHERE status = 'pending';

-- Force a join algorithm
SELECT /*+ HASH JOIN */ * FROM orders o JOIN users u ON o.user_id = u.id;
SELECT /*+ LOOKUP JOIN */ * FROM orders o JOIN users u ON o.user_id = u.id;
SELECT /*+ MERGE JOIN */ * FROM orders o JOIN users u ON o.user_id = u.id;

-- AS OF SYSTEM TIME โ€” read historical data (no contention)
SELECT * FROM orders AS OF SYSTEM TIME '-10s';
SELECT * FROM orders AS OF SYSTEM TIME '2024-06-15 12:00:00';
SELECT * FROM orders AS OF SYSTEM TIME follower_read_timestamp();

-- Session variables
SET statement_timeout = '30s';
SET idle_in_transaction_session_timeout = '60s';
SHOW statement_timeout;

-- Vectorized execution (default on)
SET vectorize = on;
SET vectorize = off;

Cluster Operations

Node management, cluster settings, and jobs

sqlยทNodes & cluster settings
-- Show all nodes
SHOW NODES;

-- Show node locality and status
SELECT node_id, address, locality, is_available, is_live FROM crdb_internal.gossip_nodes;

-- Show cluster settings
SHOW CLUSTER SETTING kv.rangefeed.enabled;
SHOW ALL CLUSTER SETTINGS;

-- Change a cluster setting
SET CLUSTER SETTING kv.rangefeed.enabled = true;
SET CLUSTER SETTING server.time_until_store_dead = '5m';
SET CLUSTER SETTING sql.defaults.default_int_size = 8;

-- Show active sessions
SHOW SESSIONS;
SHOW LOCAL SESSIONS;

-- Kill a session
CANCEL SESSION '<session-id>';

-- Show active queries
SHOW QUERIES;
CANCEL QUERY '<query-id>';

-- Show all jobs (backups, imports, schema changes)
SHOW JOBS;
SHOW AUTOMATIC JOBS;
bashยทcockroach node & debug CLI
# List nodes
cockroach node ls --insecure --host=localhost:26257

# Show node status
cockroach node status --insecure --host=localhost:26257

# Decommission a node (drain + remove from cluster)
cockroach node decommission <node-id> --insecure --host=localhost:26257

# Recommission a node
cockroach node recommission <node-id> --insecure --host=localhost:26257

# Drain a node (for graceful shutdown)
cockroach node drain <node-id> --insecure --host=localhost:26257

# Dump a database schema
cockroach dump mydb --insecure --host=localhost:26257 --dump-mode=schema > schema.sql

# Dump data
cockroach dump mydb --insecure --host=localhost:26257 --dump-mode=data > data.sql

# Debug zip (collect diagnostics)
cockroach debug zip ./debug.zip --insecure --host=localhost:26257

Import & Export

Bulk import from CSV/Postgres and export data

sqlยทIMPORT & EXPORT
-- Import CSV from S3 into a new table
IMPORT INTO users (id, email, name)
CSV DATA ('s3://my-bucket/users.csv?AWS_ACCESS_KEY_ID=xxx&AWS_SECRET_ACCESS_KEY=yyy')
WITH skip = '1',   -- skip header row
     nullif = '';

-- Import CSV from a nodelocal file
IMPORT INTO users (id, email, name)
CSV DATA ('nodelocal://1/users.csv')
WITH skip = '1';

-- Import a PostgreSQL dump
IMPORT PGDUMP 's3://my-bucket/dump.sql'
WITH ignore_unsupported_statements;

-- Import a table from a pgdump
IMPORT TABLE users FROM PGDUMP 's3://my-bucket/dump.sql';

-- Export to CSV on S3
EXPORT INTO CSV 's3://my-bucket/export?AWS_ACCESS_KEY_ID=xxx&AWS_SECRET_ACCESS_KEY=yyy'
FROM TABLE orders;

-- Export a query result
EXPORT INTO CSV 's3://my-bucket/pending-orders'
FROM SELECT * FROM orders WHERE status = 'pending';

-- Export to Parquet
EXPORT INTO PARQUET 's3://my-bucket/orders-parquet' FROM TABLE orders;

Useful Patterns

UUID keys, follower reads, schema changes, and contention tips

sqlยทUUID keys & sequences
-- Preferred: gen_random_uuid() โ€” avoids hotspots from sequential IDs
CREATE TABLE events (
  id         UUID DEFAULT gen_random_uuid() PRIMARY KEY,
  user_id    UUID NOT NULL,
  event_type STRING NOT NULL,
  created_at TIMESTAMPTZ DEFAULT now()
);

-- Also available: uuid_generate_v4()
INSERT INTO events (id, user_id, event_type) VALUES (gen_random_uuid(), 'u1', 'click');

-- Sequence (use UUID or hash-sharded keys instead for high-write tables)
CREATE SEQUENCE order_num_seq START 1000 INCREMENT 1;
SELECT nextval('order_num_seq');

-- Hash-sharded index โ€” avoids write hotspots on sequential keys
CREATE TABLE logs (
  id         INT DEFAULT unique_rowid() PRIMARY KEY,
  message    STRING,
  created_at TIMESTAMPTZ DEFAULT now()
) WITH (bucket_count = 8);

CREATE INDEX ON logs (created_at) USING HASH WITH (bucket_count = 8);
sqlยทFollower reads & contention
-- Follower reads (stale, ~4.8s behind, served from nearest replica โ€” zero contention)
SELECT * FROM orders AS OF SYSTEM TIME follower_read_timestamp()
WHERE user_id = 'u1';

-- Bounded staleness (CockroachDB 21.2+)
SELECT * FROM orders AS OF SYSTEM TIME with_max_staleness('10s')
WHERE user_id = 'u1';

-- Exact staleness
SELECT * FROM orders AS OF SYSTEM TIME '-30s';

-- Detect contention
SELECT * FROM crdb_internal.cluster_contention_events LIMIT 20;

-- Find hot ranges
SELECT range_id, lease_holder, queries_per_second
FROM crdb_internal.ranges
ORDER BY queries_per_second DESC
LIMIT 10;

-- Online schema changes (non-blocking โ€” CockroachDB runs these async)
-- Safe to run without locking the table:
ALTER TABLE users ADD COLUMN IF NOT EXISTS preferences JSONB;
CREATE INDEX CONCURRENTLY ON orders (status);

-- Show schema change jobs
SHOW JOBS WHERE job_type = 'SCHEMA CHANGE';