Mysql 使用sqlalchemy检查行和返回id是否存在速度慢

Mysql 使用sqlalchemy检查行和返回id是否存在速度慢,mysql,sqlalchemy,bulkinsert,insert-update,Mysql,Sqlalchemy,Bulkinsert,Insert Update,全部, 我正在读取一个csv文件,并使用sqlalchemy将数据添加到MySQL数据库中。其中一个表是地址表,它只应该包含唯一的地址。这些地址与另一个“语句”表之间存在关系,该表具有地址id的外键字段 因此,对于数据文件中的每一行,我创建一个新语句obj,然后获取关联地址的id。如果地址已经存在,则返回该id。否则,我将创建一个新地址obj并返回该id。这是使用下面的代码完成的,改编自 我对我的id字段使用GUID,它是地址表主键的一部分: class address(Base): _

全部,

我正在读取一个csv文件,并使用sqlalchemy将数据添加到MySQL数据库中。其中一个表是地址表,它只应该包含唯一的地址。这些地址与另一个“语句”表之间存在关系,该表具有地址id的外键字段

因此,对于数据文件中的每一行,我创建一个新语句obj,然后获取关联地址的id。如果地址已经存在,则返回该id。否则,我将创建一个新地址obj并返回该id。这是使用下面的代码完成的,改编自

我对我的id字段使用GUID,它是地址表主键的一部分:

class address(Base):
    __tablename__ = 'address'
    id = id_column()
    name               = Column(String(75), primary_key=True)
    Address_Line_One   = Column(String(50), primary_key=True)
    Address_Line_Two   = Column(String(50), primary_key=True)
    Address_Line_Three = Column(String(50), primary_key=True)
    Address_Line_Four  = Column(String(50), primary_key=True)
id\u列()
来自,但由于其他地方的限制,它已转换为
CHAR(32)
。最后,这里有一个片段:

currStatement   = statements(rec, id=currGUID)
currStatement.address = self.get_or_create(address, rec)
这一切都很好,只是速度很慢。对于一个事务中插入的65000条语句,我看到在干净的测试数据库中插入时间为1.5小时。实时观察插入显示它很快达到10000行,然后插入速度开始下降

如何加快插入时间

编辑:

经过进一步测试,我发现插入时间慢的部分原因是每个对象都是单独插入的。因此,我有大约65000行,每行都成为几个sqlalchemy对象,分别插入。使用sqlalchemy 0.7,如何批量插入对象?

好的

所以答案是,我分别插入每一行,并为每个地址检查往返到DB。地址检查是最糟糕的部分,因为它变得指数级的慢。我计算出,插入原始数据(1.5小时),然后再次插入相同的数据,需要约9小时

因此,这个答案将讨论我在转换为批量插入语句时所做的工作,以及一些需要注意的事情

  • sqlalchemy中的ORM将“帮助”
  • ORM很好,但要意识到它不能很好地与批量插入相匹配。批量插入需要在会话上使用较低级别的
    execute
    语句。它们不接受ORM对象作为输入,而是一个字典列表和一个
    insert
    对象。因此,如果要将一个充满行的csv文件转换为ORM对象,则需要而不是将它们添加到当前会话中,而是将它们转换为字典以备以后使用

    def asdict(obj):
        return dict((col.name, getattr(obj, col.name))
             for col in class_mapper(obj.__class__).mapped_table.c)
    
    currGUID = uuid.uuid4()
    currPrintOrMail = printOrMail(rec, id=currGUID)
    currStatement   = statements(rec, id=currGUID)
    currAddress = self.get_or_create(address, rec)
    currStatement.address = currAddress
    
    self.currPrintOrMail_bulk.append(asdict(currPrintOrMail))
    self.currStatement_bulk.append(asdict(currStatement))
    
    asdict方法起源于。这将获取创建的ORM对象中列的字典。它们永远不会被添加到会话中,并且很快就会从内存中消失

  • 人际关系会伤害你
  • 如果已设置ORM关系:

    class statements(Base):
        __tablename__ = 'statements'
        id = id_column()
        county   = Column(String(50),default='',nullable=False)
    
        address_id = Column(CHAR(36), ForeignKey('address.id'))
        address = relationship("address", backref=backref("statements", cascade=""))
    
        printOrMail_id = Column(CHAR(36), ForeignKey('printOrMail.id'))
        pom = relationship("printOrMail", backref=backref("statements", cascade=""))
    
        property_id = Column(CHAR(36), ForeignKey('property.id'))
        prop = relationship("property", backref=backref("statements", cascade=""))
    
    确保backref中的cascade为空!否则,将关系中的一个对象插入到会话中会导致其他对象无法访问。当您稍后尝试批量插入您的值时,它们将作为重复项被拒绝…如果您幸运的话

    这一点很重要,因为部分要求是获取有效地址(如果存在)的地址\u id,如果不存在,则添加地址。由于查询往返速度太慢,我将
    get\u或\u create
    更改为:

    def get_or_create(self, model, rec):
        """Check if current session has address. If not, query DB for it. If no one has the address, create and flush a new one to the session."""
        instance = self.session.query(model).get((rec['Name'], rec['Address_Line_One'], rec['Address_Line_Two'], rec['Address_Line_Three'], rec['Address_Line_Four']))
        if instance:
            return instance
        else:
            instance = model(rec)
            self.session.add(instance)
            self.session.flush()
            return instance
    
    使用
    get
    会导致sqlalchemy首先检查会话,从而防止跨网络的跳闸。但是,它只有在会话中添加新地址时才有效!还记得我们的关系吗?这是级联到语句插入中的。另外,如果您没有
    flush()
    或拥有
    autoflush=True
    ,则
    get
    无法看到新添加的对象

  • 创建会话时,请保留对象

    self.session=sessionmaker(autoflush=False,expire\u on\u commit=False)

  • 如果您没有包含
    expire\u on\u commit=False
    ,那么您将丢失地址,并再次开始往返

  • ORM对象没有插入
  • 现在我们有了一个字典列表,供ORM对象插入。但我们还需要一个插入对象

    self.session.execute(printOrMail.__table__.insert(), self.currPrintOrMail_bulk)
    self.session.execute(statements.__table__.insert(), self.currStatement_bulk)
    
    ,似乎可以使用
    classname.\uuuuu table\uuuu
    作为所需的表对象,这是所需的。因此,在会话中,使用ORM类获取表以获取insert对象,使用字典列表运行execute。别忘了事后承诺

  • 不要耗尽内存
  • 这将允许您成功地将批量插入和ORM与关系和查询sqlalchemy中的唯一条目混合在一起。小心内存不足。我必须一次批量插入30000条记录,否则
    py2.7(32位)
    将在使用大约
    2G时崩溃

    self.session.execute(printOrMail.__table__.insert(), self.currPrintOrMail_bulk)
    self.session.execute(statements.__table__.insert(), self.currStatement_bulk)