高性能mysql讀書筆記之二 – 架構優化和索引

第三章 架構優化和索引

第三章的主要是說合理使用不一樣的數據類型和索引。主要須要注意的內容有以下: mysql

1. 通用原則 sql

1.1. 數據類型更小一般更好。 數據庫

1.2. 數據類型越簡單越好 緩存

1.3. 儘可能避免使用NULL,要是有必要用NULL,那也可考慮使用0來進行代替。 建立表的時候定義好not null default 0。 服務器

1.4. DATETIME和TIMESTAMP都能保持一樣數據類型:日期和時間,精度爲妙。並且TIMESTAMP使用的空間只是DATETIME的一半,TIMESTAMP使用4個字節,DATETIME使用8個字節。並且TIMESTAMP還能保存時區,擁有特殊的自動更新能力,可是TIMESTAMP的範圍要比DATETIME要小的多。TIMESTAMP類型只能保存1970年1月1日零點到2038年。而DATETIME卻能保存1001年到9999年。 架構

2. VARCHAR和CHAR 併發

2.1. 你們都知道VARCHAR是可變長度的,而CHAR是定長的。 數據庫設計

2.2. 使用VARCHAR(5)和VARCHAR(200)保存’hello’佔用的空間都是同樣的。可是VARCHAR(5)只會使用較小的內存空間,由於MySQL一般會分配固定大小的內存塊來保存值。 函數

3. 索引對於查詢的性能影響是很是大的。下面先介紹下索引類型。 佈局

3.1. B-TREE索引,大部分MySQL存儲引擎都支持B-TREE。除了ARCHIVE直到5.1才支持。

3.1.1. 存儲引擎使用了不一樣的方式把索引保存在磁盤上,它們會影響必定的性能。例如MyISAM使用前綴壓縮以減小索引,而InnoDB不會壓縮索引,由於它不能把壓縮索引用於某些優化。

3.1.2. B-TREE一般意味着存儲是有序的。

3.1.3. B-TREE索引可以很好的用戶全鍵值,鍵值範圍或者鍵前綴進行查找(最左前綴)。

3.1.3.1. 匹配全名,如 where name=’timo’

3.1.3.2. 匹配最左前綴, 如 where name like ‘tim%’

3.1.3.3. 匹配範圍值,如 where name between ‘tim’ and ‘timo’

3.1.3.4. 精確匹配一部分而且匹配某個範圍內的另外一部分,如 where name=’timo’ and age between 25 and 30

3.1.3.5. B-TREE索引一般能支持至訪問索引的查詢,它不會訪問數據行

3.1.4. B-TREE索引的侷限性

3.1.4.1. 假設有以下的索引 key(first_name, last_name, age)

3.1.4.2. 若是查找沒有從索引的最左邊開始,它就沒有什麼用處。好比where first_name like ‘%mo’ 這樣的查找是不走索引的。

3.1.4.3. 不能跳過索引中的列, 如查找 where first_name=’timo’ and age = 25, 若是創建的是上面這樣的聯合索引,又跨了last_name,那就不會走索引了。

3.1.4.4. 存儲引擎不能優化訪問任何在一個範圍條件右邊的列。如查找

where first_name=’timo’ and last_name like ‘s%’ and age=25。訪問就只能使用索引的前2列,由於like是範圍條件。

3.1.4.5. 一些侷限並非B-TREE固有的,而是MySQL查詢優化器和存儲引擎使用索引的方式形成的。

3.2. 哈希索引(hash index)

3.2.1. 它值對使用索引中的每一列的精確查找有用。因此不多用,在MySQL中是有Memory存儲引擎支持顯式的哈希索引。

3.2.2. 因爲hash index是給每一個鍵值創建一個哈希表,因此它的查找速度是很是快的,可是也會有不少侷限性。

3.2.2.1. 由於索引只包含了哈希碼和行指針,而不是指自己,MySQL不能使用索引中的值來避免讀取行。

3.2.2.2. 不能進行排序

3.2.2.3. 不支持部分鍵匹配

3.2.2.4. 只支持使用 =, in() 和<=>的相等比較。

3.2.2.5. 發生碰撞的時候存儲引擎必須訪問鏈表中的每一個指針,而後逐行進行數據比較,以肯定正確的數據。

3.2.2.6. 若是有不少碰撞,一些索引維護操做就會很慢。

3.3. 空間(R-TREE)索引

3.3.1. 只有MyISAM支持,可使用GEOMETRY這樣的地理空間數據類型,必須使用MySQL GIS函數進行查找。

3.4. 全文索引

