Sqlalchemy 如何提交模型实例并一次从工作内存中删除几个实例

Sqlalchemy 如何提交模型实例并一次从工作内存中删除几个实例,sqlalchemy,pyramid,Sqlalchemy,Pyramid,我有一个金字塔视图,用于将数据从大文件加载到数据库中。对于文件中的每一行,它都会进行一些处理,然后创建一些模型实例并将它们添加到会话中。除非文件很大,否则这项工作正常。对于大文件,视图会慢慢消耗掉我所有的ram,直到所有内容都有效地停止 因此,我的想法是使用一个函数分别处理每一行,该函数创建一个会话,创建必要的模型实例,并将它们添加到当前会话中,然后提交 def commit_line(lTitles,lLine,oStartDate,oEndDate,iDS,dSettings): f

我有一个金字塔视图,用于将数据从大文件加载到数据库中。对于文件中的每一行,它都会进行一些处理,然后创建一些模型实例并将它们添加到会话中。除非文件很大,否则这项工作正常。对于大文件,视图会慢慢消耗掉我所有的ram,直到所有内容都有效地停止

因此,我的想法是使用一个函数分别处理每一行,该函数创建一个会话,创建必要的模型实例,并将它们添加到当前会话中,然后提交

def commit_line(lTitles,lLine,oStartDate,oEndDate,iDS,dSettings):
    from sqlalchemy.orm import (
            scoped_session,
            sessionmaker,
    )
    from sqlalchemy import engine_from_config
    from pyramidapp.models import Base, DataEntry
    from zope.sqlalchemy import ZopeTransactionExtension
    import transaction

    oCurrentDBSession = scoped_session(sessionmaker(extension=ZopeTransactionExtension()))
    engine = engine_from_config(dSettings, 'sqlalchemy.')
    oCurrentDBSession.configure(bind=engine)
    Base.metadata.bind = engine

    oEntry = DataEntry()
    oCurrentDBSession.add(oEntry)
    ...
    transaction.commit()
我对该功能的要求如下:

创建会话检查 检查一组模型实例 将这些实例添加到会话检查中 将这些模型提交到数据库 清除会话,以便对会话和在2中创建的对象进行垃圾收集 我已经确保新创建的会话在必要时作为参数传递,以便停止多个会话的错误等等。但是,唉!我无法让数据库连接消失,而且没有提交任何内容

我尝试将函数分离为芹菜任务,以便视图执行到完成,并执行它所需的操作,但芹菜中出现了一个错误,即无论我在提交、关闭和处理方面做了什么,都有太多mysql连接,我不确定为什么。是的,当我进行更改时,我会重新启动芹菜服务器


当然有一个简单的方法可以做到这一点?我只想做一个会话提交,然后走开,让我一个人呆着。

你只是做错了。句号

引自

高级开发人员将尝试保留会话的详细信息, 事务和异常管理尽可能从 程序工作的细节

引自

我们决定使用SQLAlchemy与我们的数据库对话。不过,我们也安装了pyramid_tm和zope.sqlalchemy

为什么?

金字塔对支持交易有着强烈的倾向。 具体来说,您可以在应用程序中安装事务管理器 应用程序,可以作为中间件,也可以作为金字塔结构。那就 在返回响应之前,您的 应用程序被执行。这意味着金字塔视图代码通常不会 管理交易

我今天的答案不是代码,而是建议遵循您正在使用的包/框架的作者推荐的最佳实践

参考资料


我可以想象,为大文件的每一行创建一个新会话会非常慢

我将尝试提交会话并每隔1000行左右从中删除所有对象:

counter = 0

for line in mymegafile:
    entry = process_line(line)
    session.add(entry)
    if counter > 1000:
        counter = 0
        transaction.commit()  # if you insist on using ZopeTransactionExtension, otherwise session.commit()
        session.expunge_all() # this may not be required actually, see https://groups.google.com/forum/#!topic/sqlalchemy/We4XGX2CYX8
    else:
        counter += 1
如果任何地方都没有对DataEntry实例的引用,Python解释器应该在某个时候对它们进行垃圾收集


