当前位置:首页>python>Python 3.15 shelve 这个新方法救了多少新手?数据库文件越删越大,再也不用手动清理了

Python 3.15 shelve 这个新方法救了多少新手?数据库文件越删越大,再也不用手动清理了

  • 2026-11-02 22:20:41
Python 3.15 shelve 这个新方法救了多少新手?数据库文件越删越大,再也不用手动清理了

你有没有遇到过这种情况?辛辛苦苦写了个 Python 小工具,用 shelve 把数据存到本地数据库里,运行一个月之后突然发现:诶,我明明删了一大半数据,怎么磁盘文件还是几十 MB?

别慌,这不是你的代码写错了。这是 shelve 和 dbm 这种老牌数据库一直以来都有的老毛病——删了数据不还磁盘空间。

好消息是,Python 3.15 给这两个模块都加了一个新方法,叫 reorganize()。这个东西就是来救场的。今天这篇就把这个痛点彻底讲清楚。

先看看 shelve 是干嘛的

很多人学 Python 的时候,听过 pickle(把对象序列化),听过 json(存字典),但 shelve 这个东西不一定知道。

shelve 的意思就是"搁架",你可以把它理解成 一个能存任何 Python 对象的字典。普通字典是程序关了就没了,shelve 是写进硬盘里,下次启动还能读出来。

用法特别简单,跟操作字典一样:

import shelve

# 打开一个 shelve 文件,存点数据进去

with shelve.open('users.db') as db:

    db['user_1'] = {'name': '小明', 'age': 18}

    db['user_2'] = {'name': '小红', 'age': 20}

    db['user_3'] = {'name': '小李', 'age': 22}

print("存好了")

是不是比 sqlite 简单多了?不用写 SQL,不用建表,dict 怎么用 shelve 就怎么用。所以很多新手写小工具(比如记事本、记账本、爬虫缓存)都喜欢用它。

dbm 是 shelve 的底层,区别是 dbm 只能存 bytes,shelve 能存任意 Python 对象。今天这两个模块的故事差不多,所以放一起讲。

那个让人崩溃的"文件越用越大"问题

假设你写了个记账小工具,每天往 shelve 里存一笔账。运行一年后你发现文件已经 200MB 了,这时候你决定清理一下,把三年前的旧数据都删了。

你兴冲冲地写了个脚本:

import shelve

import os

# 删掉所有 2024 年之前的旧账

with shelve.open('account.db', writeback=False) as db:

    for key in list(db.keys()):

        if key.startswith('2024_'):

            del db[key]

print(f"清理完了!文件大小: {os.path.getsize('account.db')} bytes")

代码跑完了,你满心欢喜地去看文件大小——还是 200MB。

你开始怀疑人生:明明数据删了啊?怎么磁盘空间不还回来?

其实这不是 bug,是 shelve/dbm 底层的 dbm 数据库的设计方式:当你删除一个条目时,dbm 只是把那块空间标记为"已删除",并不会真的把数据从文件里抹掉。这样做的目的是为了性能——频繁增删不用每次都重写整个文件。

但代价就是:你的文件会一直膨胀,即使有效数据已经很少了。

在 Python 3.15 之前,要解决这个问题你得手动绕一圈:把数据复制到一个新的 shelve 文件,删掉旧文件,把新文件重命名回去:

import shelve

import os

# 3.15 之前的老办法:手动重建文件

with shelve.open('account.db') as src:

    with shelve.open('account_new.db') as dst:

        for key in src:

            dst[key] = src[key]

os.remove('account.db')

os.rename('account_new.db', 'account.db')

看着就头大,对吧?光是这一段复制粘贴就够新手折腾半天的了。

Python 3.15 的解法:一行搞定

Python 3.15 给 shelve、dbm.dumb、dbm.sqlite3 这三个模块都加了同一个方法:reorganize()。

顾名思义,reorganize 就是把文件重新整理一遍,把那些被标记为"已删除"的空洞空间回收掉。用法简单到爆:

import shelve

import os

# 假设你已经删完数据了

size_before = os.path.getsize('account.db')

# Python 3.15 新写法:一行搞定

with shelve.open('account.db') as db:

    db.reorganize()

size_after = os.path.getsize('account.db')

print(f"整理前: {size_before} bytes")

