bigywater.tech
Flask · 88

SQLAlchemy Identity Map

SQLAlchemy 里同一行数据会不会变成两个对象?session.get 什么时候不发 SQL?为什么另一个会话改了数据我这里却看不到?这篇用 SQLite 与两个 session 把身份映射的行为逐个量出来。

0 条
session.get 命中身份映射时发出的 SQL
弱引用
没人引用也没被修改的对象会从身份映射里消失
0 → 99
另一个会话改了数据后,普通重新查询与 populate_existing 读到的 score

一行一个对象

实测 · 本机执行输出
  用两种不同的条件查同一行:a is b → True(第二次仍然发了 1 条 SQL,但返回的是已有对象)
  不同的 session 里查同一行 → 不同对象: True
  identity key: (<class '__main__.User'>, (1,), None)

上一篇:N+1 查询问题;下一篇:Session Flush 与 Commit。

session.get

实测 · 本机执行输出
  已经加载过的主键:get 发了 0 条 SQL,x is a → True
  没加载过的主键:get 发了 1 条 SQL
  再 get 一次:0 条
  不存在的主键 999:1 条 SQL,返回 None;再 get 一次 999: 1 条(不存在的结果不缓存)

弱引用

实测 · 本机执行输出
  加载 5 个对象后 identity_map 大小: 5
  del 掉列表并 gc 之后: 0
  改了一个对象再丢掉引用并 gc: 1 (被修改的对象 session 持有强引用,直到 flush)

旧数据的代价

实测 · 本机执行输出
  session A 读到 score = 0
  另一个 session 把 score 改成 99 并提交之后,session A 重新查询:a2.score = 0 (a2 is a → True )
  populate_existing=True 重新查询:score = 99
  session.refresh(a) 之后:score = 100
  session.expire(a) 之后下一次读取:score = 101

重复主键

实测 · 本机执行输出
   IntegrityError: (sqlite3.IntegrityError) UNIQUE constraint failed: user.id

Flask 里的边界

实测 · 本机执行输出
  两个请求里 get 到的是不是同一个对象: False
  同一个请求里连续 get 两次,但不保留返回值:共发 2 条 SQL(第一次的对象没人引用,被回收了,第二次只好重新查)
  保留第一次的返回值再 get:共发 1 条 SQL

分步演示

SQLAlchemy Identity Map
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / Python 3.14.7 / Flask 3.1.3 / Werkzeug 3.1.9 / Jinja2 3.1.6 / SQLAlchemy 2.1.1 / gunicorn 26.2.0(都装在自己的虚拟环境里);大多数实验用 Flask 的 test_client(直接调用 WSGI 应用,不经过网络),需要真实 HTTP 时另起 gunicorn 或开发服务器(端口 9600 以上,本机 127.0.0.1);计时用 time.perf_counter,数字每次运行会有小幅抖动。Flask-SQLAlchemy 3.1.1、SQLite 文件库(两个 session 各自连接)
    • 没有验证:PostgreSQL 的 READ COMMITTED 之外的隔离级别对「看到旧数据」的影响、with_for_update 与身份映射的交互、跨线程共享对象、subclass 继承映射下的身份键
    京ICP备2026067502号-1京公网安备11010502063487号