SQLite TEMP 同名表:主表没有改,为什么 SELECT 却读到另一份内容

前天 3阅读

调试脚本建立了一张叫item的临时表,后面原封不动执行SELECT label FROM item,结果突然变成另一份数据。主库中的item可能完全没有改变,只是未带数据库名的表引用先找到了temp里的同名对象。查询文本相同,不代表运行时一定指向同一张表。

下面的完整程序使用Python标准库sqlite3,保存为demo.py并运行python3 demo.py。已在CPython 3.12.14与SQLite 3.53.1实跑。main是新建的内存数据库,temp表也由本程序创建;演示中的UPDATE和DROP只作用于这些固定测试对象,结束时关闭连接。

SQLite TEMP 同名表:主表没有改,为什么 SELECT 却读到另一份内容

AI模型生成概念图:前方临时柜先接到普通查找,另一条明确路径绕过它到达后方主柜,用于解释同名表遮蔽;不是实际数据库管理界面。

完整实验程序

先只创建main.item并写入main-value,再建立temp.item写入temp-value。同样的主键与列结构让差异只来自对象所在的schema。每次既查询未限定名称,也查询明确的main或temp名称。

完整可运行程序

import sqlite3

db = sqlite3.connect(':memory:', isolation_level=None)
try:
    db.execute('CREATE TABLE main.item(id INTEGER PRIMARY KEY, label TEXT)')
    db.execute("INSERT INTO main.item VALUES(1, 'main-value')")
    before = db.execute('SELECT label FROM item WHERE id=1').fetchone()[0]
    assert before == 'main-value'
    print('before TEMP:', before)

    db.execute('CREATE TEMP TABLE item(id INTEGER PRIMARY KEY, label TEXT)')
    db.execute("INSERT INTO temp.item VALUES(1, 'temp-value')")
    plain = db.execute('SELECT label FROM item WHERE id=1').fetchone()[0]
    main = db.execute('SELECT label FROM main.item WHERE id=1').fetchone()[0]
    assert (plain, main) == ('temp-value', 'main-value')
    print('unqualified / main:', plain, '/', main)

    db.execute("UPDATE item SET label='temp-edited' WHERE id=1")
    temp = db.execute('SELECT label FROM temp.item WHERE id=1').fetchone()[0]
    main = db.execute('SELECT label FROM main.item WHERE id=1').fetchone()[0]
    assert (temp, main) == ('temp-edited', 'main-value')
    print('after unqualified UPDATE:', temp, '/', main)

    db.execute('DROP TABLE temp.item')
    after = db.execute('SELECT label FROM item WHERE id=1').fetchone()[0]
    assert after == 'main-value'
    print('after dropping TEMP:', after)
    assert db.execute("SELECT count(*) FROM temp.sqlite_schema WHERE name='item'").fetchone()[0] == 0
    assert db.execute("SELECT count(*) FROM main.sqlite_schema WHERE name='item'").fetchone()[0] == 1
    print('catalogs: temp=0 main=1')
finally:
    db.close()

本次实际输出

before TEMP: main-value
unqualified / main: temp-value / main-value
after unqualified UPDATE: temp-edited / main-value
after dropping TEMP: main-value
catalogs: temp=0 main=1

临时表优先被找到,主表仍然存在

第一行before TEMP是main-value,因为当时没有更优先的同名临时表。建立temp.item以后,unqualified / main左侧变为temp-value,右侧仍是main-value。两份数据同时存在,数据库没有把主表内容覆盖掉。

对于这里的普通未限定表引用,SQLite先搜索temp,再搜索main,随后才是附加数据库。显式写main.item则只在main中查找,不会因为temp存在同名表就改道。main和temp在这里是数据库schema名,不是表的别名。

同名遮蔽也会改变更新目标

第三行after unqualified UPDATE显示temp-edited / main-value。语句只写UPDATE item,因而修改的是优先解析到的temp.item;main.item仍保留初值。只看execute成功,或者看到一条记录被更新,无法确认动作落在预期数据库。

给需要固定访问主库的语句加上main限定,可以把目标意图写进SQL。临时工作表也应明确使用temp限定,并尽量取容易区分的名称。若业务本来就有意用临时表替换查询来源,应把这个连接状态作为明确前提,而不是依赖某段初始化偶然留下的对象。

去掉遮蔽物后,原来的名称又找到主表

程序随后明确执行DROP TABLE temp.item,再用相同的未限定SELECT查询,after dropping TEMP重新得到main-value。这个结果来自名称解析重新命中主表,不能解释成“刚才的更新被撤销了”;被更新和被删除的始终是临时表。

最后分别查询temp.sqlite_schema与main.sqlite_schema,得到temp=0、main=1,确认同名临时表已不存在、主表仍存在。演示特意给DROP写完整schema,避免读者把一个未限定删除机械搬到真实数据库;清理时应先核对准确目标和使用场景。

连接生命周期比一次函数调用更长

TEMP表只对创建它的连接可见,连接关闭后清理。它不会因为创建它的辅助函数返回就立刻消失。如果连接池保留这个连接,下一段逻辑仍可能遇到遗留临时表;排查偶发查询差异时,应同时记录使用的连接及其临时schema状态。

TEMP也不表示数据库承诺全部数据永远只在内存里。临时存储位置受构建和配置等因素影响,本文不依据表名推断磁盘行为。本例的main显式使用:memory:,只是为隔离实验,并不把主库名main等同于磁盘文件。

这个实验没有创建同名CTE、触发器或附加数据库。它说明的是普通表名在main与temp之间的解析,不应直接扩展为所有SQL名称查找规则。回归测试可保留临时表不存在、存在、被明确清理三种状态,并在每轮读取时验证真正的目标schema。

参考资料

官方资料核验日期:2026年10月2日。上述输出来自本文完整程序的本地执行,断言全部通过,退出状态为零。

文章版权声明:除非注明,否则均为云鹊BLOG原创文章,转载或复制请以超链接形式并注明出处。