mysql的外键探讨

在MySQL 3.23.44版本后,InnoDB引擎类型的表支持了外键约束。

一,什么是foreign key,及其完整性

    个人觉得,foreign key就是表与表之间的某种约定的关系,由于这种关系的存在,我们能够让表与表之间的数据,更加的完整,关连性更强。关于完整性,关连性我举个例子,大家就会明白了。
有二张表,一张是用户表,一张是订单表:
   1,如果我删除了用户表里的用户,那么订单表里面根这个用户有关的数据,就成了无头数据了,不完整了。
   2,如果我在订单表里面,随便插入了一条数据,这个订单在用户表里面,没有与之对应的用户。这样数据也不完整了。
   如果有外键的话,就方便多了,可以不让用户删除数据,或者删除用户的话,通过外键同样删除订单表里面的数据,这样也能让数据完整

外键的好处:可以使得两张表关联,保证数据的一致性和实现一些级联操作;

二,使用foreign key,遵守以下几点规则

1,有外键约束的表,必须是innodb型
2,外键约束的二个表,本来就相关系的表,并且要有索引关系,如果没有,创建外键时也可以创建索引。
3,不支持对外键列的索引前缀。这样的后果之一是BLOB和TEXT列不被包括在一个外键中,这是因为对这些列的索引必须总是包含一个前缀长度。
4,mysql外键的名子在数据库内要是唯一的

三,创建foreign key的语法规则

