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

Python sqlite3 备份进度回调怎样判断剩余页数

来源:17golang原创

时间:2026-10-09 09:59:53 264浏览 收藏

sqlite3.Connection.backup() 的进度回调不需要自己查询数据库大小:回调第二个参数 remaining 就是仍待复制的 SQLite 页数,第三个参数 total 是总页数。已复制页数可用 total - remaining 计算,完成百分比是 (total - remaining) / total * 100。第一个参数 status 则表示最近一次备份迭代的 SQLite 状态码。

官方文档:https://docs.python.org/3/library/sqlite3.html#sqlite3.Connection.backup

日常开发你可以先理清这几个要点
  • remaining 的单位是数据库页,不是行、字节或秒。
  • pages 决定每次迭代最多复制多少页,但最后一轮可能少于这个值。
  • remaining == 0 表示没有剩余页;结合 status == sqlite3.SQLITE_DONE 可识别完成迭代。
  • 源库被并发写入时,底层备份可能重启,remaining 可能回升,不能假定进度永远单调。

基线数据:progress 回调直接给出剩余页数

Python 文档给出的签名是 backup(target, *, pages=-1, progress=None, name='main', sleep=0.250)。只要把可调用对象传给 progress,每次备份迭代后就会收到三个整数:

参数含义适合监控的指标
status最近一次迭代的 SQLite 状态码复制中、完成或异常状态
remaining还需要复制的页面数剩余工作量
total源数据库总页面数进度分母

例如某次回调收到 remaining=768、total=1024,那么已复制 256 页,完成度为 25%。这是公式演示,不是某台机器的性能实测。

Python sqlite3 在线备份从源连接经 backup 迭代到目标连接,并把 status、remaining、total 交给进度回调的静态结构图
图1:备份迭代负责复制页面,progress 回调只接收最近一次迭代的状态与页面计数。

假设与公式:怎样从回调值算出四个指标

最稳妥的指标是“剩余页数”和“完成比例”。如果还想知道本次回调推进了多少页,需要保存上一次的 remaining:

指标计算方法注意点
剩余页数remaining回调已直接提供
已复制页数max(total - remaining, 0)不要用回调次数乘 pages
完成比例copied / total * 100先处理 total 为 0 的情况
本轮推进页数previous_remaining - remaining并发写入导致 remaining 回升时应视为重启或重新计数

“回调次数 × pages”不可靠:最后一轮可能不足 pages,锁竞争可能产生没有推进页面的迭代,并发写入还可能触发底层备份重启。

改动点:实现一个可复用的进度记录器

下面的记录器同时输出状态码、已复制页数、剩余页数、比例、单轮增量和耗时。它发现 remaining 比上次更大时,不再给出负数增量,而是标记为“重新计数”。

import sqlite3
import time


class BackupProgress:
    def __init__(self):
        # 使用单调时钟统计经过时间,避免系统时间调整干扰。
        self.started_at = time.monotonic()
        self.previous_remaining = None

    def __call__(self, status, remaining, total):
        # remaining 由回调直接提供;copied 不能用回调次数乘 pages 推算。
        copied = max(total - remaining, 0)
        percent = copied / total * 100 if total > 0 else 100.0

        restarted = (
            self.previous_remaining is not None
            and remaining > self.previous_remaining
        )
        if self.previous_remaining is None:
            step_pages = copied
        elif restarted:
            # 源库并发写入可能让备份重启,此时不输出负的批次增量。
            step_pages = 0
        else:
            step_pages = self.previous_remaining - remaining

        done_code = getattr(sqlite3, "SQLITE_DONE", 101)
        phase = "完成" if status == done_code and remaining == 0 else "复制中"
        elapsed = time.monotonic() - self.started_at
        restart_note = ",检测到重新计数" if restarted else ""

        print(
            f"{phase}: status={status}, copied={copied}/{total}, "
            f"remaining={remaining}, step={step_pages}, "
            f"progress={percent:.1f}%, elapsed={elapsed:.2f}s"
            f"{restart_note}"
        )
        self.previous_remaining = remaining

