mysql優化綜合(轉)

一 OS系統優化

1 內核優化

1)IO調度優化

調整Linux默認的IO調度算法.

IO調度器的總體目標是希望讓磁頭能夠總是往一個方向移動,移動到底了再往反方向走,這恰恰就是現實生活中的電梯模型,所以IO調度器也被叫做電梯 (elevator),而相應的算法也就被叫做電梯算法.而Linux中IO調度的電梯算法有好幾種,一個叫做as(Anticipatory),一個叫做 cfq(Complete Fairness Queueing),一個叫做deadline,還有一個叫做noop(No Operation).

IO對數據庫的影響較大,linux默認的IO調度算法為cfq,需要修改為deadline,如果是SSD或者PCIe-SSD設備,需要修改為noop,可以使用下面兩種修改方式。

1.查看默認的IO算法
dmesg | grep -i scheduler
2.在線動態修改,重啟失效。
echo "deadline" > /sys/block/vda/queue/scheduler
3.修改/etc/grub.conf,永久生效。

修改/etc/grub.conf配置文件,在kernel那行增加一個配置,例如:

elevator=deadline

IO調度算法詳解

NOOP

NOOP算法的全寫為No Operation。該算法實現了最最簡單的FIFO隊列,所有IO請求大致按照先來后到的順序進行操作。之所以說“大致”,原因是NOOP在FIFO的基礎上還做了相鄰IO請求的合并,并不是完完全全按照先進先出的規則滿足IO請求。

假設有如下的io請求序列:

100,500,101,10,56,1000

NOOP將會按照如下順序滿足:

100(101),500,10,56,1000
CFQ

CFQ算法的全寫為Completely Fair Queuing。該算法的特點是按照IO請求的地址進行排序,而不是按照先來后到的順序來進行響應。

假設有如下的io請求序列:

100,500,101,10,56,1000

CFQ將會按照如下順序滿足:

100,101,500,1000,10,56

在傳統的SAS盤上,磁盤尋道花去了絕大多數的IO響應時間。CFQ的出發點是對IO地址進行排序,以盡量少的磁盤旋轉次數來滿足盡可能多的IO請求。在CFQ算法下,SAS盤的吞吐量大大提高了。但是相比于NOOP的缺點是,先來的IO請求并不一定能被滿足,可能會出現餓死的情況。

DEADLINE

DEADLINE在CFQ的基礎上,解決了IO請求餓死的極端情況。除了CFQ本身具有的IO排序隊列之外,DEADLINE額外分別為讀IO和寫IO提供了FIFO隊列。讀FIFO隊列的最大等待時間為500ms,寫FIFO隊列的最大等待時間為5s。FIFO隊列內的IO請求優先級要比CFQ隊列中的高,,而讀FIFO隊列的優先級又比寫FIFO隊列的優先級高。

優先級可以表示如下:

FIFO(Read) > FIFO(Write) > CFQ
ANTICIPATORY

CFQ和DEADLINE考慮的焦點在于滿足零散IO請求上。對于連續的IO請求,比如順序讀,并沒有做優化。為了滿足隨機IO和順序IO混合的場景,Linux還支持ANTICIPATORY調度算法。ANTICIPATORY的在DEADLINE的基礎上,為每個讀IO都設置了6ms的等待時間窗口。如果在這6ms內OS收到了相鄰位置的讀IO請求,就可以立即滿足。

總結
noop:一般用于ssd,因為SSD速度本身已經夠快不需要再進行調度。
deadline:一般用于數據庫
cfq:為所有進程分配等量的帶寬,適合于桌面多任務及多媒體應用,默認IO調度器

2) vm.swappiness=0

vm.swappiness是操作系統控制物理內存交換出去的策略。它允許的值是一個百分比的值,最小為0,最大運行100,該值默認為60。vm.swappiness設置為0表示盡量少swap,100表示盡量將inactive的內存頁交換出去。

具體的說:當內存基本用滿的時候,系統會根據這個參數來判斷是把內存中很少用到的inactive內存交換出去,還是釋放數據的cache。cache中緩存著從磁盤讀出來的數據,根據程序的局部性原理,這些數據有可能在接下來又要被讀 取;inactive內存顧名思義,就是那些被應用程序映射著,但是長時間不用的內存。

我們可以利用vmstat看到inactive的內存的數量:

vmstat -an 1
#通過/proc/meminfo 你可以看到更詳細的信息:
cat /proc/meminfo | grep -i inact

Linux中,內存可能處于三種狀態:

free,active和inactive。Linux Kernel在內部維護了很多LRU列表用來管理內存,比如LRU_INACTIVE_ANON, LRU_ACTIVE_ANON, LRU_INACTIVE_FILE , LRU_ACTIVE_FILE, LRU_UNEVICTABLE。其中LRU_INACTIVE_ANON, LRU_ACTIVE_ANON用來管理匿名頁,LRU_INACTIVE_FILE , LRU_ACTIVE_FILE用來管理page caches頁緩存。系統內核會根據內存頁的訪問情況,不定時的將活躍active內存被移到inactive列表中,這些inactive的內存可以被交換到swap中去。

一般來說,MySQL,特別是InnoDB管理內存緩存,它占用的內存比較多,不經常訪問的內存也會不少,這些內存如果被Linux錯誤的交換出去了,將浪費很多CPU和IO資源。 InnoDB自己管理緩存,cache的文件數據來說占用了內存,對InnoDB幾乎沒有任何好處。所以,我們在MySQL的服務器上最好設置vm.swappiness=0。

設置vm.swappiness:

echo vm.swappiness = 0  >> /etc/sysctl.conf
#使其保存生效
sysctl -p

2 文件系統優化

文件描述符

內核利用文件描述符來訪問文件。文件描述符是非負整數。打開現存文件或新建文件時,內核會返回一個文件描述符。讀寫文件也需要使用文件描述符來指定待讀寫的文件,高并發場景需要把文件描述符調大

臨時調整:

ulimit -SHn 65536

永久調整

vim /etc/security/limits.conf
*  soft nofile 65536
*  hard nofile 65536
*  

優化文件系統掛載參數

文件系統掛載參數是在/etc/fstab文件中修改,重啟時候生效,noatime不記錄訪問時間,nodiratime不記錄目錄的訪問時間。

對于ext3, ext4和 reiserfs文件系統可以在mount時指定barrier=0;對于xfs可以指定nobarrier選項。

很多文件系統會在數據提交時強制底層設備刷新cache,避免數據丟失,稱之為write barriers。

但是,其實我們數據庫服務器底層存儲設備要么采用RAID卡,RAID卡本身的電池可以掉電保護;要么采用Flash卡,它也有自我保 護機制,保證數據不會丟失。所以我們可以安全的使用nobarrier掛載文件系統。

如果業務量很大的話,使用xfs文件系統,在CentOS6.4之后的系統xfs性能比ext4好;

3 關閉NUMA

numa

非一致存儲訪問結構 (NUMA : Non-Uniform Memory Access)它和對稱多處理器結構 (SMP : Symmetric Multi-Processor) 是對應的。簡單的隊別如下:


SMP訪問內存的都是代價都是一樣的;但是在NUMA架構下,本地內存的訪問和非本地內存的訪問代價是不一樣的。

你可以指定內存在本地分配,在某幾個CPU節點分配或者輪詢分配。除非是設置為--interleave=nodes輪詢分配方式,即內存可以在任意NUMA節點上分配這種方式以外。其他的方式就算其他NUMA節點上還有內存剩余,Linux也不會把剩余的內存分配給這個進程,而是采用SWAP的方式來獲得內存。有經驗的系統管理員或者DBA都知道SWAP導致的數據庫性能 下降有多么坑爹,所以最簡單的方法,還是關閉掉這個特性。

關閉特性的方法,分別有:

1.可以從BIOS,操作系統,啟動進程時臨時關閉這個特性。由于各種BIOS類型的區別,如何關閉NUMA千差萬別,我們這里就不具體展示怎么設置了。

2.在操作系統中關閉,可以直接在/etc/grub.conf的kernel行最后添加numa=off,如下所示:

kernel /vmlinuz-2.6.32-220.el6.x86_64 ... numa=off

3.啟動MySQL的時候,關閉NUMA特性:

numactl --interleave=all mysqld 或者numactl –cpunodebind=node –localalloc mysqld 

另外可以設置 vm.zone_reclaim_mode=0盡量回收內存。

MySQL5.7提供了MUMA的支持,--innodb-numa-interleave參數,如果沒有該參數,則可以

二、mysql優化

1)配置參數優化

query_cache_size

query cache是一個眾所周知的瓶頸,甚至在并發并不多時也如此。 最好是一開始就停用,設置query_cache_size = 0,并利用其他方法加速查詢:優化索引、增加拷貝分散負載或者啟用額外的緩存(比如memcache或redis)。

如果已經啟用了query cache并且還沒有發現任何問題,query cache可能有用。如果想停用它,那就得小心了。

max_connections

max_connection值被設高了(例如1000或更高)之后一個主要缺陷是當服務器運行1000個或更高的活動事務時會變的沒有響應。在應用程序里使用連接池或者在MySQL里使用進程池有助于解決這一問題。

back_log

