关于 ysql
ysql 是面向 YugabyteDB YSQL 开发与运维的最佳实践 Skill,适用于设计分布式 PostgreSQL 兼容数据库的工程师。内容涵盖驱动连接、事务重试、分片、索引、查询优化和迁移评估,并附参考资料与示例。
主要功能
- 01提供多种语言的驱动连接示例
- 02说明事务重试模式
- 03指导主键、分片与表共置设计
- 04涵盖索引设计和查询诊断
- 05提供 Voyager 迁移评估参考
适用场景
- 01为 YSQL 应用选择连接与负载均衡方式
- 02设计分布式表的主键和分片策略
- 03排查查询计划与索引效率问题
- 04评估 Voyager 数据库迁移和模式转换
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ yugabyte/ yugabytedb-skills --skill ysql安装后在 Claude Code 中输入/ysql确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/bnl-joins.mdquery-optimization.mdretry-patterns.mdsmart-drivers.mdvoyager.md详细介绍
YugabyteDB YSQL 最佳实践
此技能包含:
references/smart-drivers.md— 全部 9 种智能驱动的连接示例:Python(psycopg3、psycopg2)、Java(JDBC、R2DBC)、Go、Node.js、C#、Rust、Rubyreferences/retry-patterns.md— Python 和 Java 中的事务重试代码references/bnl-joins.md— 批量嵌套循环连接的设计影响、GUC 默认值及诊断方法references/query-optimization.md—timestamp与now()阻碍索引下推的完整示例references/voyager.md— YugabyteDB Voyager 迁移评估与架构转换审查
YugabyteDB 是一种分布式、兼容 PostgreSQL 的数据库(YSQL 使用 5433 端口),符合 ACID、高可用、水平可扩展,并支持表和索引的 哈希/范围分片。每项设计选择都应在读取效率、写入扩展性和运维成本之间取得平衡。
连接 URI 示例:postgresql://yugabyte:yugabyte@localhost:5433/yugabyte
重要提示:仅使用一个连接端点并不能保证负载均衡。尽可能使用驱动程序原生的拓扑/负载均衡功能,并在需要时结合基础设施负载均衡(例如 CSP/Kubernetes/Istio)。
架构设计
SQL 代码段展示的是不同的架构选择,而非按顺序执行的一组迁移操作。索引示例假设所列的表和列已存在;哈希分片示例使用非共置表。
主键策略
如果自然主键稳定且分布均匀,优先使用自然主键。代理键通常有助于旧系统集成和互操作,但它并非总是分布式系统中最佳的主查找键结构。
对于范围分片的访问路径,应避免使用单调递增的前导键值(序列、时间戳、UUIDv7、按字典序递增的文本 ID)。
方案 1:哈希 + 范围结构的自然键 — 适用于领域已有稳定键的情况:
CREATE TABLE order_lines (
order_id UUID,
line_id INT,
sku TEXT,
qty INT,
PRIMARY KEY ((order_id) HASH, line_id ASC)
);
这样,同一订单的所有行会保持在一起,同时不同订单会均匀分布。
方案 2:UUID 代理键 — 适用于无法使用自然键的情况:
CREATE TABLE orders (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY,
customer_id UUID NOT NULL,
total DECIMAL(12,2),
created_at TIMESTAMPTZ DEFAULT now()
);
UUIDv4 风格的随机性可使写入均匀分布。
方案 3:使用 HASH 分片的 IDENTITY — 适用于必须使用连续 ID 的情况:
CREATE TABLE tickets (
id BIGINT GENERATED ALWAYS AS IDENTITY,
title TEXT,
PRIMARY KEY (id HASH)
);
此警告专门针对使用单个哈希租户键的情况(例如,PRIMARY KEY ((tenant_id) HASH, ...)),尤其是在租户规模差异很大时。对于大型租户工作负载,请使用复合哈希键(例如,PRIMARY KEY ((tenant_id, order_id) HASH, ...)),这样能更好地分散负载。
决策指南: 首选分布均匀的自然键;其次使用 UUID;需要数字型、由序列生成的 ID 时,使用 IDENTITY + HASH。序列缓存和并发事务意味着这些 ID 既不会无间隔,也不会按提交时间排序。
分片:哈希与范围
与 PostgreSQL 相比,主要的架构差异在于主键和索引采用哈希分片还是范围分片(HASH / ASC / DESC)。
范围分片(ASC / DESC):
- 保留键顺序,支持范围扫描和
ORDER BY ... LIMIT - 单调插入(
NOW()、序列、UUIDv7、递增文本 ID)会追加到最新的范围 tablet,造成热点分片和 tablet 大小不均
-- Hash PK distributes table writes; plain ASC timestamp index can still hotspot on monotonic inserts
CREATE TABLE sensor_events (
event_id UUID DEFAULT gen_random_uuid(),
sensor_id UUID NOT NULL,
event_ts TIMESTAMPTZ NOT NULL,
payload JSONB,
PRIMARY KEY (event_id HASH)
);
CREATE INDEX idx_sensor_events_ts ON sensor_events (event_ts ASC);
重要提示:对表进行哈希分片并不能自动避免二级索引中的热点。单调递增的索引键(例如 event_ts ASC/DESC)仍可能使写入集中在最新的索引范围。
哈希分片(HASH):
- 将不同的哈希键分布到各个 tablet;对单个哈希键的流量倾斜仍可能造成热点
- 高效支持点查询和针对同一哈希值的多行查找
- 不保留哈希键上的排序顺序
- 如果二级索引的前导键是单调递增的,仍可能造成热点
- 同时需要分散写入和有序检索时,请使用哈希 + 范围组合
-- Hash distributes writes; range key preserves per-player order
CREATE TABLE player_transactions (
player_id UUID,
created_at TIMESTAMPTZ,
txn_id UUID DEFAULT gen_random_uuid(),
payload JSONB,
PRIMARY KEY ((player_id) HASH, created_at DESC, txn_id ASC)
);
平板数量管理
尽量减少平板总数,以降低开销:
- 对于平均负载,默认采用数据库的初始平板数量;省略
SPLIT INTO并不一定会创建单个平板 - 仅在初始平板数量会显著影响初期性能的高写入量/高吞吐量工作负载中,才预先拆分(
SPLIT INTO) - YugabyteDB 会随着数据增长自动拆分;初期设置宜保守,并在监控数据表明确有必要时再扩展
- 条件允许时,将访问频率低的小型表进行共置
共置
完全共置的数据库适用于存储量和写入工作负载都能由单个平板承载的数据集。共置指南 将 50 GB 以下的数据库列为典型用例,而非容量保证。共置平板不会自动拆分;对于大型表或写入密集型表,应停用共置。
CREATE DATABASE myapp WITH colocation = true;
-- Tables share a colocation tablet by default, reducing distributed reads
-- Opt out large/high-throughput tables: WITH (colocation = false)
创建热点表时,设置 WITH (colocation = false)。将现有表移出共置需要创建替代表并迁移数据,同时规划切换;这不是原地更改存储选项。重新创建表时,请保留约束、索引、权限和依赖项。
索引设计
在 YugabyteDB 中,二级索引扫描可能需要额外的存储请求来获取基表列。这些请求可能跨节点,且可以批量获取多行;远程 RPC 不一定与行数一一对应。请根据实际查询工作负载设计索引,并测量存储请求:
-- PG15-based YSQL: prioritize by total execution time
SELECT queryid, calls, total_exec_time, mean_exec_time
FROM pg_stat_statements
ORDER BY total_exec_time DESC
LIMIT 20;
-- Then validate plan quality and rows scanned
EXPLAIN (ANALYZE, DIST, COSTS) SELECT ...; -- check for Seq Scan, Storage Read Requests, Storage Rows Scanned
较旧的、基于 PG11 的 YSQL 会公开 total_time 和 mean_time。调整监控查询前,请先检查已安装的视图(\d pg_stat_statements);有关目标版本,请参阅查询统计参考。
覆盖索引 — 在适用时,使用 INCLUDE 添加查询需要且索引中尚未包含的列,使 Index Only Scan 无需获取基表数据:
-- Fetching device_type requires base-table access
CREATE INDEX idx_sessions_account_seen ON sessions (account_id, seen_at DESC);
SELECT account_id, seen_at, device_type
FROM sessions
WHERE account_id = $1
ORDER BY seen_at DESC
LIMIT 50;
-- GOOD for read-heavy query: Index Only Scan candidate
CREATE INDEX idx_sessions_account_seen_cover
ON sessions (account_id, seen_at DESC) INCLUDE (device_type);
权衡:对频繁更新的列建立索引会增加写放大。优先为读取密集、更新较少的列建立覆盖索引。
复合索引 — 用于多列查询。列的顺序很重要:等值列在前,范围列在后:
-- Better distributed example for YugabyteDB
CREATE INDEX idx_player_txn_player_created
ON player_transactions (player_id HASH, created_at DESC);
-- Uses index:
-- WHERE player_id = $1 AND created_at > now() - interval '7 days'
-- WHERE player_id = $1
-- Does not use index efficiently:
-- WHERE created_at > now() - interval '7 days' -- missing equality on player_id
-- Covering variant:
CREATE INDEX idx_player_txn_cover
ON player_transactions (player_id HASH, created_at DESC) INCLUDE (payload);
为查询建立索引,而不是为列建立索引。避免猜测性地建立实际查询模式从未使用的单列索引。
部分索引 — 只为查询实际需要的行建立索引。索引越小,存储开销越低,写入和扫描速度越快:
-- Only active users (soft-delete pattern)
CREATE INDEX idx_users_active_email ON users (email) WHERE deleted_at IS NULL;
-- Only pending orders (completed orders rarely queried by status)
CREATE INDEX idx_orders_pending ON orders (created_at DESC) WHERE status = 'pending';
-- Only non-null values (high null_frac columns)
CREATE INDEX idx_orders_shipped ON orders (shipped_at) WHERE shipped_at IS NOT NULL;
-- Check null fraction to decide: SELECT attname, null_frac FROM pg_stats WHERE tablename = 'orders';
外键索引 — 当父表更新或删除,或需要查找子表时,为引用列(子表列)建立索引。如果没有合适的现有索引,检查或级联删除父表记录时可能会扫描子表。兼容的现有复合索引可能已经能满足此用途:
CREATE INDEX idx_orders_customer ON orders (customer_id);
索引开销 — 维护索引会增加存储写入,也可能增加 RPC。不要凭猜测添加索引;请通过 pg_stat_statements 和 EXPLAIN (ANALYZE, DIST) 确认查询确实需要索引。共享首列并不意味着两个索引冗余:删除其中一个之前,请比较完整键、哈希/范围布局、排序顺序、谓词、包含列,以及唯一性或约束依赖关系。
索引排序与可扩展性
如何在扩展单调递增的 ASC/DESC 键插入时保留有序读取? 使用低基数桶前缀:
CREATE INDEX idx_events_bucket_ts
ON events ((yb_hash_code(timestamp) % 3) ASC, timestamp ASC)
SPLIT AT VALUES ((1), (2));
桶设计建议:
- 根据预期写入吞吐量确定初始桶数(而不仅是节点数);必要时增加桶数
- 对于唯一索引/PK 设计,
yb_hash_code(...)的输入应为唯一键列的子集 SPLIT AT VALUES是可选项,但建议使用,以获得可预测的初始分布- 将桶表达式放在索引键首位
- 按时间戳分桶会分散不同的时间戳值;多个行共享同一时间戳时,仍会落入同一个桶
- 全局有序读取需要合并多个桶的数据。自动合并扫描取决于版本及其规划器设置(
yb_max_merge_scan_streams,以前称为yb_max_saop_merge_streams,以及与其同期在 v2025.2 系列中引入的派生谓词设置yb_enable_derived_saops和yb_enable_derived_equalities);请检查EXPLAIN中的合并流字段,不要想当然地认为查询计划无需排序。请参阅基于桶的索引和目标版本的发行说明,了解设置方法
地理分布
表空间 — 将数据固定到区域: 集群必须包含满足每个放置块中云、区域和可用区标签要求的 tserver。
CREATE TABLESPACE us_east_ts WITH (
replica_placement = '{"num_replicas": 3, "placement_blocks": [
{"cloud": "aws", "region": "us-east-1", "zone": "us-east-1a", "min_num_replicas": 1},
{"cloud": "aws", "region": "us-east-1", "zone": "us-east-1b", "min_num_replicas": 1},
{"cloud": "aws", "region": "us-east-1", "zone": "us-east-1c", "min_num_replicas": 1}
]}'
);
CREATE TABLESPACE eu_west_ts WITH (
replica_placement = '{"num_replicas": 3, "placement_blocks": [
{"cloud": "aws", "region": "eu-west-1", "zone": "eu-west-1a", "min_num_replicas": 1},
{"cloud": "aws", "region": "eu-west-1", "zone": "eu-west-1b", "min_num_replicas": 1},
{"cloud": "aws", "region": "eu-west-1", "zone": "eu-west-1c", "min_num_replicas": 1}
]}'
);
行级地理分区:
CREATE TABLE orders (
id UUID DEFAULT gen_random_uuid(),
region TEXT DEFAULT yb_server_region(),
customer_id UUID,
PRIMARY KEY (id, region)
) PARTITION BY LIST (region);
CREATE TABLE orders_us PARTITION OF orders FOR VALUES IN ('us-east-1') TABLESPACE us_east_ts;
CREATE TABLE orders_eu PARTITION OF orders FOR VALUES IN ('eu-west-1') TABLESPACE eu_west_ts;
yb_server_region() 使用所连接 tserver 所在的区域。如果数据放置应遵循客户所在区域,请显式提供 region;向没有分区的区域插入数据会失败。在查询条件中包含区域,以便进行分区裁剪。还要在相应区域的表空间中为每个分区创建二级索引;仅将基表放在区域表空间中,并不会将其二级索引也放在那里。请参阅地理分区。
分区
如果源系统分区仅用于管理数据量,可以考虑使用更宽的分区,或不使用分区 — YugabyteDB 会自然地对关系进行分片。仅在地理分布或基于时间的数据生命周期管理(分离并删除旧分区,而不是使用 DELETE)时使用分区。
尽量减少分区数量。过细的分区(例如,保留多年的日分区)会带来不必要的 tablet 开销。
如果分区对工作负载的价值有限,请保持数据模型简单。
数据类型指南
- JSONB: 仅用于真正需要动态架构的场景。对于频繁访问的数据,常规列的性能优于 JSONB。
行和列大小指南
- 行大小: 为保证延迟稳定,建议小于 10 MB;32 MB 是文档规定的最大值,并非延迟目标。请参阅数据建模限制
- 列大小: 每列以不超过 2 MB 为目标
应用模式
智能驱动程序(客户端负载均衡)
当应用能够访问发现的地址时,使用智能驱动程序发现 tserver 并进行客户端连接均衡。启用负载均衡;需要按可用区路由时,添加拓扑键。这两类参数名称因驱动程序而异——请使用对应驱动程序的准确参数组合:
| 驱动程序 | 启用负载均衡 | 拓扑键 |
|---|---|---|
| psycopg2、pgx、rust-postgres、ruby-pg | load_balance=true | topology_keys |
| psycopg3 | load_balance_hosts=true | topology_keys |
| JDBC | load-balance=true | topology-keys |
| R2DBC | loadBalanceHosts=true | topologyKeys |
| node-postgres | loadBalance: true | topologyKeys |
| Npgsql | Load Balance Hosts=true | Topology Keys |
**psycopg3 拓扑键会将应用连接限制在偏好级别相同的部署位置;不支持优先级后缀,这些连接也不会回退到集群范围。**引导和发现用的控制连接可以连接到其他集群节点。需要保证可用区可用时,请允许多个可用区(或某个区域的 * 可用区);如果没有允许的 tserver 处于活动状态,连接就会失败。省略拓扑键即可进行集群范围的均衡。对于 rust-postgres,只有在严格要求部署位置时才启用 fallback_to_topology_keys_only=true;如有需要,请允许多个可用区。配置任一策略前,请阅读驱动程序示例和故障处理指南。
**Python 驱动程序选择:**使用代码库中已有的驱动程序。psycopg3 → psycopg-yugabytedb(导入名称仍为 psycopg)。psycopg2 → psycopg2-yugabytedb。新代码不要默认使用 psycopg2——psycopg3 是当前驱动程序。
**安装 Python 智能驱动程序前,请先检查环境。**将 psycopg-yugabytedb 与上游的 psycopg、psycopg-binary 和 psycopg-c 分开;此分支使用系统 libpq。对于 psycopg2,请选择一个 YugabyteDB 变体,不要同时安装上游 psycopg2 软件包。如果 langchain_postgres 等依赖项要求使用上游 psycopg3,请保留可正常工作的环境并说明选择:将工作负载拆分到不同的环境或进程中,或显式迁移到上游行为。更改软件包或连接设置前,请阅读软件包选择和迁移步骤。
**全部 9 种智能驱动程序的连接示例(Python psycopg3/psycopg2、Java JDBC/R2DBC、Go、Node.js、C#、Rust、Ruby):**请参阅 references/smart-drivers.md
除 psycopg3 外,其他驱动程序的拓扑键格式为 cloud.region.zone[:priority](1=主选,2=备用)。请使用集群的实际部署标签。JDBC、pgx 和 node-postgres 的文档说明可使用可用区通配符(例如 aws.us-east.*:1)进行区域范围路由;使用通配符或优先级语法前,请查阅所选驱动程序链接的文档。对于 R2DBC 和 Npgsql,除非已在与所选软件包版本对应的源代码中确认支持通配符,否则请使用明确指定的可用区,具体说明见驱动程序参考。psycopg3 也支持可用区通配符,但不支持优先级后缀(aws.us-east.*);云和区域通配符会被拒绝。请参阅其拓扑参考。
并发控制:冲突时等待
YSQL 在 v2024.1 及更高版本中默认采用冲突时等待(enable_wait_queues=true):冲突事务会等待,但在阻塞事务提交不兼容的更改后,它们仍可能中止。启用等待队列时,死锁检测也会启用,除非明确将其禁用。请在较旧版本或经过自定义配置的部署中检查这些标志。
对于争用严重的工作负载,请将 wait_queue_poll_interval_ms 保持在其文档所述的默认值附近;增大该值可能会加剧尾延迟。降低该值前请先进行基准测试,因为更频繁的轮询会增加开销。请参阅并发控制。
事务重试
在写入并发场景中,40001(序列化失败)和 40P01(死锁)仍属正常情况。务必在客户端实现重试,并使用回滚、指数退避、随机抖动和有界重试次数(通常为 3-10 次)。设计写入路径时应确保幂等性。
**Python 和 Java 的重试代码:**请参阅 references/retry-patterns.md
超时
根据应用的延迟预算以及合理的查询/事务持续时间,选择 statement_timeout 和 idle_in_transaction_session_timeout。以下 30s/60s 是示例,并非最低值。尽可能在连接/会话级别应用;数据库级默认值会影响更多工作负载。
# psycopg3 (psycopg-yugabytedb)
import psycopg
conn = psycopg.connect("...",
options="-c statement_timeout=30000 -c idle_in_transaction_session_timeout=60000")
# psycopg2 alternative (psycopg2-yugabytedb) - use one driver, not both:
# import psycopg2
# conn = psycopg2.connect("...",
# options="-c statement_timeout=30000 -c idle_in_transaction_session_timeout=60000")
预备语句
对查询值使用参数绑定。若要复用服务端计划,请配置驱动程序的协议级预备语句;仅进行参数化并不能保证复用计划。YSQL Connection Manager 支持协议级预备语句,且不会像 SQL PREPARE/EXECUTE 那样导致连接粘滞。使用 PgBouncer 时,请先确认已部署版本、连接池模式和配置支持预备语句,再启用驱动程序的自动预备功能。请参阅 Connection Manager 最佳实践。
批处理操作
尽可能使用批处理/微批处理。多行 INSERT 和 INSERT ON CONFLICT 通常比单行操作性能更好,因为它们减少了网络往返:
INSERT INTO orders (id, customer_id, total) VALUES
(gen_random_uuid(), $1, $2),
(gen_random_uuid(), $3, $4),
(gen_random_uuid(), $5, $6);
使用 COPY 批量加载
COPY orders FROM '/path/data.csv' WITH (FORMAT csv, HEADER, ROWS_PER_TRANSACTION 20000);
- 对于符合条件的普通表,如果 COPY 独立运行(不在显式事务或多命令批处理中),
ROWS_PER_TRANSACTION(默认值为 20000)用于控制提交批次大小。设置ROWS_PER_TRANSACTION 1000时,如果在第 3500 行发生错误,则第 1–3000 行已提交;正在处理的批次会回滚。在显式事务中,或对于不受支持的表/触发器配置,此选项会被忽略并发出警告。依赖部分提交前,请检查驱动程序的事务模式和服务端警告。 - 文件名指向已连接的 YB-TServer。对于客户端本地文件,请在 ysqlsh 中使用客户端侧的
\copy,或使用驱动程序的 COPY API。 DISABLE_FK_CHECK— 仅当已验证引用完整性时,才跳过外键检查。SKIP n— 通过跳过已加载的行,在部分失败后继续加载。- 对于共置表,
SET yb_fast_path_for_colocated_copy = on;仅在 COPY 不处于显式事务中运行、未指定ROWS_PER_TRANSACTION,且目标表没有触发器、规则或外键时启用快速路径。其原子批次大小遵循ysql_session_max_batch_size,包括索引写入。请参阅 COPY 选项和快速路径要求。
大批量删除时使用 TRUNCATE
DELETE 会写入墓碑,增加压缩工作量。仅当其锁定、外键和事务行为符合操作需求时,才考虑使用 TRUNCATE 进行完整清理。YSQL TRUNCATE 文档提醒,不要在多步事务中或与读写操作并发时使用;需要这些语义时,请使用事务性 DELETE。
对于时序数据:ALTER TABLE events DETACH PARTITION old_partition; DROP TABLE old_partition;
序列缓存
CREATE SEQUENCE order_seq CACHE 100;
标识列使用隐式序列。除非禁用该最小值,否则有效分配缓存至少为 ysql_sequence_cache_minval(默认值为 100);更大的序列 CACHE 值优先。根据工作负载调整缓存大小以及连接级与服务端缓存,并预期回滚或缓存值丢失后会出现间隙。请参阅序列缓存。
EXPLAIN (ANALYZE, DIST)
EXPLAIN (ANALYZE, DIST, COSTS) SELECT * FROM orders WHERE customer_id = $1;
关键指标:Storage Read Requests(RPC 数量)、Storage Rows Scanned 和扫描类型。
返回行数较少时,Index Scan 通常没有问题。对于较大的投影,当索引包含所选列时,Index Only Scan 可减少资源使用并降低延迟。
联接策略:Nested Loop 与 Batched Nested Loop (BNL)
YugabyteDB 支持标准 PostgreSQL 联接方法(Nested Loop、Hash Join、Merge Join),以及一种 YB 专用方法:Batched Nested Loop Join (BNL)。它会将对内表的查找批量合并为 = ANY(ARRAY[...]) 调用,而不是对每个外表行执行一次 RPC。
**设计影响、GUC 默认值,以及诊断本应进行批处理却未进行的计划:**请参阅 references/bnl-joins.md
长时间运行的读取快照
对于需要一致读取且不希望产生争用的批处理任务:
BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE READ ONLY DEFERRABLE;
-- long-running queries here
COMMIT;
获取快照时可能需要等待配置的最大时钟偏差(默认值为 500ms)。这样可以避免读取重启错误,但会增加启动延迟。下文的跟随者读取是另一种选择,适用于可容忍陈旧结果的工作负载。请参阅读取重启缓解措施。
跟随者读取(低延迟陈旧读取)
允许只读查询使用附近的副本;如果跟随者滞后或不可用,查询可能会回退到其他副本或主节点:
SET yb_read_from_followers = true;
SET yb_follower_read_staleness_ms = 30000; -- 30s; check clock-skew constraints before reducing
SET SESSION CHARACTERISTICS AS TRANSACTION READ ONLY;
SELECT * FROM analytics WHERE region = 'us-east'; -- eligible for follower reads
- 必须处于 READ ONLY 事务中——在读写事务中,跟随者读取会被静默忽略。
- 即使从主节点读取,也会应用陈旧度;所有读取都会陈旧
yb_follower_read_staleness_ms指定的时长。 - 降低陈旧度时,除了满足文档建议的心跳要求,还要满足所部署版本的时钟偏差约束。2025.2 事务管理器要求
yb_follower_read_staleness_ms * 1000 > 2 * max_clock_skew_usec:最大时钟偏差默认值为 500,000 微秒,因此应使用严格大于 1000ms 的值。 - 适用于不要求亚秒级数据新鲜度的仪表板、分析和只读副本。
- 这些会话设置也会影响后续事务;将连接归还连接池前,请重置这些设置。跟随者读取本身无法让多个 READ COMMITTED 语句使用同一个稳定快照。请参阅跟随者读取行为和隔离级别交互。
yb_hash_code
使用 yb_hash_code 对大型 READ/WRITE 操作进行分块:
SELECT * FROM large_table WHERE yb_hash_code(id) BETWEEN 0 AND 5000;
SELECT * FROM large_table WHERE yb_hash_code(id) BETWEEN 5001 AND 10000;
按键顺序将所有 HASH 键列传递给 yb_hash_code,以便高效执行分区边界扫描。这两个范围只是部分示例:完整作业必须覆盖 0 到 65535,且不能有间隙或重叠。对于按范围分片的表,应按键边界分块;评估已部署版本的并行查询支持,并使用 EXPLAIN 确认实际执行计划。
Advisory Locks
v2025.1 及更高版本默认提供并启用 Advisory Locks;请确认已部署版本的 advisory-lock 标志。它们使用 PostgreSQL 的 advisory-lock 函数。YugabyteDB 特定注意事项:
- 锁通过
pg_advisory_locks系统表全局分布(而非共享内存)——在所有节点上均可见。 - **需要会话粘性:**会话级锁与后端连接绑定。使用智能驱动程序或连接池时,请确保会话始终使用同一连接。
- 对于使用事务池的工作,优先使用事务级 advisory locks;会话级锁需要保留后端,直到解锁。请参阅显式锁定。
PostgreSQL 迁移策略
逐步应用以下四个级别。从级别 1 开始——只有工作负载需要时才进入下一级。
Level 1:直接迁移(始终执行)
- 替换不受支持的功能(请参阅 PG 功能认知表)
- 根据访问模式选择哈希分片或范围分片;写入密集型路径应避免将单调递增的键作为范围键前导列
- 添加缺失的索引——在分布式环境中,缺少索引造成的性能损失比单节点环境更大
- 对比键、排序、谓词、包含列和约束依赖项后,删除冗余索引。
- 对于大多为 null 的列,如果查询谓词蕴含索引谓词,可考虑使用部分索引。
Level 2:优化读取延迟
- 对重要查询,可考虑使用覆盖索引(键 +
INCLUDE),同时权衡索引宽度和写入成本。 - 如果自然主键紧凑、稳定且符合常见查找模式,可考虑使用自然主键。
- 覆盖索引的键或包含列发生变化时,需要额外维护工作;请权衡读取收益和写入开销。
Level 3:线性写入扩展能力
- 对时间戳列应用取模分桶索引(请参阅上文的索引排序和扩展能力)
- **ORDER BY LIMIT 注意事项:**取模分桶索引不会生成全局有序结果。请合并每个分桶的输出:
SELECT * FROM (
(SELECT * FROM events WHERE (yb_hash_code(timestamp) % 3) = 0 ORDER BY timestamp DESC LIMIT 10)
UNION ALL
(SELECT * FROM events WHERE (yb_hash_code(timestamp) % 3) = 1 ORDER BY timestamp DESC LIMIT 10)
UNION ALL
(SELECT * FROM events WHERE (yb_hash_code(timestamp) % 3) = 2 ORDER BY timestamp DESC LIMIT 10)
) sub ORDER BY timestamp DESC LIMIT 10;
基于分桶的扫描优化依赖版本,并在 v2025.2 系列中作为预览/早期访问功能引入。对于 v2025.2.3 及更高版本,请使用有代表性的查询评估这些会话设置;较早的 yb_max_saop_merge_streams 名称已弃用,现由 yb_max_merge_scan_streams 取代。启用这些设置前,请检查已部署的补丁版本和基于分桶的索引文档。yb_max_merge_scan_streams、yb_enable_derived_saops 和 yb_enable_derived_equalities 仅在 v2025.2 系列及更高版本中定义;在更早版本中,每条 SET 都会因 unrecognized configuration parameter 而失败,因此代码块会在遇到第一个缺失设置时中止,而不会降级处理。作为代码块运行前,请确认已部署的版本。
ANALYZE events;
SET yb_max_merge_scan_streams = 64;
SET yb_enable_derived_saops = true;
SET yb_enable_derived_equalities = true;
SET yb_enable_cbo = on;
对于符合条件的执行计划,优化器会合并分桶流,因此不需要手动使用 UNION ALL。请在 EXPLAIN 中检查 Merge Sort Key、Merge Stream Key 和 Merge Streams;不要假设所有查询都符合条件。验证工作负载后,再应用数据库级默认设置。如果需要在时间戳相同的情况下确保结果具有确定性,请为每个排序添加唯一的决胜列。
第 4 级:端到端优化
- 使用
pg_stat_statements衡量查询级工作负载,并使用表统计信息了解每张表的活动情况,以便平衡索引。 - 使用 CTE 减少客户端与服务端之间的往返,并将逻辑下推到数据库中(注意:CTE 可能成为优化屏障,阻止下推——请使用
EXPLAIN测试) - 对非等值查找,在需要时同时使用哈希分片和范围分片
应用设计
使用具有代表性的查询开销、网络延迟和读写比例,对应用的并发能力进行负载测试。逐步增加正在处理的工作量,直到吞吐量不再提升或延迟超出目标;线程数并非数据库核心数的通用倍数,尤其是在使用异步客户端或并行查询时。结合监控数据库 CPU、连接池等待时间和 p99 延迟。请参阅连接与集群规格。
PG 功能须知
| PG 功能 | YugabyteDB | 替代方案 |
|---|---|---|
SERIAL / BIGSERIAL 主键,或使用纯范围分片的 PRIMARY KEY (timestamp, ...) | 单调递增的值会追加到最新的范围 tablet,导致写入热点 | 使用 PRIMARY KEY (... HASH),或为范围分片主键采用非单调递增的自然键模式 |
CREATE UNLOGGED TABLE | 当前版本的 YSQL 接受 UNLOGGED,但对分布式表会忽略该属性;较早版本可能会拒绝 | 使用常规复制表;清理时选择 TRUNCATE 或事务性 DELETE,如上所述 |
EXCLUDE USING gist(...),或 GiST/SP-GiST 索引计划 | YSQL 不支持 GiST/SP-GiST | 使用受支持的约束重新设计,或串行化冲突的验证/写入;仅在应用或触发器中采用先检查再插入的方式,可能会发生竞态 |
| 仅使用 BRIN 的索引策略 | 不支持 BRIN | 根据查询模式使用 B-tree(以及部分索引/复合索引) |
xmin、xmax、ctid | 分布式 YSQL 表不支持 PostgreSQL 堆/MVCC 系统列行为 | 使用显式的 version INT 列进行乐观锁定 |
MERGE INTO | 文档所述的 YSQL 兼容性限制中不支持 | 对 upsert 使用 INSERT ... ON CONFLICT ... DO UPDATE SET;对其他 MERGE 分支使用显式事务逻辑 |
PREPARE TRANSACTION | 尚未实现 | 使用 Saga 或 outbox 模式 |
在 YugabyteDB 中,使用 version INT 进行乐观锁定是可行的。在同一条写入语句中比较预期版本并递增版本,然后检查是否更新了一行。使用驱动程序的占位符语法绑定预期值(JDBC 使用 ?,psycopg 使用 %s)。
请对照目标版本检查迁移兼容性限制;其中一些条目描述的是较早版本,并注明了“Fixed In”版本。
对于纬度/经度搜索模式,可使用 SQL 进行宽泛的预筛选/范围扫描,并在需要时于中间层执行精确的地理距离检查。
生产环境检查清单
- DDL 安全性: 使用单个连接执行架构变更,并留出足够时间等待目录传播;除非你的版本或配置明确启用了事务性 DDL,否则不要依赖它。
- 表级锁定: 当迁移或运维流程需要时,启用/使用显式的表级锁定模式。
- DDL 执行模式: 如果你的 YugabyteDB 版本支持,请评估并发 DDL 和事务性 DDL 功能,以更安全地在线变更架构。
- TLS: 使用
sslmode=verify-full,并通过sslrootcert指定受信任的 CA;需要客户端证书身份验证时,添加sslcert/sslkey。 - 可观测性: 记录重试次数、延迟和 SQLSTATE。对已知的瞬时故障(
40001、40P01)进行重试;语法/访问规则(42xxx)和完整性(23xxx)错误通常需要修正操作,而不是盲目重试。监控 tablet leader 的分布。 - 乐观锁定: 使用
version INT列,而非系统列(xmin/ctid) - 连接池: Yugabyte 的容量规划指南将每个 vCPU 配置 15 个服务端连接作为基准上限,而非吞吐量目标。对于延迟敏感型工作负载,应通过负载测试确定较低的并发量,并考虑内存占用。服务端连接池可以通过较少的后端连接复用大量客户端连接。
- 连接回收: 添加节点不会迁移现有连接。配置连接池的生命周期/空闲回收设置(例如,HikariCP 的
maxLifetime和idleTimeout),以便新连接能够使用新发现的节点。 - YSQL Connection Manager: 内置的服务端连接池;请确认其已配置的监听端口和客户端限制。会话功能的连接池成本各不相同:TEMP 表和 SQL
PREPARE会使连接保持固定;协议级预备语句则可避免这种固定。请查看设置和限制,并搭配支持拓扑感知连接路由的智能驱动程序使用。 - DDL 迁移: 迁移工具(Flyway、Active Record)应使用单个数据库连接。分布式缓存中的 DDL 传播可能会导致并发连接出现约束冲突。
- 扩展: 检查已部署版本的捆绑扩展和模块及其设置要求。示例包括
pg_stat_statements、pgcrypto、pgvector(SQL 名称vector)、pg_cron、pg_partman、postgres_fdw、pgaudit、pg_trgm、pg_hint_plan、uuid-ossp和hstore。SQL 扩展通常需要执行CREATE EXTENSION;有些扩展还需要预加载/配置。auto_explain是可加载模块,而非CREATE EXTENSION的目标。
查询优化
timestamp(不带时区)与 now(): now() 的类型是 timestamptz;将其与 timestamp 列比较会触发隐式转换,从而阻止索引条件下推(表现为较高的 Rows Removed by Index Recheck / Storage Index Rows Scanned ≫ 返回行数)。解决方法是将列定义为 timestamptz(首选),或在谓词中进行类型转换(now()::timestamp - interval …)。有效的 EXPLAIN (ANALYZE, DIST) 示例:references/query-optimization.md.