如何将字典快速插入SQLite数据库?超大数据量插入优化求助
优化Stack Overflow超大XML数据插入SQLite的实战方案
我太懂你现在的崩溃了——几百GB的Stack Overflow XML dump,单条插入的速度慢到让人怀疑人生。针对你的代码,我整理了几个立竿见影的优化点,直接上具体修改:
1. 复用数据库连接,砍掉频繁创建/关闭的开销
你的savingDataToDatabase函数每次插入都要打开和关闭连接,这是性能杀手之一。数据库连接的建立和销毁本身就有不小的开销,放在百万级数据量下,这个浪费会被放大无数倍。
修改思路:把连接的创建移到整个数据处理流程的最外层,全程复用同一个连接,最后再统一关闭。
修改后的代码示例:
# 初始化数据库连接,只执行一次 def init_db_connection(): conn = sqlite3.connect("database.db") # 关闭自动提交,手动控制事务更高效 conn.isolation_level = None cursor = conn.cursor() # 提前创建好需要的表 createTable("Users") createTable("Posts") return conn, cursor # 修改插入函数,不再单独处理连接 def savingDataToDatabase(cursor, tableName, element): if tableName == "Users": insertStatement = sqlInsertStatement(tableName) cursor.execute(insertStatement, [element["AccountId"], element["Reputation"], element["CreationDate"], element["CreationTime"], element["DisplayName"], element["LastAccessDate"], element["WebsiteUrl"], element["Location"], element["AboutMe"], element["Views"], element["UpVotes"], element["DownVotes"], element["Age"]])
2. 改用批量插入(executemany),直接把速度拉上去
SQLite的单条execute写入效率极低,而executemany可以一次性插入多条数据,大幅减少磁盘IO的次数。我们可以在处理XML时先收集一批数据(比如每10000条),再一次性插入。
修改思路:新增数据缓存,达到批量阈值后再执行插入,最后处理剩余的零散数据。
修改后的代码示例:
# 定义批量大小,可根据内存情况调整,比如10000条 BATCH_SIZE = 10000 def processingDataForSQL(filename, element, cursor, conn, data_cache): if filename == 'Users': user = processingUsersXML(element) data_cache[filename].append( [user["AccountId"], user["Reputation"], user["CreationDate"], user["CreationTime"], user["DisplayName"], user["LastAccessDate"], user["WebsiteUrl"], user["Location"], user["AboutMe"], user["Views"], user["UpVotes"], user["DownVotes"], user["Age"]] ) # 达到批量大小就插入并提交事务 if len(data_cache[filename]) >= BATCH_SIZE: insert_batch(cursor, conn, filename, data_cache[filename]) data_cache[filename] = [] def insert_batch(cursor, conn, tableName, batch_data): insertStatement = sqlInsertStatement(tableName) cursor.executemany(insertStatement, batch_data) # 手动提交事务,减少磁盘同步次数 conn.commit() def getDataFromXml(filename, cursor, conn, data_cache): for evt, elem in iterparse('/../usws/stackoverflowDataScience/dumpData/'+str(filename)+'.xml', events=('end',)): if elem.tag == 'row': element_fields = elem.attrib processingDataForSQL(filename, element_fields, cursor, conn, data_cache) elem.clear() # 处理最后一批不足阈值的数据 if data_cache[filename]: insert_batch(cursor, conn, filename, data_cache[filename]) def chosenXMLFile(): # 初始化连接和数据缓存 conn, cursor = init_db_connection() data_cache = {"Users": [], "Posts": []} try: getDataFromXml('Users', cursor, conn, data_cache) getDataFromXml('Posts', cursor, conn, data_cache) finally: # 最后统一关闭连接 conn.close() chosenXMLFile()
3. 开启SQLite写入优化参数,榨干性能
SQLite默认配置不是为大批量写入优化的,我们可以设置几个参数来提升性能:
PRAGMA journal_mode = WAL;:开启Write-Ahead Logging,大幅提升写入性能(即使单线程也能减少磁盘同步开销)PRAGMA synchronous = NORMAL;:降低同步级别,减少磁盘IO等待(如果对数据安全性要求极高,可保持默认的FULL)PRAGMA cache_size = -200000;:增大缓存,这里设置为200MB,可根据你的内存情况调整
把这些参数加到初始化函数里:
def init_db_connection(): conn = sqlite3.connect("database.db") cursor = conn.cursor() # 写入优化参数 cursor.execute("PRAGMA journal_mode = WAL;") cursor.execute("PRAGMA synchronous = NORMAL;") cursor.execute("PRAGMA cache_size = -200000;") # 200MB缓存 conn.isolation_level = None createTable("Users") createTable("Posts") return conn, cursor
4. 额外小优化
- 提前为每个表生成一次插入语句并缓存,避免每次调用
sqlInsertStatement时重复拼接字符串的开销。 - 如果
processingUsersXML里有耗时操作,可考虑用多线程解析XML(但SQLite的WAL模式下建议单线程写入,避免锁竞争)。不过先做好前面的优化,速度已经能提升一个数量级了。
这些优化落地后,插入速度应该能从每秒几十条跃升到每秒几万条,处理800万用户数据只需要几分钟,而不是熬几小时。
内容的提问来源于stack exchange,提问作者JoshED
相关产品推荐
相关产品推荐