getattr(sqlite3, "SQLITE_DONE", 101) 让代码既能使用模块常量,也能在较旧运行环境中按 SQLite 的固定完成码回退。实际业务如果要推送 Prometheus、日志或 GUI,只需把 print 换成对应的指标更新逻辑。

total、remaining、copied、percent、previous remaining、step pages 与 status 之间计算关系的静态指标图
图2:remaining 是核心观测值;copied、percent 和 step_pages 都应从当前与上一次快照计算。

压测方法:用 pages 控制观察粒度

pages 表示每次迭代复制的页面数。它小于或等于 0 时,Python 会在单个步骤中复制整个数据库;这样回调仍可用,但通常只能看到很少的进度节点。需要更细的进度时,可以设置一个正整数,例如 128 或 256,再根据业务负载调整。

import sqlite3


source = sqlite3.connect("app.db")
target = sqlite3.connect("app-backup.db")
progress = BackupProgress()

try:
    # 每次最多复制 256 页,便于观察中间进度并缩短单次持锁区间。
    with target:
        source.backup(
            target,
            pages=256,
            progress=progress,
            sleep=0.05,
        )
finally:
    # 无论成功还是异常都关闭两个连接,避免文件描述符长期占用。
    target.close()
    source.close()

sleep 是备份剩余页面时连续尝试之间的等待秒数,不是每复制一批页面后固定暂停的性能节流器。官方说明还指出,在线备份可以在其他客户端或同一连接并发访问数据库时工作。

结果对比:应该看 remaining,还是看回调次数

观察方式能否判断剩余页数可靠性
直接记录 remaining可以最直接,来自最近一次备份迭代
total - remaining可以反推适合显示已完成页数和百分比
回调次数 × pages不能准确判断忽略最后一轮、锁竞争与重启
数据库文件字节数不能直接替代页数与文件布局、WAL 等因素不是同一指标
经过时间不能直接替代只能用于趋势,不能说明还剩多少页

如果要做吞吐量对比,应记录真实的 step_pages 与相邻回调的时间差,再计算 pages/s。不要给 remaining 随意乘一个固定值就宣称是精确剩余字节,也不要在只有一两个样本时给出稳定 ETA。

边界条件:为什么 remaining 可能突然变大

SQLite 官方在线备份文档说明:如果另一线程或进程在备份暂停期间写入源数据库,SQLite 会检测变化,并且通常在下一次备份步骤时重启备份。因此,remaining 和 total 是最近一次 backup_step 保存的快照,不是每次读取时重新扫描源数据库得到的绝对实时值。

这会带来三个工程结论:

  • 进度条可以短暂回退,不要用 assert remaining 作为正确性检查。
  • 源库持续高频写入时,备份可能反复重启,完成时间会明显拉长。
  • 最终成功返回时,目标库仍是源库的一致、最新快照;中间进度值主要用于观测,而不是事务证明。

常见问题

remaining 为 0 就一定备份成功了吗?

它表示没有待复制页面。记录器同时检查 status == sqlite3.SQLITE_DONE,可以更明确地识别完成迭代;整个 backup() 正常返回才是调用层面的成功结果,异常仍应由外围错误处理捕获。

pages=256 是否代表每次回调一定复制 256 页?

不一定。它是单次迭代的页面数量设置,最后一轮可能不足 256 页,遇到忙或锁定状态时也可能没有产生预期增量。

能不能用 remaining 直接计算剩余秒数?

不能直接计算。需要用多次回调得到近期 pages/s,再做近似估算;一旦源库并发写入触发重启,旧速率和旧分母都可能失真。

total 为 0 时百分比怎么处理?

空数据库或特殊初始状态下要避免除零。示例把 total == 0 视为 100%,同时仍以 status 和 remaining 判断阶段。

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