Flask中的(pymysql.err.IntegrityError)(1451,'无法删除或更新父行:外键约束失败...')

问题描述 投票:-1回答:1

我知道标题中有很多关于错误的问题,但我找不到合适的解决方案。我的问题是,当使用Session.delete()删除行时,它正在抛出

sqlalchemy.exc.IntegrityError: (pymysql.err.IntegrityError) (1451, 'Cannot delete or update a parent row: a foreign key constraint fails (`transport`.`driver`, CONSTRAINT `driver_ibfk_1` FOREIGN KEY (`owner_id`) REFERENCES `truckcompany` (`id`))') [SQL: 'DELETE FROM truckcompany WHERE truckcompany.id = %(id)s'] [parameters: {'id': 4}]

楷模:

class Truck_company(Base):

    __tablename__ = 'truckcompany'

    id = Column(BigInteger, primary_key=True)

class Driver(Base):

    __tablename__ = 'driver'

    id = Column(BigInteger, primary_key=True)
    owner_id = Column(BigInteger, ForeignKey('truckcompany.id'))
    owner = relationship(Truck_company)

删除失败的视图:

@app.route('/values/deleteuser/<int:id>', methods=['POST', 'GET'])
def delete_truck(id):
    value_truckcompany = sqlsession.query(Truck_company).filter(Truck_company.id == id).first()
    if value_truckcompany:
        sqlsession.delete(value_truckcompany)
        sqlsession.commit()
        return redirect('/static/truckcompanyview', )
python flask sqlalchemy
1个回答
2
投票

为什么

在你的Driver模型中有一个引用Truck_company的外键约束:

class Driver(Base):
    ...
    owner_id = Column(BigInteger, ForeignKey('truckcompany.id'))

你已经省略了ON DELETE动作,所以MySQL defaults to RESTRICT。此外,没有SQLAlchemy ORM与级联的关系会删除相关的驱动程序。因此,当您尝试在视图中删除卡车公司时,数据库会阻止您执行此操作,因为您违反了外键约束,换句话说就是referential integrity。这是您如何建模数据库的问题,而不是Flask等。

我能做什么

最重要的事情 - 当你创建你的模型时 - 是决定当你删除一个有相关驱动程序的卡车公司时你想要发生什么。您的选择包括但不限于:

  1. 也删除了驱动程序。
  2. 将他们的owner_id设置为NULL,有效地分离它们。如果父级的默认配置中存在ORM关系,那么SQLAlchemy就会这样做。

它也是一个非常有效的解决方案来限制删除带子节点的父行,正如您已经隐式完成的那样。

您已在评论中表示您要删除相关的驱动程序。快速解决方案是手动发出DELETE:

# WARNING: Allowing GET in a data modifying view is a terrible idea.
# Prepare yourself for when Googlebot, some other spider, or an overly
# eager browser nukes your DB.
@app.route('/values/deleteuser/<int:id>', methods=['POST', 'GET'])
def delete_truck(id):
    value_truckcompany = sqlsession.query(Truck_company).get(id)

    if value_truckcompany:
        sqlsession.query(Driver).\
            filter_by(owner=value_truckcompany).\
            delete(synchronize_session=False)
        sqlsession.delete(value_truckcompany)
        sqlsession.commit()
        return redirect('/static/truckcompanyview', )

另一方面,这仅修复了这一个位置。如果你决定Driver没有其Truck_company没有意义,你可以alter the foreign key constraint包含ON DELETE CASCADE,并在相关的SQLAlchemy ORM关系中使用passive deletes

class Truck_company(Base):
    ...
    # Remember to use passive deletes with ON DELETE CASCADE
    drivers = relationship('Driver', passive_deletes=True)

class Driver(Base):
    ...
    # Let the DB handle deleting related rows
    owner_id = Column(BigInteger, ForeignKey('truckcompany.id',
                                             ondelete='CASCADE'))

或者你可以将它留给SQLAlchemy ORM级别级联来删除相关对象,但似乎你在过去遇到了一些问题。请注意,SQLAlchemy cascades定义了operation on the parent应如何传播给其子节点,因此您可以在父节关系或一对多方面定义delete和可选的delete-orphan

class Truck_company(Base):
    ...
    # If a truck company is deleted, delete the related drivers as well
    drivers = relationship('Driver', cascade='save-update, merge, delete')

在您当前的模型中,您没有从Truck_companyDriver定义的关系,因此不会发生级联。


请注意,修改Driver如:

class Driver(Base):
    ...
    owner_id = Column(BigInteger, ForeignKey('truckcompany.id',
                                             ondelete='CASCADE'))

不会神奇地迁移现有的数据库表及其约束。如果你想采取这种方式,你将不得不使用migrate manually或使用一些工具。