要求 mysql 能有的連接數量。當主要mysql線程在一個很短時間內得到非常多的連接請求,這就起作用,然后主線程花些時間檢查連接并且啟動一個新線程。back_log指明在mysql暫時停止回答新請求之前的短時間內多少個請求可以被存在堆棧中。只有如果期望在一個短時間內有很多連接,需要增加它,換句話說,該值對到來的tcp/ip連接的偵聽隊列的大小。

Innodb配置

innodb_buffer_pool_size

緩沖池緩存數據和索引:這個值越大越好,這能保證你在大多數的讀取操作時使用的是內存而不是硬盤。典型的值是5-6GB(8GB內存),20-25GB(32GB內存),100-120GB(128GB內存)。

innodb_flush_log_at_trx_commit

默認值為1,表示InnoDB完全支持ACID特性。當關注點是數據安全的時候這個值是最合適的,比如在一個主節點上。但是對于磁盤(讀寫)速度較慢的系統,它會帶來很巨大的開銷,因為每次將改變flush到redo日志都需要額外的fsyncs。如果值為0速度就更快了,但在系統崩潰時可能丟失一些數據, 所以一遍只適用于備份節點。

innodb_log_file_size

這是redo日志的大小。redo日志被用于確保寫操作快速而可靠并且在崩潰時恢復。一直到MySQL 5.1,它都難于調整,因為一方面你想讓它更大來提高性能,另一方面你想讓它更小來使得崩潰后更快恢復。幸運的是從MySQL 5.5之后,崩潰恢復的性能的到了很大提升,這樣你就可以同時擁有較高的寫入性能和崩潰恢復性能了。一直到MySQL 5.5,redo日志的總尺寸被限定在4GB(默認可以有2個log文件)。這在MySQL 5.6里被提高。

innodb_log_buffer_size

這項配置決定了為尚未執行的事務分配的緩存。但是如果事務中包含有二進制大對象或者大文本字段的話,看Innodb_log_waits狀態變量,如果它不是0,增加innodb_log_buffer_size。

innodb_file_per_table

這項設置告知InnoDB是否需要將所有表的數據和索引存放在共享表空間里(innodb_file_per_table = OFF)或者為每張表的數據單獨放在一個.ibd文件(innodb_file_per_table = ON)。

每張表一個文件允許你在drop、truncate或者rebuild表時回收磁盤空間。這對于一些高級特性也是有必要的,比如數據壓縮。但是它不會帶來任何性能收益。MySQL 5.6中,這個屬性默認值是ON。

innodb_flush_method

這項配置決定了數據和日志寫入硬盤的方式。一般來說,如果你有硬件RAID控制器,并且其獨立緩存采用write-back機制,并有著電池斷電保護,那么應該設置配置為O_DIRECT;否則,大多數情況下應將其設為fdatasync(默認值)。sysbench是一個可以幫助你決定這個選項的好工具。

其他設置

log_bin

如果你想讓數據庫服務器充當主節點的備份節點,那么開啟二進制日志是必須的。如果這么做了之后,還別忘了設置server_id為一個唯一的值。就算只有一個服務器,如果你想做基于時間點的數據恢復,這(開啟二進制日志)也是很有用的:從你最近的備份中恢復(全量備份),并應用二進制日志中的修改(增量備份)。二進制日志一旦創建就將永久保存。所以如果你不想讓磁盤空間耗盡,你可以用 PURGE BINARY LOGS 來清除舊文件,或者設置 expire_logs_days 來指定過多少天日志將被自動清除。

記錄二進制日志不是沒有開銷的,所以如果你在一個非主節點的復制節點上不需要它的話,那么建議關閉這個選項。

skip_name_resolve

當客戶端連接數據庫服務器時,服務器會進行主機名解析,并且當DNS很慢時,建立連接也會很慢。因此建議在啟動服務器時關閉skip_name_resolve選項而不進行DNS查找。唯一的局限是之后GRANT語句中只能使用IP地址了,因此在添加這項設置到一個已有系統中必須格外小心。

max_allowed_packet

接受的數據包大小;增加該變量的值十分安全,這是因為僅當需要時才會分配額外內存。例如,僅當你發出長查詢或MySQLd必須返回大的結果行時MySQLd才會分配更多內存。該變量之所以取較小默認值是一種預防措施,以捕獲客戶端和服務器之間的錯誤信息包,并確保不會因偶然使用大的信息包而導致內存溢出

table_open_cache

MySQL每打開一個表,都會讀入一些數據到table_open_cache緩存中,當MySQL在這個緩存中找不到相應信息時,才會去磁盤上讀取。假定系統有200個并發連接,則需將此參數設置為200*N(N為每個連接所需的文件描述符數目);當把table_open_cache設置為很大時,如果系統處理不了那么多文件描述符,那么就會出現客戶端失效,連接不上。

character-set

