2017-01-27 18 views
2

У меня есть код, который записывает Scrap скребущие данные на SQL-сервер db. Элементы данных состоят из некоторых основных данных о гостинице (имя, адрес, рейтинг ..) и некоторый список номеров со связанными данными (цена, занятость и т. Д.). Там могут быть несколько нитей сельдерея и несколько серверов, на которых выполняется этот код, и одновременно записывать в db разные элементы. Я сталкиваюсь с ошибками тупиковых, как:Ошибки SQL-сервера, pyobbc и тупика

[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, которая делает то же самое (проверяет отель, поднимает его, записывает идентификатор во временную переменную, для каждой комнаты проверяет, существует ли upserts против hotel id var и т. д.), затем сделать только один curs.execute(...) в коде python, тогда я больше не вижу ошибки взаимоблокировки.

Однако я не совсем понимаю, почему это имеет значение, и я не совсем уверен, что безопасно запускать большие SQL-блоки с несколькими SELECT, INSERTS, UPDATES в одном pyodbc curs.execute. Поскольку я понимаю, что pyodbc предполагает обрабатывать только отдельные операторы, однако он работает, и я вижу, что мои таблицы заполняются без ошибок взаимоблокировки.

Тем не менее, кажется, невозможно получить какой-либо результат, если я делаю такую ​​большую команду. Я попытался объявить переменную @output_string и записывать различные вещи к нему (делали мы должны вставить или обновить отель, например), прежде чем, наконец, SELECT @output_string as outputstring, но делать выборку после выполнения в pyodbc всегда терпит неудачу с

<class 'pyodbc.ProgrammingError'>: No results. Previous SQL was not a query. 

Эксперименты в пределах оболочка предложить 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, мой реальный «большой запрос», состоящий из нескольких выборок, обновлений, вставок, фактически работает и заполняет несколько таблиц в db.

Тем не менее, если я пытаюсь что-то вроде

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 Установка autocommit=True в dbargs, кажется, чтобы предотвратить ошибки тупиковыми, даже с несколькими curs.executes. Но почему это исправить?

+0

Выполняется ли ваша «большая команда SQL» (предположительно, содержащая несколько операторов) с помощью 'SET NOCOUNT ON;'? –

+0

@GordThompson Нет, это началось с «BEGIN» и заканчивается «END;», внутри него находятся несколько других блоков с «BEGIN, END», «IF/ELSE», «SELECTS», «INSERT», «UPDATES», , – fpghost

ответ

2

Setting autocommit=True in the dbargs seems to prevent the deadlock errors, even with the multiple curs.executes. But why does this fix it?

При установлении соединения, pyodbc по умолчанию будет autocommit=False в соответствии с Python DB-API спецификации. Поэтому, когда выполняется первый оператор SQL, ODBC начинает транзакцию базы данных, которая остается в силе до тех пор, пока код Python не сделает .commit() или .rollback() в соединении.

Уровень изоляции транзакции по умолчанию в SQL Server - «Чтение прочитанного». Если база данных не настроена на поддержку изоляции SNAPSHOT по умолчанию, операция записи в транзакции в режиме изоляции Read Committed помещает блокировки с транзакцией в строки, которые были обновлены. В условиях высокого параллелизма могут возникать взаимоблокировки, если несколько процессов генерируют конфликтующие блокировки. Если эти процессы используют долгоживущие транзакции, которые генерируют большое количество таких блокировок, шансы на блокировку больше.

Установка autocommit=True позволит избежать взаимоблокировок, поскольку каждый отдельный оператор SQL будет автоматически зафиксирован, что прекратит транзакцию (которая была автоматически запущена при запуске этого оператора) и освободила любые блокировки обновленных строк.

Таким образом, чтобы избежать тупиков вы можете рассмотреть несколько различных стратегий:

  • продолжать использовать autocommit=True или
  • есть код Python явно более .commit() часто, или
  • использовать SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED к «Ослабьте» уровень изоляции транзакций и избегайте постоянных блокировок, созданных операциями записи, или
  • сконфигурируйте базу данных для использования изоляции SNAPSHOT, которая позволит избежать конфликтов блокировки, но будет упростить работу SQL Server.

Вам нужно будет выполнить домашнюю работу, чтобы определить лучшую стратегию для вашего конкретного случая использования.