处理大型(10GB+)文本文件的最佳遍历方式是什么?
我需要处理一个非常大的文本文件,大小有好几个GB(具体来说是一个区域文件)。我需要对区域文件中的每一条记录运行几个查询,然后把结果存储到一个可以搜索的数据库里。
目前我选择的工具主要是因为我熟悉它们,分别是Python和MySQL。不过,我不太确定这两个工具能否处理这么大的文件。
有没有在这方面有经验的人能给我一些建议,怎样才能打开并遍历这个文件而不让我的系统崩溃?还有,一旦我能打开文件,处理文件的最有效方法是什么(比如使用多线程)以及如何存储处理后的数据?
2 个回答
@Zack Bloom的回答非常棒,我给了他点赞。这里有几点我的想法:
正如他所展示的,使用
with open(filename) as f:
和for line in f
就足够了。open()
函数会返回一个迭代器,每次给你一行文件内容。如果你想把每一行都放进数据库,就在循环里做。如果你只想要某些符合特定规则的行,那也很简单。
import re pat = re.compile(some_pattern) with open(filename) as f: for line in f: if not pat.search(line): continue # do the work to insert the line here
如果文件有好几个GB大,你可能会遇到输入输出的瓶颈。所以其实没必要担心多线程之类的。即使运行几个正则表达式,处理数据的速度也可能比读取文件或更新数据库还快。
就我个人而言,我对数据库不是很在行,喜欢用ORM(对象关系映射)。我上一个做数据库的项目中,用的是Autumn和SQLite。我发现ORM的默认设置是每插入一条记录就提交一次,这样插入很多记录会非常慢。所以我对Autumn进行了扩展,让你可以把多条插入操作放在一个提交中;这样速度快多了。(嗯,我应该让Autumn支持Python的
with
语句,这样你可以把多条插入放在一个with
块里,Autumn会自动提交。)
总之,我想说的是,处理数据库时,如果方法不对可能会非常慢。如果你发现插入数据库是个瓶颈,可能有办法解决这个问题,而Zack Bloom的回答里有几个不错的建议可以帮助你入手。
在MySQL中存储这么多数据其实没什么大问题,不过你可能无法把整个数据库都放在内存里,所以要做好可能会有一些输入输出性能问题的准备。像往常一样,运行查询之前一定要确保你有合适的索引。
最重要的是,不要试图把整个文件一次性加载到内存中。应该逐行读取文件,而不是用像readlines这样的方式,这样会一次性把整个文件都加载进来。
确保请求是批量处理的。一次加载几千行,然后把它们一起发送一个大的SQL请求。
这种方法应该有效:
def push_batch(batch):
# Send a big INSERT request to MySQL
current_batch = []
with open('filename') as f:
for line in f:
batch.append(line)
if len(current_batch) > 1000:
push_batch(current_batch)
current_batch = []
push_batch(current_batch)
区域文件的格式通常比较规范,可以考虑直接使用LOAD DATA INFILE。你也可以考虑创建一个命名管道,从Python中把部分格式化的数据推送进去,然后用LOAD DATA INFILE从MySQL中读取。
MySQL有一些很好的建议来优化插入速度,这里有一些重点:
- 在每个插入语句中使用多个值列表。
- 使用INSERT DELAYED,特别是当你同时从多个客户端推送数据时(比如使用线程)。
- 在插入之前锁定你的表。
- 调整key_buffer_size和bulk_insert_buffer_size。
最快的处理会在MySQL中完成,所以考虑一下是否可以在数据进入数据库后再进行你需要的查询,而不是在之前。如果你确实需要在Python中进行操作,使用线程也不会有帮助。因为在同一时间内,只有一个Python线程可以执行(这叫做GIL),所以除非你在做一些耗时的C语言操作,或者与外部资源交互,否则你最终也只会在一个线程中运行。
最重要的优化问题是是什么限制了速度,如果数据库是瓶颈,那就没必要启动很多线程去读取文件。真正知道的方法就是尝试一下,进行调整,直到速度满足你的需求。