Skip to content

transactions indexes

文件信息

  • 📄 原文件:04_transactions_indexes.sql
  • 🔤 语言:SQL

SQL 脚本

sql
-- ============================================================
--                    PostgreSQL 事务与索引
-- ============================================================
-- 本文件介绍 PostgreSQL 事务处理和索引优化。
-- ============================================================

-- \c learn_postgresql

-- ============================================================
--                    1. 事务基础
-- ============================================================

/*
PostgreSQL 事务特性:
- 完整的 ACID 支持
- 多种隔离级别
- 保存点支持
- 可延迟约束
*/

-- 创建测试表
CREATE TABLE IF NOT EXISTS accounts (
    id SERIAL PRIMARY KEY,
    name VARCHAR(50) NOT NULL UNIQUE,
    balance NUMERIC(10, 2) NOT NULL DEFAULT 0
        CHECK (balance >= 0)
);

TRUNCATE TABLE accounts RESTART IDENTITY;
INSERT INTO accounts (name, balance) VALUES
    ('Alice', 1000.00),
    ('Bob', 500.00),
    ('Charlie', 2000.00);

-- --- 基本事务 ---

-- 开始事务
BEGIN;
-- 或 START TRANSACTION;

-- 转账操作
UPDATE accounts SET balance = balance - 200 WHERE name = 'Alice';
UPDATE accounts SET balance = balance + 200 WHERE name = 'Bob';

-- 检查结果
SELECT * FROM accounts;

-- 提交
COMMIT;
-- 或 END;

-- --- 回滚 ---
BEGIN;
UPDATE accounts SET balance = balance - 500 WHERE name = 'Charlie';
-- 发现问题,回滚
ROLLBACK;

-- --- 保存点 ---
BEGIN;

UPDATE accounts SET balance = balance + 100 WHERE name = 'Alice';
SAVEPOINT sp1;

UPDATE accounts SET balance = balance + 100 WHERE name = 'Bob';
SAVEPOINT sp2;

UPDATE accounts SET balance = balance + 100 WHERE name = 'Charlie';

-- 回滚到 sp2
ROLLBACK TO SAVEPOINT sp2;

-- 只有 Alice 和 Bob 的更新保留
COMMIT;


-- ============================================================
--                    2. 隔离级别
-- ============================================================

/*
PostgreSQL 隔离级别:
1. READ UNCOMMITTED:在 PG 中等同于 READ COMMITTED
2. READ COMMITTED:默认级别,每条语句看到最新已提交数据
3. REPEATABLE READ:事务期间看到一致的快照
4. SERIALIZABLE:完全串行化,最严格

PostgreSQL 使用 MVCC(多版本并发控制)实现隔离
*/

-- 查看当前隔离级别
SHOW transaction_isolation;

-- 设置事务隔离级别
BEGIN ISOLATION LEVEL REPEATABLE READ;
-- 执行操作
COMMIT;

-- 设置会话级别
SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL SERIALIZABLE;

-- --- SERIALIZABLE 隔离级别 ---
-- 最严格,可能抛出序列化错误,需要重试

BEGIN ISOLATION LEVEL SERIALIZABLE;
-- 如果检测到序列化冲突
-- ERROR: could not serialize access due to read/write dependencies
COMMIT;

-- --- 只读事务 ---
BEGIN READ ONLY;
SELECT * FROM accounts;
-- UPDATE accounts SET balance = 0;  -- 错误:只读事务
COMMIT;

-- --- 可延迟事务 ---
-- 适用于长时间只读事务
BEGIN ISOLATION LEVEL SERIALIZABLE READ ONLY DEFERRABLE;
-- 系统会等待一个安全的快照点
COMMIT;


-- ============================================================
--                    3. 锁机制
-- ============================================================

/*
PostgreSQL 锁类型:
- 行级锁:FOR UPDATE, FOR SHARE 等
- 表级锁:LOCK TABLE
- 咨询锁:自定义锁

MVCC 减少了锁冲突,读写不阻塞
*/

-- --- 行级锁 ---

-- FOR UPDATE:排他锁
BEGIN;
SELECT * FROM accounts WHERE name = 'Alice' FOR UPDATE;
-- 其他事务无法更新此行
UPDATE accounts SET balance = balance + 50 WHERE name = 'Alice';
COMMIT;

-- FOR SHARE:共享锁
BEGIN;
SELECT * FROM accounts WHERE name = 'Bob' FOR SHARE;
-- 其他事务可以读取,但不能更新
COMMIT;

