Loading...
Loading...
Compare original and translation side by side
undefinedundefinedundefinedundefineda) Index Usage
- Are WHERE columns indexed?
- Are JOIN columns indexed?
- Is the index type appropriate (B-tree, GIN, BRIN)?
b) Query Plan Analysis
- Run EXPLAIN ANALYZE on complex queries
- Check for Seq Scans on large tables
- Verify row estimates match actuals
c) Common Issues
- N+1 query patterns
- Missing composite indexes
- Wrong column order in indexesa) 索引使用
- WHERE条件列是否已创建索引?
- JOIN关联列是否已创建索引?
- 索引类型是否合适(B-tree、GIN、BRIN)?
b) 查询计划分析
- 对复杂查询执行EXPLAIN ANALYZE
- 检查大表是否存在全表扫描
- 验证行估计值与实际值是否匹配
c) 常见问题
- N+1查询模式
- 缺失复合索引
- 索引中列的顺序错误a) Data Types
- bigint for IDs (not int)
- text for strings (not varchar(n) unless constraint needed)
- timestamptz for timestamps (not timestamp)
- numeric for money (not float)
- boolean for flags (not varchar)
b) Constraints
- Primary keys defined
- Foreign keys with proper ON DELETE
- NOT NULL where appropriate
- CHECK constraints for validation
c) Naming
- lowercase_snake_case (avoid quoted identifiers)
- Consistent naming patternsa) 数据类型
- 使用bigint存储ID(而非int)
- 使用text存储字符串(除非需要约束,否则避免varchar(n))
- 使用timestamptz存储时间戳(而非timestamp)
- 使用numeric存储金额(而非float)
- 使用boolean存储标识位(而非varchar)
b) 约束设置
- 已定义主键
- 外键配置了正确的ON DELETE规则
- 必要字段设置NOT NULL
- 使用CHECK约束做数据校验
c) 命名规范
- 使用lowercase_snake_case(避免带引号的标识符)
- 保持命名模式一致a) Row Level Security
- RLS enabled on multi-tenant tables?
- Policies use (select auth.uid()) pattern?
- RLS columns indexed?
b) Permissions
- Least privilege principle followed?
- No GRANT ALL to application users?
- Public schema permissions revoked?
c) Data Protection
- Sensitive data encrypted?
- PII access logged?a) 行级安全(RLS)
- 多租户表是否启用了RLS?
- 策略是否使用(select auth.uid())模式?
- RLS相关列是否已创建索引?
b) 权限控制
- 是否遵循最小权限原则?
- 未给应用用户授予GRANT ALL权限?
- 是否已撤销public schema的默认权限?
c) 数据保护
- 敏感数据是否已加密?
- PII数据访问是否已记录?-- ❌ BAD: No index on foreign key
CREATE TABLE orders (
id bigint PRIMARY KEY,
customer_id bigint REFERENCES customers(id)
-- Missing index!
);
-- ✅ GOOD: Index on foreign key
CREATE TABLE orders (
id bigint PRIMARY KEY,
customer_id bigint REFERENCES customers(id)
);
CREATE INDEX orders_customer_id_idx ON orders (customer_id);-- ❌ 不良实践:外键未创建索引
CREATE TABLE orders (
id bigint PRIMARY KEY,
customer_id bigint REFERENCES customers(id)
-- 缺失索引!
);
-- ✅ 最佳实践:为外键创建索引
CREATE TABLE orders (
id bigint PRIMARY KEY,
customer_id bigint REFERENCES customers(id)
);
CREATE INDEX orders_customer_id_idx ON orders (customer_id);| Index Type | Use Case | Operators |
|---|---|---|
| B-tree (default) | Equality, range | |
| GIN | Arrays, JSONB, full-text | |
| BRIN | Large time-series tables | Range queries on sorted data |
| Hash | Equality only | |
-- ❌ BAD: B-tree for JSONB containment
CREATE INDEX products_attrs_idx ON products (attributes);
SELECT * FROM products WHERE attributes @> '{"color": "red"}';
-- ✅ GOOD: GIN for JSONB
CREATE INDEX products_attrs_idx ON products USING gin (attributes);| 索引类型 | 使用场景 | 支持操作符 |
|---|---|---|
| B-tree(默认) | 等值、范围查询 | |
| GIN | 数组、JSONB、全文检索 | |
| BRIN | 大型时间序列表 | 排序数据的范围查询 |
| Hash | 仅等值查询 | |
-- ❌ 不良实践:用B-tree索引JSONB包含查询
CREATE INDEX products_attrs_idx ON products (attributes);
SELECT * FROM products WHERE attributes @> '{"color": "red"}';
-- ✅ 最佳实践:用GIN索引JSONB
CREATE INDEX products_attrs_idx ON products USING gin (attributes);-- ❌ BAD: Separate indexes
CREATE INDEX orders_status_idx ON orders (status);
CREATE INDEX orders_created_idx ON orders (created_at);
-- ✅ GOOD: Composite index (equality columns first, then range)
CREATE INDEX orders_status_created_idx ON orders (status, created_at);(status, created_at)WHERE status = 'pending'WHERE status = 'pending' AND created_at > '2024-01-01'WHERE created_at > '2024-01-01'-- ❌ 不良实践:创建单独的索引
CREATE INDEX orders_status_idx ON orders (status);
CREATE INDEX orders_created_idx ON orders (created_at);
-- ✅ 最佳实践:复合索引(等值列在前,范围列在后)
CREATE INDEX orders_status_created_idx ON orders (status, created_at);(status, created_at)WHERE status = 'pending'WHERE status = 'pending' AND created_at > '2024-01-01'WHERE created_at > '2024-01-01'-- ❌ BAD: Must fetch name from table
CREATE INDEX users_email_idx ON users (email);
SELECT email, name FROM users WHERE email = 'user@example.com';
-- ✅ GOOD: All columns in index
CREATE INDEX users_email_idx ON users (email) INCLUDE (name, created_at);-- ❌ 不良实践:需要从表中读取name字段
CREATE INDEX users_email_idx ON users (email);
SELECT email, name FROM users WHERE email = 'user@example.com';
-- ✅ 最佳实践:索引包含所有需要的列
CREATE INDEX users_email_idx ON users (email) INCLUDE (name, created_at);-- ❌ BAD: Full index includes deleted rows
CREATE INDEX users_email_idx ON users (email);
-- ✅ GOOD: Partial index excludes deleted rows
CREATE INDEX users_active_email_idx ON users (email) WHERE deleted_at IS NULL;WHERE deleted_at IS NULLWHERE status = 'pending'WHERE sku IS NOT NULL-- ❌ 不良实践:全索引包含已删除行
CREATE INDEX users_email_idx ON users (email);
-- ✅ 最佳实践:部分索引排除已删除行
CREATE INDEX users_active_email_idx ON users (email) WHERE deleted_at IS NULL;WHERE deleted_at IS NULLWHERE status = 'pending'WHERE sku IS NOT NULL-- ❌ BAD: Poor type choices
CREATE TABLE users (
id int, -- Overflows at 2.1B
email varchar(255), -- Artificial limit
created_at timestamp, -- No timezone
is_active varchar(5), -- Should be boolean
balance float -- Precision loss
);
-- ✅ GOOD: Proper types
CREATE TABLE users (
id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
email text NOT NULL,
created_at timestamptz DEFAULT now(),
is_active boolean DEFAULT true,
balance numeric(10,2)
);-- ❌ 不良实践:数据类型选择不当
CREATE TABLE users (
id int, -- 21亿时会溢出
email varchar(255), -- 人为限制长度
created_at timestamp, -- 无时区信息
is_active varchar(5), -- 应使用boolean
balance float -- 存在精度丢失
);
-- ✅ 最佳实践:使用合适的数据类型
CREATE TABLE users (
id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
email text NOT NULL,
created_at timestamptz DEFAULT now(),
is_active boolean DEFAULT true,
balance numeric(10,2)
);-- ✅ Single database: IDENTITY (default, recommended)
CREATE TABLE users (
id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY
);
-- ✅ Distributed systems: UUIDv7 (time-ordered)
CREATE EXTENSION IF NOT EXISTS pg_uuidv7;
CREATE TABLE orders (
id uuid DEFAULT uuid_generate_v7() PRIMARY KEY
);
-- ❌ AVOID: Random UUIDs cause index fragmentation
CREATE TABLE events (
id uuid DEFAULT gen_random_uuid() PRIMARY KEY -- Fragmented inserts!
);-- ✅ 单数据库:IDENTITY(默认推荐)
CREATE TABLE users (
id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY
);
-- ✅ 分布式系统:UUIDv7(按时间排序)
CREATE EXTENSION IF NOT EXISTS pg_uuidv7;
CREATE TABLE orders (
id uuid DEFAULT uuid_generate_v7() PRIMARY KEY
);
-- ❌ 避免:随机UUID作为主键会导致索引碎片
CREATE TABLE events (
id uuid DEFAULT gen_random_uuid() PRIMARY KEY -- 插入时会产生碎片!
);-- ✅ GOOD: Partitioned by month
CREATE TABLE events (
id bigint GENERATED ALWAYS AS IDENTITY,
created_at timestamptz NOT NULL,
data jsonb
) PARTITION BY RANGE (created_at);
CREATE TABLE events_2024_01 PARTITION OF events
FOR VALUES FROM ('2024-01-01') TO ('2024-02-01');
CREATE TABLE events_2024_02 PARTITION OF events
FOR VALUES FROM ('2024-02-01') TO ('2024-03-01');
-- Drop old data instantly
DROP TABLE events_2023_01; -- Instant vs DELETE taking hours-- ✅ 最佳实践:按月份分区
CREATE TABLE events (
id bigint GENERATED ALWAYS AS IDENTITY,
created_at timestamptz NOT NULL,
data jsonb
) PARTITION BY RANGE (created_at);
CREATE TABLE events_2024_01 PARTITION OF events
FOR VALUES FROM ('2024-01-01') TO ('2024-02-01');
CREATE TABLE events_2024_02 PARTITION OF events
FOR VALUES FROM ('2024-02-01') TO ('2024-03-01');
-- 快速删除旧数据
DROP TABLE events_2023_01; -- 瞬间完成,相比DELETE耗时数小时-- ❌ BAD: Quoted mixed-case requires quotes everywhere
CREATE TABLE "Users" ("userId" bigint, "firstName" text);
SELECT "firstName" FROM "Users"; -- Must quote!
-- ✅ GOOD: Lowercase works without quotes
CREATE TABLE users (user_id bigint, first_name text);
SELECT first_name FROM users;-- ❌ 不良实践:带引号的混合大小写标识符需要始终使用引号
CREATE TABLE "Users" ("userId" bigint, "firstName" text);
SELECT "firstName" FROM "Users"; -- 必须加引号!
-- ✅ 最佳实践:小写标识符无需引号
CREATE TABLE users (user_id bigint, first_name text);
SELECT first_name FROM users;-- ❌ BAD: Application-only filtering
SELECT * FROM orders WHERE user_id = $current_user_id;
-- Bug means all orders exposed!
-- ✅ GOOD: Database-enforced RLS
ALTER TABLE orders ENABLE ROW LEVEL SECURITY;
ALTER TABLE orders FORCE ROW LEVEL SECURITY;
CREATE POLICY orders_user_policy ON orders
FOR ALL
USING (user_id = current_setting('app.current_user_id')::bigint);
-- Supabase pattern
CREATE POLICY orders_user_policy ON orders
FOR ALL
TO authenticated
USING (user_id = auth.uid());-- ❌ 不良实践:仅靠应用层过滤
SELECT * FROM orders WHERE user_id = $current_user_id;
-- 一旦出现Bug,所有订单数据都会暴露!
-- ✅ 最佳实践:数据库层面强制RLS
ALTER TABLE orders ENABLE ROW LEVEL SECURITY;
ALTER TABLE orders FORCE ROW LEVEL SECURITY;
CREATE POLICY orders_user_policy ON orders
FOR ALL
USING (user_id = current_setting('app.current_user_id')::bigint);
-- Supabase推荐模式
CREATE POLICY orders_user_policy ON orders
FOR ALL
TO authenticated
USING (user_id = auth.uid());-- ❌ BAD: Function called per row
CREATE POLICY orders_policy ON orders
USING (auth.uid() = user_id); -- Called 1M times for 1M rows!
-- ✅ GOOD: Wrap in SELECT (cached, called once)
CREATE POLICY orders_policy ON orders
USING ((SELECT auth.uid()) = user_id); -- 100x faster
-- Always index RLS policy columns
CREATE INDEX orders_user_id_idx ON orders (user_id);-- ❌ 不良实践:函数逐行调用
CREATE POLICY orders_policy ON orders
USING (auth.uid() = user_id); -- 查询100万行时会调用100万次!
-- ✅ 最佳实践:用SELECT包裹(缓存,仅调用一次)
CREATE POLICY orders_policy ON orders
USING ((SELECT auth.uid()) = user_id); -- 速度提升100倍
-- 务必为RLS策略相关列创建索引
CREATE INDEX orders_user_id_idx ON orders (user_id);-- ❌ BAD: Overly permissive
GRANT ALL PRIVILEGES ON ALL TABLES TO app_user;
-- ✅ GOOD: Minimal permissions
CREATE ROLE app_readonly NOLOGIN;
GRANT USAGE ON SCHEMA public TO app_readonly;
GRANT SELECT ON public.products, public.categories TO app_readonly;
CREATE ROLE app_writer NOLOGIN;
GRANT USAGE ON SCHEMA public TO app_writer;
GRANT SELECT, INSERT, UPDATE ON public.orders TO app_writer;
-- No DELETE permission
REVOKE ALL ON SCHEMA public FROM public;-- ❌ 不良实践:权限过度宽松
GRANT ALL PRIVILEGES ON ALL TABLES TO app_user;
-- ✅ 最佳实践:最小权限配置
CREATE ROLE app_readonly NOLOGIN;
GRANT USAGE ON SCHEMA public TO app_readonly;
GRANT SELECT ON public.products, public.categories TO app_readonly;
CREATE ROLE app_writer NOLOGIN;
GRANT USAGE ON SCHEMA public TO app_writer;
GRANT SELECT, INSERT, UPDATE ON public.orders TO app_writer;
-- 不授予DELETE权限
REVOKE ALL ON SCHEMA public FROM public;(RAM_in_MB / 5MB_per_connection) - reserved-- 4GB RAM example
ALTER SYSTEM SET max_connections = 100;
ALTER SYSTEM SET work_mem = '8MB'; -- 8MB * 100 = 800MB max
SELECT pg_reload_conf();
-- Monitor connections
SELECT count(*), state FROM pg_stat_activity GROUP BY state;(内存大小(MB) / 每个连接占用5MB) - 预留内存-- 4GB内存示例
ALTER SYSTEM SET max_connections = 100;
ALTER SYSTEM SET work_mem = '8MB'; -- 8MB * 100 = 最大占用800MB
SELECT pg_reload_conf();
-- 监控连接状态
SELECT count(*), state FROM pg_stat_activity GROUP BY state;ALTER SYSTEM SET idle_in_transaction_session_timeout = '30s';
ALTER SYSTEM SET idle_session_timeout = '10min';
SELECT pg_reload_conf();ALTER SYSTEM SET idle_in_transaction_session_timeout = '30s';
ALTER SYSTEM SET idle_session_timeout = '10min';
SELECT pg_reload_conf();(CPU_cores * 2) + spindle_count(CPU核心数 * 2) + 磁盘数量-- ❌ BAD: Lock held during external API call
BEGIN;
SELECT * FROM orders WHERE id = 1 FOR UPDATE;
-- HTTP call takes 5 seconds...
UPDATE orders SET status = 'paid' WHERE id = 1;
COMMIT;
-- ✅ GOOD: Minimal lock duration
-- Do API call first, OUTSIDE transaction
BEGIN;
UPDATE orders SET status = 'paid', payment_id = $1
WHERE id = $2 AND status = 'pending'
RETURNING *;
COMMIT; -- Lock held for milliseconds-- ❌ 不良实践:外部API调用期间持有锁
BEGIN;
SELECT * FROM orders WHERE id = 1 FOR UPDATE;
-- HTTP调用耗时5秒...
UPDATE orders SET status = 'paid' WHERE id = 1;
COMMIT;
-- ✅ 最佳实践:最小化锁持有时间
-- 先执行外部API调用,在事务外完成
BEGIN;
UPDATE orders SET status = 'paid', payment_id = $1
WHERE id = $2 AND status = 'pending'
RETURNING *;
COMMIT; -- 锁仅持有毫秒级时间-- ❌ BAD: Inconsistent lock order causes deadlock
-- Transaction A: locks row 1, then row 2
-- Transaction B: locks row 2, then row 1
-- DEADLOCK!
-- ✅ GOOD: Consistent lock order
BEGIN;
SELECT * FROM accounts WHERE id IN (1, 2) ORDER BY id FOR UPDATE;
-- Now both rows locked, update in any order
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
UPDATE accounts SET balance = balance + 100 WHERE id = 2;
COMMIT;-- ❌ 不良实践:锁顺序不一致导致死锁
-- 事务A:先锁行1,再锁行2
-- 事务B:先锁行2,再锁行1
-- 死锁!
-- ✅ 最佳实践:保持锁顺序一致
BEGIN;
SELECT * FROM accounts WHERE id IN (1, 2) ORDER BY id FOR UPDATE;
-- 现在两行都已锁定,可按任意顺序更新
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
UPDATE accounts SET balance = balance + 100 WHERE id = 2;
COMMIT;-- ❌ BAD: Workers wait for each other
SELECT * FROM jobs WHERE status = 'pending' LIMIT 1 FOR UPDATE;
-- ✅ GOOD: Workers skip locked rows
UPDATE jobs
SET status = 'processing', worker_id = $1, started_at = now()
WHERE id = (
SELECT id FROM jobs
WHERE status = 'pending'
ORDER BY created_at
LIMIT 1
FOR UPDATE SKIP LOCKED
)
RETURNING *;-- ❌ 不良实践:工作进程互相等待
SELECT * FROM jobs WHERE status = 'pending' LIMIT 1 FOR UPDATE;
-- ✅ 最佳实践:工作进程跳过已锁定行
UPDATE jobs
SET status = 'processing', worker_id = $1, started_at = now()
WHERE id = (
SELECT id FROM jobs
WHERE status = 'pending'
ORDER BY created_at
LIMIT 1
FOR UPDATE SKIP LOCKED
)
RETURNING *;-- ❌ BAD: Individual inserts
INSERT INTO events (user_id, action) VALUES (1, 'click');
INSERT INTO events (user_id, action) VALUES (2, 'view');
-- 1000 round trips
-- ✅ GOOD: Batch insert
INSERT INTO events (user_id, action) VALUES
(1, 'click'),
(2, 'view'),
(3, 'click');
-- 1 round trip
-- ✅ BEST: COPY for large datasets
COPY events (user_id, action) FROM '/path/to/data.csv' WITH (FORMAT csv);-- ❌ 不良实践:单独插入
INSERT INTO events (user_id, action) VALUES (1, 'click');
INSERT INTO events (user_id, action) VALUES (2, 'view');
-- 产生1000次网络往返
-- ✅ 最佳实践:批量插入
INSERT INTO events (user_id, action) VALUES
(1, 'click'),
(2, 'view'),
(3, 'click');
-- 仅1次网络往返
-- ✅ 最优方案:大数据集使用COPY
COPY events (user_id, action) FROM '/path/to/data.csv' WITH (FORMAT csv);-- ❌ BAD: N+1 pattern
SELECT id FROM users WHERE active = true; -- Returns 100 IDs
-- Then 100 queries:
SELECT * FROM orders WHERE user_id = 1;
SELECT * FROM orders WHERE user_id = 2;
-- ... 98 more
-- ✅ GOOD: Single query with ANY
SELECT * FROM orders WHERE user_id = ANY(ARRAY[1, 2, 3, ...]);
-- ✅ GOOD: JOIN
SELECT u.id, u.name, o.*
FROM users u
LEFT JOIN orders o ON o.user_id = u.id
WHERE u.active = true;-- ❌ 不良实践:N+1查询模式
SELECT id FROM users WHERE active = true; -- 返回100个ID
-- 然后执行100次查询:
SELECT * FROM orders WHERE user_id = 1;
SELECT * FROM orders WHERE user_id = 2;
-- ... 还有98次
-- ✅ 最佳实践:使用ANY的单查询
SELECT * FROM orders WHERE user_id = ANY(ARRAY[1, 2, 3, ...]);
-- ✅ 最佳实践:使用JOIN
SELECT u.id, u.name, o.*
FROM users u
LEFT JOIN orders o ON o.user_id = u.id
WHERE u.active = true;-- ❌ BAD: OFFSET gets slower with depth
SELECT * FROM products ORDER BY id LIMIT 20 OFFSET 199980;
-- Scans 200,000 rows!
-- ✅ GOOD: Cursor-based (always fast)
SELECT * FROM products WHERE id > 199980 ORDER BY id LIMIT 20;
-- Uses index, O(1)-- ❌ 不良实践:OFFSET随分页深度增加变慢
SELECT * FROM products ORDER BY id LIMIT 20 OFFSET 199980;
-- 需要扫描200000行!
-- ✅ 最佳实践:基于游标(始终快速)
SELECT * FROM products WHERE id > 199980 ORDER BY id LIMIT 20;
-- 使用索引,O(1)性能-- ❌ BAD: Race condition
SELECT * FROM settings WHERE user_id = 123 AND key = 'theme';
-- Both threads find nothing, both insert, one fails
-- ✅ GOOD: Atomic UPSERT
INSERT INTO settings (user_id, key, value)
VALUES (123, 'theme', 'dark')
ON CONFLICT (user_id, key)
DO UPDATE SET value = EXCLUDED.value, updated_at = now()
RETURNING *;-- ❌ 不良实践:存在竞态条件
SELECT * FROM settings WHERE user_id = 123 AND key = 'theme';
-- 两个线程都未找到记录,都执行插入,其中一个失败
-- ✅ 最佳实践:原子性UPSERT
INSERT INTO settings (user_id, key, value)
VALUES (123, 'theme', 'dark')
ON CONFLICT (user_id, key)
DO UPDATE SET value = EXCLUDED.value, updated_at = now()
RETURNING *;CREATE EXTENSION IF NOT EXISTS pg_stat_statements;
-- Find slowest queries
SELECT calls, round(mean_exec_time::numeric, 2) as mean_ms, query
FROM pg_stat_statements
ORDER BY mean_exec_time DESC
LIMIT 10;
-- Find most frequent queries
SELECT calls, query
FROM pg_stat_statements
ORDER BY calls DESC
LIMIT 10;CREATE EXTENSION IF NOT EXISTS pg_stat_statements;
-- 查找最慢的查询
SELECT calls, round(mean_exec_time::numeric, 2) as mean_ms, query
FROM pg_stat_statements
ORDER BY mean_exec_time DESC
LIMIT 10;
-- 查找最频繁的查询
SELECT calls, query
FROM pg_stat_statements
ORDER BY calls DESC
LIMIT 10;EXPLAIN (ANALYZE, BUFFERS, FORMAT TEXT)
SELECT * FROM orders WHERE customer_id = 123;| Indicator | Problem | Solution |
|---|---|---|
| Missing index | Add index on filter columns |
| Poor selectivity | Check WHERE clause |
| Data not cached | Increase |
| | Increase |
EXPLAIN (ANALYZE, BUFFERS, FORMAT TEXT)
SELECT * FROM orders WHERE customer_id = 123;| 指标 | 问题 | 解决方案 |
|---|---|---|
大表出现 | 缺失索引 | 为过滤列添加索引 |
| 选择性差 | 检查WHERE条件 |
| 数据未缓存 | 增大 |
| | 增大 |
-- Analyze specific table
ANALYZE orders;
-- Check when last analyzed
SELECT relname, last_analyze, last_autoanalyze
FROM pg_stat_user_tables
ORDER BY last_analyze NULLS FIRST;
-- Tune autovacuum for high-churn tables
ALTER TABLE orders SET (
autovacuum_vacuum_scale_factor = 0.05,
autovacuum_analyze_scale_factor = 0.02
);-- 分析指定表
ANALYZE orders;
-- 查看上次分析时间
SELECT relname, last_analyze, last_autoanalyze
FROM pg_stat_user_tables
ORDER BY last_analyze NULLS FIRST;
-- 为高变动表调整自动清理参数
ALTER TABLE orders SET (
autovacuum_vacuum_scale_factor = 0.05,
autovacuum_analyze_scale_factor = 0.02
);-- GIN index for containment operators
CREATE INDEX products_attrs_gin ON products USING gin (attributes);
SELECT * FROM products WHERE attributes @> '{"color": "red"}';
-- Expression index for specific keys
CREATE INDEX products_brand_idx ON products ((attributes->>'brand'));
SELECT * FROM products WHERE attributes->>'brand' = 'Nike';
-- jsonb_path_ops: 2-3x smaller, only supports @>
CREATE INDEX idx ON products USING gin (attributes jsonb_path_ops);-- GIN索引用于包含操作符
CREATE INDEX products_attrs_gin ON products USING gin (attributes);
SELECT * FROM products WHERE attributes @> '{"color": "red"}';
-- 表达式索引用于特定键
CREATE INDEX products_brand_idx ON products ((attributes->>'brand'));
SELECT * FROM products WHERE attributes->>'brand' = 'Nike';
-- jsonb_path_ops:体积小2-3倍,仅支持@>
CREATE INDEX idx ON products USING gin (attributes jsonb_path_ops);-- Add generated tsvector column
ALTER TABLE articles ADD COLUMN search_vector tsvector
GENERATED ALWAYS AS (
to_tsvector('english', coalesce(title,'') || ' ' || coalesce(content,''))
) STORED;
CREATE INDEX articles_search_idx ON articles USING gin (search_vector);
-- Fast full-text search
SELECT * FROM articles
WHERE search_vector @@ to_tsquery('english', 'postgresql & performance');
-- With ranking
SELECT *, ts_rank(search_vector, query) as rank
FROM articles, to_tsquery('english', 'postgresql') query
WHERE search_vector @@ query
ORDER BY rank DESC;-- 添加生成的tsvector列
ALTER TABLE articles ADD COLUMN search_vector tsvector
GENERATED ALWAYS AS (
to_tsvector('english', coalesce(title,'') || ' ' || coalesce(content,''))
) STORED;
CREATE INDEX articles_search_idx ON articles USING gin (search_vector);
-- 快速全文检索
SELECT * FROM articles
WHERE search_vector @@ to_tsquery('english', 'postgresql & performance');
-- 带排序的检索
SELECT *, ts_rank(search_vector, query) as rank
FROM articles, to_tsquery('english', 'postgresql') query
WHERE search_vector @@ query
ORDER BY rank DESC;SELECT *SELECT *intbigintvarchar(255)texttimestamptimestamptzintbigintvarchar(255)texttimestamptimestamptzGRANT ALLGRANT ALL(SELECT auth.uid())(SELECT auth.uid())