Python SQLAlchemy是在我试图删除一行时执行额外的选择
TL;博士 我在生产中遇到超时错误:Python SQLAlchemy是在我试图删除一行时执行额外的选择,python,postgresql,sqlalchemy,Python,Postgresql,Sqlalchemy,TL;博士 我在生产中遇到超时错误: 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
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}
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}
主要原因是仅从public.tableY x中选择1,因为字段tableX_id没有索引。我正在尝试找出此查询的来源,我不需要此检查
充分解释
我有两个表,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还试图用外键删除所有相关行,因此在执行删除查询之前,它会执行
SELECT id FROM tableY where tableX_id=42
但是tableY是一个多对多关系表,所以它在tableX_id字段上没有索引,这会导致超时
创建索引不是一个好的解决方案,因为它将是无用的:我确信,当我执行删除操作时,将不会有任何相关记录,因此我将有相当大的索引,其中不包含任何相关信息。它将只包含一个垃圾信息
因此,我希望DB能够处理这种情况,并添加了被动_deletes=True
它似乎解决了一个问题,但现在我在生产中遇到了新的超时错误:
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}
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}
什么可以执行这个查询?这是来自炼金术吗?如果是,我如何禁用它
PostgreSQL 9.4
SQLAlchemy 0.9.8是的,我知道:额外的选择是由Postgresql本身完成的,目的是强制执行外键约束及其删除限制。Postgresql正在检查表Y中是否有任何行引用了表X中要删除的行。您可以通过一些测试表轻松地重现这种情况,并设置一个低得离谱的语句超时: 开始 创建表foo id串行主键 ; 创建表格栏 foo_id int在删除限制时引用foo id ; 在foo中插入默认值; 在foo中插入默认值; 从generate_Series11000001中选择2,插入到条中; -超时5毫秒 set语句_timeout=5; -尝试删除未在栏中引用的行,以便扫描 从foo中删除,其中id=1; 回降; 结果是:
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的行,但如果不进行检查,数据库将无法知道这一点。额外的选择是由Postgresql本身完成的,以便强制执行外键约束及其删除时的约束。Postgresql正在检查表Y中是否有任何行引用了表X中要删除的行。您可以通过一些测试表轻松地重现这种情况,并设置一个低得离谱的语句超时: 开始 创建表foo id串行主键 ; 创建表格栏 foo_id int在删除限制时引用foo id ; 在foo中插入默认值; 在foo中插入默认值; 从generate_Series11000001中选择2,插入到条中; -超时5毫秒 set语句_timeout=5; -尝试删除未在栏中引用的行,以便扫描 从foo中删除,其中id=1; 回降; 结果是:
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的行,但如果不进行检查,数据库将无法知道这一点