測試環境准備
本文主要圍繞的對象是mariadb 高級語法, 索引優化, 基礎sql語句調優.
下面那就開始搭建本次測試的大環境. 首先下載mariadb開發環境, 並F5 run起來. 具體參照下面文章的具體套路.
C中級 MariaDB Connector/C API 編程教程
數據庫環境搭建好了, 我們需要導入一個mysql 中一個測試的sakila數據庫進行實驗. 通過下面步驟進行demo db搭建.
sakila下載 http://dev.mysql.com/doc/index-other.html
下載成功后, 開始解壓, 會得到3個文件只會用到其中兩個sql創建腳本, 通過source按照schema -> data 順序開始導入數據.
mysql -uroot -p source H:/mariadb學習總結/mariadb優化/sakila-db/sakila-schema.sql source H:/mariadb學習總結/mariadb優化/sakila-db/sakila-data.sql
上面路徑請自行替換. 最終得到sakila數據庫環境如下一共23表. 這里扯一點, sql腳本有時候需要';', 有時候不需要. 是什么原因呢.
一種解釋是, 不需要 ';' 的是mariadb系統中內置命令, 單獨命令. 需要分號的是執行語句. 為了通用推薦都加上分號.
到這里基本數據環境搭建好了. 然后還需要搭建一個慢查詢的環境, 用於監測費時的sql操作. 在這里先入為主展現一下最終的搭建環境
這里主要圍繞mariadb 三個全局變量,
slow_query_log (是否開啟慢查詢),
slow_query_log_file (慢查詢指定輸出日志文件路徑)
long_query_time (慢查詢閥值, 單位秒, 只有超過這個時間的查詢語句才會被記錄到上面的日志文件中).
set global long_query_time = 0.001; set global slow_query_log = on;
對於需要指定的慢查詢輸出文件路基, 直接采用默認指定路徑.
然后完畢當前會話窗口, 再重新打開會話窗口. 就開啟了慢查詢了. 上面是為了測試方便, 設置慢查詢時間閥值為1毫秒.
(對於慢查詢是什么, 可以詳細查找資料. 慢查詢本質其實是把運行慢的sql語句記錄保存下來, 方便集中分析調優).
對於慢查詢分析, 我們采用默認提供 mysqldumpslow.pl perl腳本進行分析. 需要下載安裝perl解釋器ActivePerl.
perl解釋器下載 https://www.perl.org/get.html
扯一點, 程序員經常需要在本機快速查找文件. 推薦用 Everything 這個工具 !!!
下載安裝成功后, 我們簡單測試使用一下.從慢查詢日志 simplec-slow.log 中查找出前3條慢查詢語句, 如下展示.
一切正常, 通過上面能夠清晰查找出來那些語句執行的很耗時間.
對於 mysqldumpslow.pl 其它命令 參照 http://www.cnblogs.com/yuqiandoudou/p/4663749.html
扯一點, 對於慢查詢公司中有的采用這樣套路, 進行可持續好的循環.
1. 每天都會生成慢查詢日志, 並通過定時腳本切分
2. 將昨天慢查詢日志前100條, 整理成清晰好閱讀的文件.
3. 通過郵件發送給每個后端開發的相關人員.
4. 后端老大或核心人員會着手進行優化
到這里基礎環境搭建部分, 就扯淡完了. 關於其它更好的回顧方式, 聰明的同學可以自行搜索相關優化高級視頻進行學習實踐.
本文部分例子參照 : http://www.imooc.com/learn/194 ... mysql性能優化視頻
索引優化套路
首先肯定從 explain語句開始 https://mariadb.com/kb/en/mariadb/explain/
先看下面 explain select max(payment_date) from payment;
顯示查詢 type = ALL 表示查詢性能最差. 讀取了所有行, 表進行全表掃描。這是壞的 !這一切發生時,優化器找不到任何可用的索引訪問行。
那我們優化一下 為其建立一個索引
drop index if exists idx_payment_paydate on payment; create index idx_payment_paydate on payment(payment_date); explain select max(payment_date) from payment;
得到最終結果如下
后面發現因為建立了索引, 查詢之后立即得到結果, 這個因為建立了索引, 頭和尾就是最大最小, 所以沒有查詢都得到結果了.
這就是建立索引快速查詢的好處. 后面開始說索引優化的套路了. percona-toolkit => 數據庫優化的神器.
percona-toolkit https://www.percona.com/downloads/percona-toolkit/
percona-toolkit工具包的安裝和使用 http://www.cnblogs.com/zping/p/5678652.html
主要會使用到如下內嵌的perl 腳本(其它操作, 自行腦補, 異常管用) .
1. pt-index-usage -> pt-index-usage [OPTION...] [FILE...]
從log文件中讀取插敘語句,並用explain分析他們是如何利用索引。完成分析之后會生成一份關於索引沒有被查詢使用過的報告。
pt-index-usage [慢查詢文件路徑] --host=localhost --user=root --password=[密碼]
上面可以去掉冗余索引問題.
2. pt-query-digest -> pt-query-digest [OPTIONS] [FILES] [DSN]
使用上面工具分析慢查詢分析,
詳細的可以參照如下資料
pt-query-digest查詢日志分析工具 http://blog.csdn.net/seteor/article/details/24017913
以上就是關於mariadb 索引分析(究極分析)總有效, 最方便的方式. 歡迎嘗試, 屢試不爽!
高級語法溫故
下面到能看見代碼的環節了, sql高級語法無外乎, 觸發器, 存儲過程, 游標! 為什么要用這些, 因為說不定你就遇到了奇葩的jb需求呢.
1. 觸發器 trigger
觸發器可以通過下面方式記錄需要涉及的操作步驟.
監視誰 : 表名
監視動作 : insert / update / delete
觸發時間 : after 操作之后 / before 操作之前
觸發事件 : insert / update / delete
我們通過具體例子分析, 加入現在有個商品表 goods , 和一個訂單表 ords . 當訂單表完成后, 需要去掉響應商品表中庫存.
先構建數據測試環境
use test; drop table if exists test.goods; create table test.goods ( id int not null comment "唯一主鍵", name varchar(20) not null comment "商品名稱", num int not null comment "商品數量", primary key(id) ); insert into goods values (1, 'cat', 34), (2, 'dog', 18), (3, 'pig', 21); drop table if exists test.ords; create table test.ords ( id int not null comment "唯一主鍵", gid int not null comment "商品id", num int not null comment "購買數量", primary key(id) );
好那我們開始測試, 編寫符合上面需求的觸發器.
drop trigger if exists tri_ords_insert_before_goods; delimiter $ create trigger tri_ords_insert_before_goods before insert on ords for each row begin declare rnum int; select num into rnum from goods where id = new.gid; if new.num > rnum then set new.num = rnum; end if; update goods set num = num - new.num where id = new.gid; end $ delimiter ;
需要說一下, 外頭 for each row begin .. end 是必須套路(行語法級別, 每行都會執行). 這些語法多查查文獻都清楚了, 做個試驗.
到這里觸發器基本回顧完畢. 實際上觸發器還是用的. 大家可以從 use sakila; show triggers\G 看看更加具體案例.
2. 存儲過程 procedure
存儲過程可以理解為上層語言中函數. 塊語句能夠傳入數據, 也能返回結果. 同樣用一個高斯和來介紹存儲過程使用例子.
輸出書n, 輸出 s = 1 + 2 + .. n的和. 重在體會mariadb sql存儲過程的語法直觀感受.
-- 來個高斯和 drop procedure if exists proc_gauss; delimiter $ create procedure proc_gauss(in n int, out s int) begin declare i int default n; set s = 0; while i > 0 do set s = s + i; set i = i - 1; end while; select concat("gauss(", n, ") = ", s) gauss; end $ delimiter ;
測試結果如下 :
扯一點, 對於存儲過程, in 是輸出參數, 不會影響外頭傳入的值. out傳入的時候為null, 但會輸出. inout就是輸入加輸出.
set 支持 = 和 := 賦值, declare 聲明變量必須要在存儲過程開始部位.
3. 游標使用 cursor
對於cursor 游標, 也是有套路的. 總結的筆記是
-- cursor 游標 游標的標志 -- 1條sql, 對應N條資源, 取出資源的接口/句柄, 就是游標 -- 沿着游標, 可以一次取出1行 -- declare 聲明; declare 游標名, cursor for select_statement -- open 打開; open 游標名 -- fetch 取值; fetch 游標名 into var1, var2[...] -- close 關閉; close 游標名
這樣至少熟悉了游標有那些關鍵字和操作步驟, 后面同樣通過實戰例子分析. 但是最需要的還是自我練習. 因為這類腳本多是實踐中誕生的, 對動手能力依賴很強.
需求 : 我們需要得到某個查詢總集中, 按照一定權限占比得到結果. 例如第一條數據權限值是1, 第二條數據權限值為2
以goods表為例, 求出 sum = 求和 num(i) * i. 一般這種需求可以放在應用層處理, 如果只借助sql. 那需要游標來處理.
-- 游標權限處理, 數據庫層返回權限和 drop procedure if exists proc_cursor_sum; delimiter $ create procedure proc_cursor_sum(out s int) begin declare r_num int; declare i int default 1; declare lop int default 1; -- 聲明游標 declare getnums cursor for select num from goods; -- 聲明handler 必須在游標聲明之后, 當游標數據讀取完畢會觸發下面set declare continue handler for not found set lop = 0; set s = 0; -- 打開游標 open getnums; -- 操作游標, 讀取第一行數據 fetch getnums into r_num; while lop = 1 do set s = s + r_num * i; set i = i + 1; -- 讀取下一個行數據 fetch getnums into r_num; end while; -- 關閉游標 close getnums; end $ delimiter ;
最終操作結果正常
到這里關於mariadb sql常用高級語法編寫例子都有了. 是不是發現, 腳本語言還是很容易掌握的, 只需要會查詢手冊.
常用腳本優化
到這里會介紹幾種查詢腳本級別的優化方案.
1. 聚集索引的先后順序. 參照下面例子
use sakila; select * from payment where staff_id = 1 and customer_id = 345; explain select * from payment where staff_id = 1 and customer_id = 345;
首先看 索引分析結果
這里先用 staff_id 相關索引, 后用 customer_id 相關索引. 這里有個技巧, 將區分細的放在前面, 減少查詢的次數. 那么假如建立聚集索引, 就可以把區分度
高索引放在第一個位置上. 如何區分兩個索引順序可以參照下面sql語句
select count(distinct staff_id) "staff_id_cnt", count(distinct customer_id) "customer_id_cnt" from payment;
2. 對於inner join 子語句數據量越小越好.
xxx inner join ( sql ) on xxx 語句中, 自語句sql 數據量越小查找速度越快, 假如外部where 可以嘗試移動到其中.
或者最大化的將外層的 where 或者 group by 遷移到子語句的sql部分中.
3. limit 分頁優化
首先直接看下面對比, 查詢第(500, 500] 區間內數據.
select film_id, description from film order by film_id limit 500, 5; explain select film_id, description from film order by film_id limit 500, 5;
從中可以看出rows, 查詢了505行. 我們優化一下.
select film_id, description from film where film_id >500 and film_id <=505 order by film_id limit 5; explain select film_id, description from film where film_id >500 and film_id <=505 order by film_id limit 5;
這里查詢性能的提升是顯著的. 完成的功能是一模一樣, 這些優化都是有套路的輕松可以快速提升性能. 實在不行了再從硬件層考慮. 因為軟件層的優化更加低廉.
4. 設計上優化, 通過unsigned int 代替 time string, bigint 代替 ip string
思路是在mysql只保存時間戳和ip戳, 后面在代碼層, 構建轉換接口. 例如c 中自己寫,
時間串和時間戳轉換函數. 可以參照 C基礎 時間業務實戰代碼 . 對於 ip串和ip戳互相轉換也簡單.
使用系統提供的函數inet_aton和inet_ntoa. 這樣就將數據庫層運算操作移動到業務層進行運算.
再或者分表查詢(垂直分表)等. 分表查詢關注點在於 hash分流. 還有讀寫分析, 一般同主從復制配置.一台服務器寫,一台服務器讀.
5. 業務上優化, 例如對於大量的排序操作
原先優化是從mysql層剝離處理, 放在業務層處理. 一般讓后端服務器自己排序, 再發送數據給客戶端.
更加徹底的是, 后端服務器得到數據庫服務器數據, 不進行排序. 直接拋給前端單機, 讓其消耗客戶機進行特殊排序.
其實思路很簡單, 將大量重復計算業務拋給客戶機去運行. 客戶端才是一個系統性能瓶頸.
一個能夠快速突破的客戶端程序員, 一定是理解過服務器.
然后后記展望
錯誤是難免的, 歡迎補充☺.
貝多芬的悲傷 http://music.163.com/#/song?id=314773