外键的定义语法:
[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(跟随外键改动)c
SET NULL(设空值)
SET DEFAULT(设默认值)
NO ACTION(无动作,默认的)

四,外键维护数据完整性的5种方式

1,CASCADE: 从父表删除或更新,将自动删除或更新子表中匹配的行。ON DELETE CASCADE和ON UPDATE CASCADE都可用。在两个表之间,你不应定义若干在父表或子表中的同一列采取动作的ON UPDATE CASCADE子句。
2,SET NULL: 从父表删除或更新行,并设置子表中的外键列为NULL。如果外键列没有指定NOT NULL限定词,这就是唯一合法的。ON DELETE SET NULL和ON UPDATE SET NULL子句被支持。
3,NO ACTION: 在ANSI SQL-92标准中,NO ACTION意味这不采取动作,就是如果有一个相关的外键值在被参考的表里,删除或更新主要键值的企图不被允许进行(Gruber, 掌握SQL, 2000:181)。 InnoDB拒绝对父表的删除或更新操作。
4,RESTRICT: 拒绝对父表的删除或更新操作。NO ACTION和RESTRICT都一样,删除ON DELETE或ON UPDATE子句。(一些数据库系统有延期检查,并且NO ACTION是一个延期检查。在MySQL中,外键约束是被立即检查的,所以NO ACTION和RESTRICT是同样的)。
5,SET DEFAULT: 这个动作被解析程序识别,但InnoDB拒绝包含ON DELETE SET DEFAULT或ON UPDATE SET DEFAULT子句的表定义

五,实例

搞个例子,简单演示一下使用,做dage和xiaodi两个表,大哥表是主键,小弟表是外键:
建表:

 1    CREATE    TABLE  `dage` (
 2     `id`   int (  11 )   NOT    NULL  auto_increment,
 3     `name`   varchar (  32 )   default    '' ,
 4       PRIMARY    KEY   (`id`)
 5   ) ENGINE  = InnoDB   DEFAULT  CHARSET  = latin1;
 6   
 7    CREATE    TABLE  `xiaodi` (
 8     `id`   int (  11 )   NOT    NULL  auto_increment,
 9     `dage_id`   int (  11 )   default    NULL ,
10     `name`   varchar (  32 )   default    '' ,
11       PRIMARY    KEY   (`id`),
12       KEY  `dage_id` (`dage_id`),
13       CONSTRAINT  `xiaodi_ibfk_1`   FOREIGN    KEY  (`dage_id`)   REFERENCES  `dage` (`id`)
14   ) ENGINE  = InnoDB   DEFAULT  CHARSET  = latin1;

插入个大哥:
1   mysql  >    insert    into  dage(name)   values (  '  铜锣湾  ' );
2   Query OK,   1  row affected (  0.01  sec)
3   mysql  >    select    *    from  dage;
4    +  --  --+--------+ 
5    |  id   |  name     | 
6    +  --  --+--------+ 
7    |     1    |  铜锣湾   | 
8    +  --  --+--------+ 
9    1  row   in    set  (  0.00  sec)

插入个小弟:
1   mysql  >    insert    into  xiaodi(dage_id,name)   values (  1 ,  '  铜锣湾_小弟A  ' );
2   Query OK,   1  row affected (  0.02  sec)
3   
4   mysql  >    select    *    from  xiaodi;
5    +  --  --+---------+--------------+ 
6    |  id   |  dage_id   |  name           | 
7    +  --  --+---------+--------------+ 
8    |     1    |          1    |  铜锣湾_小弟A   | 
9    +  --  --+---------+--------------+

把大哥删除:
1   mysql  >    delete    from  dage   where  id  =  1 ;
2   ERROR   1451  (  23000 ): Cannot   delete    or    update  a parent row: a   foreign    key    constraint  fails (`bstar  / xiaodi`,   CONSTRAINT  `xiaodi_ibfk_1`  FOREIGN    KEY  (`dage_id`)   REFERENCES  `dage` (`id`))


提示:不行呀,有约束的,大哥下面还有小弟,可不能扔下我们不管呀!

插入一个新的小弟:

1   mysql  >    insert    into  xiaodi(dage_id,name)   values (  2 ,  '  旺角_小弟A  ' );              
2   ERROR   1452  (  23000 ): Cannot   add    or    update  a child row: a   foreign    key    constraint  fails (`bstar  / xiaodi`,   CONSTRAINT  `xiaodi_ibfk_1`   FOREIGN   KEY  (`dage_id`)   REFERENCES  `dage` (`id`))
3 


提示:小子,想造反呀!你还没大哥呢!

把外键约束增加事件触发限制:

 1   mysql  >  show   create    table  xiaodi;
 2       
 3       CONSTRAINT  `xiaodi_ibfk_1`   FOREIGN    KEY  (`dage_id`)   REFERENCES  `dage` (`id`)
 4       
 5   mysql  >    alter    table  xiaodi   drop    foreign    key  xiaodi_ibfk_1; 
 6   Query OK,   1  row affected (  0.04  sec)
 7   Records:   1   Duplicates:   0   Warnings: 
 8   mysql  >    alter    table  xiaodi   add    foreign    key (dage_id)   references  dage(id)   on    delete    cascade;   
 9   Query OK,   1  row affected (  0.04  sec)
10   Records:   1   Duplicates:   0   Warnings:   0

再次试着把大哥删了:
1   mysql  >    delete    from  dage   where  id  =  1 ;
2   Query OK,   1  row affected (  0.01  sec)
3   
4   mysql  >    select    *    from  dage;
5   Empty   set  (  0.01  sec)
6   
7   mysql  >    select    *    from  xiaodi;
8   Empty   set  (  0.00  sec)



得,这回对应的小弟也没了,没办法,谁让你跟我on delete cascade了呢!

但是删除小弟,大哥并没有删除

mysql的外键探讨_第1张图片

六,项目中使用遇到的问题

我们有三个表

env表:

CREATE TABLE `env` (  `id` varchar(36) NOT NULL,
  `env_name` varchar(50) NOT NULL,
  `env_username` varchar(30) DEFAULT NULL COMMENT '环境用户名',
  `env_password` varchar(30) DEFAULT NULL COMMENT '环境用户密码',
  `ssh_port` int(11) DEFAULT '22',
  `state` varchar(30) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

node表:

CREATE TABLE `node` (
  `id` varchar(36) NOT NULL,
  `env_id` varchar(36) NOT NULL COMMENT '环境id',
  `node_name` varchar(50) NOT NULL,
  `ip_str` varchar(50) DEFAULT NULL COMMENT '节点IP',
  `node_username` varchar(30) DEFAULT NULL COMMENT '节点用户名',
  `node_password` varchar(30) DEFAULT NULL COMMENT '节点用户密码',
  `ssh_port` int(11) DEFAULT '22',
  `state` varchar(30) DEFAULT NULL,
  `sortIndex` int(11) DEFAULT NULL,
  `is_dm` tinyint(1) DEFAULT '0' COMMENT '是否是管理节点',
  `globalCfg_taskid` int(11) DEFAULT NULL COMMENT '节点全局配置任务id',
  `netcfg_taskid` int(11) DEFAULT NULL COMMENT '节点网络配置任务id',
  `isImport` int(1) DEFAULT '0' COMMENT '节点是否为导入',
  `ip_change` int(1) DEFAULT '0' COMMENT '是否节点ip发生变化',
  `nodeType` varchar(20) DEFAULT NULL COMMENT '节点类型:x86,ppc',
  `oldIp` varchar(50) DEFAULT NULL COMMENT '网络变化前的节点IP',
  PRIMARY KEY (`id`),
  UNIQUE KEY `ip_str` (`ip_str`),
  KEY `fk_node_env_id` (`env_id`),
  CONSTRAINT `fk_node_env_id` FOREIGN KEY (`env_id`) REFERENCES `env` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

app表

CREATE TABLE `app` (
  `id` varchar(36) NOT NULL,
  `node_id` varchar(36) DEFAULT NULL COMMENT '节点id',
  `recipe_id` int(11) DEFAULT NULL COMMENT '配方id',
  `groupId` varchar(50) DEFAULT NULL COMMENT '集群的组序号',
  `config_state` varchar(30) DEFAULT NULL COMMENT '应用配置状态',
  `start_state` varchar(30) DEFAULT NULL COMMENT '应用启动状态',
  `deploy_state` varchar(30) DEFAULT NULL COMMENT '应用部署状态',
  `upgrade_state` varchar(30) DEFAULT NULL COMMENT '应用升级状态',
  `action_state` varchar(30) DEFAULT NULL COMMENT '动作状态状态',
  `upgrade_version` varchar(40) DEFAULT NULL COMMENT '可升级版本号',
  `degrade_version` varchar(40) DEFAULT NULL COMMENT '可降级版本号',
  `fail_reason` varchar(250) DEFAULT NULL COMMENT '失败原因',
  `sortIndex` int(11) DEFAULT NULL,
  `actioning` varchar(30) DEFAULT NULL COMMENT 'app执行的动作',
  `bak_flag` int(11) DEFAULT '0' COMMENT '应用是否是升级备份,1为升级备份应用',
  `guid` varchar(50) DEFAULT NULL,
  `parentId` varchar(36) DEFAULT NULL COMMENT '父应用-容器id',
  `to_delete` int(1) DEFAULT '0' COMMENT '是否待删除',
  `commonId` varchar(36) NOT NULL COMMENT 'app生命周期中唯一不变的值',
  `update_date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '最后修改时间',
  `oldGroupId` varchar(50) DEFAULT NULL,
  `scanAppFlag` tinyint(1) DEFAULT NULL COMMENT '扫描标志',
  PRIMARY KEY (`id`),
  KEY `fk_app_node_id2` (`node_id`),
  KEY `fk_app_recipe_id2` (`recipe_id`),
  CONSTRAINT `fk_app_node_id2` FOREIGN KEY (`node_id`) REFERENCES `node` (`id`),
  CONSTRAINT `fk_app_recipe_id2` FOREIGN KEY (`recipe_id`) REFERENCES `recipe` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

从这三个表可以看得出,node表有一个键是env表的id,而node表的id是app表的外键,现在碰到一个问题,我用JdbcTemplate删除node表的一条数据,

sql语句如下:

private static final String DELETE_NODE_BYID_IMPORT = "delete from node where id=?";

/**
* 根据id删除node
* @param nodeId
*/
public void deleteNodeById(String nodeId) {
    try {
        this.getSimpleJdbcTemplate().update(DELETE_NODE_BYID_IMPORT, nodeId);
    } catch (DataAccessException e) {
        throw new ValidateException("", "必须全部删除该节点下的所有子节点,error:" + e.getMessage());
    }
}
这样一执行就会报错:


很正常,因为app表对应的没有删掉,且没有级联删除,所以这儿删报错很正常,但是使用如下删除结果就正确了:

private static final String DELETE_NODE_BYID = "delete from node where id=? and env_id=?";

/** 删除节点信息,如果节点下有应用,删除失败 */
public void deleteNodeById(String nodeId, String envId) {
    try {
        this.getSimpleJdbcTemplate().update(DELETE_NODE_BYID, nodeId, envId);
    } catch (DataAccessException e) {
        throw new ValidateException("", "必须全部删除该节点下的所有子节点,error:" + e.getMessage());
    }
}
就多加了一个envId就是node表的外键,结果就成功了,按理说删除只和app表相关的。真是百思不得其解。望各位大牛解答一下。

你可能感兴趣的:(mysql的外键探讨)