3.4.1. 全文索引只有MyISAM支持。是從文本中直接找關鍵字,而不是從索引中進行比較。全文索引用戶MATCH AGAINST操做。

3.5. 高性能索引策略

3.5.1. 隔離列

3.5.1.1. 下面2條語句是不會使用索引的

1 where actor_id + 1 = 5
2  
3 where TO_DAYS(CURRENT_DATE) - TO_DAYS(date_col) <= 10

3.5.1.2. 下面2條是針對上面2條進行修改使用索引的

1 where actor_id = 4
2  
3 where date_col >= DATE_SUB(CURRENT_DATE, INTERVAL 10 DAY)

3.5.2. 前綴索引和索引選擇性

3.5.2.1. 當某一列特別長的狀況下,若是給所有長度建索引,那樣會增長索引的大小,而只作很短的前綴索引,雖然節約了空間,可是會增長選擇性。因此建前綴索引必須讓選擇性接近於所有長度的選擇性。

3.5.2.2. 平均來講前綴的選擇性能接近於0.31就能夠了。

1 select count(distinct city)/count(*) from db_name.table_name; selectcount(distinct left(city, 3))/count(*) AS sel3, count(distinct left(city, 4))/count(*) AS sel4 from db_name.table_name;

3.5.2.3. 只看平均選擇率在特殊狀況是不夠的,好比在數據分佈很是不均的狀況下。

3.5.2.4. Alter table db_name.table_name add KEY (city(7)) 這句就是隻對city這一列的前7個字母進行索引。

3.5.3. 彙集索引(clustered indexes)

3.5.3.1. 在InnoDB中彙集索引實際上在一樣的結構中保存了B-TREE索引和數據行。彙集的含義就是指實際的數據行和相關的鍵值保存在一塊兒。每一個表只能有一個彙集索引,由於不可能一次把行保存在兩個地方

3.5.3.2. 在MySQL中只有SolidDB和InnoDB是支持彙集索引的。

3.5.3.3. InnoDB是按照主鍵(Primary Key)列進行彙集。若是沒有定義主鍵,InnoDB會試着使用惟一的非空索引來代替。

3.5.3.4. 彙集索引有助於性能,可是也會致使嚴重的性能問題。總的來講它有以下的優勢:因爲把索引和數據都保存在一棵B-TREE中,所以查找數據會比一般的要快。

3.5.3.5. 彙集索引也有以下的缺點:會致使I/O密集,插入速度慢,更新索引列慢,插入新行會進行分頁,這樣致使佔用更多的磁盤空間。第二索引會比預想的大,第二索引訪問須要兩次索引查找。

3.5.3.6. 在InnoDB中是根據主鍵來進行順序插入的(這個跟InnoDB的數據佈局有關),因此主鍵最好是一個自增的值,與應用程序無關。

3.5.4. 覆蓋索引(covering indexes)

3.5.4.1. 包含(或者覆蓋)了全部知足查詢須要的數據的索引叫覆蓋索引。

3.5.4.2. 索引記錄一般遠小於全行大小,所以只讀取索引就能極大的減小數據訪問量(這個跟彙集索引的優勢同樣)

3.5.4.3. 索引是按照索引值進行排序的。

3.5.4.4. 大部分存儲引擎緩存索引比緩存數據更好(除了Falcon)。

3.5.4.5. 對於InnoDB覆蓋了查詢的第二索引在主鍵中避免了另一次索引查找。

3.5.4.6. 覆蓋索引必須保存它包含列的數據。

3.5.4.7. 當發起一個索引覆蓋的查詢,用EXPLAIN會在extra列顯示Using Index

3.5.5. 爲排序使用索引掃描

3.5.5.1. MySQL有兩種產生排序結果的方式,使用文件排序(Filesort)和掃描有序的索引。若是EXPLAIN的輸入type列的值是index。那說明MySQL會掃描索引。

3.5.5.2. 只有當索引的順序和order by字句中的順序徹底一致,而且全部列排序的方向(升序或降序)同樣才能夠。若是查詢鏈接多個表,只有在order by 字句的全部列引用的是第一個表才能夠。

3.5.5.3. 假設有以下表:

01 CREATE TABLE rental (
02  
03 …………
04  
05 PRIMARY KEY (rental_id),
06  
07 UNIQUE KEY rental_date (rental_date, inventory_id, customer_id),
08  
09 KEY idx_fx_inventory_id (inventory_id),
10  
11 KEY idx_fx_customer_id (customer_id),
12  
13 KEY idx_fx_staff_id (staff_id),
14  
15 …………
16  
17 )

