OSChina 的留言表設計說明和OSChina 用戶動態設計

OSChina 的留言表 osc_msgs ,表結構以下:數據庫

字段說明:瀏覽器

id : 留言主鍵字段,自增加
user : 留言的主人
friend : 對方的ID
sender : 留言發送者
receiver : 留言接收者
type : 留言類型(普通消息、系統消息)
content : 留言內容
send_time : 發送時間
read_time : 閱讀時間
status : 留言狀態緩存

其中 user 和 friend 稍顯特殊,其餘的字段意義已很是明確再也不說明。app

當 A 給 B 發送一條留言時,會往 osc_msgs 表中插入兩條相同的記錄,惟一不一樣的是 user 和 friend 這兩個字段的值是對調的,固然 id 由於是自增加的因此也不一樣。性能

爲何要這麼作?優化

1. 一條留言保存兩條記錄:由於每一個人都有收到的留言和已發送留言,當發送人刪除了已發送留言,不會影響到接收人查看收到的留言spa

2. user/friend/sender/receiver 這四個字段是否是多餘?.net

關鍵的問題就在於此,你還記得 osc 的留言箱嗎?進入留言箱裏顯示的是你最近的留言往來,包含你接收到的和你發出的,它們是按照時間進行排序的。設計

假設只有 sender/receiver 這兩個字段,那麼要將接收和發送的留言放在一塊兒,就必須用 UNION 來合併兩個查詢結果,而後再作排序,並且你還必須有個字段來標註究竟是接收到的留言仍是發出的留言。這樣的 SQL 可能會是這樣:排序

SELECT * FROM (
    SELECT * FROM osc_msgs WHERE type=<接收> AND receiver=<我> 
    UNION 
    SELECT * FROM osc_msgs WHERE type=<發送> AND sender=<我>
) t ORDER BY send_time DESC

這樣的 SQL 語句不用執行都知道性能不好。

那麼以冗餘來換性能的思路,咱們對這個表進行了小改造。

增長兩個字段 user 和 friend,當 A 發送留言給 B 時,會寫入兩條記錄:

記錄1. user=A,friend=B,sender=A,receiver=B
記錄2. user=B,friend=A,sender=A,receiver=B

再來看看在新的表結構下,咱們如何改寫上面的語句:

SELECT * FROM osc_msgs WHERE user = <我> ORDER BY id DESC

這兩個 SQL 語句孰優孰劣,相信你們能比較得出來。

若是是要列出我跟每一個人的最後一條留言的話(就好象留言箱首頁顯示的內容)能夠這樣寫 SQL 語句:

SELECT MAX(id) AS id, COUNT(id) AS msgCount FROM osc_msgs WHERE user = ? GROUP BY friend ORDER BY id DESC

解釋完畢。

本文只是提供一種表結構設計的參考思路,這也不是放之四海而皆準的方法,關鍵的問題在於你想解決什麼樣的問題,對 OSC 來講性能很重要,若是能簡單的經過冗餘來提高性能,這很划算。

 

用戶動態是用戶在 OSChina 上作的一些動做,例如提問、回答問題、動彈、評論動彈、發表新聞評論、代碼分享、寫博客等等一系列的行爲。比如說咱們進入 @蟋蟀哥哥 的我的空間,就能看到他最近都作了些什麼動做:

而這些不一樣的動做對應的數據實際上是存在不一樣的表中,例如話題表、回帖表、評論表等等。

今天主要是介紹 OSChina 是如何將這些屬於不一樣範圍的數據彙總到用單一時間軸進行展現的動態。

動態表

首先要說明的是動態表,這個表在 OSChina 數據庫中對應的表名是 osc_opt_logs ,從這個名字能看出意思是「操做記錄表」,字段以下:

字段說明:

id 主鍵字段,動態記錄的惟一標識
user 某人的動態
obj_type/obj_id 由這兩個字段組合起來,表示對應不一樣類型的動做,例如是新聞、提問、回帖等,每一種動做都有惟一的 obj_type 對應,這些常量有:
parent_type/parent_id 這兩個字段用來表示具備一些層次關係的動態,例如回帖:parent_type 和 parent_id 就會填充上回帖對應帖子的編號和類型
shown 轉發標識,主要用於動彈的評論是否在空間中顯示與否
reply_count 若是此動態是一條動彈,那麼此字段存放動彈的評論次數
memo 若是此動態是一條動彈,那麼此字段存放動彈的內容
attachment 動彈對應的圖片
referer 保留用
appid 動彈的方式,能夠是PC瀏覽器、手機版或者不一樣的手機客戶端
create_time 動做發生的時間

