【发布时间】:2017-01-27 18:47:49
【问题描述】:
我有一些代码可以将 Scrapy 抓取的数据写入 SQL 服务器数据库。数据项包括一些基本的酒店数据(名称、地址、评级..)和一些带有相关数据(价格、入住率等)的房间列表。可以有多个 celery 线程和多个服务器运行此代码并同时写入 db 不同的项目。我遇到了像这样的死锁错误:
[Failure instance: Traceback: <class 'pyodbc.ProgrammingError'>:
('42000', '[42000] [FreeTDS][SQL Server]Transaction (Process ID 62)
was deadlocked on lock resources with another process and has been
chosen as the deadlock victim. Rerun the transaction. (1205) (SQLParamData)')
实际执行插入/更新的代码大致如下所示:
1) Check if hotel exists in hotels table, if it does update it, else insert it new.
Get the hotel id either way. This is done by `curs.execute(...)`
2) Python loop over the hotel rooms scraped. For each room check if room exists
in the rooms table (which is foreign keyed to the hotels table).
If not, then insert it using the hotel id to reference the hotels table row.
Else update it. These upserts are done using `curs.execute(...)`.
实际上比这要复杂一些,但这说明 Python 代码在循环之前和循环期间使用了多个 curs.executes。
如果不是以上述方式更新数据,我生成一个大 SQL 命令,它执行相同的操作(检查酒店,更新它,将 id 记录到一个临时变量,为每个房间检查是否存在并更新针对酒店 id var 等),然后在 python 代码中只执行一个 curs.execute(...),然后我不再看到死锁错误。
但是,我真的不明白为什么这会有所不同,而且我也不完全确定在单个 pyodbc curs.execute 中运行具有多个 SELECTS、INSERTS、UPDATES 的大型 SQL 块是否安全。据我了解,pyodbc 假设只处理单个语句,但它似乎确实有效,而且我看到我的表填充没有死锁错误。
尽管如此,如果我执行这样的大命令,似乎不可能得到任何输出。我尝试在最终SELECT @output_string as outputstring 之前声明一个变量@output_string 并记录各种内容(例如,我们是否必须插入或更新酒店),但是在pyodbc 中执行后执行获取总是失败
<class 'pyodbc.ProgrammingError'>: No results. Previous SQL was not a query.
shell 中的实验表明 pyodbc 会忽略第一条语句之后的所有内容:
In [11]: curs.execute("SELECT 'HELLO'; SELECT 'BYE';")
Out[11]: <pyodbc.Cursor at 0x7fc52c044a50>
In [12]: curs.fetchall()
Out[12]: [('HELLO', )]
因此,如果第一条语句不是查询,则会出现该错误:
In [13]: curs.execute("PRINT 'HELLO'; SELECT 'BYE';")
Out[13]: <pyodbc.Cursor at 0x7fc52c044a50>
In [14]: curs.fetchall()
---------------------------------------------------------------------------
ProgrammingError Traceback (most recent call last)
<ipython-input-14-ad813e4432e9> in <module>()
----> 1 curs.fetchall()
ProgrammingError: No results. Previous SQL was not a query.
尽管如此,除了无法获取我的@output_string,我真正的“大查询”,包括多个选择、更新、插入,实际上可以工作并填充数据库中的多个表。
不过,如果我尝试类似的方法
curs.execute('INSERT INTO testX (entid, thecol) VALUES (4, 5); INSERT INTO testX (entid, thecol) VALUES (5, 6); SELECT * FROM testX; '
...: )
我看到这两行都插入到表 tableX 中,即使是后续的 curs.fetchall() 也会失败,并显示“以前的 SQL 不是查询”。错误,所以似乎 pyodbc execute 确实执行了所有内容……而不仅仅是第一条语句。
如果我可以相信这一点,那么我的主要问题是如何获取一些日志输出。
EDIT 在 dbargs 中设置 autocommit=True 似乎可以防止死锁错误,即使使用多个 curs.executes 也是如此。但是为什么这会解决它呢?
【问题讨论】:
-
您的“大 SQL 命令”(可能包含多个语句)是否以
SET NOCOUNT ON;开头? -
@GordThompson 不,它以“BEGIN”开始并以“END;”结束,其中有多个其他块,“BEGIN,END;”,“IF/ELSE”,“SELECTS”, “插入”、“更新”。
标签: sql-server parallel-processing scrapy celery pyodbc