如果是單一語言使用簡單的character set例如latin1。盡量少用Utf-8,utf-8占用空間較多

2)存儲引擎優化

MyISAM

MyISAM管理非事務表。它提供高速存儲和檢索,以及全文搜索能力。

MyISAM特性:

不支持事務,宕機會破壞表
使用較小的內存和磁盤空間
基于表的鎖,并發更新數據會出現嚴重性能問題
MySQL只緩存Index,數據由OS緩存

使用場景:

日志系統
只讀或者絕大部分是讀操作的應用
全表掃描
批量導入數據
沒有事務的低并發讀/寫

MyISAM優化要點:

1.聲明列為NOT NULL,可以減少磁盤存儲。

2.使用optimize table做碎片整理,回收空閑空間。注意僅僅在非常大的數據變化后運行。

3.刪除/更新/插入大量數據的時候禁止使用index。使用ALTER TABLE t DISABLE KEYS

4.設置myisam_max_[extra]_sort_file_size足夠大,可以顯著提高repair table的速度。

InnoDB

InnoDB給MySQL提供了具有提交,回滾和崩潰恢復能力的事務安全(ACID兼容)存儲引擎。

InnoDB提供行級鎖,并且也在SELECT語句提供一個Oracle風格一致的非鎖定讀。這些特色增加了多用戶部署和性能。

沒有在InnoDB中擴大鎖定的需要,因為在InnoDB中行鎖,適合非常小的空間。InnoDB也支持FOREIGN KEY約束。

在SQL查詢中,你可以自由地將InnoDB類型的表與其它MySQL的表的類型混合起來,甚至在同一個查詢中也可以混合。

InnoDB是為在處理巨大數據量時獲得最大性能而設計的。它的CPU使用效率非常高。

InnoDB存儲引擎已經完全與MySQL服務器整合,InnoDB存儲引擎為在內存中緩存數據和索引而維持它自己的緩沖池。 InnoDB存儲它的表&索引在一個表空間中,表空間可以包含數個文件(或原始磁盤分區)。這與MyISAM表不同,比如在MyISAM表中每個表被存在分離的文件中。InnoDB 表可以是任何大小,即使在文件尺寸被限制為2GB的操作系統上。

InnoDB特性:

支持事務,ACID,外鍵。
Row level locks。
支持不同的隔離級別。
和MyISAM相比需要較多的內存和磁盤空間。
沒有鍵壓縮。
數據和索引都緩存在內存hash表中。

使用場景:

需要事務的應用。
高并發的應用。
自動恢復。
較快速的基于主鍵的操作。

InnoDB優化要點:

1.盡量使用short,integer的主鍵。

2.Load/Insert數據時按主鍵順序。如果數據沒有按主鍵排序,先排序然后再進行數據庫操作。

3.在Load數據是為設置SET UNIQUE_CHECKS=0,SET FOREIGN_KEY_CHECKS=0,可以避免外鍵和唯一性約束檢查的開銷。

4.使用prefix keys。因為InnoDB沒有key壓縮功能

三、MySQL性能優化最佳實踐

原文http://www.searchdatabase.com.cn/showcontent_38045.htm

1. 為查詢緩存優化你的查詢

大多數的MySQL服務器都開啟了查詢緩存。這是提高性最有效的方法之一,而且這是被MySQL的數據庫引擎處理的。當有很多相同的查詢被執行了多次的時候,這些查詢結果會被放到一個緩存中,這樣,后續的相同的查詢就不用操作表而直接訪問緩存結果了。

這里最主要的問題是,對于程序員來說,這個事情是很容易被忽略的。因為,我們某些查詢語句會讓MySQL不使用緩存。請看下面的示例:

//  查詢緩存不開啟
$r  =  mysql_query("SELECT username FROM user WHERE signup_date >= CURDATE()");
//  開啟查詢緩存
$today  =  date("Y-m-d");
$r  =  mysql_query("SELECT username FROM user WHERE signup_date >= '$today'");

上面兩條SQL語句的差別就是 CURDATE() ,MySQL的查詢緩存對這個函數不起作用。

所以,像 NOW() 和 RAND() 或是其它的諸如此類的SQL函數都不會開啟查詢緩存,因為這些函數的返回是會不定的易變的。

所以,你所需要的就是用一個變量來代替MySQL的函數,從而開啟緩存。

2. EXPLAIN SELECT 查詢

使用 EXPLAIN 關鍵字可以讓你知道MySQL是如何處理你的SQL語句的。這可以幫你分析你的查詢語句或是表結構的性能瓶頸。

EXPLAIN 的查詢結果還會告訴你你的索引主鍵被如何利用的,你的數據表是如何被搜索和排序的……等等,等等。

