本文是對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記錄具體的外鍵引用列資訊。
插入子表的邏輯比較簡單,隻需要檢查外鍵是否存在即可,堆棧如下:
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失敗