Mysql 使用sqlalchemy检查行和返回id是否存在速度慢
全部, 我正在读取一个csv文件,并使用sqlalchemy将数据添加到MySQL数据库中。其中一个表是地址表,它只应该包含唯一的地址。这些地址与另一个“语句”表之间存在关系,该表具有地址id的外键字段 因此,对于数据文件中的每一行,我创建一个新语句obj,然后获取关联地址的id。如果地址已经存在,则返回该id。否则,我将创建一个新地址obj并返回该id。这是使用下面的代码完成的,改编自 我对我的id字段使用GUID,它是地址表主键的一部分: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): _
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小时
因此,这个答案将讨论我在转换为批量插入语句时所做的工作,以及一些需要注意的事情
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对象中列的字典。它们永远不会被添加到会话中,并且很快就会从内存中消失
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
无法看到新添加的对象
expire\u on\u commit=False
,那么您将丢失地址,并再次开始往返
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。别忘了事后承诺
py2.7(32位)
将在使用大约2G时崩溃
self.session.execute(printOrMail.__table__.insert(), self.currPrintOrMail_bulk)
self.session.execute(statements.__table__.insert(), self.currStatement_bulk)