mysql的外鍵探討

在MySQL 3.23.44版本後,InnoDB引擎類型的表支持了外鍵約束。 node

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

    我的以爲,foreign key就是表與表之間的某種約定的關係,因爲這種關係的存在,咱們可以讓表與表之間的數據,更加的完整,關連性更強。關於完整性,關連性我舉個例子,你們就會明白了。
有二張表,一張是用戶表,一張是訂單表:
   1,若是我刪除了用戶表裏的用戶,那麼訂單表裏面根這個用戶有關的數據,就成了無頭數據了,不完整了。
   2,若是我在訂單表裏面,隨便插入了一條數據,這個訂單在用戶表裏面,沒有與之對應的用戶。這樣數據也不完整了。
   若是有外鍵的話,就方便多了,能夠不讓用戶刪除數據,或者刪除用戶的話,經過外鍵一樣刪除訂單表裏面的數據,這樣也能讓數據完整

外鍵的好處:可使得兩張表關聯,保證數據的一致性和實現一些級聯操做; mysql

二,使用foreign key,遵照如下幾點規則

1,有外鍵約束的表,必須是innodb型
2,外鍵約束的二個表,原本就相關係的表,而且要有索引關係,若是沒有,建立外鍵時也能夠建立索引。
3,不支持對外鍵列的索引前綴。這樣的後果之一是BLOB和TEXT列不被包括在一個外鍵中,這是由於對這些列的索引必須老是包含一個前綴長度。
4,mysql外鍵的名子在數據庫內要是惟一的 sql

三,建立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兩個表,大哥表是主鍵,小弟表是外鍵:
建表: app

 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`))


提示:不行呀,有約束的,大哥下面還有小弟,可不能扔下咱們無論呀! ssh

插入一個新的小弟: ui

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 


提示:小子,想造反呀!你還沒大哥呢! this

把外鍵約束增長事件觸發限制: spa

 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了呢!

可是刪除小弟,大哥並無刪除

六,項目中使用遇到的問題

咱們有三個表

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表相關的。真是百思不得其解。望各位大神解答一下。

相關文章
相關標籤/搜索