挑一個你的SELECT語句(推薦挑選那個最復雜的,有多表聯接的),把關鍵字EXPLAIN加到前面。你可以使用phpmyadmin來做這個事。然后,你會看到一張表格。下面的這個示例中,我們忘記加上了group_id索引,并且有表聯接:

當我們為 group_id 字段加上索引后

我們可以看到,前一個結果顯示搜索了 7883 行,而后一個只是搜索了兩個表的 9 和 16 行。查看rows列可以讓我們找到潛在的性能問題。

3. 當只要一行數據時使用 LIMIT 1

當你查詢表的有些時候,你已經知道結果只會有一條結果,但因為你可能需要去fetch游標,或是你也許會去檢查返回的記錄數。

在這種情況下,加上 LIMIT 1 可以增加性能。這樣一樣,MySQL數據庫引擎會在找到一條數據后停止搜索,而不是繼續往后查少下一條符合記錄的數據。

下面的示例,只是為了找一下是否有“中國”的用戶,很明顯,后面的會比前面的更有效率。(請注意,第一條中是Select *,第二條是Select 1)

//  沒有效率的:
$r  =  mysql_query("SELECT * FROM user WHERE country = 'China'");
if  (mysql_num_rows($r)  >  0)  {
    //  ...
}
//  有效率的:
$r  =  mysql_query("SELECT 1 FROM user WHERE country = 'China' LIMIT 1");
if  (mysql_num_rows($r)  >  0)  {
    //  ...
}

4. 為搜索字段建索引

索引并不一定就是給主鍵或是唯一的字段。如果在你的表中,有某個字段你總要會經常用來做搜索,那么,請為其建立索引吧。

從上圖你可以看到那個搜索字串 “last_name LIKE ‘a%’”,一個是建了索引,一個是沒有索引,性能差了4倍左右。

另外,你應該也需要知道什么樣的搜索是不能使用正常的索引的。例如,當你需要在一篇大的文章中搜索一個詞時,如: “WHERE post_content LIKE ‘%apple%’”,索引可能是沒有意義的。你可能需要使用MySQL全文索引 或是自己做一個索引(比如說:搜索關鍵詞或是Tag什么的)

5. 在Join表的時候使用相當類型的例,并將其索引

如果你的應用程序有很多 JOIN 查詢,你應該確認兩個表中Join的字段是被建過索引的。這樣,MySQL內部會啟動為你優化Join的SQL語句的機制。

而且,這些被用來Join的字段,應該是相同的類型的。例如:如果你要把 DECIMAL 字段和一個 INT 字段Join在一起,MySQL就無法使用它們的索引。對于那些STRING類型,還需要有相同的字符集才行。(兩個表的字符集有可能不一樣)

