我的博客: http://www.80soho.com/?p=781
有這麼個需求:設計開發一個評論系統,要求用戶能夠評論文章以及相互回覆,無層級數限制。node
這個需求開發人員基本都遇到過,能夠先回憶或考慮這個數據表如何設計!mysql
存在遞歸關係的數據很常見,數據常會像樹或者以層級方式組織。在樹形結構中,實例被稱爲節點(node),每一個節sql
點有多個子節點和一個父節點,最上面的節點叫根(root)節點,它沒有父節點,最底層的沒有子節點的節點叫葉數據庫
(leaf), 中間的節點簡單地稱爲非葉(nonleaf)節點。閉包
評論數據就是一種樹形結構數據,評論的子節點就是它的回覆。其餘的樹形結構數據像職員與經理的關係,菜單等等不少;性能
這個多是最多見的解決方案,直接添加parent_id字段,引用同一張表中的其餘回覆。表結構以下優化
CREATE TABLE `Comments` ( `comment_id` int(11) NOT NULL AUTO_INCREMENT COMMENT '評論ID', `parent_id` int(11) NOT NULL DEFAULT '0' COMMENT '評論的父ID', `article_id` int(11) NOT NULL DEFAULT '0' COMMENT '文章ID', `comment` varchar(200) DEFAULT '' COMMENT '評論內容', PRIMARY KEY (`comment_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='簡化的評論表';
鄰接表老是依賴父節點,看看它的優缺點:spa
沒法完成樹操做中最普通的有一項,查詢一個節點的全部後代;設計
要合理的使用反模式:鄰接表設計的優點在於能快速地獲取一個給定節點的直接父節點,也很容易插入新節點。若是這樣的需求就是你的應用程序的需求,那使用鄰接表就能夠很好地工做!code
下面再看看其餘的方案:
路徑枚舉的設計經過將全部的祖先的信息聯合成一個字符串,並保存爲每一個節點的一個屬性:
CREATE TABLE `Comments` ( `comment_id` int(11) NOT NULL AUTO_INCREMENT COMMENT '評論ID', `path` varchar(1000) NOT NULL DEFAULT '0' COMMENT '路徑:eg: 1/2/4', `article_id` int(11) NOT NULL DEFAULT '0' COMMENT '文章ID', `comment` varchar(200) DEFAULT '' COMMENT '評論內容', PRIMARY KEY (`comment_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='簡化的評論表';
來看看該方案有沒有解決鄰接表的問題,
1. 經過比較每一個節點的路徑來查詢一個節點的祖先:例如查找comment_id 爲4的全部的祖先的sql:
SELECT * from Comments AS c where '1/3/4/' like CONCAT(c.path,'%');
2. 查詢一個節點的全部後臺:例如 comment_id 爲 1 的全部後臺:
SELECT * from Comments AS c where c.path like CONCAT('1/','%');
3. 插入節點:只需一份父節點的路徑便可;comment_id是自動生成,須要先插入再修改;
該方案的缺點:數據庫不能確保路徑的格式老是正確或者路徑中的節點確實存在,需應用程序的邏輯代碼來維護,且驗證字符串的正確性的開銷很大;再者,不管將varcharde的長度設定爲多大,依舊存在長度限制,於是不能支持樹結構的無限擴展。
閉包表記錄樹中全部節點間的關係,而不只僅只有那些直接的父子關係,是一個簡單而優雅的分級存儲解決方案。
該方案再也不使用Comments表來存儲樹的結構,而是將樹中任何具備祖先-後代關係的節點對都存儲在新表中,即便兩個節點不是直接的父子關係,同時,還增長一行指向節點本身,表結構以下:
CREATE TABLE `Comments` ( `comment_id` int(11) NOT NULL AUTO_INCREMENT COMMENT '評論ID', `article_id` int(11) NOT NULL DEFAULT '0' COMMENT '文章ID', `comment` varchar(200) DEFAULT '' COMMENT '評論內容', PRIMARY KEY (`comment_id`) ) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8 COMMENT='簡化的評論表';
CREATE TABLE `TreePaths` ( `ancestor` int(11) NOT NULL DEFAULT '0' COMMENT '祖先', `descendant` int(11) NOT NULL DEFAULT '0' COMMENT '後代' ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
下面看看相關操做
1. 搜索祖先:搜索評論5的祖先:
SELECT c.* from Comments as c JOIN TreePaths as t on c.`comment_id` = t.ancestor WHERE t.descendant=5;
2. 搜索後臺:搜索評論1的後代:
SELECT c.* from Comments as c JOIN TreePaths as t on c.`comment_id` = t.descendant WHERE t.ancestor=1;
3. 插入子節點:
例如評論5新增一個子節點,應首先插入一條本身到本身的關係,而後搜索TreePaths表中後代是評論5的全部節點,增長這些節點和新節點的’祖先-後代‘關係。
TreePaths表能夠繼續優化:增長path_length字段表示祖先與後代的層級數等等;
以上列舉了三個方案,沒種設計都各有優劣,如何選擇設計依賴於應用程序中的哪一種操做最須要性能上的優化: