樹型論壇的數據庫設計和快速算法
樹型論壇(即階梯式論壇)的實現算法,是一直被討論的問題。總結起來,一般無非是兩種:
第一是遞歸。這種方式最簡單,思路最清楚,但是效率也最低,特別是進行頁定位的時候。由於每進行一次遞歸調用,就必須執行一條數據庫查詢,使它在大量併發請求時的負載成爲災難性的。因此這種算法一般不實用。
第二是增加一個排序字段,思路是使用一個特殊設計的字段,例如排序串或者中值排序基數,來實現貼子的插入,在顯示的時候,只需要爲每一個主貼執行一次查詢,將所有得到的記錄按序顯示即可。這種方式在效率上有了很大提高,但是仍然不很理想,而且使得插入的代碼增加了不必要的複雜性,同時還往往導致了支持層次有限制的問題。
有沒有一種辦法可以簡單、高效地實現樹型論壇呢?
左輕侯提出一種算法,在顯示速度上超過我見的任何類似算法,實現起來也不復雜。
它的思路很簡單:就是完全不理會樹型結構本身,將整個論壇視爲一個簡單的順序表。這樣不論任何形式的頁面,只需要一條查詢即可得到。那麼如何實現樹型結構呢?方法是添加兩個格式化字段,一個記錄順序表的次序,一個記錄樹的層次,對取得的記錄集進行相應格式化,即可得到原汁原味的樹型論壇。
我的改進是,
1。取消ordernum,用messageID的順序來實現,
2。在BBSmessage表裏面取消threadID的FK,用算法來映射
3。在BBSthread表裏面增設一個endMessageID來爲最大MessageID,提高插入數據的速度
具體實現方法如下:
dbschema:
BBSmessage: 貼子表
messageID int : 貼子ID
deep smallint : 樹的深度,0爲主貼,以此類推
context varchar 4096 : 正文
title varchar 256 : 標題
clickDate timestamp : 閱讀時間
createDate timestamp : 創建時間(發佈時間)
clickCnt smallint : 人氣
rewards smallint : 得分
userID int : 創建用戶ID
flag char(1) : 標誌 D=刪除狀態 A =活躍狀態
// threadID int : 主題ID = messageID/1000
BBSthread: 話題表
threadID int : 話題ID
forumID int : 所屬論壇ID
clickDate timestamp : 閱讀時間
createDate timestamp : 創建時間
clickSum smallint : 話題人氣
replySum smallint : 回覆數量
endMessageID int : 尾部ID
// rootMessageID int : 該話題的根貼子ID,可以用threadID*1000得到rootMessageID
// replyDate timestamp : 最新回覆時間,因爲可以用lastMessageID得到時間
// title vchar(20) : 標題,可以用threadID*1000得到rootMessageID,再取得title
BBSUserInfo: 用戶表
userid int : 用戶ID
userNickName vchar 20 : 暱稱
sex char (1) : 性別
rewards smallint : 得分
PostCnt smallint : 一共發貼數
icon smallint : 圖標ID
列出全部話題列表的SQL:
// 主題 人氣 創建人 最後更新時間 回覆人 刪除屬性
select title , clickSum , replyDate , userName , deep , replySum,deep,visual
from
BBSUserInfo,BBSthread,BBSmessage
where
BBSmessage.userid = BBSUserInfo.userid
and
BBSmessage.messageID = BBSthread.rootMessageID
and
BBSthread.messageID < BBSthread.message+1000
order by messageID desc
其中threadID * 1000 = rootMessageID,
對貼子的管理實現:
1. 樹結構:
1.1 deep層,那麼n層message就縮進n個tablelet
1.2 如果插入數據,那麼就在thread表裏面的endMessageID+1並以此爲最新ID寫入BBSmessage
比左先生的select max(ordernum)再添加效果好,因爲這樣是row級鎖,而select max()是表級鎖。如果不加事務處理,必定有問題。因此,這點必須改進。
中間插入操作分析:
messageID由Thread表裏面的threadID*1000得到,因此,這個動作是如同在一個順序表裏面插入數據一般。
對此,我沒有做改進,因爲我認爲左先生的分析已經很明確了。
但是如果數據中間有+n個間隔,如0,5,10,的順序,那麼中間插入紀錄的也許速度有所提高。但是這樣有效貼子數就下降了。
例子:
└─api (1000)
├─java (1001)
│ ├─ -- 1002已經被delete
│ ├─applet (1003)-- del 1003,同時要delete 1004, 因爲在(awt 1005 deep3)前有class-use(1004,deep4)
│ │ └─class-use (1004)
│ ├─awt (1005)
│ │ ├─class-use (1006)
│ │ ├─color (1007)
│ │ │ └─ new ! <-- 1008, 同時>1008都+1
│ │ ├─datatransfer (1008)
│ │ │ └─class-use (1009) <-- new
=>
效率分析:
在message表中, messageID並不是按順序存放的,而是跳着存放,
系統限制沒個話題總回貼數不能超過1000,那麼一個long型數據在一個論壇裏面可以最大支持2147483647/1000=2147483條記錄
應該說,一張有214萬7千左右的bbs數據庫應該是很夠用了。
如果系統還要大,那麼就可以按時間段更換數據表即可實現無限量貼子。具體做法:如引入messagehistory2002表等等。
顯示速度應該不會更快了,僅僅是一條簡單的select,對一個int字段進行排序,而且支持無限的回覆層次。相比之下,遞歸需要爲一個頁面中的每一條貼子進行一次select,對datetime字段進行排序,而“主貼排序字段法”需要爲每一個主貼進行一次select,對char字段進行排序。效率比較差的只有在插入時,如果插入的位置很靠前,可能要更新大量記錄的messageid字段。但是經驗告訴我們,這種樹型論壇,回覆一般都集中在第一二頁,極少有人回覆很久以前的貼子,所以偶爾爲之,也不會增加太大的負擔。如果你實在不放心,也可以用技術手段強制禁止回覆一段時間
第一是遞歸。這種方式最簡單,思路最清楚,但是效率也最低,特別是進行頁定位的時候。由於每進行一次遞歸調用,就必須執行一條數據庫查詢,使它在大量併發請求時的負載成爲災難性的。因此這種算法一般不實用。
第二是增加一個排序字段,思路是使用一個特殊設計的字段,例如排序串或者中值排序基數,來實現貼子的插入,在顯示的時候,只需要爲每一個主貼執行一次查詢,將所有得到的記錄按序顯示即可。這種方式在效率上有了很大提高,但是仍然不很理想,而且使得插入的代碼增加了不必要的複雜性,同時還往往導致了支持層次有限制的問題。
有沒有一種辦法可以簡單、高效地實現樹型論壇呢?
左輕侯提出一種算法,在顯示速度上超過我見的任何類似算法,實現起來也不復雜。
它的思路很簡單:就是完全不理會樹型結構本身,將整個論壇視爲一個簡單的順序表。這樣不論任何形式的頁面,只需要一條查詢即可得到。那麼如何實現樹型結構呢?方法是添加兩個格式化字段,一個記錄順序表的次序,一個記錄樹的層次,對取得的記錄集進行相應格式化,即可得到原汁原味的樹型論壇。
我的改進是,
1。取消ordernum,用messageID的順序來實現,
2。在BBSmessage表裏面取消threadID的FK,用算法來映射
3。在BBSthread表裏面增設一個endMessageID來爲最大MessageID,提高插入數據的速度
具體實現方法如下:
dbschema:
BBSmessage: 貼子表
messageID int : 貼子ID
deep smallint : 樹的深度,0爲主貼,以此類推
context varchar 4096 : 正文
title varchar 256 : 標題
clickDate timestamp : 閱讀時間
createDate timestamp : 創建時間(發佈時間)
clickCnt smallint : 人氣
rewards smallint : 得分
userID int : 創建用戶ID
flag char(1) : 標誌 D=刪除狀態 A =活躍狀態
// threadID int : 主題ID = messageID/1000
BBSthread: 話題表
threadID int : 話題ID
forumID int : 所屬論壇ID
clickDate timestamp : 閱讀時間
createDate timestamp : 創建時間
clickSum smallint : 話題人氣
replySum smallint : 回覆數量
endMessageID int : 尾部ID
// rootMessageID int : 該話題的根貼子ID,可以用threadID*1000得到rootMessageID
// replyDate timestamp : 最新回覆時間,因爲可以用lastMessageID得到時間
// title vchar(20) : 標題,可以用threadID*1000得到rootMessageID,再取得title
BBSUserInfo: 用戶表
userid int : 用戶ID
userNickName vchar 20 : 暱稱
sex char (1) : 性別
rewards smallint : 得分
PostCnt smallint : 一共發貼數
icon smallint : 圖標ID
列出全部話題列表的SQL:
// 主題 人氣 創建人 最後更新時間 回覆人 刪除屬性
select title , clickSum , replyDate , userName , deep , replySum,deep,visual
from
BBSUserInfo,BBSthread,BBSmessage
where
BBSmessage.userid = BBSUserInfo.userid
and
BBSmessage.messageID = BBSthread.rootMessageID
and
BBSthread.messageID < BBSthread.message+1000
order by messageID desc
其中threadID * 1000 = rootMessageID,
對貼子的管理實現:
1. 樹結構:
1.1 deep層,那麼n層message就縮進n個tablelet
1.2 如果插入數據,那麼就在thread表裏面的endMessageID+1並以此爲最新ID寫入BBSmessage
比左先生的select max(ordernum)再添加效果好,因爲這樣是row級鎖,而select max()是表級鎖。如果不加事務處理,必定有問題。因此,這點必須改進。
中間插入操作分析:
messageID由Thread表裏面的threadID*1000得到,因此,這個動作是如同在一個順序表裏面插入數據一般。
對此,我沒有做改進,因爲我認爲左先生的分析已經很明確了。
但是如果數據中間有+n個間隔,如0,5,10,的順序,那麼中間插入紀錄的也許速度有所提高。但是這樣有效貼子數就下降了。
例子:
└─api (1000)
├─java (1001)
│ ├─ -- 1002已經被delete
│ ├─applet (1003)-- del 1003,同時要delete 1004, 因爲在(awt 1005 deep3)前有class-use(1004,deep4)
│ │ └─class-use (1004)
│ ├─awt (1005)
│ │ ├─class-use (1006)
│ │ ├─color (1007)
│ │ │ └─ new ! <-- 1008, 同時>1008都+1
│ │ ├─datatransfer (1008)
│ │ │ └─class-use (1009) <-- new
=>
效率分析:
在message表中, messageID並不是按順序存放的,而是跳着存放,
系統限制沒個話題總回貼數不能超過1000,那麼一個long型數據在一個論壇裏面可以最大支持2147483647/1000=2147483條記錄
應該說,一張有214萬7千左右的bbs數據庫應該是很夠用了。
如果系統還要大,那麼就可以按時間段更換數據表即可實現無限量貼子。具體做法:如引入messagehistory2002表等等。
顯示速度應該不會更快了,僅僅是一條簡單的select,對一個int字段進行排序,而且支持無限的回覆層次。相比之下,遞歸需要爲一個頁面中的每一條貼子進行一次select,對datetime字段進行排序,而“主貼排序字段法”需要爲每一個主貼進行一次select,對char字段進行排序。效率比較差的只有在插入時,如果插入的位置很靠前,可能要更新大量記錄的messageid字段。但是經驗告訴我們,這種樹型論壇,回覆一般都集中在第一二頁,極少有人回覆很久以前的貼子,所以偶爾爲之,也不會增加太大的負擔。如果你實在不放心,也可以用技術手段強制禁止回覆一段時間
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.