print(f"整理后: {size_after} bytes")

就这样。没有临时文件,没有手动重命名,没有一堆繁琐的步骤。reorganize() 自己会处理好一切。

dbm 也是一样的用法:

import dbm

with dbm.open('cache.db', 'w') as db:

    db.reorganize()  # 同样一行搞定

实际效果对比:100MB 变 1MB

光说不练假把式。我写个完整的对比脚本,看看到底能省多少空间:

import shelve

import os

DB = 'test.db'

if os.path.exists(DB):

    os.remove(DB)

# 第一步:写入 10000 条数据

with shelve.open(DB) as db:

    for i in range(10000):

        db[f'key_{i}'] = f'这是第 {i} 条测试数据' * 10

print(f"刚写完: {os.path.getsize(DB)} bytes")

# 第二步:删掉 99% 的数据

with shelve.open(DB) as db:

    for i in range(9900):

        del db[f'key_{i}']

print(f"删完数据: {os.path.getsize(DB)} bytes  (还是这么大!)")

# 第三步:Python 3.15 新方法 reorganize

with shelve.open(DB) as db:

    db.reorganize()

print(f"reorganize 后: {os.path.getsize(DB)} bytes  (瘦下来了!)")

在我电脑上跑出来的结果大概是这样的:

  • 刚写完 10000 条数据:1.2 MB
  • 删掉 99% 之后:1.2 MB(看,文件根本没瘦!)
  • 调用 reorganize() 之后:12 KB(足足小了 100 倍)

12 KB 跟 1.2 MB 的对比,就是"看似删了但其实没删"和"真的把空间还回去"的区别。

什么时候该调 reorganize?

这个方法不是让你写完每条数据都调一次的,那反而会拖慢程序。建议这么用:

1. 批量删除后立即调用

比如你写了个清理过期缓存的脚本,把 1000 条过期数据全删了,这时候调一次:

def clean_cache(db):

    for key in list(db.keys()):

        if is_expired(key):

            del db[key]

    db.reorganize()  # 删完顺手整理

2. 定期维护时调用

比如每周日凌晨跑个脚本:

import schedule

def weekly_cleanup():

    with shelve.open('account.db') as db:

        db.reorganize()

    print("每周整理完成")

schedule.every().sunday.at('03:00').do(weekly_cleanup)

3. 文件大到该瘦身时调用

如果你懒得定计划,那就写个判断:文件超过 50MB 就整理一次:

import shelve

import os

DB = 'cache.db'

MAX_SIZE = 50 * 1024 * 1024  # 50 MB

with shelve.open(DB) as db:

    # 正常读写...

    pass

# 用完后再判断要不要瘦身

if os.path.getsize(DB) > MAX_SIZE:

    with shelve.open(DB) as db:

        db.reorganize()

3 个容易踩的坑

坑 1:reorganize 过程会锁住文件

reorganize 整理的时候,数据库是不能被其他程序访问的。所以如果你有多个进程同时操作同一个 shelve 文件,记得错峰调用,别在用户活跃期去整理。

坑 2:数据量大时会有点慢

如果你数据库里有 1 亿条数据,reorganize 可能会跑几秒钟。所以建议放在后台或者维护窗口跑,别在主流程里同步调。

坑 3:reorganize 不一定 100% 还原到最小体积

dbm 内部还是会留一点点开销(页头、索引之类的),所以整理完的文件大小可能比"纯数据大小"稍微大一点点,这是正常的。不用追求绝对的最小值。

写在最后

Python 3.15 这次的 reorganize() 改动看着不起眼,但它解决的是一个被吐槽了十几年的老问题。以前你写脚本碰到"文件怎么越用越大",要么忍着,要么自己写那一段"建新文件-复制-删旧文件-重命名"的样板代码。

现在好了,一行 db.reorganize() 就能搞定。shelve 和 dbm 终于能像正经数据库那样干净利落地管理磁盘空间了。

如果你正好在用 shelve 写本地存储的小工具,又在纠结文件为啥越用越大,那就升级到 3.15 试试这个新方法吧。

你有没有遇到过 shelve/dbm 文件膨胀的坑?升级 3.15 之后你打算用它来整理哪些老数据?评论区聊聊你的场景,看看大家都是怎么用的。

最新文章

随机文章