登录
推荐 文章 Go 技术 课程 下载 专题 AI
首页 >  文章 >  python教程

Python sqlite3 URI 文件名怎样打开只读数据库

来源:17golang原创

时间:2026-11-01 07:04:56 218浏览 收藏

Python sqlite3 打开已有数据库并限制主库写入,使用 file: 文件 URI,加上 mode=ro,并在 sqlite3.connect() 中显式传入 uri=True。例如 file:/srv/reports/catalog.db?mode=ro。主库不存在时应打开失败,不会像普通可创建连接那样顺手生成一个空库。

更稳妥的做法是先把本地路径转换成正确编码的文件 URI,再附加由程序固定的参数。不要把普通路径拼接一个问号就当作只读连接,也不要把 immutable=1 当作所有只读场景都能使用的补丁。下面围绕“报表脚本打开了错误文件”这一假设场景说明写法与排查顺序;代码是说明性示例,没有在本机执行数据库操作。

Python sqlite3 官方地址:https://docs.python.org/3/library/sqlite3.html

SQLite 文件 URI 官方地址:https://www.sqlite.org/uri.html

先分清打开失败和误建空数据库

报表脚本原本从项目目录运行,后来改由调度器启动。相对路径仍写成 catalog.db,但工作目录变了,普通连接可能在新位置创建空数据库。此时“没有业务表”不一定是 SQL 写错,也可能根本连错了文件。

对只负责读取已有报表库的程序,应该明确规定文件必须存在,并用 mode=ro 表达打开方式。另一种 mode=rw 也要求已有数据库,但允许写入;mode=rwc 则允许不存在时创建。不要把“没有调用 INSERT”当作连接已经只读的证据。

方式主库不存在预期用途
mode=ro打开失败读取已有主库
mode=rw打开失败读写已有主库
mode=rwc可以创建允许初始化新库

如果需求是读取一个稳定发布的数据包,可以把配置设为明确的绝对路径。如果允许相对路径,应把它相对于哪一个目录解释写进配置约定,避免依赖调度器的默认目录。路径的归属决定了读取对象,URI 参数决定连接行为,两者都需要明确。

用文件 URI 与 mode=ro 建立连接

下面的函数接受普通本地文件路径,检查已存在的文件,再由 Path.as_uri 构造 URI。它只列出 sqlite_schema 中的表名,不依赖某张示例业务表。调用方把真实数据库路径替换进配置即可。

from contextlib import closing
from pathlib import Path
import sqlite3

def list_table_names(db_file: str) -> list[str]:
    # 先固定绝对路径,缺失文件在连接前就给出明确异常。
    path = Path(db_file).expanduser().resolve(strict=True)
    if not path.is_file():
        raise ValueError("数据库路径必须指向普通文件")

    # as_uri 编码路径;只读参数由本函数固定,调用者不传入任意 URI。
    db_uri = path.as_uri() + "?mode=ro"
    with closing(sqlite3.connect(db_uri, uri=True)) as conn:
        # 查询只读的结构元数据;游标和连接都显式关闭。
        with closing(conn.execute(
            "SELECT name FROM sqlite_schema "
            "WHERE type = 'table' ORDER BY name"
        )) as cursor:
            return [row[0] for row in cursor.fetchall()]

这里 sqlite3.connect 的 URI 识别开关与 URI 内的打开模式分别承担职责。不要省略 uri=True,也不要给普通文件名附加参数后依赖运行环境猜测。此函数接收路径而不是外部传入的整条 URI,是为了让打开参数始终由代码掌握。

本地路径、文件 URI、URI 识别和只读主库连接的静态关系
图1:只读 URI 结构说明图。路径编码与查询参数属于不同部分,uri=True 控制识别,mode=ro 约束目标主库的打开方式;连线不是执行步骤。

连接上下文管理器主要负责事务提交或回滚,并不等同于自动关闭连接。示例使用 contextlib.closing 处理资源生命周期,避免脚本结束前一直占用文件句柄。resolve 的存在性检查有助于诊断,但不是抵御路径替换的文件系统安全边界;如果路径来自不可信输入,仍应另行限制可访问目录。

文件名里的问号和井号必须编码

在 URI 中,问号标记查询参数开始,井号引入片段。若本地文件名恰好含这些字符,直接写成 file: 加原路径可能把部分文件名解释为 URI 结构。Path.as_uri 会生成相应编码,之后附加的 mode=ro 才是程序想要的查询部分。

