PostgreSQL教程FG059-PG并发控制:事务隔离级别与锁机制详解
本文档风哥主要介绍PostgreSQL教程059相关内容。风哥教程参考PostgreSQL官方文档Server Administration, SQL Language内容编写,适合DBA人员在学习和测试中使用,如果要应用于生产环境则需要自行确认。
更多视频教程www.fgedu.net.cn
1. 事务隔离级别概述
PostgreSQL支持四种事务隔离级别,用于控制并发事务之间的可见性。
SHOW default_transaction_isolation;
执行结果:
——————————-
read committed
(1 row)
2. 读未提交
PostgreSQL的读未提交实际上等同于读已提交,不会读取脏数据。
CREATE TABLE fgedu_accounts_isolation (
account_id SERIAL PRIMARY KEY,
account_name VARCHAR(50),
balance NUMERIC(10, 2)
);
INSERT INTO fgedu_accounts_isolation (account_name, balance) VALUES
(‘账户A’, 1000.00),
(‘账户B’, 2000.00);
— 会话1:设置读未提交
BEGIN TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
SELECT * FROM fgedu_accounts_isolation WHERE account_id = 1;
执行结果:
————+————–+———
1 | 账户A | 1000.00
(1 row)
BEGIN;
UPDATE fgedu_accounts_isolation SET balance = 1500.00 WHERE account_id = 1;
— 不提交,回到会话1查询
— 会话1:再次查询
SELECT * FROM fgedu_accounts_isolation WHERE account_id = 1;
COMMIT;
执行结果:
————+————–+———
1 | 账户A | 1000.00 — 仍然看到原值,不会读到脏数据
(1 row)
COMMIT
3. 读已提交
读已提交是PostgreSQL默认的隔离级别,每个查询看到的是查询开始时已提交的数据。
BEGIN TRANSACTION ISOLATION LEVEL READ COMMITTED;
SELECT * FROM fgedu_accounts_isolation WHERE account_id = 1;
执行结果:
from oracle:www.itpux.com风哥提示:
————+————–+———
1 | 账户A | 1000.00
(1 row)
BEGIN;
UPDATE fgedu_accounts_isolation SET balance = 1200.00 WHERE account_id = 1;
COMMIT;
— 会话1:再次查询(同一事务内)
SELECT * FROM fgedu_accounts_isolation WHERE account_id = 1;
COMMIT;
执行结果:
————+————–+———
1 | 账户A | 1200.00 — 看到了会话2提交的修改
(1 row)
COMMIT
4. 可重复读
可重复读保证在同一事务内,多次读取同样的数据看到的结果相同。
BEGIN TRANSACTION ISOLATION LEVEL REPEATABLE READ;
SELECT * FROM fgedu_accounts_isolation WHERE account_id = 1;
执行结果:
————+————–+———
1 | 账户A | 1200.00
(1 row)
BEGIN;
UPDATE fgedu_accounts_isolation SET balance = 1500.00 WHERE account_id = 1;
COMMIT;
— 会话1:再次查询(同一事务内)
SELECT * FROM fgedu_accounts_isolation WHERE account_id = 1;
COMMIT;
执行结果:
————+————–+———
1 | 账户A | 1200.00 — 仍然是原来的值,实现了可重复读
(1 row)
COMMIT
SELECT * FROM fgedu_accounts_isolation WHERE account_id = 1;
执行结果:
————+————–+———
1 | 账户A | 1500.00 — 新事务可以看到最新提交的数据
(1 row)
5. 可串行化
可串行化是最高的隔离级别,完全隔离并发事务的影响。
学习交流加群风哥微信: itpux-com
BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE;
SELECT SUM(balance) FROM fgedu_accounts_isolation;
执行结果:
学习交流加群风哥QQ113257174
———
3500.00
(1 row)
BEGIN;
INSERT INTO fgedu_accounts_isolation (account_name, balance) VALUES (‘账户C’, 500.00);
COMMIT;
— 会话1:再次统计
SELECT SUM(balance) FROM fgedu_accounts_isolation;
COMMIT;
执行结果:
更多学习教程公众号风哥教程itpux_com
———
3500.00 — 仍然是原来的值,没有看到新插入的数据
(1 row)
COMMIT
— 会话1
BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE;
UPDATE fgedu_accounts_isolation SET balance = balance + 100 WHERE account_id = 1;
— 会话2(同时执行)
BEGIN TRANSACTION ISOLATION LEVEL SERIALIZABLE;
UPDATE fgedu_accounts_isolation SET balance = balance – 100 WHERE account_id = 1;
COMMIT;
— 会话1提交
COMMIT; — 可能报错:ERROR: could not serialize access due to concurrent update
执行结果:
6. 锁机制详解
6.1 表级锁
SELECT locktype, mode, description
FROM pg_locks
JOIN pg_class ON pg_locks.relation = pg_class.oid
WHERE pg_class.relname = ‘fgedu_accounts_isolation’;
执行结果:
———-+——+————-
(0 rows) — 当前没有锁
BEGIN;
LOCK TABLE fgedu_accounts_isolation IN SHARE MODE;
SELECT * FROM fgedu_accounts_isolation;
COMMIT;
执行结果:
account_id | account_name | balance
————+————–+———
1 | 账户A | 1500.00
2 | 账户B | 2000.00
3 | 账户C | 500.00
(3 rows)
COMMIT
6.2 行级锁
BEGIN;
SELECT * FROM fgedu_accounts_isolation WHERE account_id = 1 FOR UPDATE;
— 此时其他事务无法修改这一行
— 查看当前锁
SELECT
locktype,
mode,
relation::regclass,
page,
tuple
FROM pg_locks
WHERE relation = ‘fgedu_accounts_isolation’::regclass;
执行结果:
———-+—————+————————-+——+——-
tuple | RowExclusiveLock | fgedu_accounts_isolation | 0 | 1
(1 row)
COMMIT;
6.3 行锁类型
SELECT * FROM fgedu_accounts_isolation WHERE account_id = 1 FOR UPDATE; — 排他锁
SELECT * FROM fgedu_accounts_isolation WHERE account_id = 1 FOR NO KEY UPDATE; — 较弱的排他锁
SELECT * FROM fgedu_accounts_isolation WHERE account_id = 1 FOR SHARE; — 共享锁
SELECT * FROM fgedu_accounts_isolation WHERE account_id = 1 FOR KEY SHARE; — 较弱的共享锁
执行结果:
————+————–+———
1 | 账户A | 1500.00
(4 rows)
7. 实战案例:并发问题
7.1 丢失更新问题
UPDATE fgedu_accounts_isolation SET balance = 1000.00 WHERE account_id = 1;
— 模拟丢失更新(两个事务同时读取并更新)
— 会话1
BEGIN;
SELECT balance FROM fgedu_accounts_isolation WHERE account_id = 1;
— balance = 1000
— 会话2(同时执行)
BEGIN;
SELECT balance FROM fgedu_accounts_isolation WHERE account_id = 1;
— balance = 1000
UPDATE fgedu_accounts_isolation SET balance = 1000 + 200 WHERE account_id = 1;
COMMIT;
— 会话1继续
UPDATE fgedu_accounts_isolation SET balance = 1000 + 100 WHERE account_id = 1;
COMMIT;
— 最终结果
SELECT balance FROM fgedu_accounts_isolation WHERE account_id = 1;
执行结果:
———
1100.00 — 会话2的更新被覆盖了
(1 row)
7.2 使用SELECT FOR UPDATE防止丢失更新
UPDATE fgedu_accounts_isolation SET balance = 1000.00 WHERE account_id = 1;
— 使用FOR UPDATE防止丢失更新
— 会话1
BEGIN;
SELECT balance FROM fgedu_accounts_isolation WHERE account_id = 1 FOR UPDATE;
— balance = 1000,持有行锁
— 会话2(同时执行,会等待)
BEGIN;
SELECT balance FROM fgedu_accounts_isolation WHERE account_id = 1 FOR UPDATE;
— 等待会话1释放锁…
— 会话1继续
UPDATE fgedu_accounts_isolation SET balance = 1000 + 100 WHERE account_id = 1;
COMMIT; — 释放锁
— 会话2现在可以继续
— balance = 1100(看到会话1提交的值)
UPDATE fgedu_accounts_isolation SET balance = 1100 + 200 WHERE account_id = 1;
COMMIT;
— 最终结果
SELECT balance FROM fgedu_accounts_isolation WHERE account_id = 1;
执行结果:
———
1300.00 — 两个更新都生效了
(1 row)
8. 清理环境
DROP TABLE IF EXISTS fgedu_accounts_isolation;
执行结果:
- 读已提交是默认隔离级别,适合大多数场景
- 可重复读适合需要一致性读取的场景
- 可串行化适合对一致性要求极高的场景
- 使用SELECT FOR UPDATE可以防止丢失更新
- 合理选择隔离级别和锁策略,平衡并发性和一致性
本文由风哥教程整理发布,仅用于学习测试使用,转载注明出处:http://www.fgedu.net.cn/10327.html
