MYSQL優化是一個非常大的課題,這篇文章主要介紹了跟MYSQL相關的4個方面,如果想深入研究可以查下相關資料。
一、服務器級別優化
二、操作系統級別優化
三、MYSQL級別優化
四、SQL級別優化
一、服務器級別優化
1.服務器選型
SUN小型機、DELL730xd、HPDL380、IBM3850、云服務等
2. CPU個數、內存大小
大內存,高IO,是現代基于web的數據庫的必備
3.磁盤:SAS、SSD、FIO卡
減小尋道時間、旋轉時間、傳輸時間
4. RAID卡電池,RAID級別
WriteBack, ReadAheadNone,Direct,NoWrite Cache if?Bad BBU
5.其他:網卡等
二、操作系統級別優化
1. I/O調度策略
NOOP、CFQ、Deadline、Anticipatory
臨時生效:echo “dadline” >/sys/block/sda/queue/scheduler
永久生效:/etc/grub.conf中kernel后加elevator=deadline(需要重啟)
2. SWAP使用策略
echo"vm.swappiness=10">>/etc/sysctl.conf
https://www.percona.com/blog/2014/04/28/oom-relation-vm-swappiness0-new-kernel/
3.文件系統
ext3、ext4還是使用xfs
4.避免NUMA問題
numactl --interleave=all即是允許所有的處理器可以交叉訪問所有的內存
5.?/tmp分區
tmpfs??/dev/shm??tmpfs??defaults??00
設置tmpdir=/tmp之后,某些習慣性把文件寫到tmp下的人要改一改習慣了,因為這些文件占用的是內存不是磁盤,而且如果不重啟的話是一直占用
6.?CPU
關閉服務器的節能模式
查看kondemand進程運行情況:
ps -ef |grepkondemand
三、MYSQL級別優化
1.版本的選擇,除官方版本外
2.?? 最重要的參數選項調整
default-storage-engine=innodb
innodb_buffer_pool_size、key_buffer_size
innodb_flush_log_at_trx_commit、sync_binlog
innodb_file_per_table
long_query_time
max_connection
3. Schema設計規范及SQL使用
設計自增列做主鍵
字段屬性盡量都加上NOT NULL約束
盡可能不使用TEXT/BLOB類型
多表聯接查詢時,結果集小的作為驅動表
復合索引的選擇
4.其他建議(pt-toolkit、orzdba等工具使用)
pt-duplicate-key-checker檢查并刪除重復的索引
pt-index-usage檢查并刪除使用頻率很低的索引
pt-query-digest進行慢查詢分析
pt-kill殺掉超長時間的SQL請求
pt-online-schema-change來完成大表的ONLINE DDL需求
pt-table-checksum、pt-table-sync來檢查并修復mysql主從復制的數據差異
四、Sql級別優化
案例一:URL列索引優化
T_VIDEO表的SQL操作緩慢,出現性能問題,抓取慢查詢,發現主要由大量如下類似的SQL語句執行緩慢:
select … … (這里是表的所有字段)
fromT_VIDEO video0_ ?where ???????video0_.VIDEO_PATH=‘http://www.youtube.com/watch?v=ZjxzF3fNQuI‘limit 1;
咨詢開發同學,這個是為了確認某條數據是否已經存在,需要查詢全部字段并逐一比較。并且表中只有ID列主鍵,無其他索引。
那么如何緩解這種情況呢?如何確認某條數據是否存在?
制定方案:
1)通過“主鍵(或者唯一約束)”來判斷該行數據是否存在,存在的話直接覆蓋更新。
2)堅決不建議逐個字段查詢出來一一比較!因為首先,查詢語句執行時的Sending Data的時間會加長,當數據量達到一定程度的時候還會產生大量的臨時表;其次需要消耗CPU和時間來做比較,性價比不高。
存在問題:
1)存儲的URL前n位基本相同或者只有幾種,其次URL可能會很長;
2)如果還是使用傳統的B-tree索引的話,索引會變得非常大且效率不高
解決方案:
1)大家知道hash索引性能要比B-tree索引好,且基于數字類型的索引性能要比基于字符串的索引好,那么如果我們將URL做一個hash然后在這個hash值上做索引,查詢的時候將URL和hash作為where條件,既實現了基于索引的查詢,又降低了索引的大小。
2)我們可以使用CRC32函數來實現。
在數據庫中建立冗余列URL_CRC,用于存儲URL的hash值,這里在插入的時候使用CRC32(“……”)函數,返回值是數字類型
3)在這一列上建立索引
查詢的時候使用WHEREURL_CRC=CRC32(“……”)? AND URL=”……”,查詢優化器會自動使用索引列URL_CRC,即使有重復值,還可以通過URL列二次篩選
案例二:百萬級數據分頁
項目中數據量已經動輒百萬,且會使用到分頁。
開發同學在代碼中進行分頁一般會這么寫:
select *from `table` order by iddesc limit 1000000,50;
可是當數據量到達百萬、千萬或者更多的時候,很可能會出現分頁查詢性能下降明顯的情況,可能從之前的毫秒到現在的幾秒或者幾十秒。這是為什么呢?
select * from `table` order by id desc limit100,50;???????? 0.016秒
select * from `table` order by id desc limit1000,50;?????? 0.047秒
select * from `table` order by id desc limit10000,50;????? 0.094秒
select *from `table` order by iddesc limit 100000,50;??? 0.43秒
select *from `table` order by iddesc limit 1000000,50;? 2.23秒
其實limit在實際執行的時候是“查詢1000050行數據,然后丟掉前面的1000000行,返回剩下的50行”,是不是發現了很驚悚的問題了呢?! 浪費了大量的I/O性能啊。
如何優化?
代碼級:
程序里維護一個變量,用于記錄當前要顯示的頁的數據起始值,SQL語句中使用這個變量的值;
數據庫級(SQL級)
利用覆蓋索引
selectid fromFROM `tablle`? order by id desclimit 1000000,50;
或者
SELECT* FROM`table` WHERE id <= (SELECT id FROM `table` ORDER BY id desc LIMIT1000000,1) ORDER BY id desc LIMIT 50;
或者
select* FROM`table` AS t1 JOIN (SELECT id FROM `table` ORDER BY id desc LIMIT1000000,1) ASt2 WHERE t1.id<=t2.id order by t1.id desc limit 50;
原理就是記錄住當前頁id的最大值和最小值,計算跳轉頁面和當前頁相對偏移,由于頁面相近,這個偏移量不會很大,這樣的話大大減少掃描的行數。
或者
select* from`table` where id between 1000001 and 1000050;
原理和上面類似,直接定位需要掃描的數據(頁),但是如果這個跨度區間內的ID有缺失,那么查詢出的數據就小于50條了,這一點一定要注意。
案例三:使用簡單SQL去完成復雜功能
原來的執行腳本:
INSERTINTOT_APP_APK_ID_DOWNLOAD
(APK_ID,APP_UPDATE_TIME,DOWNLOAD_NUM)
selecta.APK_ID,a.UPDATE_TIME,IFNULL(b.TOTAL_NUM,0)
from
(selectMAX(id)id,max(UPDATE_TIME) UPDATE_TIME,APK_ID from T_APP GROUP BY APK_ID) as a
LEFTJOIN
T_APP_DOWNLOAD_STATIbon a.id=b.APP_ID;
4000W數據,所需時間15min+
簡化SQL語句的重要方法就是采用臨時表暫存中間結果,但是,臨時表的好處遠遠不止這些,將臨時結果暫存在臨時表,后面的查詢就在temptable中了,這可以避免程序中多次掃描主表,也大大減少了程序執行中“共享鎖”阻塞“更新鎖”,減少了阻塞,提高了并發性能。
分拆后執行計劃步驟:
1.建立中間表
CREATETABLE `T_APP_TMP` (
`ID`int(11) NOT NULL AUTO_INCREMENTCOMMENT ‘主鍵‘,
`APP_ID`int(11) NOT NULL DEFAULT‘0‘ COMMENT ‘APK 唯一標識‘,
`UPDATE_TIME`datetime NOT NULLDEFAULT ‘2000-01-01 00:00:00‘ COMMENT ‘APK更新時間‘,
`APK_ID`varchar(150) NOT NULLDEFAULT ‘‘ COMMENT ‘APK 唯一標識‘,
PRIMARY KEY (`ID`),
KEY`idx_app_appid_code` (`APP_ID`)
)ENGINE=InnoDBAUTO_INCREMENT=1DEFAULT CHARSET=utf8 COMMENT=‘應用表‘;
2.將數據插入中間表
INSERTINTOT_APP_TMP(APP_ID,UPDATE_TIME,APK_ID) select MAX(id)id,max(UPDATE_TIME)UPDATE_TIME,APK_ID from T_APP GROUP BY APK_ID;
3.將最終結果插入結果表
INSERT INTOT_APP_APK_ID_DOWNLOAD (APK_ID,APP_UPDATE_TIME,DOWNLOAD_NUM)
selecta.APK_ID,a.UPDATE_TIME,IFNULL(b.TOTAL_NUM,0)
fromT_APP_TMP as a LEFT JOINT_APP_DOWNLOAD_STATI b
ona.APP_ID=b.APP_ID;
4.將中間表刪除
DROP TABLET_APP_TMP;
按照這個步驟執行,總共不超過5min鐘。
原文:http://www.cnblogs.com/zengkefu/p/5835491.html