Python从SQLite4000万行表生成嵌套字典后期骤降速原因咨询
这个场景我太熟悉了——处理超大规模数据集时,前半段跑得飞起,快结束时突然“卡壳”,大概率是内存和数据结构的瓶颈在作祟,咱们来拆解具体原因:
1. 内存耗尽触发虚拟内存,速度直接跳水
你想想,4000万条数据塞进嵌套字典里,每一条都要存两个字符串键+一个值,再加上Python字典本身的哈希表结构开销,到3600万条的时候,这个字典的内存占用绝对是个天文数字——保守估计几个GB起步。
当系统物理内存不够用的时候,就会开始用虚拟内存(硬盘交换空间),硬盘的读写速度比内存慢几百上千倍,程序自然就会突然变慢,看起来像卡住了,但其实还在硬着头皮从硬盘里读写数据。你可以打开任务管理器或者top命令看看内存占用,肯定已经逼近系统内存上限了。
2. 字典哈希冲突加剧,操作耗时指数级上升
Python字典是靠哈希表实现的,当键的数量越来越多,哈希冲突的概率会大幅增加。到3600万条的时候,外层字典的键数量可能已经非常庞大,每次执行if dict_key_1 not in dictionary或者dictionary[dict_key_1].update(...)时,哈希表需要做更多的冲突处理、扩容操作,这些操作的耗时会随着数据量增长呈指数级上升,直接拖慢整个循环。
3. SQLite游标默认行为的隐形开销
你用的是默认的游标迭代方式,SQLite虽然会逐步加载结果,但当数据量极大时,加上内存压力的叠加,数据库的IO效率会急剧下降。另外,你没有设置游标的arraysize,每次从数据库取的数据量太小,频繁的IO交互会把耗时一点点累积起来,到后期彻底爆发。
4. 代码里的小bug雪上加霜
看你的代码,Counter_1计数到100万后,你重置的是counter=0(注意变量名大小写,你定义的是大写的Counter_1,重置的是小写的counter),这会导致之后再也不会打印进度,让你误以为程序完全卡住了,但其实它还在慢腾腾地运行。这个小错误直接让你失去了进度反馈,加剧了“突然变慢”的感知。
给你几个可行的优化方案
- 别把所有数据塞进内存:如果不是必须要全量嵌套字典,考虑分批次处理,或者用磁盘存储的结构(比如SQLite临时表、或者用
pandas分块读取)。 - 优化字典结构:用
collections.defaultdict替代手动判断键是否存在,减少一次in查询的开销:from collections import defaultdict dictionary = defaultdict(dict) # 直接赋值就行,不用再判断键是否存在 dictionary[dict_key_1][dict_key_2] = value - 给SQLite游标加
arraysize:每次从数据库读取更多数据,减少IO次数:cursor = conn.cursor() cursor.arraysize = 10000 # 一次读1万条,根据内存情况调整 cursor.execute(selection_query) - 修复计数bug:把
counter=0改成Counter_1 = 0,这样还能看到进度,知道程序没挂。 - 换更高效的数据结构:比如用
pandas的DataFrame处理,它的内存效率比纯字典高很多,还支持分块读取:import pandas as pd chunk_size = 100000 for chunk in pd.read_sql_query(selection_query, conn, chunksize=chunk_size): # 处理每个数据块 pass
内容的提问来源于stack exchange,提问作者Öykü Öngün

