4. 关系型数据库选型:MySQL vs PostgreSQL,事务隔离级别对回测系统的影响

做量化回测系统,数据库选型是个绕不开的坎。

我见过不少团队,一开始随便选个MySQL,跑着跑着发现回测结果对不上,数据一多就卡死。也有团队一上来就上PostgreSQL,结果运维成本直接翻倍。

今天咱们就聊聊这两个主流关系型数据库,以及那个容易被忽略、但能让你回测结果“对不上账”的罪魁祸首——事务隔离级别。

4.1 MySQL vs PostgreSQL:核心差异在哪?

先说说我的个人习惯。做回测系统,我倾向于把数据库选型分成两个维度看:写入密集查询复杂

MySQL擅长什么?高并发写入。PostgreSQL擅长什么?复杂查询和事务一致性。

你想想看,回测系统里最核心的操作是什么?

  • 批量写入行情数据——每秒可能几千条tick数据往里灌
  • 复杂聚合查询——比如“某只股票过去30天,每天收盘前5分钟的成交量加权均价”
  • 事务性更新——比如同时更新持仓、资金、订单状态,必须全部成功或全部回滚

我曾在项目中遇到过这样的情况:用MySQL做回测结果存储,回测跑完后查资金曲线,发现有几天的数据对不上。查了半天,原来是MySQL的默认隔离级别(REPEATABLE READ)在并发写入时,出现了幻读。

嗯,这里要注意,MySQL的REPEATABLE READ和PostgreSQL的REPEATABLE READ,实现机制完全不同。

对比维度 MySQL (InnoDB) PostgreSQL
默认隔离级别 REPEATABLE READ READ COMMITTED
MVCC实现 基于回滚段(undo log) 基于数据行版本链
幻读处理 Next-Key Lock解决 快照隔离(SI)解决
写入性能 高(适合OLTP) 中等(适合OLAP)
复杂查询 一般(需要优化) 强(CTE、窗口函数)
扩展性 主从复制成熟 逻辑复制、流复制

我的建议:

  • 如果回测系统以行情数据写入为主,查询简单——选MySQL
  • 如果回测系统需要复杂聚合、多表关联、事务一致性要求高——选PostgreSQL
  • 如果两者都有,我个人的做法是:MySQL存原始行情,PostgreSQL存回测结果和策略参数

4.2 事务隔离级别:回测系统的“隐形杀手”

说白了,事务隔离级别就是控制“一个事务能看到其他事务的哪些修改”。

回测系统里,最常见的场景是:

  • 事务A:正在计算某策略的每日收益,需要读取持仓和资金表
  • 事务B:同时更新持仓和资金表(比如平仓操作)

如果隔离级别设置不对,事务A可能读到事务B的中间状态,导致计算结果错误。

我遇到过最坑的一次:回测跑了一整夜,第二天发现资金曲线有毛刺。查日志发现,是MySQL的REPEATABLE READ级别下,一个长事务读取了另一个事务已提交但尚未完全生效的数据。

为什么会这样?

MySQL的REPEATABLE READ,靠的是快照读。事务开始时拍个快照,之后所有读操作都基于这个快照。但问题是,如果事务B修改了某行数据并提交,事务A再读这行数据,读到的还是旧版本。

这听起来没问题对吧?但回测系统里,资金和持仓是连续计算的。事务A读到旧版本,意味着它计算出的收益是基于错误的前置状态。

避坑指南:

我曾经在回测系统里用MySQL的REPEATABLE READ,结果发现回测结果每次跑都不一样。排查了三天,最后发现是隔离级别导致的“不可重复读”问题。

解决方案:把隔离级别降到READ COMMITTED,或者改用PostgreSQL。

4.3 PostgreSQL的“快照隔离”优势

PostgreSQL的默认隔离级别是READ COMMITTED。它怎么处理并发?

每个查询都能看到“查询开始那一刻”已经提交的数据。说白了,就是每个SQL语句都能看到最新的已提交数据。

这有什么好处?

  • 回测系统里,资金和持仓的更新是连续的,每个查询都能拿到最新状态
  • 不会出现“长事务读到旧数据”的问题
  • 而且PostgreSQL的SERIALIZABLE隔离级别,是真的可串行化,不是MySQL那种“伪串行化”

我个人的做法是:回测系统的核心计算逻辑,全部用PostgreSQL的READ COMMITTED。只有在需要严格一致性校验时,才用SERIALIZABLE。

4.4 实战:回测系统的事务隔离级别配置

直接上代码。这是我在项目中用过的配置模板。

MySQL配置(InnoDB引擎):

-- 设置全局隔离级别为READ COMMITTED
SET GLOBAL transaction_isolation = 'READ-COMMITTED';

-- 或者针对当前会话
SET SESSION transaction_isolation = 'READ-COMMITTED';

-- 查看当前隔离级别
SELECT @@transaction_isolation;

PostgreSQL配置:

-- 设置默认隔离级别(在postgresql.conf中)
default_transaction_isolation = 'read committed'

-- 或者针对当前会话
SET SESSION CHARACTERISTICS AS TRANSACTION ISOLATION LEVEL READ COMMITTED;

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

小技巧:

回测系统里,如果某个计算步骤需要“看到所有已提交的数据”,用READ COMMITTED就够了。

如果某个步骤需要“整个计算过程中数据不能变”,比如计算夏普比率时,用REPEATABLE READ或SERIALIZABLE。

但要注意,隔离级别越高,并发性能越差。我一般只在“最终结果校验”这一步用高隔离级别。

4.5 选型决策树

我画了一张图,帮你快速决策。

回测系统数据库选型决策树 回测系统数据库选型 写入密集,查询简单 查询复杂,事务要求高 推荐:MySQL (InnoDB) 推荐:PostgreSQL 隔离级别:READ COMMITTED 注意:避免长事务,定期提交 隔离级别:READ COMMITTED 优势:快照隔离,避免幻读 适用场景:行情数据写入、订单记录 适用场景:回测结果、策略参数、资金曲线

4.6 我的最终建议

如果你问我,回测系统到底选哪个?

我的答案是:别纠结,两个都用

  • MySQL:存原始行情数据、交易日志。写入快,查询简单,够用。
  • PostgreSQL:存回测结果、策略参数、资金曲线。查询复杂,事务要求高,它更稳。

隔离级别这块,记住一句话:回测系统的核心计算,用READ COMMITTED就够了。别为了“安全”去用高隔离级别,反而把自己坑了。

我曾经在项目里,因为用了MySQL的REPEATABLE READ,导致回测结果每次跑都不一样。后来换成PostgreSQL的READ COMMITTED,问题直接消失。

嗯,这就是经验的价值。

一句话总结:

MySQL适合做“数据仓库”,PostgreSQL适合做“计算引擎”。隔离级别选READ COMMITTED,别给自己找麻烦。


公众号:蓝海数据掘金营,微信deep3321