代码分析peewee ForeignKeyField外键的用法

这是新年后的第一篇文章,上班第一天还在恢复上下文的状态.   就又看了下peewee的代码。

正题开始,Mysql外键的作用我想大家都知道,外键是为了更好的约束数据保证了数据的完整性,在一定程度上杜绝了bug的产生。

  1. 插入非空值时,如果主键表中没有这个值,则不能插入。
  2. 更新记录时,value值一定存在于主键表中,否在就update失败。
  3. 删除主键表记录时,你可以在建外键时设定外键记录一起级联删除还是拒绝删除。
  4. 更新主键记录时,同样有级联更新和拒绝执行的选择。

对于开发人员来说,不用再考虑数据完整性,直接搞就行了,但是对于dba来说,他们是及其反对使用外键的,为毛? 性能啊.  
这算是个缺点,外键往往在你更新子表或者删除字表数据都会去主表判断一下,这是个隐式操作。 其实开发人员对于是否使用外键也是成两派的,我其实是中立的,用也行,不用也行,如果上面没人强制我,我一般就不用外键来约束数据。

这次主要是聊下,python peewee这个orm在使用 ForeignKeyField 外键时遇到的问题.    我这里吐槽下peewee的文档不是很全面,关于ForeignKeyField是有不少例子,但例子更多的是快速执行,而没有一些细节的描述。 


文章写的不是很严谨,欢迎来喷,另外该文后续有更新的,请到原文地址查看更新。

http://xiaorui.cc/2016/02/16/%E4%BB%A3%E7%A0%81%E5%88%86%E6%9E%90peewee-foreignkeyfield%E5%A4%96%E9%94%AE%E7%9A%84%E7%94%A8%E6%B3%95/

下面是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.


大家觉得文章对你有些作用! 如果想赏钱,可以用微信扫描下面的二维码,感谢!
另外再次标注博客原地址  xiaorui.cc

1 Response

  1. 云c 2016年2月17日 / 下午2:09

    peewee还是简易了些

发表评论

邮箱地址不会被公开。 必填项已用*标注