天天看點

InnoDB foreign key 實作

本文是對innodb外鍵實作代碼路徑的簡單記錄,對外鍵實作邏輯熟悉的同學直接忽略吧。。。。。

外鍵代表兩張表之間的引用限制關系:在子表上出現的列記錄,必須在父表上已經存在。通過外鍵,我們可以確定業務上的邏輯一緻性,同時還能實作一些級聯操作;mysql目前隻有innodb引擎支援外鍵,類似myisam、tokudb等引擎都不支援外鍵。

innodb支援建立多個列的外鍵,但被外鍵限制的父表上必須對這些列建立索引,并且子表上的外鍵列 和父表上索引上的順序是一緻的。預設情況下,當删除父表中被外鍵限制的記錄時,會産生報錯,但我們也可以通過在建外鍵索引時加上on delete cascade 來級聯的更新子表,更新同理。其他行為包括restrict(限制父表的外鍵改動,預設值)、cascade(跟随父表的改動)、set null(子表對應列設定為null)、set default(設定為預設值,innodb不支援,但server層支援)、no action(無動作)。

由于innodb支援父表上被外鍵限制的索引可以不是唯一索引,是以會出現子表上一條記錄 對應父表上多條記錄;這是innodb的擴充特性,并不符合标準sql。

你可以從兩張information_ schema表來查詢執行個體上的外鍵資訊:innodb_sys_foreign_cols 及 innodb_sys_foreign

在innodb層單獨存儲了外鍵限制資訊,sys_foreign系統表記錄表和被引用表的相關資訊;sys_foreign_cols記錄具體的外鍵引用列資訊。

InnoDB foreign key 實作

插入子表的邏輯比較簡單,隻需要檢查外鍵是否存在即可,堆棧如下:

row_ins_clust_index_entry/row_ins_sec_index_entry 

|–> row_ins_check_foreign_constraints 

#針對外鍵索引,調用函數row_ins_check_foreign_constraint,檢查對應的父表中是否存在索引記錄。

更新父表觸發檢查

row_update_for_mysql

|–>row_update_for_mysql_using_upd_graph

       |–> row_upd_sec_step –>row_upd_step –>row_upd 

              |–> row_upd_sec_index_entry 

              |–> row_upd_check_references_constraints 

               #檢查表的table->referenced_set是否為空,為空表示沒有子表

               #輪詢每個被目前表限制的子表外鍵索引(table->referenced_set),檢查是否修改了外鍵限制列 or 删除記錄

               # 檢查子表記錄row_ins_check_foreign_constraint

               |–> row_ins_check_foreign_constraint

               #根據子表上的外鍵列索引,查找是否有比對的記錄(子表的外鍵列被隐含的建立了索引)

               # 當設定了on update/on delete屬性時,需要建構關聯更新的記錄,調用下述函數

                      |–> row_ins_foreign_check_on_constraint

                      #根據dict_foreign_t::type類型,查詢子表的聚集索引,建構更新vector和cascade node

        #目前表更新完成後 

        |–> #如果需要關聯更新子表,則使用之前建構的cascade node,并loop 調用row_upd_sec_step更新子表

測試表: mysql> show create table t3\g *************************** 1. row *************************** table: t3 create table: create table `t3` ( `a` int(11) not null, `b` int(11) default null, `c` int(11) default null, `d` int(11) default null, primary key (`a`) ) engine=innodb default charset=latin1 1 row in set (0.00 sec)

mysql_alter_table 

–> ha_create_table –> ha_create –> ha_innobase::create –> create_table_info_t::create_table  // 使用copy的方式建構外鍵索引

|–> row_table_add_foreign_constraints // 建立表上的外鍵限制

     |–> dict_create_foreign_constraints

          |–> dict_create_foreign_constraints_low

          # 在該函數中,會去解析sql語句,判斷sql裡的各個關鍵字,例如alter、table、foreign之類來檢查合法性,例如不允許分區表上的外鍵….居然是字元串比對!!!!

          # 配置設定記憶體對象dict_mem_foreign_create

          #  如果使用者沒有指定,自動生産foreign key的命名:dict_create_add_foreign_id

          |–>dict_scan_table_name

          #  解出父表名,并擷取對應的父表對象

               |–>dict_get_referenced_table

          # 不允許父表為分區表的外鍵限制

          #父表上被外鍵限制的字段上需要存在索引:dict_foreign_find_index

臨時表在完成ddl後,需要rename成原表,這時候需要reload外鍵限制資訊到記憶體資料詞典cache中

–> mysql_rename_table –> ha_innobase::rename_table –>  innobase_rename_table –> row_rename_table_for_mysql

|–> dict_load_foreigns

删除外鍵是in-place操作,堆棧為:

mysql_alter_table –> mysql_inplace_alter_table –> ha_innobase::commit_inplace_alter_table 

|–> commit_try_norebuild

     |–> innobase_update_foreign_try

          |–> innobase_drop_foreign_try  //删除系統表sys_foreign和sys_foreign_cols上的記錄 

|–> innobase_update_foreign_cache  // 更新cache資訊

truncate 父表之前,會檢查其是否為父表,如果是的話,直接拒絕操作,即使表上沒有任何資料(sql_cmd_truncate_table::handler_truncate  –> fk_truncate_illegal_if_parent)

當父表上被外鍵限制的列名修改時,需要修改對應的外鍵資訊,堆棧:

mysql_alter_table –> mysql_inplace_alter_table 

–> ha_innobase::commit_inplace_alter_table 

|–> commit_try_norebuild –>innobase_rename_columns_try –> innobase_rename_column_try 

     #修改系統表中資訊,包括sys_foreign_cols

     #将修改過的外鍵資訊從cache中移除:dict_foreign_remove_from_cache

drop 父表時,在函數row_drop_table_for_mysql中,檢查其是否被其他表引用,如果是的話,則drop失敗