但是,如果您在该视图中所做的只是向数据库插入新记录,那么使用SQLAlchemy核心构造或文字SQL大容量插入数据可能会更有效。这也可以解决ORM实例占用RAM的问题。有关详细信息,请参阅。

因此我尝试了很多方法,尽管使用SQLAlchemy的内置功能来解决这一问题可能是可行的,但我找不到任何方法来实现这一点

下面是我所做工作的概要:

将要处理的生产线分批处理 对于每一批行,排队等候一个芹菜任务来处理这些行 在芹菜任务中,启动了一个单独的过程,用这些线做必要的工作。 理由:

这批东西很明显 芹菜之所以被使用,是因为处理整个文件需要很长时间,所以排队才有意义 该任务启动了一个单独的进程,因为如果它没有启动,那么我就会遇到与金字塔应用程序相同的问题 一些代码:

芹菜任务:

def commit_lines(lLineData,dSettings,cwd):
    """
    writes the line data to a file then calls a process that reads the file and creates
    the necessary data entries. Then deletes the file
    """
    import lockfile
    sFileName = "/home/sheena/tmp/cid_line_buffer"
    lock = lockfile.FileLock("{0}_lock".format(sFileName))
    with lock:
        f = open(sFileName,'a') #in case the process was at any point interrupted...
        for d in lLineData:
            f.write('{0}\n'.format(d))
        f.close()

    #now call the external process
    import subprocess
    import os
    sConnectionString = dSettings.get('sqlalchemy.url')
    lArgs = [
                'python',os.path.join(cwd,'commit_line_file.py'),
                '-c',sConnectionString,
                '-f',sFileName
        ]
    #open the subprocess. wait for it to complete before continuing with stuff. if errors: raise
    subprocess.check_call(lArgs,shell=False)
    #and clear the file
    lock = lockfile.FileLock("{0}_lock".format(sFileName))
    with lock:
        f = open(sFileName,'w')
        f.close()
外部过程:

"""
this script goes through all lines in a file and creates data entries from the lines
"""
def main():
    from optparse import OptionParser
    from sqlalchemy import create_engine
    from pyramidapp.models import Base,DBSession

    import ast
    import transaction

    #get options

    oParser = OptionParser()
    oParser.add_option('-c','--connection_string',dest='connection_string')
    oParser.add_option('-f','--input_file',dest='input_file')
    (oOptions, lArgs) = oParser.parse_args()

    #set up connection

    #engine = engine_from_config(dSettings, 'sqlalchemy.')
    engine = create_engine(
        oOptions.connection_string,
        echo=False)
    DBSession.configure(bind=engine)
    Base.metadata.bind = engine

    #commit stuffs
    import lockfile
    lock = lockfile.FileLock("{0}_lock".format(oOptions.input_file))
    with lock:
        for sLine in open(oOptions.input_file,'r'):
            dLine = ast.literal_eval(sLine)
            create_entry(**dLine)

    transaction.commit()

def create_entry(iDS,oStartDate,oEndDate,lTitles,lValues):
    #import stuff
    oEntry = DataEntry()
    #do some other stuff, make more model instances...
    DBSession.add(oEntry)


if __name__ == "__main__":
    main()
他认为:

 for line in big_giant_csv_file_handler:
     lLineData.append({'stuff':'lots'})

 if lLineData:
            lLineSets = [lLineData[i:i+iBatchSize] for i in range(0,len(lLineData),iBatchSize)]
            for l in lLineSets:
                commit_lines.delay(l,dSettings,sCWD)  #queue it for celery

将CSV读取和创建SQLAlchemy模型实例封装为支持迭代器协议的内容。我称之为BatchingModelReader。它返回一个DataEntry实例集合,集合大小取决于批大小。如果模型超时更改,则无需更改芹菜任务。该任务仅将一批模型放入会话并提交事务。通过控制批量大小,可以控制内存消耗。BatchingModelReader和芹菜任务都不能保存大量中间数据。这个例子还表明,使用芹菜只是一种选择。我添加了一个链接,指向一个金字塔应用程序的代码示例,实际上我正在一个应用程序中进行重构

BatchingModelReader-封装csv.reader并使用金字塔应用程序中的现有模型