-- FOR NO KEY UPDATE:较弱的排他锁
-- 不阻塞只修改非键列的操作

-- FOR KEY SHARE:较弱的共享锁
-- 不阻塞 FOR NO KEY UPDATE

-- NOWAIT:获取不到锁立即报错
BEGIN;
SELECT * FROM accounts WHERE name = 'Alice' FOR UPDATE NOWAIT;
COMMIT;

-- SKIP LOCKED:跳过已锁定的行
BEGIN;
SELECT * FROM accounts FOR UPDATE SKIP LOCKED LIMIT 1;
COMMIT;

-- --- 表级锁 ---

-- 显式锁表
BEGIN;
LOCK TABLE accounts IN SHARE MODE;  -- 共享模式
-- LOCK TABLE accounts IN EXCLUSIVE MODE;  -- 排他模式
COMMIT;

-- --- 咨询锁(应用级锁)---

-- 获取咨询锁
SELECT pg_advisory_lock(12345);

-- 尝试获取(非阻塞)
SELECT pg_try_advisory_lock(12345);

-- 释放咨询锁
SELECT pg_advisory_unlock(12345);

-- 事务级咨询锁(事务结束自动释放)
SELECT pg_advisory_xact_lock(12345);

-- --- 查看锁信息 ---

-- 当前锁
SELECT * FROM pg_locks;

-- 锁等待
SELECT
    blocked_locks.pid AS blocked_pid,
    blocked_activity.usename AS blocked_user,
    blocking_locks.pid AS blocking_pid,
    blocking_activity.usename AS blocking_user,
    blocked_activity.query AS blocked_statement
FROM pg_catalog.pg_locks blocked_locks
JOIN pg_catalog.pg_stat_activity blocked_activity
    ON blocked_activity.pid = blocked_locks.pid
JOIN pg_catalog.pg_locks blocking_locks
    ON blocking_locks.locktype = blocked_locks.locktype
    AND blocking_locks.database IS NOT DISTINCT FROM blocked_locks.database
    AND blocking_locks.relation IS NOT DISTINCT FROM blocked_locks.relation
    AND blocking_locks.page IS NOT DISTINCT FROM blocked_locks.page
    AND blocking_locks.tuple IS NOT DISTINCT FROM blocked_locks.tuple
    AND blocking_locks.virtualxid IS NOT DISTINCT FROM blocked_locks.virtualxid
    AND blocking_locks.transactionid IS NOT DISTINCT FROM blocked_locks.transactionid
    AND blocking_locks.classid IS NOT DISTINCT FROM blocked_locks.classid
    AND blocking_locks.objid IS NOT DISTINCT FROM blocked_locks.objid
    AND blocking_locks.objsubid IS NOT DISTINCT FROM blocked_locks.objsubid
    AND blocking_locks.pid != blocked_locks.pid
JOIN pg_catalog.pg_stat_activity blocking_activity
    ON blocking_activity.pid = blocking_locks.pid
WHERE NOT blocked_locks.granted;


-- ============================================================
--                    4. 索引类型
-- ============================================================

/*
PostgreSQL 索引类型:
- B-tree:默认,适合比较和范围查询
- Hash:等值比较(PostgreSQL 10+ 已可靠)
- GiST:通用搜索树,几何、全文搜索
- SP-GiST:空间分区 GiST
- GIN:倒排索引,数组、JSONB、全文
- BRIN:块范围索引,大表按顺序存储的数据
*/

-- 创建测试表
CREATE TABLE IF NOT EXISTS logs (
    id SERIAL PRIMARY KEY,
    level VARCHAR(10),
    message TEXT,
    metadata JSONB,
    tags TEXT[],
    created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);

-- --- B-tree 索引(默认)---
CREATE INDEX idx_logs_level ON logs(level);
CREATE INDEX idx_logs_created ON logs(created_at DESC);

-- 多列索引
CREATE INDEX idx_logs_level_created ON logs(level, created_at);

-- 部分索引
CREATE INDEX idx_logs_errors ON logs(created_at)
WHERE level = 'ERROR';

-- 表达式索引
CREATE INDEX idx_logs_date ON logs(DATE(created_at));
CREATE INDEX idx_logs_lower_level ON logs(LOWER(level));

-- 唯一索引
CREATE UNIQUE INDEX idx_logs_unique ON logs(id);

-- --- Hash 索引 ---
CREATE INDEX idx_logs_level_hash ON logs USING HASH (level);

