当我尝试仅删除一行时,SQLAlchemy正在做额外的选择

时间:2018-07-02 15:30:59

标签: python postgresql sqlalchemy

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,并且在sqlalchemy中的TableY中定义的关系为:

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字段上没有索引-导致超时。

创建索引不是一个好的解决方案,因为它将毫无用处:我确信在执行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 :(得分:1)

“额外” SELECT由Postgresql本身完成,以强制执行外键约束及其on Delete约束。 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

There are ways to disable foreign key checks,但您必须知道自己在做什么,以免破坏参照完整性。另一个选择是考虑您是否完全需要on delete限制,或者仅创建索引;您提到表Y是关联表,因此引用表X id的列可能应该是其主键的一部分。尽管您确定删除时Y上没有引用X的行,但是数据库不检查就无法知道这一点。