使用Python3和SQLite的大容量插入性能较差

使用Python3和SQLite的大容量插入性能较差,python,sqlite,Python,Sqlite,我有几个包含URL的文本文件。我正在尝试创建一个SQLite数据库来将这些URL存储在一个表中。URL表有两列,即主键(整数)和URL(文本) 我尝试在一个insert命令和循环中插入100000个条目,直到完成URL列表。基本上,读取所有文本文件内容并保存在列表中,然后我使用创建100000个条目的较小列表并插入到表中 文本文件中的URL总数为4591415,文本文件总大小约为97.5 MB 问题: 当我选择文件数据库时,插入大约需要7-7.5分钟。我觉得这不是一个很快的插入,因为我有固态硬盘

我有几个包含URL的文本文件。我正在尝试创建一个SQLite数据库来将这些URL存储在一个表中。URL表有两列,即主键(整数)和URL(文本)

我尝试在一个insert命令和循环中插入100000个条目,直到完成URL列表。基本上,读取所有文本文件内容并保存在列表中,然后我使用创建100000个条目的较小列表并插入到表中

文本文件中的URL总数为4591415,文本文件总大小约为97.5 MB

问题

  • 当我选择文件数据库时,插入大约需要7-7.5分钟。我觉得这不是一个很快的插入,因为我有固态硬盘,读/写速度更快。除此之外,我还有大约10GB的RAM可用,如task manager中所示。处理器为i5-6300U 2.4Ghz

  • 文本文件总数约为97.5 MB。但在我将URL插入SQLite之后,SQLite数据库大约有350MB,即几乎是原始数据大小的3.5倍。由于数据库不包含任何其他表、索引等,因此此数据库大小看起来有点奇怪

  • 对于问题1,我尝试使用参数,并根据不同参数的测试运行得出最佳参数

    表,th,td{
    边框:1px纯黑;
    边界塌陷:塌陷;
    }
    th,td{
    填充:15px;
    文本对齐:左对齐;
    }
    
    配置
    时间
    50000-带日记账=删除且无交易0:12:09.888404
    50000-带日记账=删除,带事务处理0:22:43.613580
    50000-带日志=内存和事务0:09:01.140017
    50000-带日志=内存0:07:38.820148
    50000-日志=内存,同步=0 0:07:43.587135
    50000-日志=内存,同步=1,页面大小=65535 0:07:19.778217
    50000-日志=内存,同步=0,页面大小=65535 0:07:28.186541
    50000-日志=删除,同步=1,页面大小=65535 0:07:06.539198
    50000-日志=删除,同步=0,页面大小=65535 0:07:19.810333
    50000-日记账=wal,同步=0,页码=65535 0:08:22.856690
    50000-日记账=wal,同步=1,页码=65535 0:08:22.326936
    50000-日志=删除,同步=1,页面大小=4096 0:07:35.365883
    50000-日志=内存,同步=1,页面大小=4096 0:07:15.183948
    1,00000-日记账=删除,同步=1,页面大小=65535 0:07:13.402985
    
    列“url”上的唯一约束是在url上创建隐式索引。这可以解释尺寸增加的原因

    我不认为您可以填充表,然后添加唯一约束

    你的瓶颈当然是CPU。请尝试以下操作:

  • 安装工具Z:
    pip安装工具Z
  • 使用此方法:

    from toolz import partition_all
    
    def add_blacklist_url(self, urls):
        # print('add_blacklist_url:: entries = {}'.format(len(urls)))
        start_time = datetime.now()
        for batch in partition_all(100000, urls):
            try:
                start_commit = datetime.now()
                self.cursor.executemany('''INSERT OR IGNORE INTO blacklist(url) VALUES(:url)''', batch)
                end_commit = datetime.now() - start_commit
                print('add_blacklist_url:: total time for INSERT OR IGNORE INTO blacklist {} entries = {}'.format(len(templist), end_commit))
            except sqlite3.Error as e:
                print("add_blacklist_url:: Database error: %s" % e)
            except Exception as e:
                print("add_blacklist_url:: Exception in _query: %s" % e)
        self.db.commit()
        time_elapsed = datetime.now() - start_time
        print('add_blacklist_url:: total time for {} entries = {}'.format(records, time_elapsed))
    

  • 未测试代码。

    默认情况下,SQLite使用自动提交模式。这允许省略
    开始事务
    。但是这里我们希望所有的插入都在一个事务中,唯一的方法是使用
    begintransaction
    启动一个事务,这样所有要运行的语句都在该事务中

    方法
    executemany
    只是在Python外部完成的一个循环
    execute
    ,它只调用SQLite prepare语句函数一次

    以下是从列表中删除最后N项的非常糟糕的方法:

        templist = []
        i = 0
        while i < self.bulk_insert_entries and len(urls) > 0:
            templist.append(urls.pop())
            i += 1
    
    slice和del slice即使在空列表上也能工作


    两者可能具有相同的复杂性,但100K次追加和弹出调用的成本远远高于让Python在解释器外部执行的成本。

    是否有一个选项可以在完成后修剪索引?其次,我认为瓶颈可能是数据库本身,因为我已尝试关闭所有应用程序并仅运行脚本,在Task Manager中,我看到大约3-4 MB/s的写入速度。但当我只是阅读文本文件时,它显示大约30-35MB/sA的bootleneck必须是您计算机的一个资源。它可能是磁盘或CPU。如果你看不到CPU 100%使用一个内核,那就是磁盘。修剪索引是什么意思?像是一种压实?我认为这是不可能的,但您可以尝试完全真空。当我运行脚本时,任务管理器中也没有100%运行脚本。CPU接近50%,磁盘在5-7%之间。通过所有优化和单次提交=0:02:13.202081通过所有优化并一次性提交每批100k=0:04:03.772711,通过使用新的列表机制和添加事务,没有多少收益。1.无事务原始列表机制=0:05:13.974366 2。无事务切片/del机制=0:05:05.689453 3。仅在事务和切片/删除列表机制中包装insert语句=0:07:27.693308 4。while循环和切片/删除列表机制之外的事务=0:05:07.213928
       templist = urls[-self.bulk_insert_entries:]
       del urls[-self.bulk_insert_entries:]
       i = len(templist)