-- --- GIN 索引(用于 JSONB 和数组)---
CREATE INDEX idx_logs_metadata ON logs USING GIN (metadata);
CREATE INDEX idx_logs_tags ON logs USING GIN (tags);

-- JSONB 路径操作索引
CREATE INDEX idx_logs_metadata_ops ON logs USING GIN (metadata jsonb_path_ops);

-- --- GiST 索引(用于几何和全文)---
-- CREATE INDEX idx_posts_search ON posts USING GiST (search_vector);

-- --- BRIN 索引(块范围索引)---
-- 适合大表,数据按物理顺序与索引列相关
CREATE INDEX idx_logs_created_brin ON logs USING BRIN (created_at);

-- --- 并发创建索引 ---
-- 不阻塞写操作
CREATE INDEX CONCURRENTLY idx_logs_message ON logs(message);

-- --- 覆盖索引(INCLUDE)---
-- PostgreSQL 11+
CREATE INDEX idx_logs_level_include ON logs(level) INCLUDE (message, created_at);


-- ============================================================
--                    5. 索引维护
-- ============================================================

-- 查看索引
SELECT
    schemaname,
    tablename,
    indexname,
    indexdef
FROM pg_indexes
WHERE tablename = 'logs';

-- 查看索引大小
SELECT
    indexrelname AS index_name,
    pg_size_pretty(pg_relation_size(indexrelid)) AS index_size
FROM pg_stat_user_indexes
WHERE relname = 'logs';

-- 查看索引使用情况
SELECT
    indexrelname AS index_name,
    idx_scan AS times_used,
    idx_tup_read AS tuples_read,
    idx_tup_fetch AS tuples_fetched
FROM pg_stat_user_indexes
WHERE relname = 'logs';

-- 重建索引
REINDEX INDEX idx_logs_level;
REINDEX TABLE logs;

-- 并发重建(PostgreSQL 12+)
REINDEX INDEX CONCURRENTLY idx_logs_level;

-- 删除索引
DROP INDEX IF EXISTS idx_logs_message;


-- ============================================================
--                    6. 执行计划
-- ============================================================

-- 基本 EXPLAIN
EXPLAIN SELECT * FROM employees WHERE department_id = 1;

-- 详细分析(实际执行)
EXPLAIN (ANALYZE, BUFFERS, FORMAT TEXT)
SELECT * FROM employees WHERE department_id = 1;

-- JSON 格式
EXPLAIN (ANALYZE, FORMAT JSON)
SELECT * FROM employees WHERE salary > 20000;

/*
EXPLAIN 输出解读:
- Seq Scan:顺序扫描(全表扫描)
- Index Scan:索引扫描
- Index Only Scan:仅索引扫描(覆盖索引)
- Bitmap Heap Scan:位图堆扫描
- Nested Loop:嵌套循环连接
- Hash Join:哈希连接
- Merge Join:合并连接

关键指标:
- cost:估计成本(启动成本..总成本)
- rows:估计行数
- actual time:实际执行时间
- loops:循环次数
*/

-- 强制使用/不使用索引
SET enable_seqscan = off;  -- 禁用顺序扫描
EXPLAIN SELECT * FROM employees WHERE department_id = 1;
SET enable_seqscan = on;


-- ============================================================
--                    7. 表分区
-- ============================================================

/*
PostgreSQL 10+ 原生分区支持:
- RANGE:范围分区
- LIST:列表分区
- HASH:哈希分区(PostgreSQL 11+)
*/

-- --- 范围分区 ---
CREATE TABLE IF NOT EXISTS orders (
    id SERIAL,
    user_id INTEGER,
    total_amount NUMERIC(10, 2),
    order_date DATE NOT NULL,
    status VARCHAR(20)
) PARTITION BY RANGE (order_date);

-- 创建分区
CREATE TABLE orders_2023 PARTITION OF orders
    FOR VALUES FROM ('2023-01-01') TO ('2024-01-01');

CREATE TABLE orders_2024 PARTITION OF orders
    FOR VALUES FROM ('2024-01-01') TO ('2025-01-01');

-- 默认分区
CREATE TABLE orders_default PARTITION OF orders DEFAULT;

-- 在分区上创建索引(自动在所有分区创建)
CREATE INDEX idx_orders_date ON orders(order_date);

-- 插入数据(自动路由到正确分区)
INSERT INTO orders (user_id, total_amount, order_date, status)
VALUES (1, 100.00, '2024-03-15', 'completed');