3.5.5.3.1. 下面的這幾個語句是使用到索引的。

1 WHERE rental_date='2010-05-02' ORDER BY inventory_id desc;
2  
3 WHERE rental_date > '2010-05-02' ORDER BY rental_date, inventory_id; 在where字句是範圍的時候須要用最左前綴索引進行排序。

3.5.5.3.2. 而下面的都是不能使用索引進行排序的。

1 ... WHERE rental_date = '2005-05-25' ORDER BY inventory_id, staff_id;
2  
3 ... WHERE rental_date = '2005-05-25' ORDER BY customer_id;
4  
5 ... WHERE rental_date > '2005-05-25' ORDER BY inventory_id, customer_id; 緣由是第一句裏用到staff_id不在索引的列中,第二句是沒能造成索引的最左前綴,第三句是因爲在索引的第一列有範圍條件那MySQL就不會使用餘下的索引。還有多個where等於條件也不會使用索引排序。

3.5.6. 壓縮(前綴壓縮)索引

3.5.6.1. 這個只在MyISAM存儲引擎纔有效

3.5.6.2. 壓縮完成會塊的佔用空間會較小,可是某些操做會變慢。排序若是是順序沒有問題,可是倒序會很是的慢。

3.5.6.3. CPU的負載會比日常多幾倍。

3.5.6.4. 壓縮後索引大小也許只有原來的1/10

3.5.6.5. 能夠在使用CREATE TABLE命令的時候用PACK_KEYS來控制索引壓縮的方式。

3.5.7. 多餘和重複索引

3.5.7.1. 之前我一直以爲多建索引只是多佔用系統的磁盤大小,而看了這裏感受本身錯了,仍是須要注意不能隨便建索引。

3.5.7.2. MySQL自己不會提醒你建立了重複的索引。這樣MySQL不得不單獨維護每個索引,而且查詢優化器在優化查詢的時候會逐個考慮它們,這樣就會嚴重影響性能。

3.5.7.3. 看下面這個例子: CREATE TABLE test (ID INT NOT NULL PRIMARY KEY, UNIQUE(ID) INDEX(ID)); 我本身之前也有過這樣的寫法,其實這樣對於ID這一列建立了3個相同的索引。事實上MySQL利用索引實現了UNIQUE約束和PRIMARY KEY約束。因此一般不須要這樣作,除非你要在同一列上有不一樣的索引知足不一樣類型的查詢(好比KEY和FULLTEXT KEY)

3.5.7.4. 多餘索引。有幾個例子,好比創建了(A,B)2列這樣的索引,那就沒有必要建(A)索引了,可是仍是有必要創建(B)索引。一樣創建了(A,B)若是有須要仍是能夠創建(B,A)索引的。

3.5.7.5. 同時在大部分狀況下,爲了不它,應該擴展索已有索引,而不是添加新索引。

3.5.8. 索引和鎖定(Indexes and Locking)

3.5.8.1. 索引可讓查詢鎖定更少的行,由於在InnoDB只有在事務提交後纔會給行解鎖。

3.5.8.2. InnoDB進行行鎖定仍是有一些開銷的,鎖定超過須要的行會增長鎖競爭和減小併發。

3.5.8.3. 下面是一個例子

1 Set autocommit=0
2  
3 SELECT actor_id FROM table_name WHERE actor_id < 5 AND actor_id <> 1 FOR UPDATE;

該查詢實際以獨佔的方式鎖定了1到4行,InnoDB鎖定第一行的緣由是「從索引的開頭開始,而且提取全部行直到 actor_id < 5不成立」。若是沒有索引,MySQL無論是否須要行,都會進行全表掃描而且鎖定每一行(MySQL5.1中 READ COMMITTED事務隔離級別也有這個問題)。

InnoDB能在第二索引上放置共享(讀)鎖,可是獨佔(寫)鎖要求訪問主鍵。這消除了使用覆蓋索引的可能性,而且能致使SELECT FOR UPDATE比LOCK IN SHARE MODE或非鎖定要慢不少。

3.5.9. 索引策略總結

3.5.9.1. 爲任何耗時很長的查詢添加索引。

3.5.9.2. 在任何可能的地方,都要試着擴展索引,而不是新增索引。若是不知道查詢的分佈,就要儘量地使索引變得更有選擇性。

3.6. 索引實例研究

3.6.1. 這個主要是一個婚戀網站的維護,比較簡單。

3.7. 索引和表維護

3.7.1. 維護的目的是爲了減小碎片

3.7.2. 查找並修復表損壞

