淺談MYSQL中樹(shù)形結(jié)構(gòu)表3種設(shè)計(jì)優(yōu)劣分析與分享
簡(jiǎn)介
在開(kāi)發(fā)中經(jīng)常遇到樹(shù)形結(jié)構(gòu)的場(chǎng)景,本文將以部門(mén)表為例對(duì)比幾種設(shè)計(jì)的優(yōu)缺點(diǎn);
問(wèn)題
需求背景:根據(jù)部門(mén)檢索人員,
問(wèn)題:選擇一個(gè)頂級(jí)部門(mén)情況下,跨級(jí)展示當(dāng)前部門(mén)以及子部門(mén)下的所有人員,表怎么設(shè)計(jì)更合理 ?
遞歸嗎 ?遞歸可以解決,但是勢(shì)必消耗性能
設(shè)計(jì)1:鄰接表
注:(常見(jiàn)父Id設(shè)計(jì))
表設(shè)計(jì)
CREATE TABLE `dept_info01` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '自增主鍵', `dept_id` int(10) NOT NULL COMMENT '部門(mén)id', `dept_name` varchar(100) NOT NULL COMMENT '部門(mén)名稱', `dept_parent_id` int(11) NOT NULL COMMENT '父部門(mén)id', `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '創(chuàng)建時(shí)間', `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改時(shí)間', PRIMARY KEY (`id`) USING BTREE ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
這樣是最常見(jiàn)的設(shè)計(jì),能正確的表達(dá)菜單的樹(shù)狀結(jié)構(gòu)且沒(méi)有冗余數(shù)據(jù),但在跨層級(jí)查詢需要遞歸處理。
SQL示例
1.查詢某一個(gè)節(jié)點(diǎn)的直接子集
SELECT * FROM dept_info01 WHERE dept_parent_id =1001
優(yōu)點(diǎn)
結(jié)構(gòu)簡(jiǎn)單 ;
缺點(diǎn)
1.不使用遞歸情況下無(wú)法查詢某節(jié)點(diǎn)所有父級(jí),所有子集
設(shè)計(jì)2:路徑枚舉
在設(shè)計(jì)1基礎(chǔ)上新增一個(gè)父部門(mén)id集字段,用來(lái)存儲(chǔ)所有父集,多個(gè)以固定分隔符分隔,比如逗號(hào)。
表設(shè)計(jì)
CREATE TABLE `dept_info02` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '自增主鍵', `dept_id` int(10) NOT NULL COMMENT '部門(mén)id', `dept_name` varchar(100) NOT NULL COMMENT '部門(mén)名稱', `dept_parent_id` int(11) NOT NULL COMMENT '父部門(mén)id', `dept_parent_ids` varchar(255) NOT NULL DEFAULT '' COMMENT '父部門(mén)id集', `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '創(chuàng)建時(shí)間', `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改時(shí)間', PRIMARY KEY (`id`) USING BTREE ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
SQL示例
1.查詢所有子集
1).通過(guò)模糊查詢
SELECT * FROM dept_info02 WHERE dept_parent_ids like '%1001%'
2).推薦使用 FIND_IN_SET 函數(shù)
SELECT * FROM dept_info02 WHERE FIND_IN_SET( '1001', dept_parent_ids )
優(yōu)點(diǎn)
- 方便查詢所有的子集 ;
- 可以因此通過(guò)比較字符串dept_parent_ids長(zhǎng)度獲取當(dāng)前節(jié)點(diǎn)層級(jí) ;
缺點(diǎn)
- 新增節(jié)點(diǎn)時(shí)需要將dept_parent_ids字段值處理好 ;
- dept_parent_ids字段的長(zhǎng)度很難確定,無(wú)論長(zhǎng)度設(shè)為多大,都存在不能夠無(wú)限擴(kuò)展的情況 ;節(jié)
- 點(diǎn)移動(dòng)復(fù)雜,需要同時(shí)變更所有子集中的dept_parent_ids字段值 ;
設(shè)計(jì)3:閉包表
- 閉包表是解決分級(jí)存儲(chǔ)的一個(gè)簡(jiǎn)單而優(yōu)雅的解決方案,這是一種通過(guò)空間換取時(shí)間的方式 ;
- 需要額外創(chuàng)建了一張TreePaths表它記錄了樹(shù)中所有節(jié)點(diǎn)間的關(guān)系 ;
- 包含兩列,祖先列與后代列,即使這兩個(gè)節(jié)點(diǎn)之間不是直接的父子關(guān)系;同時(shí)增加一行指向節(jié)點(diǎn)自己 ;
表設(shè)計(jì)
主表
CREATE TABLE `dept_info03` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '自增主鍵', `dept_id` int(10) NOT NULL COMMENT '部門(mén)id', `dept_name` varchar(100) NOT NULL COMMENT '部門(mén)名稱', `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '創(chuàng)建時(shí)間', `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改時(shí)間', PRIMARY KEY (`id`) USING BTREE ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
祖先后代關(guān)系表
CREATE TABLE `dept_tree_path_info` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '自增主鍵', `ancestor` int(10) NOT NULL COMMENT '祖先id', `descendant` int(10) NOT NULL COMMENT '后代id', `depth` tinyint(4) NOT NULL DEFAULT '0' COMMENT '層級(jí)深度', PRIMARY KEY (`id`) USING BTREE ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
注:depth 層級(jí)深度字段 ,自我引用為 1,直接子節(jié)點(diǎn)為 2,再一下層為 3,一次類推,第幾層就是幾 。
SQL示例
插入新節(jié)點(diǎn)
INSERT INTO dept_tree_path_info (ancestor, descendant,depth) SELECT t.ancestor, 3001,t.depth+1 FROM dept_tree_path_info AS t WHERE t.descendant = 2001 UNION ALL SELECT 3001,3001,1
查詢所有祖先
SELECT c.* FROM dept_info03 AS c INNER JOIN dept_tree_path_info t ON c.dept_id = t.ancestor WHERE t.descendant = 3001
查詢所有后代
SELECT c.* FROM dept_info03 AS c INNER JOIN dept_tree_path_info t ON c.dept_id = t.descendant WHERE t.ancestor = 1001
刪除所有子樹(shù)
DELETE FROM dept_tree_path_info WHERE descendant IN ( SELECT a.dept_id FROM ( SELECT descendant dept_id FROM dept_tree_path_info WHERE ancestor = 1001 ) a )
刪除葉子節(jié)點(diǎn)
DELETE FROM dept_tree_path_info WHERE descendant = 2001
移動(dòng)節(jié)點(diǎn)
- 刪除所有子樹(shù)(先斷開(kāi)與原祖先的關(guān)系)
- 建立新的關(guān)系
優(yōu)點(diǎn)
- 非遞歸查詢減少冗余的計(jì)算時(shí)間 ;
- 方便非遞歸查詢?nèi)我夤?jié)點(diǎn)所有的父集 ;
- 方便查詢?nèi)我夤?jié)點(diǎn)所有的子集 ;
- 可以實(shí)現(xiàn)無(wú)限層級(jí) ;
- 支持移動(dòng)節(jié)點(diǎn) ;
缺點(diǎn)
- 層級(jí)太多情況下移動(dòng)樹(shù)節(jié)點(diǎn)會(huì)帶來(lái)關(guān)系表多條操作 ;
- 需要單獨(dú)一張表存儲(chǔ)對(duì)應(yīng)關(guān)系,在新增與編輯節(jié)點(diǎn)時(shí)操作相對(duì)復(fù)雜 ;
結(jié)合使用
可以將鄰接表方式與閉包表方式相結(jié)合使用。實(shí)際上就是將父id冗余到主表中,在一些只需要查詢直接關(guān)系的業(yè)務(wù)中就可以直接查詢主表,而不需要關(guān)聯(lián)2張表了。在需要跨級(jí)查詢時(shí)祖先后代關(guān)系表就顯得尤為重要。
表設(shè)計(jì)
主表
CREATE TABLE `dept_info04` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '自增主鍵', `dept_id` int(10) NOT NULL COMMENT '部門(mén)id', `dept_name` varchar(100) NOT NULL COMMENT '部門(mén)名稱', `dept_parent_id` int(11) NOT NULL COMMENT '父部門(mén)id', `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '創(chuàng)建時(shí)間', `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改時(shí)間', PRIMARY KEY (`id`) USING BTREE ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
祖先后代關(guān)系表
CREATE TABLE `dept_tree_path_info` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '自增主鍵', `ancestor` int(10) NOT NULL COMMENT '祖先id', `descendant` int(10) NOT NULL COMMENT '后代id', `depth` tinyint(4) NOT NULL DEFAULT '0' COMMENT '層級(jí)深度', PRIMARY KEY (`id`) USING BTREE ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
總結(jié)
其實(shí),在以往的工作中,曾見(jiàn)過(guò)不同類型的設(shè)計(jì),鄰接表,路徑枚舉,鄰接表路徑枚舉一起來(lái)的都見(jiàn)過(guò)。每種設(shè)計(jì)都各有優(yōu)劣,如果選擇設(shè)計(jì)依賴于應(yīng)用程序中哪種操作最需要性能上的優(yōu)化。
設(shè)計(jì) | 表數(shù)量 | 查詢直接子 | 查詢子樹(shù) | 同時(shí)查詢多個(gè)節(jié)點(diǎn)子樹(shù) | 插入 | 刪除 | 移動(dòng) |
---|---|---|---|---|---|---|---|
鄰接表 | 1 | 簡(jiǎn)單 | 需要遞歸 | 需要遞歸 | 簡(jiǎn)單 | 簡(jiǎn)單 | 簡(jiǎn)單 |
枚舉路徑 | 1 | 簡(jiǎn)單 | 簡(jiǎn)單 | 查多次 | 相對(duì)復(fù)雜 | 簡(jiǎn)單 | 復(fù)雜 |
閉包表 | 2 | 簡(jiǎn)單 | 簡(jiǎn)單 | 簡(jiǎn)單 | 相對(duì)復(fù)雜 | 簡(jiǎn)單 | 復(fù)雜 |
綜上所述
- 只需要建立子父集關(guān)系中可以使用鄰接表方式 ;
- 涉及向上查找,向下查找的需要建議使用閉包表方式 ;
到此這篇關(guān)于淺談MYSQL中樹(shù)形結(jié)構(gòu)表3種設(shè)計(jì)優(yōu)劣分析與分享的文章就介紹到這了,更多相關(guān)MYSQL 樹(shù)形結(jié)構(gòu)表內(nèi)容請(qǐng)搜索本站以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持本站!
版權(quán)聲明:本站文章來(lái)源標(biāo)注為YINGSOO的內(nèi)容版權(quán)均為本站所有,歡迎引用、轉(zhuǎn)載,請(qǐng)保持原文完整并注明來(lái)源及原文鏈接。禁止復(fù)制或仿造本網(wǎng)站,禁止在非www.sddonglingsh.com所屬的服務(wù)器上建立鏡像,否則將依法追究法律責(zé)任。本站部分內(nèi)容來(lái)源于網(wǎng)友推薦、互聯(lián)網(wǎng)收集整理而來(lái),僅供學(xué)習(xí)參考,不代表本站立場(chǎng),如有內(nèi)容涉嫌侵權(quán),請(qǐng)聯(lián)系alex-e#qq.com處理。