我在做一個網站程序,大致要求效果如下。
用戶分為1-5這五個級別,數字越大權限越高。
我有一堆內容,等級越高的用戶可見內容越多。
例如有內容:A、B、C、D、E,
用戶群組1的可見:A
用戶群組2的可見:A、B
…………
用戶群組5的可見:A、B、C、D、E
如果要實現這種功能,該如何建立資料庫索引比較好呢?
之前有朋友跟我說在內容(topic)表格加上一列"group",
寫上可見的使用者等級1-5,然後建立group_tid
的聯合索引。
然後查詢tid
<100周圍文章(例如目前使用者群組為3)時的語句就是:
SELECT * FROM topic
WHERE group
>=3 AND tid
<100 LIMIT 10;
可實際發現這種索引是先將group
>3的所有資料讀出來,再進行選擇查詢。
假如有100萬條數據,有50萬個group
>3,該語句執行就要從50萬條種篩選,效率極低。
看起來單列的索引只適用於group
=*這樣的限制條件,而不能是<或>。
所以想在此請教各路大神,有沒有過類似的需求?如何正確建立索引或分錶?
深表感謝!
補充1:
其實把問題改變一下,就是如何在mysql兩個索引中使用<或>限定。
這是個邏輯問題,目前的group_tid
索引建立後類似下圖:
即便我對group
進行了範圍限制,後面的tid
還是在group
的基礎上按順序排列的。
如果我想知道group
>1且tid
<6的這種情況,我不得不先把group
2/3全部讀出再篩選。
看起來只有重新規劃表結構,各位有沒有類似經驗?
補充2:
剛才收到了熱心朋友的幫忙回答,說這種情況他曾經遇過。
解決的方法是修改發布機制,將符合條件的貼文發佈至各個等級。
例如內容A的層級是3,那麼發文時要同時建立三個資料行:group
=1,tid
=Agroup
=2,###tid=A
group=3,
tid=A
這樣在內容讀取時直接請求WHERE
group=*都可以讀出符合條件內容。
但這種方法需要增加大量的關聯數據,甚至造成重複,有沒有其他解決途徑呢?
其實你的思路已經很對了。
tid上建立索引,依group分錶。
如果group >=3的群組,在程式中動態組合sql如下:
以上索引生效,邏輯可用。
首先說明一下,在 Innodb 中,索引生不生效跟你使用 沒有必然關係。也不是說用 = 就一定能用上索引。當全表查的效能要高於索引檢索查詢時,MySQL 會智慧的放棄索引,選擇全表查詢。
如圖:
回到你的問題,如果某個索引,如 tid
如果這兩個索引的結果集都很大的話,是否考慮添加其他過濾條件,例如根據創建時間只查近一個月的內容。
分頁問題也可以透過主鍵ID來再次過濾。
首先,要明白以下幾點:
對於一個表格的查詢,每次最多只使用一個索引
對於聯合索引,從左往右依次進行資料的篩選,所以如果第一個篩選條件針對了大於或小於的話,第二個篩選條件由於在整個可選區域內沒有確切的索引範圍,所以會將第一個篩選條件篩除來的數據都跑一遍
B-Tree索引的結構類似樹狀結構,見下圖,聯合索引由左往右的檢索,起始是這個結構從上往下找出分支的過程
索引的機制,簡單說來就是創建一個值到資料項的對應表,這樣可以快速的從某一字段某個值定位到某一行,省卻了跑整個表去找對應行的操作,所以比較快
B-Tree索引的結構:
然後回到你的問題上,如果要大幅度提高效率,那麼聯合索引的第一步就需要大幅度減少可以用於後續篩選的數據量,所以如果你要查
tid 的話,先用
tid
篩選才能夠大幅減少後續的B-Tree索引分支,所以如果要用聯合索引,則應該是(tid, group)
。group條件的篩選性很差,單獨建立索引意義不大。
根據你描述的場景,只要tid的值不是太大(幾千的數量級),針對tid建立索引就可以了。
如果還擔心tid條件過濾後的資料量大,可以建立tid,group的組合索引。
首先非常感謝各位對我問題的關注與回答! !
問題解決之後針對boxsnake的建議有一個思考,在這裡發一下。
group_tid
這種索引方式除了解決讀取之外還能解決分頁問題,例如我每頁文章數量是10,用戶級別為3,那麼讀取時分別從group1、group2、group3中,
按範圍
tid
但如果用
tid_group
這種索引方式來讀取,如果需要group比方說取10篇,tid90-tid99,如果他們的group都是4,那麼就無法取出符合條件的數值。而
tid_group
在限定group
之前又必須對tid
進行限定,所以就沒法使用了。