要特别区分“原始路径”和“已经编码的 URI”。本函数不接受 file: 开头的配置值,不要对现成 URI 再调用 Path,也不要把 %23 先手动塞进普通文件名再交给 as_uri。配置中保存真实路径,编码步骤只做一次,比较容易定位问题。

本例只处理运行机器上的本地普通文件。跨平台路径应由目标平台自己的 Path 处理,不要把 Windows 盘符反斜杠直接粘到 Unix 文件 URI 中。网络路径和特定 VFS 的行为需要单独确认,不能由本地示例推断它们都能工作。

WAL 数据库还依赖哪些文件

如果目标库处于 WAL 模式,只看 catalog.db 是否可读还不够。SQLite 的 WAL 文档说明,较新版本支持只读打开,但需要满足相应条件,例如配套 -wal 与 -shm 文件已存在且可读,或所在目录允许创建它们。应同时了解 Python 实际链接的 SQLite 库版本,而不是只看 Python 的版本号。

因此 mode=ro 不等于数据库目录完全不会产生辅助文件,更不等于整个进程失去所有写入能力。它约束的是该主库的打开方式;若程序之后连接其他库或 ATTACH,仍需要对那些对象分别设置策略。

主数据库、WAL 文件、共享内存文件和只读连接的静态依赖关系
图2:WAL 文件依赖结构说明图。主库、配套文件与目录权限共同影响只读访问;图不表示所有文件在任何模式下都必须同时存在,也不表示可以手工删除 WAL。

不要为让连接成功就删除 -wal 或 -shm,也不要只复制正在写入的主库文件后当作完整备份。WAL 可能包含已提交但尚未合入主文件的内容。需要离线报表副本时,应使用受控的 SQLite 备份或导出流程,由数据提供方交付一致的数据包。

immutable=1 声明底层文件确实不会被任何进程改变,SQLite 会据此跳过锁与变化检测。若其他进程还在更新该文件,这个声明就不成立,可能得到错误结果或错误报告。只有真实不可变且一致的部署快照才值得评估它;对于活跃业务库,先修正实际文件依赖。

按错误层次修正连接条件

排查时先确定报错发生在路径解析、connect,还是查询阶段。文件不存在可以检查发布位置和配置目录;连接失败可以检查父目录可遍历性、文件访问权限、挂载状态及 WAL 配套文件;已连接但提示没有表,则检查目标文件是不是预期数据包。不要对所有问题统一改成 mode=rwc。

现象优先查看修正方向
Path 报文件不存在部署路径、相对目录修正配置,避免自动造库
无法打开数据库权限、URI、WAL 依赖恢复合法可读条件
查询找不到业务表实际目标库、数据包版本确认文件归属与结构
业务代码尝试修改主库失败调用用途与写语句来源拆开报表读取与业务写入

在自己的环境中,读者可以用已知只读副本核对表名和关键记录,并确认不存在路径仍会失败。若要演练写入拒绝,应针对专门的临时副本,不能拿生产库随意尝试写入。这里仅说明读者的集成观察目标,没有把未执行的结果当作证据。

对于连接异常,日志可记录阶段、受控路径标识和异常类型;不要直接把整条敏感路径或业务数据输出到公共日志。只读报表程序的目的,是清楚地失败在错误对象或不满足条件的输入上,而不是通过放宽模式掩盖配置错误。

只读连接的三个常见问题

加 uri=True 就只读了吗?

没有。它开启 URI 解析,打开模式由 mode 等参数决定。只读写法需要两者配合,而且目标需要是合法 file URI。

mode=ro 能防止别人改数据库吗?

不能。其他合法写连接仍可能修改活跃数据库。需要稳定时间点的数据时,应设计快照或事务读取策略,不能把只读等同于不可变。

把文件权限改成只读后,为什么还打不开?

还可能涉及父目录访问、挂载与 WAL 辅助文件条件。文件系统权限和 SQLite 打开模式是两层约束,逐层检查才能定位,不要直接加 immutable 或关闭锁来绕过问题。

参考:Python sqlite3 连接与资源管理、SQLite URI 参数、SQLite WAL 与只读数据库。

声明:本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
相关阅读
更多>
最新阅读
更多>
课程推荐
更多>