Python 为什么插入速度会随着数据库的增长而减慢?

Python 为什么插入速度会随着数据库的增长而减慢?,python,sqlite,pysqlite,Python,Sqlite,Pysqlite,我正在做一个生成大量数据的个人项目,我认为将其存储在本地数据库中是有意义的。然而,随着DB的增长,我看到了疯狂的减速,这使得运行变得不可行 我做了一个简单的测试来说明我在做什么。我制作了一个字典,在这里我进行了大量的本地处理(大约100万个条目),然后批量将其插入SQLite数据库,然后循环并再次执行。代码如下: from collections import defaultdict import sqlite3 import datetime import random def log(s)

我正在做一个生成大量数据的个人项目,我认为将其存储在本地数据库中是有意义的。然而,随着DB的增长,我看到了疯狂的减速,这使得运行变得不可行

我做了一个简单的测试来说明我在做什么。我制作了一个字典,在这里我进行了大量的本地处理(大约100万个条目),然后批量将其插入SQLite数据库,然后循环并再次执行。代码如下:

from collections import defaultdict
import sqlite3
import datetime
import random

def log(s):
    now = datetime.datetime.now()
    print(str(now) + ": " + str(s))

def create_table():
    conn = create_connection()
    with conn:
        cursor = conn.cursor()

        sql = """
            CREATE TABLE IF NOT EXISTS testing (
                test text PRIMARY KEY,
                number integer
            );"""
        cursor.execute(sql)
    conn.close()

def insert_many(local_db):
    sql = """INSERT INTO testing(test, number) VALUES(?, ?) ON CONFLICT(test) DO UPDATE SET number=number+?;"""

    inserts = []
    for key, value in local_db.items():
        inserts.append((key, value, value))

    conn = create_connection()
    with conn:
        cursor = conn.cursor()
        cursor.executemany(sql, inserts)
    conn.close()

def main():
    i = 0
    log("Starting to process records")
    for i in range(1, 21):
        local_db = defaultdict(int)
        for j in range(0, 1000000):
            s = "Testing insertion " + str(random.randrange(100000000))
            local_db[s] += 1
        log("Created local DB for " + str(1000000 * i) + " records")
        insert_many(local_db)
        log("Finished inserting " + str(1000000 * i) + " records")

def create_connection():
    conn = None
    try:
        conn = sqlite3.connect('/home/testing.db')
    except Error as e:
        print(e)

    return conn

if __name__ == '__main__':
    create_table()
    main()
这运行了一秒钟,然后像疯了一样慢下来。这是我刚得到的输出:

2019-10-23 15:28:59.211036: Starting to process records
2019-10-23 15:29:01.308668: Created local DB for 1000000 records
2019-10-23 15:29:10.147762: Finished inserting 1000000 records
2019-10-23 15:29:12.258012: Created local DB for 2000000 records
2019-10-23 15:29:28.752352: Finished inserting 2000000 records
2019-10-23 15:29:30.853128: Created local DB for 3000000 records
2019-10-23 15:39:12.826357: Finished inserting 3000000 records
2019-10-23 15:39:14.932100: Created local DB for 4000000 records
2019-10-23 17:21:37.257651: Finished inserting 4000000 records
...

如您所见,第一个百万次插入需要9秒,然后下一个百万次插入需要16秒,然后它膨胀到10分钟,然后是1小时40分钟(!)。我正在做的是不是有什么奇怪的事情导致了这种疯狂的减速,或者这是sqlite的一个限制?

使用您的程序进行一次(小的?)修改,我得到了下面显示的非常合理的计时。修改是使用
sqlite3.connect
而不是
pysqlite.connect

使用sqlite3.connect的计时 #注释是近似值

2019-10-23 13:00:37.843759: Starting to process records
2019-10-23 13:00:40.253049: Created local DB for 1000000 records
2019-10-23 13:00:50.052383: Finished inserting 1000000 records          # 12s
2019-10-23 13:00:52.065007: Created local DB for 2000000 records
2019-10-23 13:01:08.069532: Finished inserting 2000000 records          # 18s
2019-10-23 13:01:10.073701: Created local DB for 3000000 records
2019-10-23 13:01:28.233935: Finished inserting 3000000 records          # 20s
2019-10-23 13:01:30.237968: Created local DB for 4000000 records
2019-10-23 13:01:51.052647: Finished inserting 4000000 records          # 23s
2019-10-23 13:01:53.079311: Created local DB for 5000000 records
2019-10-23 13:02:15.087708: Finished inserting 5000000 records          # 24s
2019-10-23 13:02:17.075652: Created local DB for 6000000 records
2019-10-23 13:02:41.710617: Finished inserting 6000000 records          # 26s
2019-10-23 13:02:43.712996: Created local DB for 7000000 records
2019-10-23 13:03:18.420790: Finished inserting 7000000 records          # 37s
2019-10-23 13:03:20.420485: Created local DB for 8000000 records
2019-10-23 13:04:03.287034: Finished inserting 8000000 records          # 45s
2019-10-23 13:04:05.593073: Created local DB for 9000000 records
2019-10-23 13:04:57.871396: Finished inserting 9000000 records          # 54s
2019-10-23 13:04:59.860289: Created local DB for 10000000 records       
2019-10-23 13:05:54.527094: Finished inserting 10000000 records # 57s
...
文本主键的成本 我认为速度放缓的主要原因是将
test
定义为文本主键。这需要巨大的索引成本,正如这段从删除“主键”和“冲突”声明的运行中得到的提示:

2019-10-23 13:26:22.627898: Created local DB for 10000000 records
2019-10-23 13:26:24.010171: Finished inserting 10000000 records
...
2019-10-23 13:26:58.350150: Created local DB for 20000000 records
2019-10-23 13:26:59.832137: Finished inserting 20000000 records

在1000万条记录的大关上,这个数字不到1.4,在2000万条记录的大关上,这个数字不多。

(与其说是答案,不如说是扩展评论)

SQLite只支持BTree索引。对于长度可能不同的字符串,树存储行ID。读取树的复杂度是O(log(n)),其中n是表的长度,但是,它将乘以从表中读取和比较字符串值的复杂度。因此,除非有很好的理由,否则最好将整型字段作为主键

更糟糕的是,在这种情况下,插入的字符串具有相当长的共享前缀(“测试插入”),因此搜索第一个不匹配项需要更长的时间

加速建议,按预期效果大小排序:

  • 真实数据库(MariaDB、Postgres)支持哈希索引,这将解决这个问题
  • 禁用自动提交(跳过不必要的磁盘写入;非常昂贵)
  • 反转文本字符串(固定文本之前的数字),甚至只保留数字部分
  • 使用大量插入(一条语句中有多条记录)

@peak的答案通过不使用索引避免了整个问题。如果根本不需要索引,那么这绝对是一种方法。

好吧,您使用的是文本主键。.sqlite并不是性能最好的数据库there@Marat你能详细说明一下吗?文本主键是否明显比int慢?如果是这样的话,我如何才能提高这个特定问题的性能?@zvone您对其他能够更好地解决这个问题的DBs有什么建议吗?我想大多数DBs都更快,但这不是您面临的主要问题,如果您关心速度,这只是一个评论。peak给出了一个很好的答案,marat给出了一条评论,其中提到了更重要的事情。好的,但我确实需要插入或更新(即删除冲突上的
声明也会阻止我的代码工作)。有没有办法在检查冲突的同时提高速度?您可能想试试APSW: