Claude Opus 4.6 vs ChatGPT 5.3:一次 SQLAlchemy 事务问题的复盘
摘要
记录一次vibe coding中的典型失误:Claude Opus 4.6 未意识到 Python enum 类在 SQLAlchemy 中的查询陷阱, 甚至编造了连接池快照理论,并且胆敢修改数据库隔离级别。ChatGPT 5.3 则准确指出了核心问题。 这一案例揭示了AI为了逻辑自洽会编造理论,缺乏对变更后果(胆敢修改项目默认数据库隔离级别)。
一、真实问题:Python Enum 的查询陷阱
在优化 DAO 层状态枚举时,Claude 引入 Python enum 类:
import enum
class Status(enum.Enum):
PENDING = 1
ACTIVE = 2
# 错误:比较的是枚举对象,而非其值,结果是查不出值的
session.query(User).filter(User.status == Status.ACTIVE).all()
# 正确:使用 .value 比较
session.query(User).filter(User.status == Status.ACTIVE.value).all()
SQLAlchemy框架传入枚举对象筛选时默认比较枚举对象本身,而非其底层值,导致查询无结果。 这是代码层面的类型匹配问题,与事务隔离级别无关。
二、Claude 的错误分析路径
2.1 错误归因:事务隔离级别
Claude 将问题归因于 MySQL 的”可重复读”隔离级别,认为符合”A写入数据,B查询不到”的现象。
2.2 编造的理论:连接池快照复用
为自圆其说,Claude 构建了一套错误理论:
- SQLAlchemy 连接池复用旧连接
- 旧连接保留之前的 ReadView(MVCC 快照)
- 只读事务的
commit()是空操作,不会销毁快照
2.3 危险的修复建议
基于上述错误理论,Claude 建议:
- 修改数据库隔离级别:将”可重复读”改为”读已提交”
- 实现强制刷新机制:在查询前直接对底层连接执行
COMMIT
为了不影响项目中其它引用,我命令Claude只修改新增部分,它产出了如下的代码建议。每次事务先commit一下以获取最新的数据库快照。
@contextmanager
def _get_fresh_session(db_name: str = 'chatbi'):
"""WebSocket 场景下获取 session,查询前强制提交以刷新 MVCC 快照。
连接池复用时,旧连接的 ReadView 不会自动更新(ROLLBACK 不刷新快照),
通过底层连接直接执行 COMMIT(绕过 SQLAlchemy 事务管理),
让 MySQL 在下一条 SQL 时重建 ReadView,从而读取到其他事务最新提交的数据。
注意:不能用 session.execute(text("COMMIT")),SQLAlchemy 2.x 的 autobegin
机制会将其包在当前事务内执行,MySQL 不会真正结束事务,ReadView 不会刷新。
"""
with db_manager.get_session(db_name) as session:
# 用底层连接直接发 COMMIT,绕过 SQLAlchemy 事务层,真正刷新 ReadView
conn = session.connection()
conn.execute(text("COMMIT"))
yield session
当质疑”HTTP 请求使用相同连接池为何无此问题”时,Claude 狡辩称 HTTP 请求有写操作所以 commit() 是真实提交。
这一解释十分离谱,如果是这样,那http请求这么多只读操作,岂不是都会乱套?为此我在codeBuddy中切换ChatGPT5.3,查看历史会话消息
请它来独立评估一下。
三、ChatGPT 5.3 的正确评估
3.1 核心错误识别
ChatGPT 5.3 指出 Claude 的根本错误:将”事务级快照”误解为”连接级快照”。
3.2 关键论点
(1)ReadView 是事务级的,非连接级
在 MySQL InnoDB 中:
- ReadView 与事务绑定,非 TCP 连接永久绑定
- 事务正常结束(
COMMIT或ROLLBACK)后,ReadView 即销毁 - 下一次事务会建立新的 ReadView
结论:连接池复用 ≠ 快照复用。
(2)当前 Session 是短事务模式
@contextmanager
def get_session(self, db_name: str = 'adw'):
session = self._session_factories[db_name]()
try:
yield session
session.commit()
except Exception:
session.rollback()
raise
finally:
session.close()
每轮 DAO 调用新建 Session,使用结束后 commit() + close(),事务生命周期是短的。
(3)只读事务的 COMMIT 非空操作
COMMIT包含两层语义:结束事务(控制层)+ 刷脏页(持久化层)- 只读事务仅影响第 2 层,第 1 层(结束事务、销毁 ReadView)依然有效
- “无数据落盘”≠“事务未结束”
(4)底层 COMMIT 破坏 ORM 事务边界
手动对底层连接发 COMMIT 会让连接真实状态与 SQLAlchemy Session 维护的状态不一致,属于不规范写法。
(5)绕过 session.commit() 不合理
问题不在于”数据库不认”,而在于破坏 SQLAlchemy 对事务边界的一致性维护。
四、Claude 的自我反思
下面放一下claude认错原文截图,殊不知这么“专业”、“详实”的注释正式出自它自己笔下。😂

4.1 权威偏见
看到注释写得专业详实(“MVCC 快照”、“ReadView”、“autobegin”),便直接采信,未独立验证核心前提。
4.2 确认偏见
面对”HTTP 请求为何无此问题”的质疑,思维模式是”在已有结论框架内解释新问题”,而非”用新问题检验已有结论”。
4.3 模式切换延迟
直到用户直接问”这个说法是否正确”,才切换到”独立评估”模式。教训:关键判断应主动切换至此模式。
4.4 归因问题
将旧观点归因于”另一个 AI”模糊了自己犯错的事实,更诚实的做法应是直接承认错误。
五、结论
_get_fresh_session() 缺乏理论依据,应移除。
| 论点 | 实际情况 |
|---|---|
| ReadView 复用 | ❌ 事务级,非连接级 |
| 只读 commit 无效 | ❌ 结束事务语义依然有效 |
| 底层 COMMIT 更安全 | ❌ 破坏 ORM 事务边界 |
根本原因:Python enum 查询应使用 .value,而非枚举对象本身。