//  在state中查找company
$r  =  mysql_query("SELECT company_name FROM users
    LEFT JOIN companies ON (users.state = companies.state)
    WHERE users.id = $user_id");
//  兩個  state  字段應該是被建過索引的,而且應該是相當的類型,相同的字符集。

6. 千萬不要 ORDER BY RAND()

想打亂返回的數據行?隨機挑一個數據?真不知道誰發明了這種用法,但很多新手很喜歡這樣用。但你確不了解這樣做有多么可怕的性能問題。

如果你真的想把返回的數據行打亂了,你有N種方法可以達到這個目的。這樣使用只讓你的數據庫的性能呈指數級的下降。這里的問題是:MySQL會不得不去執行RAND()函數(很耗CPU時間),而且這是為了每一行記錄去記行,然后再對其排序。就算是你用了Limit 1也無濟于事(因為要排序)

下面的示例是隨機挑一條記錄

//  千萬不要這樣做:
$r  =  mysql_query("SELECT username FROM user ORDER BY RAND() LIMIT 1");
//  這要會更好:
$r  =  mysql_query("SELECT count(*) FROM user");
$d  =  mysql_fetch_row($r);
$rand  =  mt_rand(0,$d[0]  -  1);
$r  =  mysql_query("SELECT username FROM user LIMIT $rand, 1");

7. 避免 SELECT *

從數據庫里讀出越多的數據,那么查詢就會變得越慢。并且,如果你的數據庫服務器和WEB服務器是兩臺獨立的服務器的話,這還會增加網絡傳輸的負載。所以,你應該養成一個需要什么就取什么的好的習慣。

//  不推薦
$r  =  mysql_query("SELECT * FROM user WHERE user_id = 1");
$d  =  mysql_fetch_assoc($r);
echo  "Welcome {$d['username']}";
//  推薦
$r  =  mysql_query("SELECT username FROM user WHERE user_id = 1");
$d  =  mysql_fetch_assoc($r);
echo  "Welcome {$d['username']}";

8. 永遠為每張表設置一個ID

我們應該為數據庫里的每張表都設置一個ID做為其主鍵,而且最好的是一個INT型的(推薦使用UNSIGNED),并設置上自動增加的AUTO_INCREMENT標志。

就算是你 users 表有一個主鍵叫 “email”的字段,你也別讓它成為主鍵。使用 VARCHAR 類型來當主鍵會使用得性能下降。另外,在你的程序中,你應該使用表的ID來構造你的數據結構。

而且,在MySQL數據引擎下,還有一些操作需要使用主鍵,在這些情況下,主鍵的性能和設置變得非常重要,比如,集群,分區……

在這里,只有一個情況是例外,那就是“關聯表”的“外鍵”,也就是說,這個表的主鍵,通過若干個別的表的主鍵構成。我們把這個情況叫做“外鍵”。比如:有一個“學生表”有學生的ID,有一個“課程表”有課程ID,那么,“成績表”就是“關聯表”了,其關聯了學生表和課程表,在成績表中,學生ID和課程ID叫“外鍵”其共同組成主鍵。

9. 使用 ENUM 而不是 VARCHAR

ENUM類型是非常快和緊湊的。在實際上,其保存的是 TINYINT,但其外表上顯示為字符串。這樣一來,用這個字段來做一些選項列表變得相當的完美。

如果你有一個字段,比如“性別”,“國家”,“民族”,“狀態”或“部門”,你知道這些字段的取值是有限而且固定的,那么,你應該使用 ENUM 而不是 VARCHAR。

MySQL也有一個“建議”(見第十條)告訴你怎么去重新組織你的表結構。當你有一個 VARCHAR 字段時,這個建議會告訴你把其改成 ENUM 類型。使用 PROCEDURE ANALYSE() 你可以得到相關的建議。

10. 從 PROCEDURE ANALYSE() 取得建議

PROCEDURE ANALYSE() 會讓 MySQL 幫你去分析你的字段和其實際的數據,并會給你一些有用的建議。只有表中有實際的數據,這些建議才會變得有用,因為要做一些大的決定是需要有數據作為基礎的。

例如,如果你創建了一個 INT 字段作為你的主鍵,然而并沒有太多的數據,那么,PROCEDURE ANALYSE()會建議你把這個字段的類型改成 MEDIUMINT 。或是你使用了一個 VARCHAR 字段,因為數據不多,你可能會得到一個讓你把它改成 ENUM 的建議。這些建議,都是可能因為數據不夠多,所以決策做得就不夠準。

一定要注意,這些只是建議,只有當你的表里的數據越來越多時,這些建議才會變得準確。一定要記住,你才是最終做決定的人。

11. 盡可能的使用 NOT NULL

除非你有一個很特別的原因去使用 NULL 值,你應該總是讓你的字段保持 NOT NULL。這看起來好像有點爭議,請往下看。

首先,問問你自己“Empty”和“NULL”有多大的區別(如果是INT,那就是0和NULL)?如果你覺得它們之間沒有什么區別,那么你就不要使用NULL。(你知道嗎?在 Oracle 里,NULL 和 Empty 的字符串是一樣的!)

不要以為 NULL 不需要空間,其需要額外的空間,并且,在你進行比較的時候,你的程序會更復雜。 當然,這里并不是說你就不能使用NULL了,現實情況是很復雜的,依然會有些情況下,你需要使用NULL值。

12. 無緩沖的查詢

正常的情況下,當你在當你在你的腳本中執行一個SQL語句的時候,你的程序會停在那里直到沒這個SQL語句返回,然后你的程序再往下繼續執行。你可以使用無緩沖查詢來改變這個行為。

mysql_unbuffered_query()發送一個SQL語句到MySQL而并不像mysql_query()一樣去自動fethch和緩存結果。這會相當節約很多可觀的內存,尤其是那些會產生大量結果的查詢語句,并且,你不需要等到所有的結果都返回,只需要第一行數據返回的時候,你就可以開始馬上開始工作于查詢結果了。

然而,這會有一些限制。因為你要么把所有行都讀走,或是你要在進行下一次的查詢前調用 mysql_free_result() 清除結果。而且, mysql_num_rows()mysql_data_seek()將無法使用。所以,是否使用無緩沖的查詢你需要仔細考慮。

13. 把IP地址存成 UNSIGNED INT

很多程序員都會創建一個 VARCHAR(15) 字段來存放字符串形式的IP而不是整形的IP。如果你用整形來存放,只需要4個字節,并且你可以有定長的字段。而且,這會為你帶來查詢上的優勢,尤其是當你需要使用這樣的WHERE條件:IP between ip1 and ip2。

我們必需要使用UNSIGNED INT,因為 IP地址會使用整個32位的無符號整形。

而你的查詢,你可以使用 INET_ATON() 來把一個字符串IP轉成一個整形,并使用 INET_NTOA() 把一個整形轉成一個字符串IP。在PHP中,也有這樣的函數 ip2long() 和 long2ip()。

$r  =  "UPDATE users SET ip = INET_ATON('{$_SERVER['REMOTE_ADDR']}') WHERE user_id = $user_id";

14. 固定長度的表會更快

如果表中的所有字段都是“固定長度”的,整個表會被認為是 “static” 或 “fixed-length”。 例如,表中沒有如下類型的字段: VARCHAR,TEXT,BLOB。只要你包括了其中一個這些字段,那么這個表就不是“固定長度靜態表”了,這樣,MySQL 引擎會用另一種方法來處理。

固定長度的表會提高性能,因為MySQL搜尋得會更快一些,因為這些固定的長度是很容易計算下一個數據的偏移量的,所以讀取的自然也會很快。而如果字段不是定長的,那么,每一次要找下一條的話,需要程序找到主鍵。

并且,固定長度的表也更容易被緩存和重建。不過,唯一的副作用是,固定長度的字段會浪費一些空間,因為定長的字段無論你用不用,他都是要分配那么多的空間。

使用“垂直分割”技術(見下一條),你可以分割你的表成為兩個一個是定長的,一個則是不定長的。

15. 垂直分割

“垂直分割”是一種把數據庫中的表按列變成幾張表的方法,這樣可以降低表的復雜度和字段的數目,從而達到優化的目的。(以前,在銀行做過項目,見過一張表有100多個字段,很恐怖)

示例一:在Users表中有一個字段是家庭地址,這個字段是可選字段,相比起,而且你在數據庫操作的時候除了個人信息外,你并不需要經常讀取或是改寫這個字段。那么,為什么不把他放到另外一張表中呢? 這樣會讓你的表有更好的性能,大家想想是不是,大量的時候,我對于用戶表來說,只有用戶ID,用戶名,口令,用戶角色等會被經常使用。小一點的表總是會有好的性能。

示例二: 你有一個叫 “last_login” 的字段,它會在每次用戶登錄時被更新。但是,每次更新時會導致該表的查詢緩存被清空。所以,你可以把這個字段放到另一個表中,這樣就不會影響你對用戶ID,用戶名,用戶角色的不停地讀取了,因為查詢緩存會幫你增加很多性能。

另外,你需要注意的是,這些被分出去的字段所形成的表,你不會經常性地去Join他們,不然的話,這樣的性能會比不分割時還要差,而且,會是極數級的下降。

16. 拆分大的 DELETE 或 INSERT 語句

如果你需要在一個在線的網站上去執行一個大的 DELETE 或 INSERT 查詢,你需要非常小心,要避免你的操作讓你的整個網站停止相應。因為這兩個操作是會鎖表的,表一鎖住了,別的操作都進不來了。

Apache 會有很多的子進程或線程。所以,其工作起來相當有效率,而我們的服務器也不希望有太多的子進程,線程和數據庫鏈接,這是極大的占服務器資源的事情,尤其是內存。

如果你把你的表鎖上一段時間,比如30秒鐘,那么對于一個有很高訪問量的站點來說,這30秒所積累的訪問進程/線程,數據庫鏈接,打開的文件數,可能不僅僅會讓你泊WEB服務Crash,還可能會讓你的整臺服務器馬上掛了。

所以,如果你有一個大的處理,你定你一定把其拆分,使用 LIMIT 條件是一個好的方法。下面是一個示例:

while  (1)  {
    //每次只做1000條
    mysql_query("DELETE FROM logs WHERE log_date <= '2009-11-01' LIMIT 1000");
    if  (mysql_affected_rows()  ==  0)  {
        //  沒得可刪了,退出!
        break;
    }
    //  每次都要休息一會兒
    usleep(50000);
}

17. 越小的列會越快

對于大多數的數據庫引擎來說,硬盤操作可能是最重大的瓶頸。所以,把你的數據變得緊湊會對這種情況非常有幫助,因為這減少了對硬盤的訪問。

如果一個表只會有幾列罷了(比如說字典表,配置表),那么,我們就沒有理由使用 INT 來做主鍵,使用 MEDIUMINT, SMALLINT 或是更小的 TINYINT 會更經濟一些。如果你不需要記錄時間,使用 DATE 要比 DATETIME 好得多。

當然,你也需要留夠足夠的擴展空間,不然,你日后來干這個事,你會死的很難看,參看Slashdot的例子(2009年11月06日),一個簡單的ALTER TABLE語句花了3個多小時,因為里面有一千六百萬條數據。

以上轉于http://www.cnblogs.com/linuxops/p/6373042.html

18、選擇一個正確的存儲引擎

在 MySQL 中有兩個存儲引擎 MyISAM 和 InnoDB,每個引擎都有利有弊。酷殼以前文章《MySQL: InnoDB 還是 MyISAM?》討論和這個事情。

MyISAM 適合于一些需要大量查詢的應用,但其對于有大量寫操作并不是很好。甚至你只是需要update一個字段,整個表都會被鎖起來,而別的進程,就算是讀進程都無法操作直到讀操作完成。另外,MyISAM 對于 SELECT COUNT(*) 這類的計算是超快無比的。

InnoDB 的趨勢會是一個非常復雜的存儲引擎,對于一些小的應用,它會比 MyISAM 還慢。他是它支持“行鎖” ,于是在寫操作比較多的時候,會更優秀。并且,他還支持更多的高級應用,比如:事務。

19、小心“永久鏈接”

“永久鏈接”的目的是用來減少重新創建MySQL鏈接的次數。當一個鏈接被創建了,它會永遠處在連接的狀態,就算是數據庫操作已經結束了。而且,自從我們的Apache開始重用它的子進程后——也就是說,下一次的HTTP請求會重用Apache的子進程,并重用相同的 MySQL 鏈接。

PHP手冊:mysql_pconnect()
在理論上來說,這聽起來非常的不錯。但是從個人經驗(也是大多數人的)上來說,這個功能制造出來的麻煩事更多。因為,你只有有限的鏈接數,內存問題,文件句柄數,等等。

而且,Apache 運行在極端并行的環境中,會創建很多很多的了進程。這就是為什么這種“永久鏈接”的機制工作地不好的原因。在你決定要使用“永久鏈接”之前,你需要好好地考慮一下你的整個系統的架構。

參考

20、當查詢較慢的時候,可用Join來改寫一下該查詢來進行優化

mysql> select sql_no_cache * from guang_deal_outs \
where deal_id in \
(select id from guang_deals where id = 100017151) ;

----Empty set (18.87 sec)

mysql> select sql_no_cache a.* from guang_deal_outs a\ 
inner join guang_deals b on a.deal_id = b.id \ 
where b.id = 100017151;


----Empty set (0.01 sec)

原因

mysql> desc select sql_no_cache * from guang_deal_outs where deal_id in (select id from guang_deals where id = 100017151) ;
+----+--------------------+-----------------+-------+---------------+---------+---------+-------+----------+-------------+
| id | select_type        | table           | type  | possible_keys | key     | key_len | ref   | rows     | Extra       |
+----+--------------------+-----------------+-------+---------------+---------    +---------+-------+----------+-------------+
|  1 | PRIMARY            | guang_deal_outs | ALL   | NULL          | NULL    |     NULL    | NULL  | 18633779 | Using where |
|  2 | DEPENDENT SUBQUERY | guang_deals     | const | PRIMARY       | PRIMARY |     4       | const |        1 | Using index |
+----+--------------------+-----------------+-------+---------------+---------    +---------+-------+----------+-------------+
2 rows in set (0.04 sec)

mysql> desc select sql_no_cache a.* from guang_deal_outs a inner join guang_deals b on a.deal_id = b.id where b.id = 100017151;
+----+-------------+-------+-------+----------------------    +----------------------+---------+-------+------+-------------+
| id | select_type | table | type  | possible_keys        | key                      | key_len | ref   | rows | Extra       |
+----+-------------+-------+-------+----------------------    +----------------------+---------+-------+------+-------------+
|  1 | SIMPLE      | b     | const | PRIMARY              | PRIMARY                  | 4       | const |    1 | Using index |
|  1 | SIMPLE      | a     | ref   | idx_guang_dlout_dlid |     idx_guang_dlout_dlid | 4       | const |    1 |             |
+----+-------------+-------+-------+----------------------    +----------------------+---------+-------+------+-------------+  
 2 rows in set (0.05 sec)

其實在 guang_deal_outs 在deal_id 上也是有索引的。

其實我想把子查詢設置為

select * from guang_deal_outs where deal_id in (select id from guang_deals where id = 100017151);

變成下面的樣子

select * from guang_deal_outs where deal_id in (100017151);

但不幸的是,實際情況正好相反。MySQL試圖讓它和外面的表產生聯系來“幫助”優化查詢,它認為下面的exists形式更有效率

select * from guang_deal_outs \
where exists 
(select * from guang_deals \
where id = 100017151 and id = guang_deal_outs.deal_id);

這種in子查詢的形式,在外部表(比如上面的guang_deals)數據量比較大的時候效率是很差的(如果對于較小的表,不會造成顯著地影響)。

參考:

http://codingstandards.iteye.com/blog/1344833

http://coolshell.cn/articles/1846.html

http://hi.baidu.com/yzx110/item/74892ab6fc4601a5eaba93e1

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容