python - 通过 sqlalchemy 重复插入到 sqlite 数据库导致内存泄漏?

标签 python pandas sqlite memory-leaks sqlalchemy

当通过 sqlalchemy 和 pandas to_sql 以及指定的 chucksize 将一个巨大的 pandas 数据帧插入到 sqlite 中时,我会遇到内存错误。

起初我认为这是 to_sql 的问题,但我尝试了一种解决方法,我没有使用 chunksize,而是使用了 for i in range(100): df.iloc[i * 100000 :(i+1):100000].to_sql(...) 并且仍然导致错误。

似乎在某些情况下,通过 sqlalchemy 重复插入 sqlite 会导致内存泄漏。

我很难尝试通过一个最小的示例来复制转换数据时发生的内存泄漏。但这非常接近。

import string
import numpy as np
import pandas as pd
from random import randint
import random

def make_random_str_array(size=10, num_rows=100, chars=string.ascii_uppercase + string.digits):
    return (np.random.choice(list(chars), num_rows*size)
            .view('|U{}'.format(size)))

def alt(size, num_rows):
    data = make_random_str_array(size, num_rows=2*num_rows).reshape(-1, 2)
    dfAll = pd.DataFrame(data)
    return dfAll

dfAll = alt(randint(1000, 2000), 10000)

for i in range(330):
    print('step ', i)
    data = alt(randint(1000, 2000), 10000)
    df = pd.DataFrame(data)
    dfAll = pd.concat([ df,  dfAll ])

import sqlalchemy

from sqlalchemy import create_engine
engine = sqlalchemy.create_engine('sqlite:///testtt.db')

for i in range(500):
    print('step', i)
    dfAll.iloc[(i%330)*10000:((i%330)+1)*10000].to_sql('test_table22', engine, index = False, if_exists= 'append')

这是在 Google Colab CPU 环境中运行的。

数据库本身不会导致内存泄漏,因为我可以重新启动我的环境,并且之前插入的数据仍然存在,并且连接到该数据库不会导致内存增加。问题似乎是在某些情况下通过循环 to_sql 或一个指定了 chucksize 的 to_sql 重复插入。

有没有一种方法可以在不导致内存使用量最终增加的情况下运行此代码?

编辑:

要完全重现错误,请运行此笔记本

https://drive.google.com/open?id=1ZijvI1jU66xOHkcmERO4wMwe-9HpT5OS

notebook 要求您将此文件夹导入 Google Drive 的主目录

https://drive.google.com/open?id=1m6JfoIEIcX74CFSIQArZmSd0A8d0IRG8

笔记本也会挂载你的谷歌硬盘,你需要授权它访问你的谷歌硬盘。由于数据托管在我的 Google 驱动器上,因此导入数据不应占用您分配的任何数据。

最佳答案

Google Colab 实例开始时有大约 12.72GB 的可用 RAM。 创建 DataFrame theBigList 后,大约使用了 9.99GB 的 RAM。 这已经是一个相当不舒服的情况,因为这对 Pandas 操作需要与其操作的 DataFrame 一样多的额外空间。 因此,如果可能的话,我们应该尽量避免使用这么多 RAM,幸运的是,有一种简单的方法可以做到这一点:只需加载每个 .npy 文件并将其数据一次一个地存储在 sqlite 数据库中无需创建 theBigList(见下文)。

但是,如果我们使用您发布的代码,我们可以看到 RAM 使用量缓慢增加 因为 theBigList 的 block 被迭代地存储在数据库中。

theBigList DataFrame 将字符串存储在 NumPy 数组中。但在这个过程中 将字符串传输到 sqlite 数据库,NumPy 字符串是 转换为 Python 字符串。这需要额外的内存。

根据 this Theano tutoral其中讨论了 Python 内部内存管理,

To speed-up memory allocation (and reuse) Python uses a number of lists for small objects. Each list will contain objects of similar size: there will be a list for objects 1 to 8 bytes in size, one for 9 to 16, etc. When a small object needs to be created, either we reuse a free block in the list, or we allocate a new one.

... The important point is that those lists never shrink.

Indeed: if an item (of size x) is deallocated (freed by lack of reference) its location is not returned to Python’s global memory pool (and even less to the system), but merely marked as free and added to the free list of items of size x. The dead object’s location will be reused if another object of compatible size is needed. If there are no dead objects available, new ones are created.

If small objects memory is never freed, then the inescapable conclusion is that, like goldfishes, these small object lists only keep growing, never shrinking, and that the memory footprint of your application is dominated by the largest number of small objects allocated at any given point.

我相信这准确地描述了您在执行此循环时看到的行为:

for i in range(0, 588):
    theBigList.iloc[i*10000:(i+1)*10000].to_sql(
        'CS_table', engine, index=False, if_exists='append')

尽管许多死对象的位置被重新用于新字符串,但它是 对于本质上随机的字符串(例如 theBigList 中的字符串)来说,额外的空间偶尔会出现并非难以置信 需要,因此内存占用量不断增长。

该进程最终达到了 Google Colab 的 12.72GB RAM 限制,内核因内存错误而被终止。


在这种情况下,避免大量内存使用的最简单方法是永远不要实例化整个 DataFrame —— 相反,只需一次加载和处理 DataFrame 的一小块:

import numpy as np
import pandas as pd
import matplotlib.cbook as mc
import sqlalchemy as SA

def load_and_store(dbpath):
    engine = SA.create_engine("sqlite:///{}".format(dbpath))    
    for i in range(0, 47):
        print('step {}: {}'.format(i, mc.report_memory()))                
        for letter in list('ABCDEF'):
            path = '/content/gdrive/My Drive/SummarizationTempData/CS2Part{}{:02}.npy'.format(letter, i)
            comb = np.load(path, allow_pickle=True)
            toPD = pd.DataFrame(comb).drop([0, 2, 3], 1).astype(str)
            toPD.columns = ['title', 'abstract']
            toPD = toPD.loc[toPD['abstract'] != '']
            toPD.to_sql('CS_table', engine, index=False, if_exists='append')

dbpath = '/content/gdrive/My Drive/dbfile/CSSummaries.db'
load_and_store(dbpath)

打印

step 0: 132545
step 1: 176983
step 2: 178967
step 3: 181527
...         
step 43: 190551
step 44: 190423
step 45: 190103
step 46: 190551

每行的最后一个数字是进程消耗的内存量,由 matplotlib.cbook.report_memory .有许多不同的内存使用量度。在 Linux 上,mc.report_memory() 正在报告 the size of the physical pages of the core image进程(包括文本、数据和堆栈空间)。


顺便说一下,另一个可以用来管理内存的基本技巧是使用函数。 当函数终止时,函数内部的局部变量被释放。 这减轻了您手动调用 delgc.collect() 的负担。

关于python - 通过 sqlalchemy 重复插入到 sqlite 数据库导致内存泄漏?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/56418825/

相关文章:

python - Plotly:如何更改 3D 曲面图的配色方案?

python - 单独归一化 groupby

python - 在 2 个数据帧之间相乘

c# - 如何在 xamarin 跨平台应用程序中创建支持 sqlite 的 PCL

python - AssertListEqual 不改变类 __eq__ 方法

python - 难以格式化 tf.example 以向 Tensorflow 服务发出请求

python - 具有函数 : Columns Varying 的 Pandas DataFrame

python - 使用 Python 处理许多文件

Android:重命名数据库文件名?

python - Django 1.9 在两个页面形式中使用 django session