5 存取或更新數據的查詢速度
首先,一件事情影響所有的詢問。你有的許可系統設置越復雜,你得到更多的開銷。
如果你不讓任何GRANT語句執行,MySQL將稍微優化許可檢查。因此如果你有很大量,值得花時間來避免授權,否則更多的許可檢查有更大的開銷。
如果你的問題是與一些明顯的MySQL函數有關,你總能在MySQL客戶中計算其時間:
mysql> select benchmark(1000000,1+1);
+------------------------+
| benchmark(1000000,1+1) |
+------------------------+
| 0 |
+------------------------+
1 row in set (0.32 sec)
上面顯示MySQL能在PentiumII 400MHz上以0.32秒執行1,000,000個+表達式。
所有MySQL函數應該被高度優化,但是以可能有一些例外并且benchmark(loop_count,expression)是找出是否你的查詢有問題的一個極好工具。
5.1 估計查詢性能
在大多數情況下,你能通過計算磁盤尋道估計性能。對小的表,你通常能在1次磁盤尋道中找到行(因為這個索引可能被緩沖)。對更大的表,你能估計它(使用 B++ 樹索引),你將需要:log(row_count)/log(index_block_length/3*2/(index_length + data_pointer_length))+1次尋道找到行。
在MySQL中,索引塊通常是1024個字節且數據指針通常是4個字節,這對一個有一個索引長度為3(中等整數)的 500,000 行的表給你:log(500,000)/log(1024/3*2/(3+4)) + 1= 4 次尋道。
象上面的索引將要求大約 500,000 * 7 * 3/2 = 5.2M,(假設索引緩沖區被充滿到2/3(它是典型的)),你將可能在內存中有索引的大部分并且你將可能僅需要1-2調用從OS讀數據來找出行。
然而對于寫,你將需要 4 次尋道請求(如上)來找到在哪兒存放新索引并且通常需2次尋道更新這個索引并且寫入行。
注意,上述不意味著你的應用程序將緩慢地以 N log N 退化!當表格變得更大時,只要一切被OS或SQL服務器緩沖,事情將僅僅或多或少地更慢。在數據變得太大不能被緩沖后,事情將開始變得更慢直到你的應用程序僅僅受磁盤尋道限制(它以N log N增加)。為了避免這個增加,索引緩沖隨數據增加而增加。見10.2.3 調節服務器參數。
5.2 SELECT查詢的速度
總的來說,當你想要使一個較慢的SELECT ... WHERE更快,檢查的第一件事情是你是否能增加一個索引。見10.4 MySQL 索引的使用。在不同表之間的所有引用通常應該用索引完成。你可以使用EXPLAIN來確定哪個索引用于一條SELECT語句。見7.22 EXPLAIN句法(得到關于一條SELECT的信息)。
一些一般的建議:
* 為了幫助MySQL更好地優化查詢,在它已經裝載了相關數據后,在一個表上運行myisamchk --analyze。這為每一個更新一個值,指出有相同值地平均行數(當然,對唯一索引,這總是1。)
* 為了根據一個索引排序一個索引和數據,使用myisamchk --sort-index --sort-records=1(如果你想要在索引1上排序)。如果你有一個唯一索引,你想要根據該索引地次序讀取所有的記錄,這是使它更快的一個好方法。然而注意,這個排序沒有被最佳地編寫,并且對一個大表將花很長時間!
5.3 MySQL怎樣優化WHERE子句
where優化被放在SELECT中,因為他們最主要在那里使用里,但是同樣的優化被用于DELETE和UPDATE語句。
也要注意,本節是不完全的。MySQL確實作了許多優化而我們沒有時間全部記錄他們。
由MySQL實施的一些優化列在下面:
* 刪除不必要的括號:
((a AND b) AND c OR (((a AND b) AND (c AND d))))
-> (a AND b AND c) OR (a AND b AND c AND d)
* 常數調入:
(a<b AND b=c) AND a=5
-> b>5 AND b=c AND a=5
* 刪除常數條件(因常數調入所需):
(B>=5 AND B=5) OR (B=6 AND 5=5) OR (B=7 AND 5=6)
-> B=5 OR B=6
* 索引使用的常數表達式僅計算一次。
* 在一個單個表上的沒有一個WHERE的COUNT(*)直接從表中檢索信息。當僅使用一個表時,對任何NOT NULL表達式也這樣做。
* 無效常數表達式的早期檢測。MySQL快速檢測某些SELECT語句是不可能的并且不返回行。
* 如果你不使用GROUP BY或分組函數(COUNT()、MIN()……),HAVING與WHERE合并。
* 為每個子聯結(sub join),構造一個更簡單的WHERE以得到一個更快的WHERE計算并且也盡快跳過記錄。
* 所有常數的表在查詢中的任何其他表前被首先讀出。一個常數的表是:
o 一個空表或一個有1行的表。
o 與在一個UNIQUE索引、或一個PRIMARY KEY的WHERE子句一起使用的表,這里所有的索引部分使用一個常數表達式并且索引部分被定義為NOT NULL。
所有下列的表用作常數表:
mysql> SELECT * FROM t WHERE primary_key=1;
mysql> SELECT * FROM t1,t2
WHERE t1.primary_key=1 AND t2.primary_key=t1.id;
* 對聯結表的最好聯結組合是通過嘗試所有可能性來找到:(。如果所有在ORDER BY和GROUP BY的列來自同一個表,那么當廉潔時,該表首先被選中。
* 如果有一個ORDER BY子句和一個不同的GROUP BY子句,或如果ORDER BY或GROUP BY包含不是來自聯結隊列中的第一個表的其他表的列,創建一個臨時表。
* 如果你使用SQL_SMALL_RESULT,MySQL將使用一個在內存中的表。
* 因為DISTINCT被變換到在所有的列上的一個GROUP BY,DISTINCT與ORDER BY結合也將在許多情況下需要一張臨時表。
* 每個表的索引被查詢并且使用跨越少于30% 的行的索引。如果這樣的索引沒能找到,使用一個快速的表掃描。
* 在一些情況下,MySQL能從索引中讀出行,甚至不咨詢數據文件。如果索引使用的所有列是數字的,那么只有索引樹被用來解答查詢。
* 在每個記錄被輸出前,那些不匹配HAVING子句的行被跳過。
下面是一些很快的查詢例子:
mysql> SELECT COUNT(*) FROM tbl_name;
mysql> SELECT MIN(key_part1),MAX(key_part1) FROM tbl_name;
mysql> SELECT MAX(key_part2) FROM tbl_name
WHERE key_part_1=constant;
mysql> SELECT ... FROM tbl_name
ORDER BY key_part1,key_part2,... LIMIT 10;
mysql> SELECT ... FROM tbl_name
ORDER BY key_part1 DESC,key_part2 DESC,... LIMIT 10;
下列查詢僅使用索引樹就可解決(假設索引列是數字的):
mysql> SELECT key_part1,key_part2 FROM tbl_name WHERE key_part1=val;
mysql> SELECT COUNT(*) FROM tbl_name
WHERE key_part1=val1 AND key_part2=val2;
mysql> SELECT key_part2 FROM tbl_name GROUP BY key_part1;
下列查詢使用索引以排序順序檢索,不用一次另外的排序:
mysql> SELECT ... FROM tbl_name ORDER BY key_part1,key_part2,...
mysql> SELECT ... FROM tbl_name ORDER BY key_part1 DESC,key_part2 DESC,...
5.4 MySQL怎樣優化LEFT JOIN
在MySQL中,A LEFT JOIN B實現如下:
* 表B被設置為依賴于表A。
* 表A被設置為依賴于所有用在LEFT JOIN條件的表(除B外)。
* 所有LEFT JOIN條件被移到WHERE子句中。
* 進行所有標準的聯結優化,除了一個表總是在所有它依賴的表之后被讀取。如果有一個循環依賴,MySQL將發出一個錯誤。
* 進行所有標準的WHERE優化。
* 如果在A中有一行匹配WHERE子句,但是在B中沒有任何行匹配LEFT JOIN條件,那么在B中生成所有列設置為NULL的一行。
* 如果你使用LEFT JOIN來找出在某些表中不存在的行并且在WHERE部分你有下列測試:column_name IS NULL,這里column_name 被聲明為NOT NULL的列,那么MySQL在它已經找到了匹配LEFT JOIN條件的一行后,將停止在更多的行后尋找(對一特定的鍵組合)。
5.5 MySQL怎樣優化LIMIT
在一些情況中,當你使用LIMIT #而不使用HAVING時,MySQL將以不同方式處理查詢。
* 如果你用LIMIT只選擇一些行,當MySQL一般比較喜歡做完整的表掃描時,它將在一些情況下使用索引。
* 如果你使用LIMIT #與ORDER BY,MySQL一旦找到了第一個 # 行,將結束排序而不是排序整個表。
* 當結合LIMIT #和DISTINCT時,MySQL一旦找到#個唯一的行,它將停止。
* 在一些情況下,一個GROUP BY能通過順序讀取鍵(或在鍵上做排序)來解決,并然后計算摘要直到鍵值改變。在這種情況下,LIMIT #將不計算任何不必要的GROUP。
* 只要MySQL已經發送了第一個#行到客戶,它將放棄查詢。
* LIMIT 0將總是快速返回一個空集合。這對檢查查詢并且得到結果列的列類型是有用的。
* 臨時表的大小使用LIMIT #計算需要多少空間來解決查詢。
5.6 INSERT查詢的速度
插入一個記錄的時間由下列組成:
* 連接:(3)
* 發送查詢給服務器:(2)
* 分析查詢:(2)
* 插入記錄:(1 x 記錄大。
* 插入索引:(1 x 索引)
* 關閉:(1)
這里的數字有點與總體時間成正比。這不考慮打開表的初始開銷(它為每個并發運行的查詢做一次)。
表的大小以N log N (B 樹)的速度減慢索引的插入。
加快插入的一些方法:
* 如果你同時從同一客戶插入很多行,使用多個值表的INSERT語句。這比使用分開INSERT語句快(在一些情況中幾倍)。
* 如果你從不同客戶插入很多行,你能通過使用INSERT DELAYED語句得到更高的速度。見7.14 INSERT句法。
* 注意,用MyISAM,如果在表中沒有刪除的行,能在SELECT:s正在運行的同時插入行。
* 當從一個文本文件裝載一個表時,使用LOAD DATA INFILE。這通常比使用很多INSERT語句快20倍。見7.16 LOAD DATA INFILE句法。
* 當表有很多索引時,有可能多做些工作使得LOAD DATA INFILE更快些。使用下列過程:
1. 有選擇地用CREATE TABLE創建表。例如使用mysql或Perl-DBI。
2. 執行FLUSH TABLES,或外殼命令mysqladmin flush-tables。
3. 使用myisamchk --keys-used=0 -rq /path/to/db/tbl_name。這將從表中刪除所有索引的使用。
4. 用LOAD DATA INFILE把數據插入到表中,這將不更新任何索引,因此很快。
5. 如果你有myisampack并且想要壓縮表,在它上面運行myisampack。見10.6.3 壓縮表的特征。
6. 用myisamchk -r -q /path/to/db/tbl_name再創建索引。這將在將它寫入磁盤前在內存中創建索引樹,并且它更快,因為避免大量磁盤尋道。結果索引樹也被完美地平衡。
7. 執行FLUSH TABLES,或外殼命令mysqladmin flush-tables。
這個過程將被構造進在MySQL的某個未來版本的LOAD DATA INFILE。
* 你可以鎖定你的表以加速插入。
mysql> LOCK TABLES a WRITE;
mysql> INSERT INTO a VALUES (1,23),(2,34),(4,33);
mysql> INSERT INTO a VALUES (8,26),(6,29);
mysql> UNLOCK TABLES;
主要的速度差別是索引緩沖區僅被清洗到磁盤上一次,在所有INSERT語句完成后。一般有與有不同的INSERT語句那樣奪的索引緩沖區清洗。如果你能用一個單個語句插入所有的行,鎖定就不需要。鎖定也將降低多連接測試的整體時間,但是對某些線程最大等待時間將上升(因為他們等待鎖)。例如:
thread 1 does 1000 inserts
thread 2, 3, and 4 does 1 insert
thread 5 does 1000 inserts
如果你不使用鎖定,2、3和4將在1和5前完成。如果你使用鎖定,2、3和4將可能不在1或5前完成,但是整體時間應該快大約40%。因為INSERT, UPDATE和DELETE操作在MySQL中是很快的,通過為多于大約5次連續不斷地插入或更新一行的東西加鎖,你將獲得更好的整體性能。如果你做很多一行的插入,你可以做一個LOCK TABLES,偶爾隨后做一個UNLOCK TABLES(大約每1000行)以允許另外的線程存取表。這仍然將導致獲得好的性能。當然,LOAD DATA INFILE對裝載數據仍然是更快的。
為了對LOAD DATA INFILE和INSERT得到一些更快的速度,擴大關鍵字緩沖區。見10.2.3 調節服務器參數。
5.7 UPDATE查詢的速度
更改查詢被優化為有一個寫開銷的一個SELECT查詢。寫速度依賴于被更新數據大小和被更新索引的數量。
使更改更快的另一個方法是推遲更改并且然后一行一行地做很多更改。如果你鎖定表,做一行一行地很多更改比一次做一個快。
注意,動態記錄格式的更改一個較長總長的記錄,可能切開記錄。因此如果你經常這樣做,時不時地OPTIMIZE TABLE是非常重要的。見7.9 OPTIMIZE TABLE句法。
5.8 DELETE查詢的速度
刪除一個記錄的時間精確地與索引數量成正比。為了更快速地刪除記錄,你可以增加索引緩存的大小。見10.2.3 調節服務器參數。
從一個表刪除所有行比刪除行的一大部分也要得多。
6 選擇一種表類型
用MySQL,當前(版本 3.23.5)你能從一個速度觀點在4可用表的格式之間選擇。
靜態MyISAM
這種格式是最簡單且最安全的格式,它也是在磁盤格式最快的。速度來自于數據能在磁盤上被找到的難易方式。當所定有一個索引和靜態格式的東西時,它很簡單,只是行長度乘以行數量。而且在掃描一張表時,用每次磁盤讀取來讀入常數個記錄是很容易的。安全性來自于如果當寫入一個靜態MyISAM文件時,你的計算機崩潰,myisamchk能很容易指出每行在哪兒開始和結束,因此它通常能回收所有記錄,除了部分被寫入的那個。注意,在MySQL中,所有索引總能被重建。
動態MyISAM
這種格式有點復雜,因為每一行必須有一個頭說明它有多長。當一個記錄在更改時變長時,它也可以在多于一個位置上結束。你能使用OPTIMIZE table或myisamchk整理一張表。如果你在同一個表中有象某些VARCHAR或BLOB列那樣存取/改變的靜態數據,將動態列移入另外一個表以避免碎片可能是一個好主意。
壓縮MyISAM
這是一個只讀類型,用可選的myisampack工具生成。
內存(HEAP 堆)
這種表格式對小型/中型查找表十分有用。對拷貝/創建一個常用的查找表(用聯結)到一個(也許臨時)HEAP表有可能加快多個表聯結。假定我們想要做下列聯結,用同樣數據可能要幾倍時間。
SELECT tab1.a, tab3.a FROM tab1, tab2, tab3
WHERE tab1.a = tab2.a and tab2.a = tab3.a and tab2.c != 0;
為了加速它,我們可用tab2和tab3的聯結創建一張臨時表,因為用相同列( tab1.a )查找。這里是創建該表和結果選擇的命令。
CREATE TEMPORARY TABLE test TYPE=HEAP
SELECT
tab2.a as a2, tab3.a as a3
FROM
tab2, tab3
WHERE
tab2.a = tab3.a and c = 0;
SELECT tab1.a, test.a3 from tab1, test where tab1.a = test.a1;
SELECT tab1.b, test.a3 from tab1, test where tab1.a = test.a1 and something;
6.1 靜態(定長)表的特點
* 這是缺省格式。它用在表不包含VARCHAR、BLOB或TEXT列時候。
* 所有的CHAR、NUMERIC和DECIMAL列充填到列寬度。
* 非?。
* 容易緩沖。
* 容易在崩潰后重建,因為記錄位于固定的位置。
* 不必被重新組織(用myisamchk),除非一個巨量的記錄被刪除并且你想要歸還空閑磁盤空間給操作系統。
* 通常比動態表需要更多的磁盤空間。
6.2 動態表的特點
* 如果表包含任何VARCHAR、BLOB或TEXT列,使用該格式。
* 所有字符串列是動態的(除了那些長度不到4的列)。
* 每個記錄前置一個位圖,對字符串列指出哪個列是空的(''),或對數字列哪個是零(這不同于包含NULL值的列)。如果字符串列在刪除尾部空白后有零長度,或數字列有零值,它在位圖中標記并且不保存到磁盤上。非空字符串存儲為一個長度字節加字符串內容。
* 通常比定長表占更多的磁盤空間。
* 每個記錄僅使用所需的空間。如果一個記錄變得更大,它按需要被切開多段,這導致記錄碎片。
* 如果你與超過行長度的信息更新行,行將被分段。在這種情況中,你可能必須時時運行myisamchk -r以使性能更好。使用myisamchk -ei tbl_name做一些統計。
* 在崩潰后不容易重建,因為一個記錄可以是分很多段并且一個連接(碎片)可以丟失。
* 對動態尺寸記錄的期望行長度是:
3
+ (number of columns + 7) / 8
+ (number of char columns)
+ packed size of numeric columns
+ length of strings
+ (number of NULL columns + 7) / 8
對每個連接有6個字節的懲罰。無論何時更改引起記錄的增大,一個動態記錄被鏈接。每個新鏈接將至少是20個字節,因此下一增大將可能在同一鏈連中。如果不是,將有另外一個鏈接。你可以用myisamchk -ed檢查有多少鏈接。所有的鏈接可以用 myisamchk -r 刪除。
6.3 壓縮表的特點
* 一張用myisampack實用程序制作的只讀表。所有具有MySQL擴展電子郵件支持的客戶可以為其內部使用保留一個myisampack拷貝。
* 解壓縮代碼存在于所有MySQL分發,以便甚至沒有myisampack的客戶能讀取用myisampack壓縮的表。
* 占據很小的磁盤空間,使磁盤使用量減到最小。
* 每個記錄被單獨壓縮(很小的存取開銷)。對一個記錄的頭是定長的(1-3 字節),取決于表中最大的記錄。每列以不同方式被壓縮。一些壓縮類型是:
o 通常對每列有一張不同的哈夫曼表。
o 后綴空白壓縮。
o 前綴空白壓縮。
o 用值0的數字使用1位存儲。
o 如果整數列的值有一個小范圍,列使用最小的可能類型來存儲。例如,如果所有的值在0到255的范圍,一個BIGINT列(8個字節)可以作為一個TINYINT列(1字節)存儲。
o 如果列僅有可能值的一個小集合,列類型被變換到ENUM。
o 列可以使用上面的壓縮方法的組合。
* 能處理定長或動態長度的記錄,然而不能處理BLOB或TEXT列。
* 能用myisamchk解壓縮。
MySQL能支持不同的索引類型,但是一般的類型是ISAM。這是一個B樹索引并且你能粗略地為索引文件計算大小為(key_length+4)*0.67,在所有的鍵上的總和。(這是對最壞情況,當所有鍵以排序順序被插入時。)
字符串索引是空白壓縮的。如果第一個索引部分是一個字符串,它也將壓縮前綴。如果字符串列有很多尾部空白或是一個總不能用到全長的VARCHAR列,空白壓縮使索引文件更小。如果很多字符串有相同的前綴,前綴壓縮是有幫助的。
6.4 內存表的特點
堆桌子僅存在于內存中,因此如果mysqld被關掉或崩潰,它們將丟失,但是因為它們是很快,不管怎樣它們是有用的。
MySQL內部的HEAP表使用沒有溢出區的100%動態哈希并且沒有與刪除有關的問題。
你只能通過使用在堆表中的一個索引的用等式存取東西(通常用=操作符)。
堆表的缺點是:
1. 你要為你想要同時使用的所有堆表需要足夠的額外內存。
2. 你不能在索引的一個部分上搜索。
3. 你不能順序搜索下一個條目(即使用這個索引做一個ORDER BY)。
4. MySQL也不能算出在2個值之間大概有多少行。這被優化器使用來決定使用哪個索引,但是在另一方面甚至不需要磁盤尋道。
7 其他優化技巧
對加快系統的未分類的建議是:
* 使用持久的連接數據庫以避免連接開銷。
* 總是檢查你的所有詢問確實使用你已在表中創建了的索引。在MySQL中,你可以用EXPLAIN命令做到。見7.22 EXPLAIN句法(得到關于SELECT的信息)。
* 嘗試避免在被更改了很多的表上的復雜的SELECT查詢。這避免與鎖定表有關的問題。
* 在一些情況下,使得基于來自其他表的列的信息引入一個“ 哈!钡牧杏幸饬x。如果該列較短并且有合理的唯一值,它可以比在許多列上的一個大索引快些。在MySQL中,很容易使用這個額外列:SELECT * from table where hash='calculated hash on col1 and col2' and col_1='constant' and col_2='constant' and .. 。
* 對于有很多更改的表,你應該試著避免所有VARCHAR或BLOB列。只要你使用單個VARCHAR或BLOB列,你將得到動態行長度。見9.4 MySQL表類型。
* 只是因為行太大,分割一張表為不同的表一般沒有什么用處。為了存取行,最大的性能命沖擊是磁盤尋道以找到行的第一個字節。在找到數據后,大多數新型磁盤對大多數應用程序來說足夠快,能讀入整個行。它確實有必要分割的唯一情形是如果其動態行尺寸的表(見上述)能變為固定的行大小,或如果你很頻繁地需要掃描表格而不需要大多數列。見9.4 MySQL表類型。
* 如果你很經常地需要基于來自很多行的信息計算(如計數),引入一個新表并實時更新計數器可能更好一些。類型的更改UPDATE table set count=count+1 where index_column=constant是很快的!當你使用象MySQL那樣的只有表級鎖定(多重讀/單個寫)的數據庫時,這確實重要。這也將給出大多數數據庫較好的性能,因為鎖定管理器在這種情況下有較少的事情要做。 11111111111111111111111
* 如果你需要從大的記錄文件表中收集統計信息,使用總結性的表而不是掃描整個表。維護總結應該比嘗試做“實時”統計要快些。當有變化而不是必須改變運行的應用時,從記錄文件重新生成新的總結表(取決于業務決策)要快多了!
* 如果可能,應該將報告分類為“實時”或“統計”,這里統計報告所需的數據僅僅基于從實際數據產生的總結表中產生。
* 充分利用列有缺省值的事實。當被插入值不同于缺省值時,只是明確地插入值。這減少MySQL需要做的語法分析并且改進插入速度。
* 在一些情況下,包裝并存儲數據到一個BLOB中是很方便的。在這種情況下,你必須在你的應用中增加額外的代碼來打包/解包BLOB中的東西,但是這種方法可以在某些階段節省很多存取。當你有不符合靜態的表結構的數據時,這很實用。
* 在一般情況下,你應該嘗試以第三范式保存數據,但是如果你需要這些以獲得更快的速度,你應該不用擔心重復或創建總結表。
* 存儲過程或UDF(用戶定義函數)可能是獲得更好性能的一個好方法,然而如果你使用某些不支持它的數據庫,在這種情況中,你應該總是有零一個方法(較慢的)做這些。
* 你總是能通過在你的應用程序中緩沖查詢/答案并嘗試同時做很多插入/更新來獲得一些好處。如果你的數據庫支持鎖定表(象MySQL和Oracle),這應該有助于確保索引緩沖在所有更新后只清空一次。
* 但你不知道何時寫入你的數據時,使用INSERT /*! DELAYED */。這加快處理,因為很多記錄可以用一次磁盤寫入被寫入。
* 當你想要讓你的選擇顯得更重要時,使用INSERT /*! LOW_PRIORITY */。
* 使用SELECT /*! HIGH_PRIORITY */來取得塞入隊列的選擇,它是即使有人等待做一個寫入也要完成的選擇。
* 使用多行INSERT語句來存儲很多有一條SQL命令的行(許多SQL服務器支持它)。
* 使用LOAD DATA INFILE裝載較大數量的數據。這比一般的插入快并且當myisamchk集成在mysqld中時,甚至將更快。
* 使用AUTO_INCREMENT列構成唯一值。
* 當使用動態表格式時,偶爾使用OPTIMIZE TABLE以避免碎片。見7.9O PTIMIZE TABLE句法。
* 可能時使用HEAP表以得到更快的速度。見9.4 MySQL表類型。
* 當使用一個正常Web服務器設置時,圖象應該作為文件存儲。這僅在數據庫中存儲的一本文件的引用。這樣做的主要原因是是一個正常的Web服務器在緩沖文件比數據庫內容要好得多,因此如果你正在使用文件,較容易得到一個較快的系統。
* 對經常存取的不重要數據(象有關對沒有cookie用戶最后顯示標語的信息)使用內存表。
* 在不同表中具有相同信息的列應該被聲明為相同的并有相同的名字。在版本 3.23 前,你只能靠較慢的聯結。嘗試使名字簡單化(在客戶表中使用name而不是customer_name)。為了使你的名字能移植到其他SQL服務器,你應該使他們短于18 個字符。
* 如果你需要確實很高的速度,你應該研究一下不同SQL服務器支持的數據存儲的底層接口!例如直接存取MySQL MyISAM,比起使用SQL 接口,你能得到2-5倍的速度提升。然而為了能做到它,數據必須是在與應用程序性在同一臺機器的服務器上,并且通常它只應該被一個進程存取(因為外部文件鎖定確實很慢)。通過在MySQL服務器中引進底層MyISAM命令能消除以上問題(如果需要,這可能是獲得更好性能的一個容易的方法)。借助精心設計的數據庫接口,應該相當容易支持這類優化。
* 在許多情況下,從一個數據庫存取數據(使用一個實時連接)比存取一個文本文件快些,只是因為數據庫比文本文件更緊湊(如果你使用數字數據)并且這將涉及更少的磁盤存取。你也節省代碼,因為你不須分析你的文本文件來找出行和列的邊界。
* 你也能使用復制加速。見19.1 數據庫復制。
8 使用你自己的基準測試
你決定應該測試你的應用程序和數據庫,以發現瓶頸在哪兒。通過修正它(或通過用一個“啞模塊”代替瓶頸),你能容易確定下一個瓶頸(等等)。即使對你的應用程序來說,整體性能“足夠好”,你至少應該對每個瓶頸做一個“計劃”,如果某人“確實需要修正它”,如何解決它。
對于一些可移植的基準程序的例子,參見MySQL基準套件。見11 MySQL 基準套件。你能利用這個套件的任何程序并且為你的需要修改它。通過這樣做,你能嘗試不同的你的問題的解決方案并測試哪一個對你是最快的解決方案。
在系統負載繁重時發生一些問題是很普遍的,并且我們有很多與我們聯系的客戶,他們在生產系統中有一個(測試)系統并且有負載問題。到目前為止,被一種這些的情況是與基本設計有關的問題(表掃描在高負載時表現不好)或OS/庫問題。如果系統已經不在生產系統中,它們大多數將很容易修正。
為了避免這樣的問題,你應該把一些力氣放在在可能最壞的負載下測試你的整個應用!
9 設計選擇
MySQL在分開的文件中存儲行數據和索引數據。許多(幾乎全部)其他數據庫在同一個文件中混合行和索引數據。我們相信,MySQL的選擇對非常廣泛的現代系統的來說是較好的。
存儲行數據的另一個方法是在一個分開的區域保存每列信息(例子是SDBM和Focus)。這將對每個存取多于一列的查詢獲得一個性能突破。因為在多于一列被存取時,這快速退化,我們相信這個模型對通用功能的數據庫不是最好。
更常見的情形是索引和數據一起被存儲(就象Oracle/Sybase)。在這種情況中,你將在索引的葉子頁面上找到行信息。有這布局的好處是它在許多情況下(取決于這個索引被緩沖得怎樣)節省一次磁盤讀。有這布局的壞處是:
* 表掃描更慢,因為你必須讀完索引以獲得數據。
* 你損失很多空間,因為你必須重復來自節點的索引(因為你不能在節點上存儲行)
* 刪除將隨時間變化惡化數據庫表(因為節點中的索引在刪除后通常不被更新)。
* 你不能僅使用索引表為一個查詢檢索數據。
* 索引數據很難緩沖。
10 MySQL設計局限/折衷
因為MySQL使用極快的表鎖定(多次讀/一次寫),留下的最大問題是在同一個表中的一個插入的穩定數據流與慢速選擇的一個混合。
我們相信,在其他情況下,對大多數系統,異?焖俚男阅苁顾蔀橐粋贏家。這種情形通常也可能通過表的多個副本來解決,但是它要花更多的力氣和硬件。
對一些常見的應用環境,我們也在開發一些擴展功能以解決此問題。
11 可移植性
因為所有SQL服務器實現了SQL的不同部分,要花功夫編寫可移植的SQL應用程序。對很簡單的選擇/插入,它是很容易的,但是你需要越多,它越困難,而且如果你想要應用程序對很多數據庫都快,它變得更難!
為了使一個復雜應用程序可移植,你需要選擇它應該與之工作的很多SQL服務器。
當你能使用MySQL的crash-me 程序(http://www.mysql.com/crash-me-choose.htmy)來找出你能與之使用的數據庫服務器的選擇的函數、類型和限制。crash-me現在對任何可能的事情測試了很長時間,但是它仍然理解測試過的大約450件事情。
例如,如果你想要能使用Informix 或 DB2,你不應該有比18個字符更長的列名。
MySQL基準程序和crash-me是獨立于數據庫的。通過觀察我們怎么處理它,你能得到一個感覺,你必須為編寫你的獨立于數據庫的應用程序做什么;鶞时旧砜稍贛ySQL源代碼分發的“sql-bench”目錄下找到。他們用DBI數據庫接口以Perl寫成(它解決問題的存取部分)。
到http://www.mysql.com/benchmark.html看這個基準的結果。
正如你可在這些結果看見的那樣,所有數據庫都有一些弱點。這就是他們不同的設計折衷導致的不同行為。
如果你為數據庫的獨立性而努力,你需要獲得每個SQL服務器瓶頸的良好感受。MySQL在檢索和更新方面很快,但是在同一個表上混合讀者/寫者方面將有一個問題。在另一方面,當你試圖存取你最近更新了的行時,Oracle有一個很大問題(直到他們被清空到磁盤上)。事務數據庫總的來說在從記錄文件表中生成總結表不是很好,因為在這種情況下,行級鎖定幾乎沒用處。
為了使你的應用程序“確實獨立于數據庫”,你需要定義一個容易的可擴展的接口,用它你可操縱你的數據。因為C++在大多數系統上可以得到的,使用數據庫的一個C++ 類接口是有意義的。
如果你使用一些某個數據庫特定的功能(在MySQL中,象REPLACE命令),你應該為SQL服務器編碼一個方法以實現同樣的功能 (但是慢些)。用MySQL,你能使用/*! */語法把MySQL特定的關鍵詞加到查詢中。在/**/中的代碼將被大多數其他SQL服務器視為一篇注釋(被忽略)。
如果高性能真的比準確性更重要,就像在一些web應用程序那樣。一種可能性是創建一個應用層,緩沖所有的結果以給你更高的性能。通過只是讓老的結果在短時間后‘過期’,你能保持緩存合理地刷新。這在極高負載的情況下是相當不錯的,在此情況下,你能動態地增加緩存到更大并且設定較高的過期時限直到一切回到正常。
在這種情況下,創建信息的表應該包含緩存初始大小和表一般應該被刷新幾次的信息。
12 我們已將MySQL用在何處?
在MySQL起初開發期間,MySQL的功能適合我們的最大客戶。他們為在瑞典的一些最大的零售商處理數據倉庫。
我們從所有商店得到所有紅利卡交易的每周總結并且我們被期望為所有店主提供有用的信息以幫助他們得出他們的廣告戰如何影響他們的顧客。
數據是相當巨量的(大約每月7百萬宗交易總結)并且我們保存4-10年來的數據需要呈現給用戶。我們每周顧客那里得到請求,他們想要“立刻”訪問來自該數據的新報告。
我們通過每月將所有信息存儲在壓縮的“交易”表中來解決它。我們有一套簡單的宏/腳本用來生成來自交易表的不同條件( 產品組,顧客id,商店...)的總結表。報告是由一個進行語法分析網頁的小perl腳本動態生成的網頁,在腳本中執行SQL語句并且插入結果,F在我們很想使用PHP或mod_perl,但是那時他們沒有。
對圖形數據,我們用C語言編寫了一個簡單的工具,它能基于SQL查詢的結果(對結果的一些處理)產生贈品,這也從分析HTML文件的perl腳本中動態地執行。
在大多數情況下,一個新的報告通過簡單地復制一個現有腳本并且修改其中的SQL查詢來完成。在一些情況下,我們將需要把更多的字段加到一個現有的總結表中或產生一個新的,但是這也相當簡單,因為我們在磁盤上保存所有交易表。(目前我們有至少50G的交易表和200G的其他顧客數據)。
我們也讓我們的顧客直接用ODBC存取總結表以便高級用戶能自己試驗數據。
我們用非常中檔的Sun Ultra sparcstation ( 2x200 Mz )來處理,沒有任何問題。最近我們升級了服務器之一到一臺2個CPU 400 Mz的Ultra sparc,并且我們現在計劃處理產品級的交易,這將意味著數據增加10番。我們認為我們能通過只是為我們的系統增加更多的磁盤就能趕上它。
我們也在試驗Intel-Linux以便能更便宜地得到更多的cpu動力。既然我們有二進制可移植的數據庫格式(在3.32中引入),我們將開始在應用程序的某些部分使用它。
我們最初的感覺是Linux在低到中等負載時執行的較好,但是你開始得到導致的高負載時,Solaris將表現更好,因為磁盤IO的極限,但是我們還沒有關于這方面的任何結論。在與一些Linux核心開發者的討論以后,這可能是 Linux 的副作用,它給批處理以太多的資源使得交互的性能變得很低。當大的批處理正在進行時,這使機器感覺很慢且無反應。希望這將在未來的Linux內核中解決。
文章來源于領測軟件測試網 http://www.kjueaiud.com/