在瞭解什麼是『預排序遍歷樹算法』之前,我們先思考一個問題如何處理『多級分類的子分類查詢』。例如:
要存儲表示層級關係的數據,一種最簡單的方案,存儲當前分類的名稱,以及上一級分類的名稱,通常我們稱這種存儲結構爲『鄰接表』。
數據庫存儲結構:
分類名稱 | 父級分類 |
---|---|
food | |
fruit | food |
meat | food |
apple | fruit |
breef | meat |
pork | meat |
這種方式有什麼問題:查詢效率過低。當我們在程序裏查詢 food 的子分類時,要先在數據庫中,查詢 food 的一級子分類(fruit、meat)、在對一級分類的子分類進行遞歸查詢,需要進行 logN 次( N 爲子分類個數) I/O 操作(向數據庫進行查詢),這樣的查詢效率不高,但是很容易實現。
而 MPTT 預排序生成樹算法正是爲了解決多層級關係數據的查詢效率問題。
MPTT 是什麼?
MPTT(Modified Preorder Tree Taversal)預排序遍歷樹算法,主要應用於層級關係的存儲和遍歷。
在 MPTT 中是如何表示層級關係的:
MTTP 不直接存儲父分類信息,而是通過 left、right 指來表示層級關係。
還是以 food 爲例,我們從頭開始構建一棵『預排序遍歷樹』。
# 創建數據表,left_value、right_value 表示左右子樹區間
CREATE TABLE `product_category` (
`id` int(11) NOT NULL AUTO_INCREMENT,
category_name varchar(20) not null,
left_value int(10) not null,
right_value int(10) not null,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
然後開始插入 food 的一級子分類,根據當前分類的 left_value 來確定插入分類的左右值,同時更新受影響的其他分類。
show code,插入子分類
INSERT into product_category(category_name,`left_value`,`right_value`)
values('food',1,2); # 插入 food 數據
# 鎖表,防止被同時修改,數據不一致
lock table `product_category` write;
# 插入 food 子分類
select @myLeft := `left_value`
from `product_category`
where category_name='food';
# 更新其他受影響分類左右區間值
update product_category SET left_value=left_value+2
where left_value>@myLeft;
update product_category set right_value=right_value+2
where right_value>@myLeft;
# 插入子分類信息
INSERT into product_category(category_name,`left_value`,`right_value`)
values('meat',@myLeft+1,@myLeft+2);
unlock tables; # 解除表鎖定
除了插入子分類的方式,我們還可以選擇 插入同級分類,和插入子分類的區別在於,插入點的左右值取決於被插入點的 right_value。
# 插入 meat 同級分類
lock table `product_category` write;
select @myRight := `right_value`
from `product_category`
where category_name='meat';
update product_category SET left_value=left_value+2
where left_value>@myRight;
update product_category set right_value=right_value+2
where right_value>@myRight;
INSERT into product_category(category_name,`left_value`,`right_value`)
values('fruit',@myRight+1,@myRight+2);
unlock tables;
刪除指定分類,也等於刪除包括指定分類在內的所有子分類,所有受影響左右子分類都受影響。
# 刪除 meat 分類
lock table `product_category` write;
select @myLeft:= `left_value`,@myRight:= `right_value`,@myDiff:=@myRight-@myLeft+1
from `product_category`
where `category_name`='meat';
delete from `product_category`
where `left_value`>=@myLeft and `right_value`<=@myRight;
update `product_category`
set `right_value`=`right_value`-@myDiff
where `right_value`>@myRight;
update `product_category`
set `left_value`=`right_value`-@myDiff
where `left_value`>@myLeft;
unlock tables;
那如何 查詢指定分類的子分類 呢?
select @myLeft:= `left_value`,@myRight:= `right_value`
from `product_category`
where `category_name`='meat'; # 指定分類名稱
select *
from `product_category`
where `left_value`>=@myLeft and `right_value`<=@myRight; # 不包括指定分類,去掉等號即可
通過 MPTT 我們查詢某一特定分類的子分類信息只要做兩次查詢操作,解決了『鄰接表』遞歸查詢的低效查詢問題。
『鄰接表』和『預排序遍歷樹』的優劣
『鄰接表』,適用於 增刪改 操作較多的場景,每次刪改只需要修改一條數據。在查詢方面,隨着 分類層級的增加『鄰接表』的遞歸查詢效率逐漸降低。
『預排序遍歷樹』,適用於 查詢 操作較多的場景,查詢的效率不受分類層級的增加的影響,但是隨着數據的增多,每增刪數據,都要同時操作多條受影響數據,執行效率逐漸下降。
具體要選擇哪一種存儲結構和算法,需要根據具體的應用場景來做選擇。