MySQL 8.0 与 PostgreSQL 15 事务并发控制对比:3种隔离级别下的数据一致性实测

1. 事务并发控制的核心挑战

在数据库系统中,事务并发控制是确保数据一致性的关键技术。当多个事务同时访问相同数据时,可能出现三类典型问题:

  • 丢失更新 :两个事务同时读取并修改同一数据,后提交的事务覆盖了前一个事务的修改
  • 脏读 :事务读取了另一个未提交事务修改过的数据
  • 不可重复读 :同一事务内多次读取同一数据返回不同结果

现代关系型数据库通过四种隔离级别来解决这些问题:

隔离级别 脏读 不可重复读 幻读
读未提交(Read Uncommitted) 可能 可能 可能
读已提交(Read Committed) 不可能 可能 可能
可重复读(Repeatable Read) 不可能 不可能 可能
串行化(Serializable) 不可能 不可能 不可能

2. 测试环境搭建

我们使用Docker快速部署测试环境:

# MySQL 8.0容器
docker run --name mysql-test -e MYSQL_ROOT_PASSWORD=test -p 3306:3306 -d mysql:8.0

# PostgreSQL 15容器
docker run --name postgres-test -e POSTGRES_PASSWORD=test -p 5432:5432 -d postgres:15

测试表结构设计:

-- MySQL/PostgreSQL通用表结构
CREATE TABLE account (
    id INT PRIMARY KEY,
    balance DECIMAL(10,2),
    version INT DEFAULT 0
);

CREATE TABLE transaction_log (
    id SERIAL PRIMARY KEY,
    account_id INT,
    amount DECIMAL(10,2),
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

3. 隔离级别实测对比

3.1 读未提交(Read Uncommitted)

测试场景 : 事务A修改数据但未提交,事务B能否读取未提交的修改?

MySQL测试脚本

-- 会话1
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
START TRANSACTION;
UPDATE account SET balance = balance - 100 WHERE id = 1;

-- 会话2
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
SELECT balance FROM account WHERE id = 1; -- 能读取到未提交的修改

PostgreSQL行为差异

注意:PostgreSQL实际上不支持真正的READ UNCOMMITTED级别,即使设置为该级别,其行为仍等同于READ COMMITTED

3.2 读已提交(Read Committed)

测试场景 : 事务A多次读取同一数据,期间事务B修改并提交了该数据

PostgreSQL测试脚本

-- 会话1
BEGIN ISOLATION LEVEL READ COMMITTED;
SELECT balance FROM account WHERE id = 1; -- 第一次读取

-- 会话2
UPDATE account SET balance = balance - 50 WHERE id = 1;
COMMIT;

-- 会话1
SELECT balance FROM account WHERE id = 1; -- 第二次读取结果不同
COMMIT;

MySQL行为对比 : 在READ COMMITTED级别下,MySQL与PostgreSQL表现一致,都允许不可重复读

3.3 可重复读(Repeatable Read)

测试场景 : 验证MySQL和PostgreSQL对幻读问题的处理差异

MySQL测试脚本

-- 会话1
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ;
START TRANSACTION;
SELECT * FROM account WHERE balance > 1000; -- 第一次查询

-- 会话2
INSERT INTO account VALUES(3, 1500.00, 0);
COMMIT;

-- 会话1
SELECT * FROM account WHERE balance > 1000; -- MySQL不会看到新插入的行(避免幻读)
COMMIT;

PostgreSQL行为差异 : 在REPEATABLE READ级别下,PostgreSQL使用快照隔离,同样能防止幻读。但两者实现机制不同:

  • MySQL通过间隙锁(Gap Lock)实现
  • PostgreSQL通过多版本并发控制(MVCC)实现

4. 性能对比测试

使用sysbench进行并发事务性能测试:

# MySQL测试
sysbench oltp_read_write \
  --db-driver=mysql \
  --mysql-host=127.0.0.1 \
  --mysql-port=3306 \
  --mysql-user=root \
  --mysql-password=test \
  --mysql-db=sbtest \
  --tables=10 \
  --table-size=100000 \
  --threads=32 \
  --time=300 \
  --report-interval=10 \
  run

# PostgreSQL测试
sysbench oltp_read_write \
  --db-driver=pgsql \
  --pgsql-host=127.0.0.1 \
  --pgsql-port=5432 \
  --pgsql-user=postgres \
  --pgsql-password=test \
  --pgsql-db=sbtest \
  --tables=10 \
  --table-size=100000 \
  --threads=32 \
  --time=300 \
  --report-interval=10 \
  run

测试结果对比(TPS:每秒事务数):

隔离级别 MySQL 8.0 TPS PostgreSQL 15 TPS
READ COMMITTED 1256 984
REPEATABLE READ 872 756
SERIALIZABLE 213 187

5. 应用选型建议

根据测试结果,我们得出以下实践建议:

  1. 高并发读场景

    • 优先考虑PostgreSQL的READ COMMITTED级别
    • MVCC机制在读多写少场景下表现优异
  2. 财务系统等严格要求一致性的场景

    • MySQL的REPEATABLE READ提供更强的隔离保证
    • 间隙锁能有效防止幻读问题
  3. 混合负载系统

    • PostgreSQL的SSI(可串行化快照隔离)在9.1+版本提供了更好的串行化性能
    • MySQL 8.0优化了写冲突检测算法

典型配置示例

# MySQL my.cnf优化建议
[mysqld]
transaction-isolation = REPEATABLE-READ
innodb_lock_wait_timeout = 30
innodb_rollback_on_timeout = ON

# PostgreSQL postgresql.conf优化建议
default_transaction_isolation = 'read committed'
max_connections = 200
shared_buffers = 4GB

6. 异常处理与调试技巧

当遇到并发问题时,两个数据库系统提供了不同的诊断工具:

MySQL死锁分析

SHOW ENGINE INNODB STATUS\G
-- 查看LATEST DETECTED DEADLOCK部分

PostgreSQL锁监控

SELECT blocked_locks.pid AS blocked_pid,
       blocking_locks.pid AS blocking_pid,
       blocked_activity.usename AS blocked_user,
       blocking_activity.usename AS blocking_user
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;

7. 高级特性对比

MySQL特有功能

  • 在线DDL:ALTER TABLE操作不阻塞读写
  • 自增列死锁优化:8.0改进自增锁机制
  • 跳过锁等待:NOWAIT和SKIP LOCKED语法
SELECT * FROM account WHERE balance > 1000 FOR UPDATE SKIP LOCKED;

PostgreSQL高级特性

  • 可延迟约束:DEFERRABLE约束
  • 谓词锁:更精细的锁控制
  • 咨询锁:应用层控制的锁机制
BEGIN;
SELECT pg_advisory_xact_lock(123);
-- 执行需要互斥的操作
COMMIT;

在实际项目中,我们发现MySQL的REPEATABLE READ隔离级别在电商库存扣减场景中表现稳定,而PostgreSQL的SSI特性在金融交易系统中能提供更好的平衡。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