3.7.2.1. 這個通常MyISAM引擎會因爲服務器崩潰致使表損壞,可使用myisamchk來進行修復,修復的方法有不少。至於InnoDB我本身是歷來沒有碰到過表損壞的問題出現。

3.7.3. 更新索引統計

3.7.3.1. MySQL查詢優化器在決定如何使用索引的時候會調用兩個API,以瞭解索引如何分佈,一個是調用records_in_range(),它接受範圍結束點而且返回範圍內記錄的數量。第二個info(),它返回不一樣類型的數據。

3.7.3.2. 查詢優化器的開銷指標是查詢會訪問多少數據。若是統計永遠沒有產生,或者過期了,優化器就會作出很差的決定。能夠運行ANALYZE TABLE來解決這個問題。

3.7.3.3. InnoDB是在第一次打開表的時候利用隨機索引進行估計。InnoDB上的ANALYZE TABLE命令就使用了隨機索引,一樣,ANALYZE TABLE在InnoDB不是阻塞性的,而且開銷也不大,所以能夠在不大影響服務器的狀況下在線更新統計。

3.7.3.4. 咱們可使用SHOW INDEX FROM table_name命令來檢查索引的基數性。須要特別注意Cardinality列,它顯示了存儲引擎估計的索引中惟一值的數量。在MySQL5.0以及以上版本能夠經過INFORMATION_SCHEMA.STATISTICS表來獲得這些數據。

3.7.4. 減小索引和數據碎片

3.7.4.1. B-TREE索引會有它的造成機制,故會造成碎片並下降了性能。

3.7.4.2. 表的數據存儲也能變的碎片化。有兩種碎片分別爲

3.7.4.2.1. 行碎片(row fragmentation):當行存儲在多個地方的多個片斷中就會產生這種碎片。

3.7.4.2.2. 內部行碎片(intra-row fragmentation):當邏輯上順序的頁面或行在磁盤上沒有被順序的存儲時就會產生這種碎片。它影響了全表掃描和彙集索引這樣的操做。

3.7.4.3. 爲了消除數據碎片能夠運行OPTIMIZE TABLE來轉儲或者從新加載數據。對於不支持OPTIMIZE TABLE的存儲引擎能夠用ALTER TABLE從新創建表。

3.8. 範式化和非範式化(Normalization and Denormalization)

3.8.1. 這個屬於數據庫設計的範疇,第一範式,第二範式,第三範式,BCNF

3.8.2. 範式化架構的利弊:這個基本上就是BCNF的好處,當寫入負載大的時候,使用範式化架構師比較好的。它的優缺點以下:

3.8.2.1. 範式化更新比非範式化更新快

3.8.2.2. 當數據被不多的範式化後,就不多或者很是少的重複數據,所以改動的數據會變少

3.8.2.3. 範式化表一般較小,容易被裝載到內存中而且性能更好

3.8.2.4. 因爲缺乏冗餘數據,在取得數據的時候會較少採用DISTINCT或者GROUP BY,這樣有時候要查數據會鏈接多個表。這樣就很是難創建索引策略。

3.8.3. 非範式化架構的利弊:非範式化架構因爲全部數據都在一個表裏面,避免了鏈接,因此性能不錯。因爲不須要鏈接表,對於大多數查詢,甚至不使用索引,這會比鏈接快的多,由於它避免了隨機I/0。因此非範式化架構用在查詢很是多的狀況是很是好的。

3.8.4. 結合範式化和非範式化:在真實環境中不多會所有範式化和非範式化,一般都是結合者2中方案的,也許是採用部分範式化,緩存表以及其它技巧。

3.8.4.1. 非範式化最多見的技巧是複製,緩存,把一個表中部分列選到另一個表中,在MySQL5.0以及以上版本,可使用觸發器來更新緩存的值。

3.8.5. 緩存和彙總表

3.8.5.1. 有時需構造徹底不一樣的彙總或者緩存表,爲獲取數據進行特別的調優。

3.8.5.2. 緩存表的含義就是可以容易的得到數據,若是這樣更慢的話就會經過scheme來獲取數據。緩存表對於優化搜索和獲取數據的查詢是有用的。

3.8.5.3. 彙總表的意思就是咱們能夠經過GROUP BY來彙總查詢數據。

3.8.5.4. 當使用緩存和彙總表的時候,你不得不決定是否要進行實施數據維護或週期性重建。不過能夠經過使用影子表來解決這個問題。

1 DROP TABLE IF EXISTS my_summary_new, my_summary_old;
2  
3 CREATE TABLE my_summary_new LIKE my_summary;
4  
5 RENAME TABLE my_summary TO my_summary_old, my_summary_new TO my_summary;

