我想使用Python向SQLite插入100万条记录。我尝试了多种方法来对其进行改进,但仍然不是很满意。数据库将文件加载到内存的时间为0.23秒(以下搜索pass
),而SQLite加载时间为1.77秒,然后插入文件。
Intel Core i7-7700 @ 3.6GHz
16GB RAM
Micron 1100 256GB SSD,Windows 10 x64
Python 3.6.5 Minconda
sqlite3.version 2.6.0
我以与我的真实数据相同的格式生成了100万个测试输入数据。
import time
start_time = time.time()
with open('input.ssv', 'w') as out:
symbols = ['AUDUSD','EURUSD','GBPUSD','NZDUSD','USDCAD','USDCHF','USDJPY','USDCNY','USDHKD']
lines = []
for i in range(0,1*1000*1000):
q1, r1, q2, r2 = i//100000, i%100000, (i+1)//100000, (i+1)%100000
line = '{} {}.{:05d} {}.{:05d}'.format(symbols[i%len(symbols)], q1, r1, q2, r2)
lines.append(line)
out.write('\n'.join(lines))
print(time.time()-start_time, i)
测试数据如下。
AUDUSD 0.00000 0.00001
EURUSD 0.00001 0.00002
GBPUSD 0.00002 0.00003
NZDUSD 0.00003 0.00004
USDCAD 0.00004 0.00005
...
USDCHF 9.99995 9.99996
USDJPY 9.99996 9.99997
USDCNY 9.99997 9.99998
USDHKD 9.99998 9.99999
AUDUSD 9.99999 10.00000
// total 1 million of lines, taken 1.38 second for Python code to generate to disk
Windows正确显示23,999,999字节的文件大小。
import time
class Timer:
def __enter__(self):
self.start = time.time()
return self
def __exit__(self, *args):
elapsed = time.time()-self.start
print('Imported in {:.2f} seconds or {:.0f} per second'.format(elapsed, 1*1000*1000/elapsed))
with Timer() as t:
with open('input.ssv', 'r') as infile:
infile.read()
with open('input.ssv', 'r') as infile:
infile.read()
以0.13秒或每秒7.6 M的速度导入
它测试读取速度。
with open('input.ssv', 'r') as infile:
with open('output.ssv', 'w') as outfile:
outfile.write(infile.read()) // insert here
以0.26秒或每秒3.84 M的速度导入
它可以在不解析任何内容的情况下测试读写速度
with open('input.ssv', 'r') as infile:
lines = infile.read().splitlines()
for line in lines:
pass # do insert here
以0.23秒或每秒4.32 M的速度导入
当我逐行解析数据时,它会获得很高的输出。
这使我们对测试机上的IO和字符串处理操作有多快了解。
outfile.write(line)
以0.52秒或每秒1.93 M的速度导入
tokens = line.split()
sym, bid, ask = tokens[0], float(tokens[1]), float(tokens[2])
outfile.write('{} {:.5f} {%.5f}\n'.format(sym, bid, ask)) // real insert here
以2.25秒或每秒445 K的速度导入
conn = sqlite3.connect('example.db', isolation_level=None)
c.execute("INSERT INTO stocks VALUES ('{}',{:.5f},{:.5f})".format(sym,bid,ask))
isolation_level = None(自动提交)时,程序需要花费很多时间才能完成(我等不了那么长时间)
请注意,输出数据库文件的大小为32,325,632字节,即32MB。它比输入文件ssv文件大小23MB大10MB。
conn = sqlite3.connect('example.db', isolation_level=’DEFERRED’) # default
c.execute("INSERT INTO stocks VALUES ('{}',{:.5f},{:.5f})".format(sym,bid,ask))
以7.50秒或每秒133,296的速度导入
这与写入BEGIN
,BEGIN TRANSACTION
或BEGIN DEFERRED TRANSACTION
相同,而不是BEGIN IMMEDIATE
或BEGIN EXCLUSIVE
。
使用上面的事务可以得到令人满意的结果,但是应该注意,使用Python的字符串操作是不希望的,因为它要经过SQL注入。此外,与参数替换相比,使用字符串的速度较慢。
c.executemany("INSERT INTO stocks VALUES (?,?,?)", [(sym,bid,ask)])
以2.31秒或每秒432,124的速度导入
在数据到达物理磁盘表面之前未将同步设置为EXTRA
或FULL
时,电源故障会损坏数据库文件。当我们可以确保电源和操作系统运行状况良好时,可以将其同步到OFF
,以便在将数据传递到操作系统层之后不进行同步。
conn = sqlite3.connect('example.db', isolation_level='DEFERRED')
c = conn.cursor()
c.execute('''PRAGMA synchronous = OFF''')
以2.25秒或每秒444,247的速度导入
在某些应用程序中,不需要数据库的回滚功能,例如时间序列数据插入。当我们可以确保电源和操作系统正常运行时,可以将journal_mode
更改为off
,以便完全禁用回滚日志,并禁用原子提交和回滚功能。
conn = sqlite3.connect('example.db', isolation_level='DEFERRED')
c = conn.cursor()
c.execute('''PRAGMA synchronous = OFF''')
c.execute('''PRAGMA journal_mode = OFF''')
以2.22秒或每秒450,653的速度导入
在某些应用程序中不需要将数据写回磁盘,例如,向Web应用程序提供查询数据的应用程序。
conn = sqlite3.connect(":memory:")
以2.17秒或每秒460,405的速度导入
我们应该考虑将计算的每一位都保存在一个密集的循环中,例如避免分配给变量和字符串操作。
tokens = line.split()
c.executemany("INSERT INTO stocks VALUES (?,?,?)", [(tokens[0], float(tokens[1]), float(tokens[2]))])
以2.10秒或每秒475,964的速度导入
当我们将空格分隔的数据视为固定宽度格式时,我们可以直接指示每个数据到数据头的距离。
这意味着line.split()[1]
变成line[7:14]
c.executemany("INSERT INTO stocks VALUES (?,?,?)", [(line[0:6], float(line[7:14]), float(line[15:]))])
以1.94秒或514,661秒的速度导入
当我们将executemany()
与?
占位符一起使用时,我们不需要事先将字符串转换为float。
executemany("INSERT INTO stocks VALUES (?,?,?)", [(line[0:6], line[7:14], line[15:])])
以1.59秒或每秒630,520的速度导入
import time
class Timer:
def __enter__(self):
self.start = time.time()
return self
def __exit__(self, *args):
elapsed = time.time()-self.start
print('Imported in {:.2f} seconds or {:.0f} per second'.format(elapsed, 1*1000*1000/elapsed))
import sqlite3
conn = sqlite3.connect('example.db')
c = conn.cursor()
c.execute('''DROP TABLE IF EXISTS stocks''')
c.execute('''CREATE TABLE IF NOT EXISTS stocks
(sym text, bid real, ask real)''')
c.execute('''PRAGMA synchronous = EXTRA''')
c.execute('''PRAGMA journal_mode = WAL''')
with Timer() as t:
with open('input.ssv', 'r') as infile:
lines = infile.read().splitlines()
for line in lines:
c.executemany("INSERT INTO stocks VALUES (?,?,?)", [(line[0:6], line[7:14], line[15:])])
conn.commit()
conn.close()
以1.77秒或每秒564,611的速度导入
我有一个23MB的文件,其中有100万条记录,由一条文本作为符号名称和2个浮点数作为出价和要价组成。当您在上面的pass
中搜索时,测试结果显示每秒对普通文件插入4.32 M插入。当我插入到健壮的SQLite数据库时,每秒插入次数为0.564M。您还有什么想法想使它在SQLite中变得更快?如果不是SQLite而是其他数据库系统怎么办?
答案 0 :(得分:0)
如果python的解释器实际上是时序(第9节)与SQLite性能之间的重要因素,那么您可能会发现PyPy会大大提高性能(Python的sqlite3接口是在纯python中实现的。)纯python,但是如果您要执行更多的字符串操作或使用for循环,则值得从CPython切换。
很明显,如果SQLite之外的性能确实很重要,则可以尝试使用C / C ++等更快的语言编写。多线程可能会或可能不会有所帮助,具体取决于实现数据库锁的方式。