-- 查询分区信息
SELECT
    parent.relname AS parent_table,
    child.relname AS partition_name,
    pg_get_expr(child.relpartbound, child.oid) AS partition_expression
FROM pg_inherits
JOIN pg_class parent ON pg_inherits.inhparent = parent.oid
JOIN pg_class child ON pg_inherits.inhrelid = child.oid
WHERE parent.relname = 'orders';

-- --- 列表分区 ---
CREATE TABLE IF NOT EXISTS sales (
    id SERIAL,
    region VARCHAR(50) NOT NULL,
    amount NUMERIC(10, 2),
    sale_date DATE
) PARTITION BY LIST (region);

CREATE TABLE sales_east PARTITION OF sales
    FOR VALUES IN ('北京', '上海', '天津');

CREATE TABLE sales_south PARTITION OF sales
    FOR VALUES IN ('广州', '深圳', '海南');

-- --- 哈希分区 ---
CREATE TABLE IF NOT EXISTS sessions (
    id SERIAL,
    user_id INTEGER NOT NULL,
    data JSONB,
    created_at TIMESTAMP
) PARTITION BY HASH (user_id);

CREATE TABLE sessions_0 PARTITION OF sessions
    FOR VALUES WITH (MODULUS 4, REMAINDER 0);

CREATE TABLE sessions_1 PARTITION OF sessions
    FOR VALUES WITH (MODULUS 4, REMAINDER 1);

CREATE TABLE sessions_2 PARTITION OF sessions
    FOR VALUES WITH (MODULUS 4, REMAINDER 2);

CREATE TABLE sessions_3 PARTITION OF sessions
    FOR VALUES WITH (MODULUS 4, REMAINDER 3);

-- --- 分区维护 ---

-- 分离分区
ALTER TABLE orders DETACH PARTITION orders_2023;

-- 附加分区
ALTER TABLE orders ATTACH PARTITION orders_2023
    FOR VALUES FROM ('2023-01-01') TO ('2024-01-01');

-- 删除分区
-- DROP TABLE orders_2023;


-- ============================================================
--                    8. 性能优化
-- ============================================================

-- --- 统计信息 ---

-- 更新统计信息
ANALYZE employees;
ANALYZE accounts;

-- 查看表统计信息
SELECT
    relname AS table_name,
    n_live_tup AS live_rows,
    n_dead_tup AS dead_rows,
    last_vacuum,
    last_autovacuum,
    last_analyze,
    last_autoanalyze
FROM pg_stat_user_tables
WHERE relname IN ('employees', 'accounts');

-- --- VACUUM ---

-- 回收空间
VACUUM employees;

-- 详细信息
VACUUM VERBOSE employees;

-- 完全清理(锁表)
VACUUM FULL employees;

-- 自动 vacuum 配置
SHOW autovacuum;
-- ALTER TABLE employees SET (autovacuum_vacuum_threshold = 50);

-- --- 配置优化 ---

-- 查看配置
SHOW shared_buffers;
SHOW work_mem;
SHOW effective_cache_size;

-- 会话级调整
SET work_mem = '256MB';

-- --- 连接池 ---
-- 使用 PgBouncer 或内置连接池

-- 查看当前连接
SELECT * FROM pg_stat_activity;

-- 终止连接
-- SELECT pg_terminate_backend(pid);


-- ============================================================
--                    总结
-- ============================================================

/*
事务特性:
- BEGIN / COMMIT / ROLLBACK
- SAVEPOINT
- 隔离级别:READ COMMITTED(默认)、REPEATABLE READ、SERIALIZABLE
- 只读事务、可延迟事务

锁机制:
- FOR UPDATE / FOR SHARE
- NOWAIT / SKIP LOCKED
- 咨询锁 pg_advisory_lock
- MVCC 减少锁冲突

索引类型:
- B-tree:默认,比较和范围
- Hash:等值比较
- GIN:数组、JSONB、全文
- GiST:几何、全文
- BRIN:大表顺序数据

索引特性:
- 部分索引
- 表达式索引
- 覆盖索引(INCLUDE)
- 并发创建(CONCURRENTLY)

分区:
- RANGE:范围分区
- LIST:列表分区
- HASH:哈希分区
- 自动路由

性能优化:
- EXPLAIN ANALYZE
- VACUUM / ANALYZE
- 合理配置参数
- 使用连接池
*/

💬 讨论

使用 GitHub 账号登录后即可参与讨论

基于 MIT 许可发布