可以作为芹菜任务运行-使用适当的任务装饰器

from .models import DBSession
import transaction

def import_from_csv(path_to_csv, batchsize)
    """given a CSV file and batchsize iterate over batches of model instances and import them to database"""
    for batch in BatchingModelReader(path_to_csv, batchsize):
        with transaction.manager:
            DBSession.add_all(batch)
金字塔视图-只需保存巨大的CSV文件, 启动任务,立即返回响应

@view_config(...):
def view(request):
    """gets file from request, save it to filesystem and start celery task"""
    with open(path_to_csv, 'w') as f:
        f.write(big_giant_csv_file)

    #start task with parameters
    import_from_csv.delay(path_to_csv, 1000)
代码示例

金字塔炼金术

炼金术内件


六羟甲基三聚氰胺六甲醚。。。TMI。如果你编辑内容只是为了回答问题,我会把它标记为正确。例如,可以安全地假设我知道如何定义模型并提交它们,因此第一个链接不包含任何相关信息。。。考虑到工作模型类和视图已经就位,并且唯一的问题是一个地方有太多的模型实例,该模型的哪一部分是相关的?整个文档都是无关紧要的,除非有一些脚注隐藏在调试工具栏所做的有趣的事情的某个地方,这是非常相关的。在回答问题时,这似乎有点像猎枪。在这里,有大量的信息,你的答案就在那里的某个地方。答案就在那里,你能指出吗?如果不是这样的话,我会给你一个要点:缩短commit_line函数将是一个明显的解决方案:对于大文件,视图会慢慢地消耗掉我所有的ram,直到一切都有效地停止。我想创建一个会话,创建一些模型实例,将实例添加到会话中,将它们提交到数据库中,将会话和实例从ram中取出,冲洗并重复。我不能让这对您更容易。请仔细阅读我完全重写的信息。建议的解决方案不是缩短提交行,而是避免使用数据库引擎和会话设置代码。这是我最后一次编辑。祝你好运。你的解决方案就是我首先尝试的。确切地我认为你没有正确地阅读这个问题,但我对它进行了编辑,以便更好地解释我的目的。拥有一个会话并允许金字塔通过其常用机制提交它根本不能解决问题。哪怕是一点点。如果您相信我的要求是必需的,那么我需要,如特别说明的,不建立与请求相关的单个会话等。换句话说,您的答案是对环境和坚实的硬件强加的限制闭上眼睛8Gb ram根本不够?无法读取您收到的异常消息。它清楚地表明了问题所在。@Saschagotfried:没有异常消息。RTFQ:所有的ram都用完了,计算机开始疯狂地寻呼,所有的一切都停止了。ram的使用是可以观察到的,所以我观察到了。逻辑:使用了太多内存=>一次使用内存的东西太多=>解决方案是从内存中删除东西。说真的,我很高兴你试着去帮助别人,除了让别人去做白日梦,因为你实际上不知道答案,也不知道如何得到答案,这对任何人来说都不是一件有用的事情。在这个问题的第一个版本中,你有一个无效的错误。如果Python不再提出这个问题,那么到目前为止您做了哪些更改?这是修复初始问题的失败尝试的一个例子。这本身并不是问题的症状。谢谢Sergy,由于某种原因,它并没有解决我的问题。一定有一些我找不到的秘密参考资料,但它是一本很好的书。使用普通的OLSQL并不是很理想,因为我需要编写很多。我发布了我的解决方案作为答案…是的。发布尽可能多的代码。那么我就不需要猜测上下文了。@Saschagotfried:上下文已经解释得很充分了,但这里有一些代码回答需要时间,并将围绕中解释的概念来说明解决方案-正如Sergey建议的那样。这些脚本可以处理任何负载。这种方法的最大区别在于通过Session.commit使用SQLALchemy会话本身进行提交,从而重复刷新和提交。在卸载脚本中不使用transaction.commit,因为这是一个与SQLAlchemy集成到pyramid中相关的概念。当您想要使用模型时,请查看SQLAlchemy orm方法的函数“test\u SQLAlchemy\u orm”。添加了另一个答案。非常感谢您的反馈。