Продолжение транзакции после ошибки нарушения первичного ключа - PullRequest
9 голосов
/ 03 марта 2010

Я делаю массовую вставку записей в базу данных из файла журнала. Иногда (~ 1 строка из каждой тысячи) одна из строк нарушает первичный ключ и вызывает сбой транзакции. В настоящее время пользователь должен вручную просмотреть файл, вызвавший сбой, и удалить строку, вызвавшую сбой, перед попыткой повторного импорта. Учитывая, что импортировать эти сотни файлов нецелесообразно.

Мой вопрос: Как я могу пропустить вставку записей, которые нарушат ограничение первичного ключа, без необходимости делать оператор SELECT перед каждой строкой, чтобы увидеть, существует ли он уже?

Примечание. Мне известен очень похожий вопрос # 1054695 , но, похоже, это специфический ответ для SQL Server, и я использую PostgreSQL (импорт через Python / psycopg2).

Ответы [ 4 ]

13 голосов
/ 03 марта 2010

Вы также можете использовать SAVEPOINTs в транзакции.

Pythonish псевдокод является иллюстрацией со стороны приложения:

database.execute("BEGIN")
foreach data_row in input_data_dictionary:
    database.execute("SAVEPOINT bulk_savepoint")
    try:
        database.execute("INSERT", table, data_row)
    except:
        database.execute("ROLLBACK TO SAVEPOINT bulk_savepoint")
        log_error(data_row)
        error_count = error_count + 1
    else:
        database.execute("RELEASE SAVEPOINT bulk_savepoint")

if error_count > error_threshold:
    database.execute("ROLLBACK")
else:
    database.execute("COMMIT")

Edit: Вот фактический пример этого в действии в psql, основанный на небольшом изменении примера в документации (операторы SQL с префиксом ">"):

> CREATE TABLE table1 (test_field INTEGER NOT NULL PRIMARY KEY);
NOTICE:  CREATE TABLE / PRIMARY KEY will create implicit index "table1_pkey" for table "table1"
CREATE TABLE

> BEGIN;
BEGIN
> INSERT INTO table1 VALUES (1);
INSERT 0 1
> SAVEPOINT my_savepoint;
SAVEPOINT
> INSERT INTO table1 VALUES (1);
ERROR:  duplicate key value violates unique constraint "table1_pkey"
> ROLLBACK TO SAVEPOINT my_savepoint;
ROLLBACK
> INSERT INTO table1 VALUES (3);
INSERT 0 1
> COMMIT;
COMMIT
> SELECT * FROM table1;  
 test_field 
------------
          1
          3
(2 rows)

Обратите внимание, что значение 3 было вставлено после ошибки, но все еще внутри той же транзакции!

Документация для SAVEPOINT находится по адресу http://www.postgresql.org/docs/8.4/static/sql-savepoint.html.

4 голосов
/ 03 марта 2010

Я бы использовал хранимую процедуру, чтобы перехватить исключения ваших уникальных нарушений. Пример:

CREATE OR REPLACE FUNCTION my_insert(i_foo text, i_bar text)
  RETURNS boolean LANGUAGE plpgsql AS
$BODY$
begin   
    insert into foo(x, y) values(i_foo, i_bar);
    exception
        when unique_violation THEN -- nothing

    return true;
end;
$BODY$;

SELECT my_insert('value 1','another value');
1 голос
/ 03 февраля 2017

Вы можете сделать rollback для транзакции или откат до точки сохранения непосредственно перед кодом, который вызывает исключение (cr - курсор):

name = uuid.uuid1().hex
cr.execute('SAVEPOINT "%s"' % name)
try:
    # your failing query goes here
except Exception:
    cr.execute('ROLLBACK TO SAVEPOINT "%s"' % name)
    # your alternative code goes here 
else:
    cr.execute('RELEASE SAVEPOINT "%s"' % name)

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

Django postgresql backend создает курсоры непосредственно из psycopg . Возможно, в будущем они создадут прокси-класс для курсора Django, аналогичный курсору odoo . Они расширяют курсор с помощью следующего кода (self является курсором):

@contextmanager
@check
def savepoint(self):
    """context manager entering in a new savepoint"""
    name = uuid.uuid1().hex
    self.execute('SAVEPOINT "%s"' % name)
    try:
        yield
    except Exception:
        self.execute('ROLLBACK TO SAVEPOINT "%s"' % name)
        raise
    else:
        self.execute('RELEASE SAVEPOINT "%s"' % name)

Таким образом, контекст делает ваш код проще, это будет:

try:
    with cr.savepoint():
        # your failing query goes here
except Exception:
    # your alternative code goes here 

и код более читабелен, потому что там нет информации о транзакции.

0 голосов
/ 03 марта 2010

Или вы можете использовать SSIS, и ошибочные строки выбирают путь, отличный от успешных.

Так как вы используете другую базу данных, можете ли вы массово вставить файлы в промежуточную таблицу, а затем использовать код SQL для выбора только тех записей, которые не имеют существующего идентификатора?

...