obj_type 取值常量表:

public final static byte TYPE_PROJECT = 0x01;
public final static byte TYPE_QUESTION = 0x02;	//問題
public final static byte TYPE_ANSWER = 0x11;		//答案
public final static byte TYPE_BLOG = 0x03;
public final static byte TYPE_NEWS = 0x04;
public final static byte TYPE_CODE = 0x05;

public final static byte TYPE_JOB = 0x06;//招聘職位
public final static byte TYPE_GROUP_BUY = 0x0F;	//團購信息

public final static byte TYPE_NEWS_COMMENT 	= 0x10;	//新聞評論
public final static byte TYPE_BLOG_COMMENT 	= 0x12;	//博客評論
public final static byte TYPE_CODE_COMMENT 	= 0x13;	//代碼評論
public final static byte TYPE_JOB_COMMENT 	= 0x14;	//招聘職位評論

public final static byte TYPE_TWEET = 100;//動彈
public final static byte TYPE_TWEET_REPLY = 101;//動彈評論

假設某個動態是回帖,那麼 obj_type 值爲 TYPE_ANSWER,而 obj_id 的值則爲對應回帖的惟一編號。

動彈和動彈評論

動彈和動彈評論是比較特殊的動態,它沒有對應到其餘表中的關係。動彈對應的 obj_type 值爲 TYPE_TWEET,動彈評論對應的 obj_type 值爲 TYPE_TWEET_REPLY。而 memo 則存放了動彈或者評論的內容。

當咱們進入某個用戶空間的時候,會列出這個用戶的全部動態,查詢很簡單:

SELECT * FROM osc_opt_logs WHERE user = ? ORDER BY id DESC

若是是查看某個類別的動態,例如咱們只想看 @蟋蟀哥哥 的動彈,只須要:

SELECT * FROM osc_opt_logs WHERE user = ? AND obj_type = 100 ORDER BY id DESC

其中 100 就是 TYPE_TWEET 常量值。

這樣簡單的查詢,在任何數據庫上執行都是很是之快的。

惟一麻煩的在於,取出了動態列表後,如何加載它們對應的目標數據,若是是一個提問,那必須取出對應的帖子記錄。

這個的確是很麻煩,代碼量也比較大,只能簡單的介紹一下思路:

首先將獲取的動態列表按照 obj_type 進行分組,而後根據 obj_id 批量去 load 對應的數據,例如咱們可組合從像下面這樣的 SQL 語句來 load 數據:

SELECT * FROM osc_questions WHERE id IN (?,?,?,?,?,?)

這樣在首次進入某個用戶空間,光是動態這塊可能須要執行七八個SQL語句來獲取顯示完整的動態列表,好在這些 SQL 語句都是最基本的查詢,再結合緩存的使用,性能仍是很是之好的。

查看本身的空間

以上都是關於看別人空間時的說明,但你們應該都發現了,在看本身的空間時,並不僅是本身的動態,還會包含本身所關注的人的全部動態。

我本身的以及我關注的人的動態。這個查詢條件看似簡單,通常人會這樣寫 SQL 語句:

SELECT l.* FROM osc_opt_logs l,osc_friends f WHERE l.user = ? OR (l.user=f.friend AND f.user = ?) ORDER BY l.id DESC

請注意這裏用到了 OR 查詢,這個 OR 查詢讓你的全部優化都白作,索引也用不上。在你絞盡腦汁研究怎麼改 SQL 語句的時候,不妨換一個角度來思考。

不就是差一個我本身的 id 嗎?若是在 osc_friends 表裏也有我本身的記錄,那這個 SQL 不就能夠簡化爲:

SELECT l.* FROM osc_opt_logs l,osc_friends f WHERE l.user=f.friend AND f.user = ? ORDER BY l.id DESC

這裏沒有 OR 查詢,查詢速度快多了。惟一須要處理的就是每次用戶註冊的時候往 osc_friends 表中插入一條本身是本身好友的記錄,也就是 user=個人用戶id,friend=個人用戶id。

這仍是個人偏好,經過冗餘數據來提高查詢性能。是好是壞,你們本身掂量。

我只能說目前這種方案是適合 OSChina 目前的狀態,若是規模再大一點時候確定還有改造,至於怎麼改,到時候再說。涉及到用戶動態的其餘方面的都不值一提。

全文完,但願對你們有所幫助。

相關文章
相關標籤/搜索