首先我們需要明確我們什麼時候需要用到數據庫:
1. 當緩存並不能解決你的問題,比如寫操作,事務操作
2. 緩存的創建或過期需要通過數據庫。
其次,我們可能需要一個專業的工具來指導我們優化:
mysqlreport
這是作爲一個Mysql第三方的狀態報告工具,其實就是將一下兩行命令所獲得的數據以一種更加人性化的方法呈現到我們眼前:
mysql> show status;
mysql> show engine innodb status;
你完全可以在自己電腦上先使用這個工具,這時候我們將逐條講解工具爲我們呈現的數據
s-db:~ # mysqlreport
MySQL 5.0.37-log uptime 1 15:11:47 Fri Apr 24 15:35:49 2009
__ Key ______________________________________________________________
Buffer used 100.37M of 2.00G %Used: 4.90
Current 1.02G %Usage: 51.07
Write hit 99.26%
Read hit 98.71%
__ Questions ________________________________________________________
Total 58.09M 411.7/s
DMS 30.62M 217.0/s %Total: 52.71
Com_ 21.23M 150.4/s 36.54
COM_QUIT 6.14M 43.5/s 10.58
+Unknown 104.71k 0.7/s 0.18
Slow 1 s 722 0.2/s 0.04 %DMS: 0.08 Log: ON
DMS 30.62M 217.0/s 52.71
SELECT 26.20M 185.7/s 45.10 85.56
UPDATE 3.72M 26.3/s 6.40 12.14
INSERT 649.28k 4.6/s 1.12 2.12
DELETE 54.13k 0.4/s 0.09 0.18
REPLACE 1.88k 0.0/s 0.00 0.01
Com_ 21.23M 150.4/s 36.54
change_db 21.18M 150.1/s 36.45
show_variab 8.94k 0.1/s 0.02
show_status 8.93k 0.1/s 0.02
__ SELECT and Sort ___________________________________________________
Scan 1.52M 10.8/s %SELECT: 5.80
Range 865.18k 6.1/s 3.30
Full join 3.18k 0.0/s 0.01
Range check 0 0/s 0.00
Full rng join 0 0/s 0.00
Sort scan 2.34M 16.6/s
Sort range 2.10M 14.9/s
Sort mrg pass 739 0.0/s
__ Table Locks ______________________________________________________
Waited 9.17k 0.1/s %Total: 0.03
Immediate 32.03M 227.0/s
__ Tables ___________________________________________________________
Open 512 of 512 %Cache: 100.00
Opened 2.78M 19.7/s
__ Connections ______________________________________________________
Max used 98 of 100 %Max: 98.00
Total 6.15M 43.6/s
__ Created Temp _____________________________________________________
Disk table 332.75k 2.4/s
Table 2.25M 16.0/s Size: 32.0M
File 1.48k 0.0/s
__ Threads __________________________________________________________
Running 2 of 5
Cached 0 of 0 %Hit: 0
Created 6.15M 43.6/s
Slow 0 0/s
__ Aborted __________________________________________________________
Clients 5.70k 0.0/s
Connects 36 0.0/s
__ Bytes ____________________________________________________________
Sent 2.74G 19.4k/s
Received 3.70G 26.2k/s
__ InnoDB Buffer Pool ________________________________________________
Usage 1.00G of 1.00G %Used: 100.00
Read hit 99.84%
Pages
Free 1 %Total: 0.00
Data 65.16k 99.43 %Drty: 4.00
Misc 374 0.57
Latched 0 0.00
Reads 78.05M 553.2/s
From file 125.51k 0.9/s 0.16
Ahead Rnd 1740 0.0/s
Ahead Sql 7 0.0/s
Writes 16.06M 113.8/s
Flushes 1.30M 9.2/s
Wait Free 0 0/s
__ InnoDB Lock ______________________________________________________
Waits 1441 0.0/s
Current 0
Time acquiring
Total 1178 ms
Average 0 ms
Max 39 ms
__ InnoDB Data, Pages, Rows __________________________________________
Data
Reads 142.84k 1.0/s
Writes 1.26M 8.9/s
fsync 191.53k 1.4/s
Pending
Reads 0
Writes 0
fsync 0
Pages
Created 1.05k 0.0/s
Read 173.91k 1.2/s
Written 1.30M 9.2/s
Rows
Deleted 0 0/s
Inserted 149.59k 1.1/s
Read 3.72G 26.4k/s
Updated 2.51M 17.8/s
首先最上面的uptime
中我們可以看到MySQL數據庫的運行時間,而報告中的各項數據統計,都是通過這段運行時間的累計數據計算而得,所以,我們希望這段時間儘可能地長,至少覆蓋站點高負載的時段,這樣我們採集的數據才具有較好的代表性。
索引的優化說明
對於索引的介紹這裏就不做過多的重複了。
我們需要知道的是數據庫中大概分爲兩種掃描模式:
1. 全表掃描
2. 索引掃描
大多數時候索引掃描會比全表掃描有着更好的性能,但是並非絕對,當我們需要一個表中絕大多數數據(這個數量可能是60%以上)的時候,全表掃描就可能獲得比索引掃描更好的性能,相信這並不難理解,畢竟我們閱讀一本書的時候,如果向獲取書中絕大部分的知識可能一頁一頁閱讀更加迅速。
當然,或許有時候數據庫的查詢優化器會幫助我們鑑別什麼時候我們需要使用這些索引,但是我認爲這應該是當我們創建一個表的時候需要考慮的內容。
索引有挺多種的,除了普通索引,還有唯一索引,主鍵,全文索引等,但是,它們只要是索引,都會具備索引掃描的功能,不同的是它們可能會滿足我們一些額外的需求。
需要特別強調的是:構建索引,構建什麼索引,在哪個鍵構建索引,這是我們自己的事,沒有什麼工具能夠幫助我們完成這些本是我們的工作。
一般來說,如果一個字段出現在查詢語句中基於行的選擇、過濾或排序條件中,那麼爲該字段建立索引便是有價值的,但這也不是絕對的。我們很難給出一個描述了所有情況的列表供你參考,因爲查詢的過程非常複雜,它可能包含多列索引、組合索引以及聯合查詢等情況,所以,關鍵在於掌握分析方法,這樣你便能夠應付任何的困境。
這時候有一個分析方法是我們必須掌握的:explain
。它只複雜查詢語句的分析。
我們或許可以看看實例:
CREATE TABLE `test` (
`id` int(11) NOT NULL auto_increment,
`name` varchar(255) NOT NULL,
PRIMARY KEY (`id`))
然後我填充了一些記錄,內容是什麼並不重要,我們這裏只是用explain來分析查詢語句是否使用了索引。
讓我們先使用主鍵試試
mysql> explain select * from test where id=1;
+----+-------------+-------+-------+---------------+---------+---------+-------+------+-------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+-------+---------------+---------+---------+-------+------+-------+
| 1 | SIMPLE | test | const | PRIMARY | PRIMARY | 4 | const | 1 | |
+----+-------------+-------+-------+---------------+---------+---------+-------+------+-------+
這裏我們可以看到:
- type
爲const
,意味着這次查詢通過索引直接找到一個匹配行,所以優化器認爲它的時間複雜度爲常量。
- key
爲PRIMARY
,意味着這次查詢使用了主鍵索引。
現在讓我們換成普通屬性:
mysql> explain select * from test where name='colin';
+----+-------------+-------+------+---------------+------+---------+------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+---------------+------+---------+------+------+-------------+
| 1 | SIMPLE | test | ALL | NULL | NULL | NULL | NULL | 4 | Using where |
+----+-------------+-------+------+---------------+------+---------+------+------+-------------+
這時候的type
就變成了ALL
,表示全表掃描了。
也許我們加上索引情況會有所不同吧?
mysql> alter table test add key name(name);
mysql> explain select * from test where name='colin';
+----+-------------+-------+------+---------------+------+---------+-------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+---------------+------+---------+-------+------+-------------+
| 1 | SIMPLE | test | ref | name | name | 257 | const | 1 | Using where |
+----+-------------+-------+------+---------------+------+---------+-------+------+-------------+
意料之中~
也許……我們可以試試模糊查詢:
mysql> explain select * from test where name like '%colin';
+----+-------------+-------+------+---------------+------+---------+------+------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+---------------+------+---------+------+------+-------------+
| 1 | SIMPLE | test | ALL | NULL | NULL | NULL | NULL | 4 | Using where |
+----+-------------+-------+------+---------------+------+---------+------+------+-------------+
看來此時的索引是幫不上忙了(少用模糊查詢)
對於包含group by
的查詢,數據庫一般需要先將記錄分組後放置在新的臨時表中,然後分別對它們進行函數計算,比如count()
、sum()
或max()
等。當有恰當的索引存在時,group by
有時也可以使用索引來取代創建臨時表,這當然是我們所希望的。以下這個SQL語句便利用了normal_key
索引,避免了創建臨時表。
mysql> explain select count(id) from key_t where key1=777 group by key3;
+----+-------------+-------+------+---------------+------------+---------+-------+------+-----------------------------------------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+---------------+------------+---------+-------+------+-----------------------------------------------------------+
| 1 | SIMPLE | key_t | ref | normal_key | normal_key | 4 | const | 2154 | Using where; Using index; Using temporary; Using filesort |
+----+-------------+-------+------+---------------+------------+---------+-------+------+-----------------------------------------------------------+
先等等,我們還有一個重點
組合索引
(就如同書目錄的章和節)
最左前綴:
顧名思義,最左優先,當我們創建了一個(key1, key2, key3...)
的索引的時候會按照如下的索引,key1
,(key1, key2)
,(key1, key2, key3)
…
這時候就會有個坑了……
先讓我們來看看這條查詢:
mysql> select * from key_t where key2=777 limit 10;
+--------+------+------+------+
| id | key1 | key2 | key3 |
+--------+------+------+------+
| 327233 | 643 | 777 | 781 |
| 686994 | 765 | 777 | 781 |
| 159907 | 766 | 777 | 782 |
| 61518 | 769 | 777 | 780 |
| 274629 | 769 | 777 | 780 |
| 633439 | 769 | 777 | 780 |
| 774191 | 769 | 777 | 780 |
| 109562 | 769 | 777 | 781 |
| 130013 | 769 | 777 | 781 |
| 139458 | 769 | 777 | 781 |
+--------+------+------+------+
10 rows in set (0.38 sec)
380ms!沒錯,可能你會想,我們並沒有使用索引,它是全表查詢,但是……:
mysql> explain select * from key_t where key2=777 limit 10;
+----+-------------+-------+-------+---------------+------------+---------+------+---------+--------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+-------+---------------+------------+---------+------+---------+--------------------------+
| 1 | SIMPLE | key_t | index | NULL | normal_key | 12 | NULL | 1000417 | Using where; Using index |
+----+-------------+-------+-------+---------------+------------+---------+------+---------+--------------------------+
對不起,它用了normal_key
,仔細看上面十行數據似乎都是按照key1
字段來排序的,事實也是如此,這說明查詢是依據normal_key
來掃描而不是數據本身掃描,在Innodb
類型表中數據的存儲順序是按照主鍵來排序的。
下面讓我們將這個錶轉換爲MyISAM類型來看看:
mysql> alter table key_t type=myisam;
Query OK, 1000000 rows affected, 1 warning (33.49 sec)Records: 1000000 Duplicates: 0 Warnings: 0
然後查詢:
mysql> select * from key_t_myisam where key2=777 limit 10;
+------+------+------+------+
| id | key1 | key2 | key3 |
+------+------+------+------+
| 1035 | 771 | 777 | 781 |
| 3175 | 771 | 777 | 781 |
| 4126 | 771 | 777 | 781 |
| 5443 | 770 | 777 | 780 |
| 6066 | 771 | 777 | 781 |
| 6267 | 770 | 777 | 780 |
| 6317 | 770 | 777 | 780 |
| 6496 | 771 | 777 | 781 |
| 8262 | 770 | 777 | 780 |
| 9083 | 771 | 777 | 780 |
+------+------+------+------+
10 rows in set (0.00 sec)
看看分析結果:
mysql> explain select * from key_t_myisam where key2=777 limit 10;
+----+-------------+-------+------+---------------+------+---------+------+---------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+---------------+------+---------+------+---------+-------------+
| 1 | SIMPLE | key_t | ALL | NULL | NULL | NULL | NULL | 1000000 | Using where |
+----+-------------+-------+------+---------------+------+---------+------+---------+-------------+
這纔是貨真價實的全表掃描啊!
我們足可以想象當組合索引不能直接發揮作用的時候,反而給查詢帶來了巨大的負擔,一個包含多個字段的組合索引的尺寸可能已經超過了數據本身。
如果你希望結果仍然按照key1
排序,這不是什麼問題,你可以增加一個包含(key2,key1)
字段的組合索引,注意它們的順序,(key1,key2)
索引和(key2,key1)
索引完全不同,你必須根據需要來進行抉擇,而優化器對此無能爲力。
以上我們只是舉了個簡單的例子,並不是鼓勵你用全表掃描取代索引掃描,而是希望藉此強調,一定要根據查詢的需要來設計有針對性的組合索引,因爲在實際應用中,很多查詢不像例子中的那麼簡單,一個量身定製的組合索引肯定要比全表掃描更加高效,這是毋庸置疑的。
慢查詢工具分析
我們很容易對所有查詢語句進行分析,並建立適當的索引,但這只是紙上談兵,當到了運行環境後,隨着實際數據的積累,查詢計算的開銷也逐漸增加,也許你會發現有些索引設計得並不理想,不可否認,錯誤是在所難免的。可是,在運行環境上對各種查詢進行explain
分析顯然很不現實,更難的是你不知道何時去分析哪些查詢。
最好的辦法是對每一條查詢語句的時間進行記錄然後再對其中比較慢的查詢去分析,通過Web應用的日誌功能可能也是一個比較不錯的主意。
但是我們這裏是介紹數據庫,數據庫中還有一種方法可以幫助我們達到我們所想要的目的,它提供了慢查詢日誌,可以將執行時間超過預設值的所有查詢記錄到日誌中,以供後期分析。
在Mysql中的my.cnf開啓慢查詢工具
long_query_time = 1
log-slow-queries = /data/var/mysql_slow.log
這意味着MySQL會自動將執行時間超過1秒的查詢記錄在指定路徑的mysql_slow.log文件中。除此之外,你還可以將所有沒有使用索引的查詢也記錄下來,只需增加以下選項即可:
log-queries-not-using-indexes
而在之前提及的mysqlreport
中,我們也可以看到有關慢查詢的統計:
Slow 1 s 722 0.2/s 0.04 %DMS: 0.08 Log: ON
我們可以使用第三方工具mysqlsla,它有着比之其他工具更加清晰的統計風格。
我們還有一個優化策略,使用緩存,緩存索引,這樣使用到索引的時候就不需要在磁盤中讀取數據了。
在mysqlreport中也有這方面的信息:
__ Key ______________________________________________________________
Buffer used 100.37M of 2.00G %Used: 4.90
Current 1.02G %Usage: 51.07
Write hit 99.26%
Read hit 98.71%
這裏的緩存和之前介紹的其他緩存在本質上沒什麼兩樣,Write hit和Read hit分別代表了寫緩存的命中率和讀緩存的命中率,這裏都在98%以上,比較正常。
當然最後在索引這兒提一下使用它的代價(我們大多數優化策略其實都需要付出一些代價的)
首先是空間,索引使用空間比之普通文件較大,但是在如今存儲空間不值錢的年代這完全不是事。
其次,當建立索引的字段發生更新時,會引發索引本身的更新,這將產生不小的計算開銷。
最後,索引需要我們花費一些額外的時間來維護。
鎖
鎖機制是保證當有多個用戶併發訪問數據庫某個資源的時候,併發訪問的一致性。
我們可以認爲查詢的時間開銷主要包括兩部分,即
1. 查詢本身的計算時間
2. 查詢開始前的等待時間
所以說索引影響的是前者,而鎖機制影響的是後者。顯然,我們的目標很明確,那就是減少等待時間。
在mysqlreport中也有相應的記錄:
__ Table Locks ______________________________________________________
Waited 9.17k 0.1/s %Total: 0.03
Immediate 32.03M 227.0/s
Waited
表示有多少次查詢需要等待表鎖定;Immediate
表示有多少次查詢可以立即獲得表鎖定,同時後面還有一個比例,表示等待表鎖定的查詢次數佔所有查詢次數的百分比,這裏是0.03%,非常好,但爲什麼這麼低呢?這需要了解MyISAM的表鎖定機制。
MyISAM的表鎖定可以允許多個線程同時讀取數據,比如select
查詢,它們之間是不需要鎖等待的。但是對於更新操作(如update
操作),它會排斥對當前表的所有其他查詢,包括select
查詢。除此之外,更新操作有着默認的高優先級,這意味着當表鎖釋放後,更新操作將先獲得鎖定,然後才輪到讀取操作。也就是說,如果有很多update
操作排着長隊,那麼對當前表的select
查詢必須等到所有的更新都完成之後才能開始。
如果你的站點主要依靠用戶創造內容,那麼頻繁的數據更新在所難免,它將會給select
等讀取操作帶來很大的影響,你可能會需要innodb的行鎖定來爲你提高一定的性能。
但是我們需要認清楚,行鎖定只是能夠提高你在update
上的等待時間,但是卻不能加速update
的時間,當你的update
太過密集的時候,你的磁盤讀寫速度會稱爲限制你的性能的一個門檻,這種情形下或許行鎖定的消耗就決定它並不是最好的選擇了。
事務
事務可以說是吸引大家選擇innodb的一個主要原因,但是,在選擇它的時候你需要先問問自己,你是否真的這麼需要事務,如果沒有事務,你有什麼不能解決的問題,畢竟事務對於性能也是一大消耗。
Innodb的實現方式是預寫日誌方式(WAL),當有事務提交時,也就是隻有當事務日誌寫入磁盤後才更新數據和索引,這樣即使數據庫崩潰,也可以通過事務日誌來恢復數據和索引。Innodb首先將它寫到內存中的事務日誌緩衝區,隨後當事務日誌寫入磁盤時,Innodb才更新實際數據和索引。這裏有一個關鍵點,那就是事務日誌何時寫入磁盤。
爲此,MySQL提供了一個配置選項,它有三個可選的值:
- innodb_flush_log_at_trx_commit = 1
表示事務提交時立即將事務日誌寫入磁盤,同時數據和索引也立即更新。這符合事務的持久性原則。
- innodb_flush_log_at_trx_commit = 0
表示事務提交時不立即將事務日誌寫入磁盤,而是每隔1秒寫入磁盤文件一次,並且刷新到磁盤,同時更新數據和索引。這樣一來,如果mysqld崩潰,那麼在內存中事務日誌緩衝區最近1秒的數據將會丟失,這些更新將永遠無法恢復。
- innodb_flush_log_at_trx_commit = 2
表示事務提交時立即寫入磁盤文件,但是不立即刷新到磁盤,而是每隔1秒刷新到磁盤一次,同時更新數據和索引。在這種情況下,即使mysqld崩潰後,位於內核緩衝區的事務日誌仍然不會丟失,只有當操作系統崩潰的時候纔會丟失最後1秒的數據。
顯然,將innodb_flush_log_at_trx_commit
設置爲0可以獲得最佳性能,同時它的數據丟失可能性也最大。
另一個重要的配置選項是Innodb數據和索引的內存緩衝池大小,MySQL提供了innodb_buffer_pool_size
選項來設置這個數值,如果你在MySQL中大量使用Innodb類型表,則可以將緩衝池大小設置爲物理內存的80%,並持續關注它的使用率,這時候mysqlreport又提供了方便。
__ InnoDB Buffer Pool ________________________________________________
Usage 1.00G of 1.00G %Used: 100.00
Read hit 99.84%
使用查詢緩存
查詢緩存的目的很簡單,將select
查詢的結果緩存在內存中,以供下次直接獲取。在默認情況下,MySQL是沒有開啓查詢緩存的,我們可以進行以下配置:
query_cache_size = 268435456
query_cache_type = 1
query_cache_limit = 1048576
這樣一來,MySQL將擁有256MB的內存空間來緩存查詢結果。對於以select查詢爲主的應用,查詢緩存理所當然地起到性能提升的作用,不論是Innodb還是MyISAM,查詢緩存都可以很好地工作,因爲它在邏輯中位於比較高的層次。
但是,查詢緩存有一個需要注意的問題,那就是緩存過期策略,MySQL採用的機制是,當一個數據表有更新操作(比如update
或者insert
)後,那麼涉及這個表的所有查詢緩存都會失效。這的確令人比較沮喪,但是MySQL這樣做是不希望引入新的開銷而自找麻煩,所以“寧可錯殺一千,不可放過一個”。這樣一來,對於select
和update
混合的應用來說,查詢緩存反而可能會添亂
關於mysqlreport中查詢緩存的報告:
__ Query Cache ______________________________________________________
Memory usage 38.05M of 256.00M %Used: 14.86
Block Fragmnt 4.29%
Hits 12.74k 33.3/s
Inserts 58.21k 152.4/s
Insrt:Prune 58.21k:1 152.4/s
Hit:Insert 0.22:1
如果你的應用中對於密集select
的數據表很少更新,很適合於使用查詢緩存。
臨時表
我們或許看到一些explain
查詢在分析時出現Using temporary的狀態,這意味着查詢過程中需要創建臨時表來存儲中間數據,我們需要通過合理的索引來避免它。另一方面,當臨時表在所難免時,我們也要儘量減少臨時表本身的開銷,通過mysqlreport報告中的Created Temp部分,我們可以看到:
_ Created Temp _____________________________________________________
Disk table 864.89k 2.0/s
Table 7.06M 16.1/s Size: 32.0M
File 9.22k 0.0/s
MySQL可以將臨時表創建在磁盤(Disk table)、內存(Table)以及臨時文件(File)中,顯然,在磁盤上創建臨時表的開銷最大,所以我們希望MySQL儘量不要在磁盤上創建臨時表。
在MySQL的配置中,我們可以通過tmp_table_size
選項來設置用於存儲臨時表的內存空間大小,一旦這個空間不夠用,MySQL將會啓用磁盤來保存臨時表,你可以根據mysqlreport的統計儘量給臨時表設置較大的內存空間。
線程池
我們知道,MySQL採用多線程來處理併發的連接,通過mysqlreport中的Threads部分,我們可以看到線程創建的統計結果:
Threads _____________________________________________________
Running 2 of 5
Cached 0 of 0 %Hit: 0
Created 6.15M 43.6/s
Slow 0 0/s
也許你會覺得創建線程的消耗不值一提,但是我們所謂優化都是在你係統繁忙下的救命稻草。
一個比較好的辦法是在應用中儘量使用持久連接,這將在一定程度上減少線程的重複創建。另一方面,從上面的Cached=0可以看出,這些線程並沒有被複用,我們可以在my.cnf中設置以下選項:
thread_cache_size = 100
拋棄關係型數據庫
我們應該知道,在關係型數據庫裏指導我們設計數據庫表的時候可以根據範式來設計,我們一般會選擇第三範式,簡單地說,第三範式要求在一個數據表中,非主鍵字段之間不能存在依賴關係,這樣一來,它可以避免更新異常、插入異常和刪除異常,保證關係的一致性,並且減少數據冗餘。
其實我們並不需要去刻意設計,只要你設計表的時候思維正常,大多數時候都是第三範式。
但是對於某些應用這樣做可能會極大降低我們的性能
比如對於這樣兩張表
(用戶ID,好友ID,好友暱稱)
(用戶ID,用戶暱稱,用戶郵箱,註冊時間,聯繫電話)
它們肯定不符合第三範式,因爲好友暱稱依賴與好友ID,但是這兩個很容易是同時取出,而修改好友暱稱的機會少得可憐,甚至你的應用可能根本不想支持,這時候拋棄範式來設計就會有着更好的效果。
還有一個比較經典的例子:
社交網站中的好友Feed功能,你一定非常熟悉,當你的好友或者關注的人發生一些事件的時候,你會在自己的空間看到這些動態,看起來很簡單的功能,你將如何實現呢?如果在你每次刷新空間的時候都要對每個好友的最新事件進行一番查詢,甚至使用可怕而昂貴的join聯合查詢,當你有500個好友的時候,開銷可想而知。這個時候,出於性能的考慮,可以使用反範式化設計,將這些動態爲所有關注它的用戶保存一份副本,當然,這會帶來大量的寫開銷,但是這些寫操作完全可以異步進行,而不影響用戶的體驗。
這時候如果使用NOSQL來存儲這些冗餘的副本可能會給你帶來意想不到的性能的提升。
下面是一些啓發性優化策略:
Mysql性能優化20+條經驗
最後參考資料:
《構建高性能Web站點》,推薦必讀書