MySQL小技巧
簡介篇
儲存引擎
MyISAM是MySQL5.1及之前的版本的預設儲存引擎。MyISAM提供了大量的特性,包括全文索引、壓縮、空間函式(GIS)等,但是MyISAM不支援事務和行級鎖,而且有一個毫無疑問的缺陷就是崩潰後無法安全回覆。
MyISAM會將表儲存在兩個檔案中:資料檔案和索引檔案,分別以.MYD和.MYI為副檔名。
InnoDB表是基於聚簇索引建立的。
推薦InnoDB儲存引擎
資料型別
MySQL支援的資料型別非常多,總體上分為:數字、字元、日期、JSON。選擇正確的資料型別對於獲得高效能至關重要。不管儲存那種型別的資料,下面幾個簡單的原則都有助於作出更好的選擇。
1)更小的通常更好
一般情況下,應該儘量使用可以正確儲存資料的最小資料型別。更小的資料型別通常更快,因為他們佔用更少的磁碟、記憶體和CPU快取,並且處理時需要的CPU週期也更少。
2)簡單就好
簡單的資料型別的操作通常需要更少的CPU週期。例如,整型比字元操作代價更低,因為字符集和校對規則(排序規則)使字元比較比整型比較更復雜。
3)儘量避免NULL
很多表都包含可為NULL的列,即使應用程式並不需要儲存NULL也是如此,這是因為可為NULL是列的預設屬性。通常情況下最好指定列為NOT NULL,除非真的需要儲存NULL值。
用NULL會浪費儲存空間,因為InnoDB需要一個額外的位元組儲存。
NULL欄位的複合索引無效。
如果查詢中包含可為NULL的列,對MySQL來說更難優化,因為可為NULL的列使得索引、索引統計和值比較都更負責。當可為NULL的列被索引時,每個索引記錄需要一個額外的位元組,在MyISAM裡甚至還可能導致固定大小的索引(例如只有一個整數列的索引)變成可變大小的索引。
日期型別
DATE型別表示日期的年月日部分,範圍在1000-01-01到9999-12-31之間。
DATETIME型別表示日期的年月日時分秒部分,範圍在1000-01-01 00:00:00到9999-12-31 23:59:59之間。
TIMESTAMP型別表示日期的年月日時分秒部分,範圍在1970-01-01 00:00:01UTC到2038-01-19 03:14:07UTC之間。
可以讓DATETIME和TIMESTAMP自動初始化並且隨其他欄位的修改而自動修改。
例如:
CREATE TABLE aaa
(
id
bigint(11) NOT NULL AUTO_INCREMENT,
ts
timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
dt
datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
name
varchar(20) DEFAULT NULL,
PRIMARY KEY (id
)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
主鍵
整數通常是主鍵的最好選擇,因為他們很快並且可以使用AUTO_INCREMENT。
如果可能,應該避免使用字串型別作為主鍵,因為他們很消耗空間,並且通常比數字型別慢。尤其在MyISAM表裡使用字串作為主鍵要特別小心。MyISAM預設對字串使用壓縮索引,這會導致查詢慢很多。
對於完全“隨機”的字串也許多加註意,例如MD5()、SHA1()、或者UUID()產生的字串。這些函式生成的新值會任意分佈在很大的空間內,這回導致INSERT以及一些SELECT語句變得很慢:
因為插入值會隨機地寫到索引的不通位置,所以是的INSERT語句更慢。這回導致頁分裂、磁碟隨機訪問,以及對於聚簇儲存引擎產生聚簇索引碎片。
SELECT語句會變得更慢,因為邏輯上相鄰的行會分佈在磁碟和記憶體的不同地方。
隨機值導致快取對所有型別的查詢語句效果都很差,因為會使得快取賴以工作的訪問區域性性原理失效。如果整個資料集都一樣的“熱”,那麼快取任何一部分特定資料到記憶體都沒有好處;如果工作集比記憶體大,快取將會有很多重新整理和不命中。
如果儲存UUID值,則應該移除“-”符號;或者更好的做法是,用UNHEX()函式轉換UUID值為16位元組的數字,並且儲存在一個BINARY(16)列中。檢索時可以通過HEX()函式來格式化為十六進位制格式。
UUID()生成的值與加密雜湊函式例如SHA1()生成的值有不同的特徵:UUID值雖然分佈也不均勻,但還是有一定順序的。儘管如此,還是不如遞增的整數好用。
索引
B-Tree索引
B-Tree索引能夠加快訪問資料的速度,因為儲存引擎不再需要進行全表掃描來獲取需要的資料,取而代之的是從索引的根節點開始進行搜尋。
B+樹索引並不能找到一個給定鍵值的具體行。B+樹索引能找到的只是被查詢資料行所在的頁。然後資料庫通過把頁讀入記憶體,再在記憶體中進行查詢,最終得到要查詢的資料。
限制:
如果不是按照索引的最左列開始查詢,則無法使用索引。
不能跳過索引中的列。
如果查詢中有某個列的範圍查詢,則其右邊所有列都無法使用索引優化查詢。
Hash索引
高效能索引策略:
分割槽表
分割槽功能並不是在儲存引擎層完成的。分割槽有水平分割槽和垂直分割槽兩種,但是MySQL不支援垂直分割槽。
大多數DBA會有這樣一個誤區:只要啟用了分割槽,資料庫就會變得更快。這個結論是存在很多問題的。其實,分割槽對於某些SQL語句效能可能會帶來提高,但是分割槽主要用語高可用性,利於資料庫的管理。在OLTP應用中,對於分割槽的使用應該非常小心。如果只是一味使用分割槽,而不理解分割槽是如何工作的,也不清楚你的應用如是使用分割槽,那麼分許極有可能只會對效能產生負面的影響。
MySQL資料庫支援以下幾種分割槽型別:
RANGE分割槽:
LIST分割槽:
HASH分割槽:
KEY分割槽:
不論建立哪種型別的分割槽,如果表中存在主鍵或者是唯一索引時,分割槽列必須是唯一索引的一個組成部分。
唯一索引可以是允許NULL值的,並且分割槽列只要是唯一索引的一個組成部分,不需要整個唯一索引列都是分割槽列。
當建表時沒有指定主鍵和唯一索引時,可以指定任何一列為分割槽列。
規範篇
規範存在意義
保證線上資料庫schema規範
減少出問題概率
方便自動化管理
規範需要長期堅持,對開發和DBA是一個雙贏的事情
核心規範
不在資料庫做運算:cpu計算務必移至業務層
控制單表資料量:單表記錄控制在1000w
控制列數量:欄位數控制在20以內
平衡正規化與冗餘:為提高效率犧牲正規化設計,冗餘資料
拒絕3B:拒絕大sql,大事物,大批量
基本命名和約束規範
表字符集選擇UTF8 ,如果需要儲存emoj表情,需要使用UTF8mb4(MySQL 5.5.3以後支援)
儲存引擎使用InnoDB
變長字串儘量使用varchar varbinary
不在資料庫中儲存圖片、檔案等
單表資料量控制在1000w以下
庫名、表名、欄位名不使用保留字
庫名、表名、欄位名、索引名使用小寫字母,以下劃線分割 ,需要見名知意
庫表名不要設計過長,儘可能用最少的字元表達出表的用途
索引規範
單個索引欄位數不超過5,單表索引數量不超過5,索引設計遵循B+ Tree索引最左字首匹配原則
選擇區分度高的列作為索引
建立的索引能覆蓋80%主要的查詢,不求全,解決問題的主要矛盾
DML和order by和group by欄位要建立合適的索引
不在索引做列運算
避免索引的隱式轉換
避免冗餘索引
不要用外來鍵
關於索引規範,一定要記住索引這個東西是一把雙刃劍,在加速讀的同時也引入了很多額外的寫入和鎖,降低寫入能力,這也是為什麼要控制索引數原因。之前看到過不少人給表裡每個欄位都建了索引,其實對查詢可能起不到什麼作用。
冗餘索引例子
idx_abc(a,b,c)
idx_a(a) 冗餘
idx_ab(a,b) 冗餘
隱式轉換例子
欄位:remark varchar(50) NOT Null
MySQL>SELECT id, gift_code FROM gift WHERE deal_id = 640 AND remark=115127; 1 row in set (0.14 sec)
MySQL>SELECT id, gift_code FROM pool_gift WHEREdeal_id = 640 AND remark=‘115127’; 1 row in set (0.005 sec)
欄位定義為varchar,但傳入的值是個int,就會導致全表掃描,要求程式端要做好型別檢查
欄位規範
所有欄位均定義為NOT NULL ,除非你真的想存Null
欄位型別在滿足需求條件下越小越好,使用UNSIGNED儲存非負整數 ,實際使用時候儲存負數場景不多
使用datetime儲存時間
使用varchar儲存變長字串 ,當然要注意varchar(M)裡的M指的是字元數不是位元組數;使用UNSIGNED INT儲存IPv4 地址而不是CHAR(15) ,這種方式只能儲存IPv4,儲存不了IPv6
使用DECIMAL儲存精確浮點數,用float有的時候會有問題
少用blob text
SQL規範
儘量不使用儲存過程、觸發器、函式等
避免使用大表的JOIN,MySQL優化器對join優化策略過於簡單
避免在資料庫中進行數學運算和其他大量計算任務
SQL合併,主要是指的DML時候多個value合併,減少和資料庫互動
UPDATE、DELETE語句不使用LIMIT,容易造成主從不一致
不用select *
慎用count(*)
避免負向%
請使用同類型比較
sql語句儘可能簡單
一條sql只能在一個cpu運算
大語句拆小語句以減少鎖時間
一條大sql可以堵死整個庫
簡單的事務
事務時間儘可能短
bad case:
上傳圖片事務
OR改寫為IN()
or的效率是n級別
in的訊息時log(n)級別
in的個數建議控制在200以內
select id from t where phone=’159′ or phone=’136′;
=>
select id from t where phone in (’159′, ’136′);
OR改寫為UNION
mysql的索引合併很弱智
select id from t where phone = ’159′ or name = ‘john’;
=>
select id from t where phone=’159′
union
select id from t where name=’jonh’
limit高效分頁
limit越大,效率越低
select id from t limit 10000, 10;
=>
select id from t where id > 10000 limit 10;
使用union all替代union
union有去重開銷
使用load data導資料
load data比insert快約20倍;