尝试在SQLAlchemy中重用主键ID的问题
我在用SQLAlchemy重用一个表里的主键时,遇到了外键约束错误。
简单来说:
- 使用的数据库是PostgreSQL 8.4
- 编程语言是Python 2.7
- 使用的SQLAlchemy版本是0.7
我有三个表:用户(User)、库存(Inventories)和设备(Devices)。库存和设备与用户之间是一对一的关系。用户的id是库存和设备表中的外键,分别叫做Inventories.user_id和Devices.user_id。
我已经按照标准的Python做法,在models/文件夹里设置好了用户、设备和库存。
在交互式Python环境中,我可以顺利执行以下命令:
>>>newUser = User.create()
>>>newUser.device = User.create_device(<*args>)
>>>Session.add(newUser)
>>>Session.commit()
(在代码中会自动创建一个库存记录)
现在,假设我想重用用户记录1(这是唯一一个能让代码中调用reset方法的记录,出于安全和内部测试的原因)
>>>oldUser = User.retrieve(1)
>>>Session.delete(oldUser)
>>>Session.commit()
(确认用户1不再存在)
>>>newUser = User.create()
>>>newUser.device = User.create_device(<*args>)
>>>newUser.id = 1
>>>Session.add(newUser)
>>>Session.commit()
此时,我会收到一个错误,提示Key(id)=(<id>
)仍然被“devices”(或“inventories”)表引用,其中<id>
是新用户的id,在重新分配为id 1之前。
我查过级联删除的设置,也尝试了各种选项(比如all、save-update等),但都没有效果。
如果能告诉我哪里出错了,我会非常感激。
谢谢,
Krys
3 个回答
我不是在用SQLAlchemy,所以不能给出准确的答案,但我可以告诉你,首先要问自己,你真的需要这样做吗?
因为,
- 你可能会破坏数据的完整性,这可能会引发严重的问题。
- 你需要打破ID的自动递增结构,所以在那之前,你得手动分配ID,或者使用手写的预保存触发器来获取合适的ID。
- 如果你的表中有一个用户外键,并且设置为不允许为空,那么在删除用户后,你可能会遇到释放与该用户相关的记录的问题。如果不把它们设为null,重新使用的ID会导致严重的数据完整性问题(错误的引用关系)……
所以首先,你得决定这是否值得?
为了处理你看到的错误,你可以在提交之前,更新所有与那个 User
模型相关的 Device
和 Inventory
模型中的外键。你需要确保你的 User
模型的 id 不会自动增加(也就是说,它不是一个 PostgreSQL 的序列)。
比如,SQLAlchemy 模型的声明应该是
class User(base):
__tablename__ = 'user'
id = Column('id', Integer, primary_key=True, unique=True, nullable=False)
而不是
class User(base):
__tablename__ = 'user'
id = Column('id', Integer, Sequence('user_id_seq'), primary_key=True)
不过,这可能不是最好的做法!更好的设计是使用一个序列来处理 User.id
(就像第二个模型声明那样),并在用户表中添加一个字段,表示这个用户是否是管理员(为了你提到的安全性和测试目的)。这样,你就不需要在应用程序逻辑中依赖一些神秘的数字(例如,用户 id),特别是在安全方面。
因为这个问题在实际使用中不应该出现,所以可以直接使用 SET CONSTRAINTS
。你也可以在你的 FOREIGN KEY
上使用 INITIALLY DEFERRED
,但我不太推荐这样做,因为你并不是在处理生产环境中存在的循环依赖问题。