【问题标题】:Insert slowing down over time as data base grows (no index)随着数据库的增长,插入速度随着时间的推移而变慢(无索引)
【发布时间】:2016-02-13 01:49:34
【问题描述】:

我正在尝试创建超过 200GB 的(单个)数据库文件(将定期更新/偶尔部分重新创建/偶尔查询),在我看来相对较大。大约有 16k 个表,大小从几 kb 到 ~1gb;他们有 2-21 列。最长的表有近 1500 万行。

我编写的脚本一个一个地遍历输入文件,进行一系列处理和正则表达式以获取可用数据。它定期发送一批(0.5-1GB)以写入 sqlite3,每个表都有一个单独的 executemany 语句插入数据。在这些执行语句之间没有提交或创建表语句等,所以我相信所有这些都属于一个事务

最初,该脚本的运行速度足以满足我的目的,但随着时间的推移,它在接近完成时会显着减慢 - 不幸的是,我需要进一步减慢它以保持我的笔记本电脑在正常使用中的内存使用可控。

我做了一些快速的基准测试,比较了将相同的样本数据插入空数据库与插入 200GB 数据库。后来的测试执行插入语句的速度慢了约 3 倍(相对速度提交更差,但绝对而言它微不足道)——除此之外没有显着差异

当我研究这个话题之前主要是returned results for indexes slowing down inserts on large tables。答案似乎是在没有索引的表上插入应该保持或多或少相同的速度,而不管大小。因为我不需要对该数据库运行大量查询,所以我没有创建任何索引。我什至仔细检查并检查了索引,如果我做对了,应该将其排除为原因:

c.execute('SELECT name FROM sqlite_master WHERE type="index"')

print(c.fetchone()) #returned none

出现的另一个问题是事务,但我不明白仅针对相同的脚本和要写入的相同数据写入大型数据库会有什么问题。

相关代码缩写:

#process pre defined objects, files, retrieve data in batch - 
#all fine, no slowdown on full database

conn = sqlite3.connect(db_path)

c = conn.cursor()

table_breakdown=[(tup[0]+'-'+tup[1],tup[0],tup[1]) for tup in all_tup] # creates list of tuples
# (tuple name "subject-item", subject, item)

targeted_create_tables=functools.partial(create_tables,c) #creates new table if needed
#for new subjects/items- 
list(map(targeted_create_tables,table_breakdown)) #no slowdown on full database

targeted_insert_data=functools.partial(insert_data,c) #inserts data for specific 
#subject item combo

list(map(targeted_insert_data,table_breakdown)) # (3+) X slower

conn.commit() # significant relative slowdown, but insignificant in absolute terms
conn.close()

及相关插入函数:

def insert_data(c,tup):
    global collector ###list of tuples of data for a combo of a subject and item
    global sql_length ###pre defined dictionary translating the item into the 
    #right length (?,?,?...) string
    tbl_name=tup[0]
    subject=tup[1]
    item=tup[2]
    subject_data=collector[subject][item]
    if not (subject_data==[]):

        statement='''INSERT INTO "{0}" VALUES {1}'''.format(tbl_name,sql_length[item])

        c.executemany(statement,subject_data)#massively slower, about 80% of 
    #inserts > twice slower

        subject_data=[]

编辑:每个 CL 请求的表创建功能。我知道这是低效的(以这种方式检查表名是否存在与创建表所需的时间大致相同),但这对减慢速度并不重要。

def create_tables(c,tup):
    global collector
    global title #list of column schemes to match to items
    tbl_name=tup[0]
    bm_unit=tup[1]
    item=tup[2]
    subject_data=bm_collector[bm_unit][item]

    if not (subject_data==[]):
        c.execute('SELECT * FROM sqlite_master WHERE name = "{0}" and type="table"'.format(tbl_name))
        if c.fetchone()==None:
            c.execute('CREATE TABLE "{0}" {1}'.format(tbl_name,title[item]))    

标题字典中有 65 种不同的列方案,但这是它们的外观示例:

title.append(('WINDFOR','(TIMESTAMP TEXT, SP INTEGER, SD TEXT, PUBLISHED TEXT, WIND_CAP NUMERIC, WIND_FOR NUMERIC)'))

有人对在哪里查看或可能导致此问题的原因有任何想法吗?如果我遗漏了重要信息或遗漏了一些可怕的基本信息,我深表歉意,我完全冷漠地进入了这个主题领域。

【问题讨论】:

  • 显示表定义。
  • 你打算用 1500 万行且没有索引的 sqlite 表做什么?这将是根本不可能查询的。
  • 抱歉@CL,您能否通过表格定义扩展您的需求/意思?
  • @Falmarri 绝对无法查询,还是很慢?我不会定期或频繁地查询大多数表(尤其是那个,如果我需要它,它几乎就在那里),我的印象是不使用索引然后在执行后添加它们会更快插入
  • CREATE TABLE 语句。

标签: python sqlite


【解决方案1】:

将行追加到表的末尾是插入数据的最快方法(而且您不是在用rowid 玩游戏,所以您确实是追加到末尾)。

但是,您使用的不是单个表,而是 16k 个表,因此管理表结构的开销成倍增加。

尝试增加cache size。但最有希望的变化是使用更少的表。

【讨论】:

  • 感谢您的建议,我会在接下来的几天里尝试一些迭代,看看是否有帮助。您是否知道有关如何设置缓存大小的任何准则?即是否有与系统内存/表大小等相关的经验法则?
  • 准则是“测量”。
  • 初始测试似乎表明增加缓存大小确实显着提高了写入速度(从默认值到 10,000 的大约 2 倍)。令人讨厌的是,当我第一次完整运行脚本以创建数据库时,我没有实现某种日志记录,因此很难完全进行基准测试,因此我需要运行更多测试。除了增加 sqlite 消耗系统资源的能力之外,增加缓存大小是否有任何缺点?例如,如果我将其增加到 100,000 会产生负面影响吗?
  • 应指定此脚本在一台 4GB 的个人工作笔记本电脑上运行,而不是在第三方手机或类似设备上运行
  • 增加 SQLite 的缓存大小将隐式减少操作系统用于其文件缓存的内存量。
【解决方案2】:

对我来说,INSERT 的时间随着数据库大小的增加而增加是有道理的。打开/关闭/写入较大的文件时,操作系统本身可能会变慢。当然,索引可以让事情变得更慢,但这并不意味着没有索引就不会减速。

【讨论】:

    猜你喜欢
    • 2020-02-19
    • 1970-01-01
    • 2012-11-25
    • 2014-02-04
    • 2021-07-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多