Python SQLAlchemy IntegrityError和批量数据导入

Python SQLAlchemy IntegrityError和批量数据导入,python,sqlalchemy,Python,Sqlalchemy,我正在使用REF完整性规则将几个10k记录插入数据库。不幸的是,有些数据行是重复的(因为它们已经存在于数据库中)。在插入前检查数据库中每一行的存在性代价太高,因此我打算继续处理SQLAlchemy抛出的IntegrityError异常,记录错误,然后继续 我的代码如下所示: # establish connection to db etc. tbl = obtain_binding_to_sqlalchemy_orm() datarows = load_rows_to_import() tr

我正在使用REF完整性规则将几个10k记录插入数据库。不幸的是,有些数据行是重复的(因为它们已经存在于数据库中)。在插入前检查数据库中每一行的存在性代价太高,因此我打算继续处理SQLAlchemy抛出的IntegrityError异常,记录错误,然后继续

我的代码如下所示:

# establish connection to db etc.

tbl = obtain_binding_to_sqlalchemy_orm()
datarows = load_rows_to_import()

try:
    conn.execute(tbl.insert(), datarows)
except IntegrityError as ie:
    # eat error and keep going
except Exception as e:
    # do something else
我在上面所做的(隐含的)假设是SQLAlchemy并不是将多个插入滚动到一个事务中。如果我的假设是错误的,则意味着如果发生IntegrityError,则插入的其余部分将中止。有人能确认上面的伪代码“模式”是否能按预期工作吗?或者我是否会因为抛出IntegrityError异常而丢失数据


另外,如果有人对此有更好的想法,我很想听听。

如果您之前没有启动任何事务,它可能会这样工作,就像本例中的sqlalchemy将启动一样。但是您应该按照链接中的描述显式设置。

在解析ASCII数据文件以将数据导入表时,我也遇到了这个问题。问题是,我本能地和直觉地希望SQLAlchemy跳过重复的行,同时允许唯一的数据。或者,由于当前的SQL引擎(例如不允许使用unicode字符串),可能会在一行中引发随机错误

但是,此行为超出了SQL接口定义的范围。SQLAPI,因此SQLAlchemy只理解事务和提交,而不考虑这种选择性行为。此外,依赖自动提交功能听起来很危险,因为插入在异常发生后会停止,留下其余的数据

我的解决方案(我不确定它是否最优雅)是处理循环中的每一行,捕获并记录异常,并在最后提交更改

假设您以某种方式获取了列表列表中的数据,即列值列表中的行列表。然后读取循环中的每一行:

# Python 3.5
from sqlalchemy import Table, create_engine
import logging

# Create the engine
# Create the table
# Parse the data file and save data in `rows`

conn = engine.connect() 
trans = conn.begin() # Disables autocommit

exceptions = {}
totalRows = 0
importedRows = 0

ins = table.insert()

for currentRowIdx, cols in enumerate(rows):
    try:
        conn.execute(ins.values(cols)) # try to insert the column values
        importedRows += 1

    except Exception as e:
        exc_name = type(e).__name__ # save the exception name
        if not exc_name in exceptions:
            exceptions[exc_name] = []
        exceptions[exc_name].append(currentRowIdx)

    totalRows += 1

for key, val in exceptions.items():
    logging.warning("%d out of %d lines were not imported due to %s."%(len(val), totalRows, key))

logging.info("%d rows were imported."%(importedRows))

trans.commit() # Commit at the very end
conn.close()
为了最大限度地提高此操作的速度,应禁用自动提交。我将此代码用于SQLite,它仍然比仅使用
sqlite3
的旧版本慢3-5倍,即使禁用了自动提交。(我移植到SQLAlchemy的原因是能够将它与MySQL一起使用。)


它不是最优雅的解决方案,因为它没有SQLite的直接接口那么快。如果我分析代码并在不久的将来找到瓶颈,我将用解决方案更新此答案。

我尝试过此方法,但无效。这是发生错误时回滚事务的原则。。。使此代码工作的唯一方法是不使用事务,这当然会降低插入速度。