3.9. 加速ALTER TABLE

3.9.1. 這個是有技巧的,不是全部的ALTER TABLE操做都會致使重建表。

下面這個例子是想把這一列默認值變爲5,可是第一句它實際上會執行1000次讀取和1000次插入,這樣就會很是慢。而第二句只修改默認值,列的默認值實際保存在表的.frm文件中,因此能夠不不接觸表而更改它。任何MODIFY COLUMN操做都會致使表重建。

1 ALTER TABLE table_name MODIFY COLUMN column_name TINYINT(3) NOT NULL DEFAULT 5;
2  
3 ALTER TABLE table_name ALTER COLUMN column_name SET DEFAULT 5;

3.9.2. 若是願意承擔必定風險,可讓MySQL作其它類型的修改時也不重建表。

3.9.2.1. 如下操做能夠不重建表:移除(但不是添加)列的AUTO_INCREMENT屬性

3.9.2.2. 添加,移除或更改ENUM和SET常量。

3.9.2.3. 官方說這是不受支持的技巧,使用後果自負。

2010-05-05

——————————————————————————————————————————

架構優化和索引 
  1.數據類型 
  浮點:FLOAT和DOUBLE使用的是平臺的浮點數,分別佔用4字節和8字節。DECIMAL是表示精確的小數。 
  字符串:VARCHAR(255)表示最大長度255字節,對於uft-8編碼,將不知道能保留多少字符,中文是3字節,字母1字節,這一點要注意。另外設高上限,由於內存的分配mysql是固定長度的,這樣可能會致使內存的浪費。另外會佔用額外的1或2字節來記錄長度。該類型的問題是修改字段會改變行的大小,可能引發分頁。帶來額外的工做。但這個問題不是很大,由於預寫式日誌的存在。CHAR優點比較小了,對於固定長度的,卻是能夠用一下,問題是會丟失字符串後面的空格。 
  時間:DATETIME實際上是封裝爲YYYYMMDDHHMMSS的整數,8字節,至關與bigint,精確到秒 
  TIMESTAMP:從1970年1月1日到如今的秒數,佔用4字節 
  BIT和SET,都是位集,要使用的時候研究一下跟使用INT有什麼區別。 
  IP地址用無符號整數保存,mysql提供了INET_ATION()和INET_NTOA()實現轉化,我這個項目恰好能用上。 
  2.索引 
  大部分存儲引擎都是用B-Tree(一個節點不少分支,深度同樣,爲低速IO和按序查找而優化的)來實現。能夠匹配全名,匹配最左前綴(找到第一層小於前綴的風格點和第一個大於前綴的風格點,遞歸)匹配範圍值(找到第一層最大小於下屆的和最小大於上界的,遞歸),like也能夠當作一個範圍值。好比 like ("123%" > "123" and <"124"). 這三種狀況能夠變成兩種。能夠組合好比,Key(a,b,c),索引中也是按順序存儲的, a='A'and b like 'B%',至關於abc like "AB%"能夠索引 a='A' and b>"B1" and B<"B2" 至關於abc >"AB1" abc<"AB2" 
like也能夠當作一個範圍值。對於多列索引,順序很重要,
若是前一列使用了範圍條件,後一列的索引就不能使用了,由於mysql不知道這個範圍裏包括了多少值,沒法組合出索引,因此這個時候In就特別有用,它能夠是mysql知道如何組合條件使用索引。 
如何select * form employee where gender>=0 and gender <=1 and birthday="1987-08-19",該表上有索引key(gender,birthday),這種寫法會掃描全表, 
而select * form employee where gender in (0,1) and birthday="1987-08-19"就會使用索引。這個其實優化器還可能優化,而若是gender是字符串,就無從優化了。 
  Hash索引不能處理範圍檢索。能夠用索引一個保存hash值的列來優化索引的性能。 
  全文索引:經過分詞,創建一個相似B-Tree的結構。 
  3.索引優化: 
  (1).有索引的列要單獨出如今表達式的一邊,這是最簡單的,第二,表達式也最好不要有其餘的變量,好比CURRENT_DATE,這個會影響緩存。(這個也許新的版本能優化) 
  (2).前綴索引,把字符串的前面一部分用作索引,Text字段會很須要,要關注選擇性和平均性兩個指標,語法alert table table_name add key (column_name(N)). 

from:http://zizipo.iteye.com/blog/1012975

http://zizipo.iteye.com/blog/1011641

相關文章
相關標籤/搜索