这是新年后的第一篇文章,上班第一天还在恢复上下文的状态. 就又看了下peewee的代码。
正题开始,Mysql外键的作用我想大家都知道,外键是为了更好的约束数据保证了数据的完整性,在一定程度上杜绝了bug的产生。
- 插入非空值时,如果主键表中没有这个值,则不能插入。
- 更新记录时,value值一定存在于主键表中,否在就update失败。
- 删除主键表记录时,你可以在建外键时设定外键记录一起级联删除还是拒绝删除。
- 更新主键记录时,同样有级联更新和拒绝执行的选择。
对于开发人员来说,不用再考虑数据完整性,直接搞就行了,但是对于dba来说,他们是及其反对使用外键的,为毛? 性能啊.
这算是个缺点,外键往往在你更新子表或者删除字表数据都会去主表判断一下,这是个隐式操作。 其实开发人员对于是否使用外键也是成两派的,我其实是中立的,用也行,不用也行,如果上面没人强制我,我一般就不用外键来约束数据。
这次主要是聊下,python peewee这个orm在使用 ForeignKeyField 外键时遇到的问题. 我这里吐槽下peewee的文档不是很全面,关于ForeignKeyField是有不少例子,但例子更多的是快速执行,而没有一些细节的描述。
文章写的不是很严谨,欢迎来喷,另外该文后续有更新的,请到原文地址查看更新。
下面是mysql官方给出的外键的定义语法:
[CONSTRAINT symbol] FOREIGN KEY [id] (index_col_name, …)
REFERENCES tbl_name (index_col_name, …)
[ON DELETE {RESTRICT | CASCADE | SET NULL | NO ACTION | SET DEFAULT}]
[ON UPDATE {RESTRICT | CASCADE | SET NULL | NO ACTION | SET DEFAULT}]
该语法可以在 CREATE TABLE 和 ALTER TABLE 时使用,如果不指定CONSTRAINT symbol,MYSQL会自动生成一个名字。
ON DELETE、ON UPDATE表示事件触发限制,可设参数:
RESTRICT(限制外表中的外键改动)
CASCADE(跟随外键改动)
SET NULL(设空值)
SET DEFAULT(设默认值)
NO ACTION(无动作,默认的)
接着我们来看下peewee的源码,记得上次写过peewee源码结构的文章。 下面是peewee ForeignKeyField类的实现源码,代码是精简过的.
#blog: xiaorui.cc class ForeignKeyField(IntegerField): def __init__(self, rel_model, related_name=None, on_delete=None, on_update=None, extra=None, to_field=None, *args, **kwargs): if rel_model != 'self' and not \ isinstance(rel_model, (Proxy, DeferredRelation)) and not \ issubclass(rel_model, Model): raise TypeError('Unexpected value for `rel_model`. Expected ' '`Model`, `Proxy`, `DeferredRelation`, or "self"') self.rel_model = rel_model self._related_name = related_name self.deferred = isinstance(rel_model, (Proxy, DeferredRelation)) self.on_delete = on_delete self.on_update = on_update self.extra = extra self.to_field = to_field super(ForeignKeyField, self).__init__(*args, **kwargs) def clone_base(self, **kwargs): return super(ForeignKeyField, self).clone_base( rel_model=self.rel_model, related_name=self.related_name, on_delete=self.on_delete, on_update=self.on_update, extra=self.extra, to_field=self.to_field, **kwargs) ... ... ... ... class QueryCompiler(object): def foreign_key_constraint(self, field): ddl = [ SQL('FOREIGN KEY'), EnclosedClause(field.as_entity()), SQL('REFERENCES'), field.rel_model.as_entity(), EnclosedClause(field.to_field.as_entity())] if field.on_delete: ddl.append(SQL('ON DELETE %s' % field.on_delete)) if field.on_update: ddl.append(SQL('ON UPDATE %s' % field.on_update)) return Clause(*ddl) ...... ......
这次结合peewee的ForeignKeyField类,最终搞定是怎么传递cascade restrict外键参数.
另外我们注意下foreign_key_constraint这函数,他是用来组建ddl语句的,该函数主要把on_delete , on_update参数拼凑成ddl建表语句。 这里的on_delete肯定就是cascade,restrict了.
这是最后的执行实例,在ForeignKeyField里可以传递on_delete, on_update参数.
#blog: xiaorui.cc #!/usr/bin/env python # -*- coding:utf-8 -*- from peewee import * mysql_db = MySQLDatabase("rui", user = "root", passwd = "xiaorui.cc") mysql_db.connect() #连接数据库 print mysql_db.get_conn() #获取数据库连接 class BaseModel(Model): class Meta: database = mysql_db class User(BaseModel): username = CharField() join_date = DateTimeField() about_me = TextField() class Message(BaseModel): xiaorui = ForeignKeyField(User, related_name='messages',on_delete='CASCADE') body = TextField() send_date = DateTimeField() User.create_table() Message.create_table()
我们在mysql终端下看看message表的结构。
#blog: xiaorui.cc mysql> show create table message; +---------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | Table | Create Table | +---------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | message | CREATE TABLE `message` ( `id` int(11) NOT NULL AUTO_INCREMENT, `xiaorui` int(11) NOT NULL, `body` longtext NOT NULL, `send_date` datetime NOT NULL, PRIMARY KEY (`id`), KEY `message_xiaorui_id` (`xiaorui_id`), CONSTRAINT `message_ibfk_1` FOREIGN KEY (`xiaorui_id`) REFERENCES `user` (`id`) ON DELETE CASCADE ) ENGINE=InnoDB DEFAULT CHARSET=latin1 | +---------+----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ 1 row in set (0.00 sec)
注意到一个报错,期初没有注意 ERROR 1215 (HY000): Cannot add foreign key constraint
1. 作为外键所关联的键,必须是unique或者primary的, 否则会出现“ERROR 1215 (HY000): Cannot add foreign key constraint”
2. 字段锁采用的数据类型要一致,比如都是int。
3. 最重要的是库表引擎要选择是innodb。 现只有innodb才能使用外键约束。
再次说明下, Tokudb是不支持外键 !!! 我被坑过一次, 以为是peewee的问题,翻弄了半天…
END.
peewee还是简易了些