MariaDB 復合語句和優化套路


測試環境准備

   本文主要圍繞的對象是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

   


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM