SQLAlchemy делает дополнительный выбор, когда я пытаюсь просто удалить строку - PullRequest
0 голосов
/ 02 июля 2018

TL; DR

Я получаю сообщение об ошибке тайм-аута на производстве:

OperationalError: (QueryCanceledError) canceling statement due to statement timeout CONTEXT: SQL statement "SELECT 1 FROM ONLY "public"."tableY" x WHERE $1 OPERATOR(pg_catalog.=) "tableX_id" FOR KEY SHARE OF x" 'DELETE FROM tableX WHERE tableX.id = %(id)s' {'id': 42}

и основной причиной является SELECT 1 FROM ONLY "public"."tableY" x, потому что по полю tableX_id нет индекса. Я пытаюсь выяснить, откуда исходит этот запрос, мне не нужна эта проверка.

Полное объяснение

У меня есть 2 таблицы, tableX и tableY, и отношение, определенное в TableY в sqlalchemy как:

class TableY(Base):
    ...
    tableX = relationship(
        'TableX',
        backref=backref(
            'rows_y',
            uselist=True,
            lazy='dynamic',
        ),
        uselist=False,
    )

и в SQL как

create table if not exists tableY
(
...
tableX_id integer not null
    constraint fk_tableX_id_tableY
        references state_purchase
            on update cascade on delete restrict,
)

Я пытаюсь удалить строку из таблицы

tableX_obj.delete()

SQLAlchemy пытается удалить также все связанные строки (с внешним ключом), поэтому ДО выполнения запроса DELETE он выполняет

SELECT id FROM tableY where tableX_id=42

но tableY - это таблица отношений "многие ко многим", поэтому у нее нет индекса для поля tableX_id - что приводит к TIMEOUT.

Создание индекса не было хорошим решением, потому что оно будет бесполезным: я уверен, что когда я делаю DELETE, не будет никаких связанных записей, поэтому у меня будет довольно большой индекс, который НЕ будет содержать никаких релевантных Информация. Он будет содержать только информацию о мусоре.

Итак, я хотел, чтобы БД справилась с этой ситуацией, и добавил passive_deletes=True

state_purchase = relationship(
    'StatePurchase',
    backref=backref(
        'recommendations',
        uselist=True,
        lazy='dynamic',
        passive_deletes=True,
    ),
    uselist=False,
)

Казалось, проблема решена, НО теперь я получаю новую ошибку тайм-аута на производстве:

OperationalError: (QueryCanceledError) canceling statement due to statement timeout CONTEXT: SQL statement "SELECT 1 FROM ONLY "public"."tableY" x WHERE $1 OPERATOR(pg_catalog.=) "tableX_id" FOR KEY SHARE OF x" 'DELETE FROM tableX WHERE tableX.id = %(id)s' {'id': 42}

Что может выполнить этот запрос? Это происходит от SQLAlchemy? Если так, как я могу отключить это?

PostgreSQL 9.4 SQLAlchemy 0.9.8 (да, я знаю :()

1 Ответ

0 голосов
/ 03 июля 2018

«Дополнительный» SELECT выполняется самим Postgresql, чтобы обеспечить ограничение вашего внешнего ключа и ограничение на удаление. Postgresql проверяет, ссылается ли какая-либо строка в таблице Y на строку, подлежащую удалению, в таблице X. Вы можете легко воспроизвести это условие с некоторыми тестовыми таблицами и установить смехотворно низкое время ожидания оператора:

begin;
create table foo (
        id serial primary key
);

create table bar (
        foo_id int references foo (id) on delete restrict
);

insert into foo default values;
insert into foo default values;

insert into bar select 2 from generate_series(1, 1000001);

-- timeout of 5 ms
set statement_timeout = 5;
-- try and delete a row not referenced in bar, so scan
delete from foo where id = 1;
rollback;

и результат:

BEGIN
CREATE TABLE
CREATE TABLE
INSERT 0 1
INSERT 0 1
INSERT 0 1000001
SET
psql:test.sql:18: ERROR:  canceling statement due to statement timeout
CONTEXT:  SQL statement "SELECT 1 FROM ONLY "public"."bar" x WHERE $1 OPERATOR(pg_catalog.=) "foo_id" FOR KEY SHARE OF x"
ROLLBACK

Есть способы отключить проверки внешнего ключа , но вы должны знать, что делаете, чтобы не нарушить свою ссылочную целостность. Другой вариант - подумать, нужно ли вам вообще использовать ограничение на удаление или просто создать индекс; Вы упоминаете, что таблица Y является таблицей ассоциации, поэтому, возможно, столбец, ссылающийся на таблицу X id, должен быть частью ее первичного ключа. Хотя вы уверены, что в Y не осталось строк, ссылающихся на X при удалении, база данных не сможет узнать об этом без проверки.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...