MYSQL優化淺談,工具及優化點介紹,mysqldumpslow,pt-query-digest,explain等


MYSQL優化淺談

msyql是開發常用的關系型數據庫,快速、穩定、開源等優點就不說了。 
個人認為,項目上線,標志着一個項目真正的開始。從運維,到反饋,到再分析,再版本迭代,再優化… 這是一個漫長且考驗耐心的過程。在這個過程中,作為數據存儲的關鍵–>數據庫的優化起到尤為重要的作用。 
語文學的不好,廢話也不多說,下面結合實例咱們說說MYSQL需要從哪些方面進行優化。 
在說優化之前先聲明一下環境

名稱 版本 備注
系統1 WIN 7——x64 SP1 個人電腦
系統2 centos6.4 個人電腦
數據庫 5.5.22 MySQL Community Server (GPL) 社區版
內存 8GB  
CPU i7-3520 四線程

優化需要考慮的幾個方面 
1. SQL優化 
2. 索引優化 
3. 數據庫結構優化 
4. 系統配置優化 
5. 硬件優化

SQL優化

本次使用我自己的庫LMS為優化對象

mysql> use lms
Database changed
mysql> show tables; +------------------------+ | Tables_in_lms | +------------------------+ | lm_a_mresource | | lm_a_mresourcerole | | lm_a_role | | lm_a_user | | lm_a_userrole | | lm_c_dept部門 | | lm_c_suppliers | | lm_c_usecomp | | lm_d_applyforpayment | | lm_d_arrival | | lm_d_billing | | lm_d_cgcontract | | lm_d_cgcontractcredit | | lm_d_dlcontract | | lm_d_payed | | lm_d_plan需求計划 | | lm_d_planchangedetails | | lm_d_procurementsource | | lm_d_received | | lm_d_xscontract | +------------------------+ 20 rows in set (0.01 sec)

 

開啟MYSQL慢查詢日志,監控有效率問題的SQL

如何發現有問題的SQL呢? 
1. 通過設置slow_query_log開啟慢查詢日志

mysql> show variables like 'slow_query_log'; +----------------+-------+ | Variable_name | Value | +----------------+-------+ | slow_query_log | OFF | +----------------+-------+ 1 row in set (0.00 sec) mysql> set global slow_query_log=on; Query OK, 0 rows affected (0.07 sec)
  1. 通過設置log_queries_not_using_indexes開啟為使用索引的監控
mysql> show variables like 'log_queries_not_using_indexes'; +-------------------------------+-------+ | Variable_name | Value | +-------------------------------+-------+ | log_queries_not_using_indexes | ON | +-------------------------------+-------+ 1 row in set (0.00 sec) mysql> set global log_queries_not_using_indexes=on; Query OK, 0 rows affected (0.00 sec)

 

  1. 通過long_query_time設置監控閥值,也就是超過多少秒就記錄,單位是秒,此處設置為0.1也就是100毫秒
mysql> set global long_query_time=0.1; Query OK, 0 rows affected (0.00 sec) mysql> show variables like 'long_query_time'; +-----------------+----------+ | Variable_name | Value | +-----------------+----------+ | long_query_time | 0.100000 | +-----------------+----------+ 1 row in set (0.00 sec)

 

 

通過以上設計,基本上可以開始優化工作了。 
set global 只是全局session生效,重啟后失效,如果需要以上配置永久生效,需要在mysql.ini(linux mysql.cnf)中配置

[mysqld]
slow_query_log  = 1 log_queries_not_using_indexes = 1 long_query_time = 0.1 slow_query_log_file = c:\mysql\log\mysqlslowquery.log

 

 

我在配置文件中設置了。所以我重啟讓配置生效。 
ps:slow_query_log_file這個路徑要有效且有權限,否則重啟后無法寫入log

C:\Users\sjm>net stop mysql
MySQL 服務正在停止..
MySQL 服務已成功停止。
C:\Users\sjm>net start mysql
MySQL 服務正在啟動 ...
MySQL 服務已經啟動成功。

 

如果是linux,就通過以下命令或service等其他方式重啟

啟動:/etc/init.d/mysqld(mysql) start
停止:/etc/init.d/mysqld(mysql) stop
重啟:/etc/init.d/mysqld(mysql) restart

最后查看慢查詢日志所在的位置,通過查看slow_query_log_file

mysql> show variables like 'slow_query_log_file' \G *************************** 1. row *************************** Variable_name: slow_query_log_file Value: c:\mysql\log\mysqlslowquery.log 1 row in set (0.00 sec)
  • 1
  • 2
  • 3
  • 4
  • 5

測試

mysql> select sleep(1); +----------+ | sleep(1) | +----------+ | 0 | +----------+ 1 row in set (1.00 sec)

通過以上超出0.1秒的測試,slow_query_log_file就會記錄該信息,格式如下:

//Time int類型時間戳 加 時間
# Time: 150610 10:24:07
//執行SQL的主機信息
# User@Host: root[root] @ localhost [::1]
//SQL執行信息依次是 執行時間,鎖定時間,返回記錄數,掃描行數
# Query_time: 1.000057  Lock_time: 0.000000 Rows_sent: 1  Rows_examined: 0
//SQL執行用時
SET timestamp=1433903047; //SQL內容 select sleep(1);

 

分析工具之一mysqldumpslow查看並分析慢查詢日志

以下是windows環境需要安裝其他支持軟件的步驟,非windows直接忽略 
windows要執行mysqldumpslow需要安裝ActivePerl因為windows上這是一個perl腳本,需要安裝ActivePerl才能執行,linux下就可以直接執行mysqldumpslow了。 
安裝ActivePerl的步驟我就不多說了。安裝好后會在環境變量PATH中找到perl的bin目錄。我重啟了電腦讓環境變量生效。 
驗證ActivePerl是否安裝成功

C:\Users\sjm>perl -v

This is perl 5, version 20, subversion 2 (v5.20.2) built for MSWin32-x86-multi-thread-64int (with 1 registered patch, see perl -V for more detail) Copyright 1987-2015, Larry Wall

 

如果打印和我差不多的就安裝成功了。 
找到mysqldumpslow的目錄。也就是mysql的bin目錄(linux用戶可直接運行mysqldumpslow) 
windows下驗證mysqldumpslow可用運行 
mysqldumpslow常用命令說明:

C:\Program Files\MySQL\MySQL Server 5.5\bin>perl mysqldumpslow.pl -help Usage: mysqldumpslow [ OPTS... ] [ LOGS... ] Parse and summarize the MySQL slow query log. Options are --verbose verbose 查看版本 --debug debug 調試 --help write this text to standard output -v verbose 查看版本 -d debug 調試 -s ORDER what to sort by (al, at, ar, c, l, r, t), 'at' is default 通過下面的方式排序日志↓↓↓ al: average lock time 平均鎖定時間排序 ar: average rows sent 平均發送行數排序 at: average query time 平均查詢時間排序 c: count 執行次數排序 l: lock time 鎖表時間排序 r: rows sent 總結果行數排序 t: query time 總查詢時間排序 -r reverse the sort order (largest last instead of first) 正序排序,即從小到大排序 -t NUM just show the top n queries 查看前X條日志 -a don't abstract all numbers to N and strings to 'S' 顯示出數字和字符串,默認數字為 N 字符串為 'S' -n NUM abstract numbers with at least n digits within names -g PATTERN grep: only consider stmts that include this string 過濾字符串,后接正則表達式,如'10$' 以10為結尾的條件,大小寫不敏感 -h HOSTNAME hostname of db server for *-slow.log filename (can be wildcard), default is '*', i.e. match all -i NAME name of server instance (if using mysql.server startup script) -l don't subtract lock time from total time

windows支持軟件安裝完成

優化開始… … 
查詢lm_d_plan全表條數

mysql> use lms
Database changed
mysql> select count(*) from lm_d_plan;
+----------+
| count(*) |
+----------+
|    57106 | +----------+ 1 row in set (1.94 sec)

 

超過0.1秒 記錄到日志中,並查看

命令解析:排序(-s)按執行次數(c)倒序(-a)
C:\Program Files\MySQL\MySQL Server 5.5\bin>perl mysqldumpslow.pl -s c -a c:\mysql\log\mysqlslowquery.log Reading mysql slow query log from c:\mysql\log\mysqlslowquery.log 執行次數 Count: 3 執行時間Time=0.27s (0s) 鎖定時間Lock=1.67s (1s) 發送行數Rows=1.0 (1), 執行地址root[root]@localhost 內容:select count(*) from lm_d_plan Count: 1 Time=0.00s (0s) Lock=0.00s (0s) Rows=0.0 (0), 0users@0hosts C:\Program Files\MySQL\MySQL Server 5.5\bin\mysqld, Version: 5.5.22-log (MySQL Community Server (GPL)). started with: TCP Port: 3306, Named Pipe: (null)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

分析工具之二pt-query-digest

pt-query-digest是用於分析mysql慢查詢的一個工具,它可以分析binlog、General log、slowlog,也可以通過SHOWPROCESSLIST或者通過tcpdump抓取的MySQL協議數據來進行分析。可以把分析結果輸出到文件中,分析過程是先對查詢語句的條件進行參數化,然后對參數化以后的查詢進行分組統計,統計出各查詢的執行時間、次數、占比等,可以借助分析結果找出問題進行優化。 
pt-query-digest只有linux版本,所以只能用linux了。同理設置/etc/my.cnf

 [mysqld]
slow_query_log  = 1 log_queries_not_using_indexes = 1 long_query_time = 0.1 #這個目錄一定要有權限,最好就設置到mysql目錄並且通過設置用戶權限授權目錄及文件可讀寫 slow_query_log_file = /var/lib/mysql/log/slow.log

 

順便帖一下授權命令,目的是讓log目錄及文件對mysql組的mysql用戶可讀寫,已授權就可以無視。

[root@localhost mysql]# cd /var/lib/mysql [root@localhost mysql]# mkdir log [root@localhost mysql]# chown mysql log [root@localhost mysql]# chgrp mysql log [root@localhost mysql]# chmod ug+rwx log [root@localhost mysql]# cd log/ [root@localhost log]# vi slow.log [root@localhost log]# chown mysql slow.log [root@localhost log]# chgrp mysql slow.log [root@localhost log]# chmod 777 slow.log 

 

安裝pt-query-digest 
地址:https://www.percona.com/doc/percona-toolkit/2.2/pt-query-digest.html#downloading 
選擇 wget percona.com/get/percona-toolkit.rpm(也可以選擇其他類型安裝) 
安裝過程會出現:

[root@localhost ~]# rpm -ivh percona-toolkit-2.2.14-1.noarch.rpm warning: percona-toolkit-2.2.14-1.noarch.rpm: Header V4 DSA/SHA1 Signature, key ID cd2efd2a: NOKEY error: Failed dependencies: perl(DBI) >= 1.13 is needed by percona-toolkit-2.2.14-1.noarch perl(DBD::mysql) >= 1.0 is needed by percona-toolkit-2.2.14-1.noarch perl(Time::HiRes) is needed by percona-toolkit-2.2.14-1.noarch perl(IO::Socket::SSL) is needed by percona-toolkit-2.2.14-1.noarch perl(Term::ReadKey) is needed by percona-toolkit-2.2.14-1.noarch
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

缺少依賴庫 
通過命令yum -y install perl perl-IO-Socket-SSL perl-DBD-MySQL perl-Time-HiRes安裝 
我的安裝過程比較曲折,安裝perl-DBD-MySQL的時候需要安裝依賴庫mysql-libs-5.1.73-3.el6_5.x86_64但是我安裝的是mysql5.6.20,所以報錯**file** /usr/share/mysql/ukrainian/errmsg.sys **from install of** mysql-libs-5.1.73-3.el6_5.x86_64 **conflicts with file from package** MySQL-server-advanced-5.6.20-1.el6.x86_64,就是說安裝的這個mysql-libs依賴庫版本與我本身的mysql版本沖突,他的版本低,而且肯定不能刪除本地數據庫,所以需要安裝MySQL-shared-compat-5.6.20-1.linux_glibc2.5.x86_64.rpm (與本地數據庫版本匹配才可以)來解決mysql-libs的問題。 
還有通過yum安裝的時候最好添加國內的源,我添加是163的,詳情如下

來源:http://blog.chinaunix.net/uid-23683795-id-3477603.html 網易(163)yum源是國內最好的yum源之一 ,無論是速度還是軟件版本,都非常的不錯,將yum源設置為163yum,可以提升軟件包安裝和更新的速度,同時避免一些常見軟件版本無法找到。具體設置方法如下: 1,進入yum源配置目錄 cd /etc/yum.repos.d 2,備份系統自帶的yum源 mv CentOS-Base.repo CentOS-Base.repo.bk 下載163網易的yum源: wget http://mirrors.163.com/.help/CentOS6-Base-163.repo 3,更新玩yum源后,執行下邊命令更新yum配置,使操作立即生效 yum makecache 4,除了網易之外,國內還有其他不錯的yum源,比如中科大和搜狐的,大家可以根據自己需求下載 中科大的yum源: wget http://centos.ustc.edu.cn/CentOS-Base.repo sohu的yum源 wget http://mirrors.sohu.com/help/CentOS-Base-sohu.repo 理論上講,這些yum源redhat系統以及fedora也是可以用 的,但是沒有經過測試,需要的站長可以自己測試一下。
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

這樣pt-query-digest安裝告一段落 
通過命令pt-query-digest --help可以查看幫助文檔,同時說明安裝是沒問題的 
語法及重要選項 
pt-query-digest [OPTIONS] [FILES] [DSN] 
–create-review-table 當使用–review參數把分析結果輸出到表中時,如果沒有表就自動創建。 
–create-history-table 當使用–history參數把分析結果輸出到表中時,如果沒有表就自動創建。 
–filter 對輸入的慢查詢按指定的字符串進行匹配過濾后再進行分析 
–limit限制輸出結果百分比或數量,默認值是20,即將最慢的20條語句輸出,如果是50%則按總響應時間占比從大到小排序,輸出到總和達到50%位置截止。 
–host mysql服務器地址 
–user mysql用戶名 
–password mysql用戶密碼 
–history 將分析結果保存到表中,分析結果比較詳細,下次再使用–history時,如果存在相同的語句,且查詢所在的時間區間和歷史表中的不同,則會記錄到數據表中,可以通過查詢同一CHECKSUM來比較某類型查詢的歷史變化。 
–review 將分析結果保存到表中,這個分析只是對查詢條件進行參數化,一個類型的查詢一條記錄,比較簡單。當下次使用–review時,如果存在相同的語句分析,就不會記錄到數據表中。 
–output 分析結果輸出類型,值可以是report(標准分析報告)、slowlog(Mysql slow log)、json、json-anon,一般使用report,以便於閱讀。 
–since 從什么時間開始分析,值為字符串,可以是指定的某個”yyyy-mm-dd [hh:mm:ss]”格式的時間點,也可以是簡單的一個時間值:s(秒)、h(小時)、m(分鍾)、d(天),如12h就表示從12小時前開始統計。 
–until 截止時間,配合—since可以分析一段時間內的慢查詢。

直接分析: 
pt-query-digest /var/lib/mysql/log/slow.log

第一部分:總體統計結果
# 160ms user time, 10ms system time, 22.46M rss, 199.00M vsz # Current date: Wed Jun 10 18:09:50 2015 # Hostname: localhost.localdomain # Files: /var/lib/mysql/log/slow.log # Overall: 1 total(一共多少條sql), 1 unique(查詢條件進行參數化以后,總共有多少個不同的查詢少), 0 QPS, 0x concurrency ______________________ # Time range(日志的時間范圍): all events occurred at 2015-06-10 14:31:21 # Attribute total min max avg 95% stddev median # ============ ======= ======= ======= ======= ======= ======= ======= # Exec time(執行時間) 23ms 23ms 23ms 23ms 23ms 0 23ms # Lock time(鎖定時間) 253us 253us 253us 253us 253us 0 253us # Rows sent(返回條數) 8 8 8 8 8 0 8 # Rows examine(掃描條數)5.51k 5.51k 5.51k 5.51k 5.51k 0 5.51k # Query size() 50 50 50 50 50 0 50 第二部分:查詢分組統計結,這部分對查詢進行參數化並分組,然后對各類查詢的執行情況進行分析,結果按總執行時長,從大到小排序 # Profile # Rank Query ID Response time Calls R/Call V/M Item # ==== ================== ============= ===== ====== ===== =============== # 1 0x9E3ACC745D5A7770 0.0228 100.0% 1 0.0228 0.00 SELECT lm_d_plan Response 總的響應時間 time 該查詢在本次分析中總的時間占比 calls: 執行次數,即本次分析總共有多少條這種類型的查詢語句。 R/Call: 平均每次執行的響應時間。 Item : 查詢對象 # 第三部分:每一種查詢的詳細統計結果 # **Query 1**: 0 QPS, 0x concurrency, ID 0x9E3ACC745D5A7770 at byte 0 ________ # This item is included in the report because it matches --limit. # Scores: V/M = 0.00 # Time range: all events occurred at 2015-06-10 14:31:21 # Attribute pct total min max avg 95% stddev median # ============ === ======= ======= ======= ======= ======= ======= ======= # Count 100 1 # Exec time 100 23ms 23ms 23ms 23ms 23ms 0 23ms # Lock time 100 253us 253us 253us 253us 253us 0 253us # Rows sent 100 8 8 8 8 8 0 8 # Rows examine 100 5.51k 5.51k 5.51k 5.51k 5.51k 0 5.51k # Query size 100 50 50 50 50 50 0 50 # String: # Hosts localhost # Users root # Query_time distribution # 1us # 10us # 100us # 1ms # 10ms ################################################################ # 100ms # 1s # 10s+ # Tables # SHOW TABLE STATUS LIKE 'lm_d_plan'\G # SHOW CREATE TABLE `lm_d_plan`\G # EXPLAIN /*!50100 PARTITIONS*/ select itemsName from lm_d_plan group by FK_deptNo\G 查詢的詳細統計結果,最上面的表格列出了執行次數、最大、最小、平均、95%等各項目的統計。 Databases: 庫名 //這個示例中沒有 Users: 各個用戶執行的次數(占比) Query_time distribution : 查詢時間分布, 長短體現區間占比,本例中1s-10s之間查詢數量是10s以上的兩倍。 Tables: 查詢中涉及到的表 Explain: 示例

 

用法示例 
(1)直接分析慢查詢文件: 
pt-query-digest [參數] /var/lib/mysql/log/slow.log > slow.report 
(2)分析最近12小時內的查詢: 
pt-query-digest --since=12h /var/lib/mysql/log/slow.log > slow_report2.log 
(3)分析指定時間范圍內的查詢: 
pt-query-digest /var/lib/mysql/log/slow.log --since '2014-04-17 09:30:00' --until '2014-04-17 10:00:00'> > slow_report3.log 
(4)分析指含有select語句的慢查詢 
pt-query-digest--filter '$event->{fingerprint} =~ m/^select/i' /var/lib/mysql/log/slow.log> slow_report4.log 
(5) 針對某個用戶的慢查詢 
pt-query-digest--filter '($event->{user} || "") =~ m/^root/i' /var/lib/mysql/log/slow.log> slow_report5.log 
(6) 查詢所有所有的全表掃描或full join的慢查詢 
pt-query-digest--filter '(($event->{Full_scan} || "") eq "yes") ||(($event->{Full_join} || "") eq "yes")' slow.log> slow_report6.log
(7)把查詢保存到query_review表 
pt-query-digest --user=root –password=abc123 --review h=localhost,D=test,t=query_review--create-review-table /var/lib/mysql/log/slow.log
(8)把查詢保存到query_history表 
pt-query-digest --user=root –password=abc123 --review h=localhost,D=test,t=query_ history--create-review-table /var/lib/mysql/log/slow.log
pt-query-digest --user=root –password=abc123--review h=localhost,D=test,t=query_history--create-review-table /var/lib/mysql/log/slow.log
(9)通過tcpdump抓取mysql的tcp協議數據,然后再分析 
tcpdump -s 65535 -x -nn -q -tttt -i any -c 1000 port 3306 > mysql.tcp.txt 
pt-query-digest --type tcpdump mysql.tcp.txt> slow_report9.log 
(10)分析binlog 
mysqlbinlog mysql-bin.000093 > mysql-bin000093.sql 
pt-query-digest --type=binlog mysql-bin000093.sql > slow_report10.log 
(11)分析general log 
pt-query-digest --type=genlog localhost.log > slow_report11.log

如何通過慢查詢日志找出有問題的SQL

  1. 查詢次數多且每次查詢時間較長的sql 
    通常為pt-query-digest分析的前個查詢
  2. IO大的SQL(數據庫的瓶頸之一是IO) 
    注意pt-query-digest分析結果中 Rows examine項
  3. 未命中索引的SQL 
    注意pt-query-digest分析結果中 Rows examine很大 但是Rows Sent很小

通過explain分析SQL

數據庫會先進行計划分析,再進行查詢。執行計划從側面反映了SQL的執行效率,那么通過explain查看並分析執行計划是非常有必要的

mysql> explain select * from lm_d_plan group by fk_deptno; +----+-------------+-----------+-------+---------------+-----------+---------+------+------+-------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-----------+-------+---------------+-----------+---------+------+------+-------+ | 1 | SIMPLE | lm_d_plan | index | FK_deptNo | FK_deptNo | 99 | NULL | 5416 | NULL | +----+-------------+-----------+-------+---------------+-----------+---------+------+------+-------+ 1 row in set (0.00 sec)

 

explain返回的各列含義 
select_type 
SIMPLE:簡單SELECT(不使用UNION或子查詢等) 
PRIMARY:我的理解是最外層的select 
UNION : UNION中的第二個或后面的SELECT語句 
DEPENDENT UNION : UNION中的第二個或后面的SELECT語句,取決於外面的查詢 
UNION RESULT : UNION的結果 
SUBQUERY : 子查詢中的第一個SELECT 
DEPENDENT SUBQUERY : 子查詢中的第一個SELECT,取決於外面的查詢 
DERIVED:派生表的SELECT(FROM子句的子查詢)

table: 顯示這條sql涉及到的那些表,有時不是真實的表名字,看到的是derivedx(x是個數字,我的理解是第幾步執行的結果) 
type : 這是重要的列,顯示連接是那種類型,從最好到最壞依次是:const(常數查找,主鍵或唯一索引查找),system:(這是const聯接類型的一個特例。表僅有一行滿足條件),eq_reg: (范圍查找,主鍵或者索引范圍的查找),ref(常見連接查詢,一個表是基於某個索引的查找),range(基於索引范圍的查找),index(索引的掃描),all(表掃描) 
possible_keys : 顯示可能用到的索引。如果為空,沒有可以用到的索引 
key : 實際使用的索引,如果為空則沒有使用索引 
key_len : 使用索引的長度,在不損失精確性的情況下,長度越短越好 
ref: 顯示索引的哪一列被使用了。如果可能的話,是一個常數。 
rows : mysql認為必須檢查的用來返回數據的行數 
Extra : 擴展列,需要注意的返回值: 
Using filesort 看到這個就需要優化了。mysql需要通過額外的步驟發現如何對返回行進行排序,他需要根據連接類型以及存儲排序鍵值和匹配條件的所有行的行指針進行所有行排序,通常發生order by操作上。 
Using temporary 看到這個就需要優化了,mysql需要建立臨時表來存儲結果,這通常發生在對不同的列集進行order 
by操作上,而不是group by操作。

具體的實例MAX()優化

案例1:我們需要lm_d_plan的最后創建時間是什么

mysql> select max(createtime) from lm_d_plan; +-----------------+ | max(createtime) | +-----------------+ | 20150528143338 | +-----------------+ 1 row in set (0.11 sec) mysql> explain select max(createtime) from lm_d_plan; +----+-------------+-----------+------+---------------+------+---------+------+-------+-------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-----------+------+---------------+------+---------+------+-------+-------+ | 1 | SIMPLE | lm_d_plan | ALL | NULL | NULL | NULL | NULL | 55562 | NULL | +----+-------------+-----------+------+---------------+------+---------+------+-------+-------+ 1 row in set (0.00 sec)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

select_type 這是一個簡單的查詢 
table 查詢的是lm_d_plan 表 
type ALL說明是全表掃描 
rows 需要掃描的行數55562 
通過以上信息,顯然是一個低效的sql,隨着數據量增大,它的IO也隨之增大,很可能拖慢整個服務器,說明肯定是需要優化的。 
策略:通常情況下在 createtime上建立索引

mysql> create index createime on lm_d_plan(createtime); Query OK, 0 rows affected (0.65 sec) Records: 0 Duplicates: 0 Warnings: 0 mysql> explain select max(createtime) from lm_d_plan; +----+-------------+-------+------+---------------+------+---------+------+------+------------------------------+ | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra | +----+-------------+-------+------+---------------+------+---------+------+------+------------------------------+ | 1 | SIMPLE | NULL | NULL | NULL | NULL | NULL | NULL | NULL | Select tables optimized away | +----+-------------+-------+------+---------------+------+---------+------+------+------------------------------+ 1 row in set (0.00 sec) mysql> select max(createtime) from lm_d_plan; +-----------------+ | max(createtime) | +-----------------+ | 20150528143338 | +-----------------+ 1 row in set (0.00 sec)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

Extra 返回 Select tables optimized away ,意思就是並不需要查詢具體的表,直接通過查詢索引就可以完成。因為索引是順序排列的,通過索引的統計信息就可以獲得最大的createime。這也是個覆蓋索引,完全通過索引的信息就可以完成查詢。

案例2:我們需要找出各部門lm_c_dept的提報的需求計划lm_d_plan是多少

mysql> SELECT d.deptName,count(*) from lm_d_plan p INNER JOIN lm_c_dept d ON d.deptNo = p.FK_deptNo GROUP BY p.FK_deptNo; +--------------------------------+----------+ | deptName | count(*) | +--------------------------------+----------+ | xx一部 | 1573 | | xx板塊業務部 | 282 | | yy板塊業務二部 | 781 | | aa塊業務部 | 4753 | | cc塊業務部 | 12252 | | dd板塊業務部 | 6827 | | xx設備業務部 | 4442 | | cccc物資業務部 | 26196 | +--------------------------------+----------+ 8 rows in set (0.09 sec) mysql> explain SELECT d.deptName,count(*) from lm_d_plan p INNER JOIN lm_c_dept d ON d.deptNo = p.FK_deptNo GROUP BY p.FK_deptNo\G *************************** 1. row *************************** id: 1 select_type: SIMPLE table: d(可能是表中間有下划線顯示有問題lm_c_dept ) type: ALL possible_keys: deptNo key: NULL key_len: NULL ref: NULL rows: 11 Extra: Using temporary; Using filesort select_type 這是一個簡單的查詢 table 查詢的是lm_c_dept 表 type ALL說明是全表掃描,因為沒有where條件,表掃描也可以說是正常的。 possible_keys 可能用到的索引是deptNo rows 需要掃描的行數11 Extra 既使用了臨時表,也用了文件排序,這是效率極低的。 *************************** 2. row *************************** id: 1 select_type: SIMPLE table: p(可能是表中間有下划線顯示有問題lm_d_plan ) type: ref possible_keys: FK_deptNo key: FK_deptNo key_len: 99 ref: lms.d.deptNo(這里應該是lms.lm_c_dept.deptNo) rows: 3968 Extra: Using index select_type 這是一個簡單的查詢 table 查詢的是lm_d_plan 表 type ref常見連接查詢,一個表是基於索引的查找 possible_keys 可能用到的索引是FK_deptNo key 使用的索引是FK_deptNo key_len 索引的長度是99 ref lms.lm_c_dept.deptNo這列索引被使用了 rows 需要掃描的行數3968 Extra 使用索引 2 rows in set (0.00 sec)

 

策略:通過以上分析,顯然最需要優化的地方是臨時表和文件排序了,因為這種低效率查詢會直接對服務器產生極大的資源消耗,同時影響整個服務器性能。通過優化sql語句來改善這種情況。

mysql> explain SELECT d.deptName,c.ct FROM lm_c_dept d INNER JOIN(SELECT p.FK_deptNo,count(*) ct from lm_d_plan p GROUP BY p.FK_deptNo) c ON d.deptNo = c.FK_deptNo\G *************************** 1. row *************************** id: 1 select_type: PRIMARY table: d(可能是表中間有下划線顯示有問題lm_c_dept ) type: ALL possible_keys: deptNo key: NULL key_len: NULL ref: NULL rows: 11 Extra: NULL *************************** 2. row *************************** id: 1 select_type: PRIMARY table: <derived2> type: ref possible_keys: <auto_key0> key: <auto_key0> key_len: 99 ref: lms.d.deptNo rows: 555 Extra: NULL *************************** 3. row *************************** id: 2 select_type: DERIVED table: p(可能是表中間有下划線顯示有問題lm_d_plan ) type: index possible_keys: FK_deptNo key: FK_deptNo key_len: 99 ref: NULL rows: 55562 Extra: Using index 3 rows in set (0.00 sec)

 

  • 34
  • 35

通過執行計划可以看出Extra已經沒有Using filesort 和 Using temporary臨時表和使用文件排序已經改善。執行結果也加從0.09 sec 加快到0.02 sec,如果有條件,最好在子查詢里增加過濾,而不是外查詢。

索引優化

如何選擇合適的列建立索引

  1. 從where從句中,group by從句中,order by從句中,on從句中,select列中,還可以建立覆蓋索引(也就是指索引包含所有查詢的列,直接查索引就可以完成任務的),
  2. 索引字段越小越好,因為mysql存儲是已頁為單位的,如果從單頁中獲取更多的結果,減少IO操作,那么就起到了積極的作用。
  3. 離散程度越高的列放在聯合索引的前面,因為離散度越大的列可選擇性越高,可以通過count統計函數查看離散度
mysql> select count(distinct cartCode),count(distinct cartlineNum) from lm_d_plan; +--------------------------+-----------------------------+ | count(distinct cartCode) | count(distinct cartlineNum) | +--------------------------+-----------------------------+ | 40782 | 341 | +--------------------------+-----------------------------+ 1 row in set (0.14 sec)

 

由此可見,如果將cartCode和cartlineNum建立聯合索引,cartCode應該放到前面。 
create index cartCode_cartlineNum on lm_d_plan(cartCode,cartlineNum);

SQL及索引優化

索引的維護和優化之重復及冗余索引 
重復索引是最同一列多次建立索引,比如在primay key列建立唯一索引,因為primay key已經是主鍵索引了。再建立唯一索引就索引重復了。 
冗余索引是指多個索引的前綴列相同,或者聯合索引中包含的主鍵索引。(Innodb會在每個索引的后面附加主鍵索引。) 
常用索引操作 
查看索引 
show index from tablename; 
刪除索引,其中,在前面的兩條語句中,都刪除了table_name中的索引index_name。而在最后一條語句中,只在刪除PRIMARY KEY索引中使用,因為一個表只可能有一個PRIMARY KEY索引,因此不需要指定索引名。

drop index index_name on table_name ; alter table table_name drop index index_name ; alter table table_name drop primary key ;
  • 1
  • 2
  • 3

創建索引:

CREATE [UNIQUE|FULLTEXT|SPATIAL] INDEX index_name [USING index_type] ON tbl_name (index_col_name,...) index_col_name: col_name [(length)] [ASC | DESC]
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

使用alter的方法創建索引

alter table table_name add index index_name (column_list) ; alter table table_name add unique (column_list) ; alter table table_name add primary key (column_list) ;
  • 1
  • 2
  • 3

對於CHAR和VARCHAR列,只用一列的一部分就可創建索引。創建索引時,使用col_name(length)語法,對前綴編制索引。前綴包括每列值的前length個字符。BLOB和TEXT列也可以編制索引,但是必須給出前綴長度。

此處展示的語句用於創建一個索引,索引使用列名稱的前5個字符。 
create index itemsCode on lm_d_plan(itemsCode(5)); 
因為多數名稱的前10個字符通常不同,所以此索引不會比使用列的全名創建的索引速度慢很多。另外,使用列的一部分創建索引可以使索引文件大大減小,從而節省了大量的磁盤空間,有可能提高INSERT操作的速度。

前綴最長為255字節。對於MyISAM和InnoDB表,前綴最長為1000字節。注意前綴的限長以字節計,而CREATE INDEX語句中的前綴長度指的是字符的數目。對於使用多字節字符集的列,在指定列的前綴長度時,要考慮這一點。

在MySQL中: 
· 只有當您正在使用MyISAM, InnoDB或BDB表類型時,您可以向有NULL值的列中添加索引。 
· 只有當您正在使用MyISAM, BDB或InnoDB表類型時,您可以向BLOB或TEXT列中添加索引。

一個index_col_name規約可以以ASC或DESC為結尾。這些關鍵詞將來可以擴展,用於指定遞增或遞減索引值存儲。目前,這些關鍵詞被分析,但是被忽略;索引值均以遞增順序存儲。

部分儲存引擎允許在創建索引時指定索引類型。index_type指定語句的語法是USING type_name。不同的儲存引擎所支持的type_name值已顯示在下表中。如果列有多個索引類型,當沒有指定index_type時,第一個類型是默認值。

存儲引擎 允許的索引類型
MyISAM BTREE
InnoDB BTREE
MEMORY/HEAP HASH, BTREE

示例:

CREATE TABLE lookup (id INT) ENGINE = MEMORY; CREATE INDEX id_index USING BTREE ON lookup (id);
  • 1
  • 2

TYPE type_name可以作為USING type_name的同義詞,用於指定索引類型。但是,USING是首選的格式。另外,在索引規約語法中,位於索引類型前面的索引名稱不能使用TYPE。這是因為,與USING不同,TYPE不是保留詞,因此會被認為是一個索引名稱。 
如果您指定的索引類型在給定的儲存引擎中不合法,但是有其它的索引類型適合引擎使用,並且不會影響查詢功能,則引擎應使用此類型。 
FULLTEXT索引只能對CHAR, VARCHAR和TEXT列編制索引,並且只能在MyISAM表中編制。 
SPATIAL索引只能對空間列編制索引,並且只能在MyISAM表中編制。 
索引的維護和優化之pt-duplicate-key-checker 
使用方法:pt-duplicate-key-checker -uroot -p 'xxxx' -h 127.0.0.1 
[root@localhost ~]# pt-duplicate-key-checker -uroot -p ‘123456’ -h localhost

# ########################################################################
# lms.lm_d_plan                                                           
# ########################################################################

# oldPlanNum is a left-prefix of oldPlanNum_oldPlanlineNum
oldPlanNum索引 已經是oldPlanNum_oldPlanlineNum聯合索引的左前綴了。也就是說查詢oldPlanNum可以直接命中oldPlanNum_oldPlanlineNum聯合索引,而毋需再創建oldPlanNum 索引了。
# Key definitions:
關鍵的定義
#   KEY `oldPlanNum` (`oldPlanNum`),
#   KEY `oldPlanNum_oldPlanlineNum` (`oldPlanNum`,`oldPlanlineNum`),
# Column types:
列信息
#     `oldplannum` varchar(20) default null comment '原始號'
#     `oldplanlinenum` int(4) default null comment '原始行號'
# To remove this duplicate index, execute:
建議是remove這個重復的索引,執行以下命令
ALTER TABLE `lms`.`lm_d_plan` DROP INDEX `oldPlanNum`; 同樣的錯誤類型如下: # cartCode is a left-prefix of cartCode_cartlineNum # Key definitions: # KEY `cartCode` (`cartCode`), # KEY `cartCode_cartlineNum` (`cartCode`,`cartlineNum`), # Column types: # `cartcode` varchar(20) default null comment '?????' # `cartlinenum` int(4) default null comment '?????' # To remove this duplicate index, execute: ALTER TABLE `lms`.`lm_d_plan` DROP INDEX `cartCode`; # ######################################################################## # Summary of indexes # ######################################################################## # Size Duplicate Indexes 7000812 # Total Duplicate Indexes 2 # Total Indexes 155

 

通過pt-dulication-key-checker可以高效的優化數據庫的索引。 
索引的維護和優化之刪除無用索引pt-index-usage 
pt-index-usage有空補充,大家有興趣可以自己研究一下。

數據庫結構優化

選擇合適的數據類型

  1. 可以存下我們數據的最小數據類型
  2. 使用簡單的數據類型,int類型要比varchar類型,mysql處理更簡單
  3. 盡量使用not null字段,因為innodb存儲類型,盡量設置默認值
  4. 盡量少用text類型,非用不可的話需要考慮是否分表的方式解決。 
    范例如下: 
    用int來存儲時間,from_unixtime(),unix_timestamp()兩個函數進行轉換。
insert into tablename(timestr) values( unix_timestamp('2015-05-05 20:20:00')); select from_unixtime(timestr) from tablename;
  • 1
  • 2

**用bigint存儲ip地址,利用inet_aton(),inet_ntoa()兩個函數進行轉換。

insert into tablename(ipaddr) values( inet_aton('192.168.1.1')); select inet_ntoa(ipaddr) from tablename;
  • 1
  • 2

表的范式優化

表的范式化

范式化是指數據庫設計遵循的設計規范,目前說的范式化一般是指第三設計范式,也就是要求表中非關鍵字段對任意候選關鍵字段不存在傳遞函數依賴則符合第三范式。 
示例:

商品名稱 價格 重量 有效期 分類 分類描述
可樂 3.5 600ml 2015-09-09 酒水飲料 碳酸飲料
黃瓜 1.0 100g   生鮮食品 蔬菜

存在以下傳遞函數依賴管理 
商品名稱—>分類—>分類描述 
很顯然如果商品名稱是關鍵字段(任意候選關鍵字段),那么非關鍵字段分類描述依賴分類依賴商品名稱。也就是說分類描述傳遞函數依賴關鍵字段商品名稱。 
進行標准第三范式化修改后: 
Table 1

商品名稱 價格 重量 有效期
可樂 3.5 600ml 2015-09-09
黃瓜 1.0 100g  

Table 2

分類 分類描述
酒水飲料 碳酸飲料
生鮮食品 蔬菜

Table 3

商品名稱 分類
可樂 酒水飲料
黃瓜 生鮮食品

表的反范式化

反范式化是為了查詢效率的考慮,把原本符合第三范式的表適當的增加冗余,以達到優化查詢的目的,反范式化是一種以空間換取時間的策略。 
示例:

訂單表:訂單ID,訂單編號,用戶ID,下單時間,訂單狀態
用戶表:用戶ID,姓名,電話,地址
訂單商品表:訂單ID,商品ID,訂單數量,商品價格
商品表:商品ID,名稱,描述
  • 1
  • 2
  • 3
  • 4

需求描述:需要給客戶展現訂單的整體概況信息,包括訂單,價格,商品信息,構造的SQL如下:

SELECT a.訂單編號,b.姓名,b.電話,b.地址,SUM(c.訂單數量 * c.商品價格) as 訂單價格 FROM 訂單表 a LEFT JOIN 用戶表 b ON a.用戶ID = b.用戶ID LEFT JOIN 訂單商品表 c ON a.訂單ID = c.訂單ID
  • 1
  • 2
  • 3
  • 4
  • 5

我們要查詢這些信息,至少關聯三張表,表結構就是這樣的。 
如果進行一些反范式化設計,將會大大改善上述查詢效率極低的SQL。

訂單表:訂單ID,訂單編號,用戶ID,下單時間,訂單狀態,訂單價格,姓名,電話,地址
用戶表:用戶ID,姓名,電話,地址
訂單商品表:訂單ID,商品ID,訂單數量,商品價格
商品表:商品ID,名稱,描述
  • 1
  • 2
  • 3
  • 4

同樣的需求,構造SQL如下:

SELECT a.訂單編號,a.姓名,a.電話,a.地址,a.訂單價格 FROM 訂單表 a
  • 1
  • 2
  • 3

顯然,在優化數據庫的時候,進行一些適當的反范式化,會在很大程度上提升執行效率。

表的垂直拆分

所謂垂直拆分就是把原來一個很多列的表拆分成多個表,解決表的寬度問題,通常垂直拆分可以按以下原則進行: 
1. 把不常用的字段單獨存放在一張表中 
2. 把大字段獨立存放在一張表中 
3. 把經常使用的字段存放到一起

表的水平拆分

水平拆分是為了解決單表數據量過大的問題,水平拆分每一個表結構都是一樣的。 
如何將數據平均插入到N張表呢? 
常用的水平拆分的方法: 
1. 對ID進行hash運算,如果要拆分成5個表,則使用mod(ID,5)(取模)取出0-4個值,取模后0插0表,取1插1表 
2. 針對不同的hashID,把數據存儲到不同的表中 
拆分后所面臨的挑戰: 
1. 跨分區進行查詢 
2. 統計及后台報表操作。 
建議:前后台表進行拆開,前台考慮效率所以進行水平拆分,后台考慮方便完整的數據統計,單獨生成統計及報表所用的表

系統配置優化

操作系統配置優化

數據庫是基於操作系統之上的,目前大多數mysql都運行在linux上,所以對操作系統的一些配置也會影響到mysql 的性能,下面就列出常用的優化配置。 
網絡方面的配置,要修改/etc/sysctl.conf文件 
1. 增加TCP支持的隊列數 
net.ipv4.tcp_max_syn_backlog=65535 
2. 減少斷開連接時間,資源回收,由於TCP/IP是有三個狀態的,為了加快timewait狀態回收,優化以下參數。

net.ipv4.tcp_max_tw_buckets=8000 net.ipv4.tcp_tw_reuse=1 net.ipv4.tcp_tw_recycle=1 net.ipv4.tcp_fin_timeout=10
  • 1
  • 2
  • 3
  • 4

優化打開文件數,因為mysql的表都是已文件格式存儲的,對innodb類型的查詢都會打開表文件,所以增加文件打開數在一定程度上提升了mysql查詢效率。可以使用ulimit -a查看目錄的各限制,可以修改/etc/security/limits.conf 文件,增加以下內容增加文件打開數限制

* soft nofile 65535 * hard nofile 65535
  • 1
  • 2

修改前后對比:

[root@localhost ~]# ulimit -a ... open files (-n) 1024 ... [root@localhost ~]# ulimit -a ... open files (-n) 65535 ...
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

除此以外最好在mysql所在服務器關閉iptables selinux等防火牆軟件,通過硬件防火牆等方式解決安全問題。

MYSQL配置優化

mysql可以通過啟動時指定配置參數和使用配置文件進行配置,在大多數情況下配置文件存儲在/etc/my.cnf或是/etc/mysql/my.cnf。windows一般在mysql目錄中my.ini文件。mysql查找配置文件的順序可以通過以下命令獲取。 
$ /usr/sbin/mysqld --verbose --help | grep -A 1 'Default options' 
注意,如果多個位置存在配置文件,則后面的會覆蓋前面的配置。

mysql常用配置優化

innodb_buffer_poor_size: 
非常重要的參數,用來配置Innodb的緩沖池,如果數據庫中只有innodb表,則推薦配置為總內存的75%。

mysql> SELECT ENGINE,ROUND(SUM(data_length + index_length)/1024/1024,1) as 'Total MB' FROM INFORMATION_SCHEMA.TABLES WHERE table_schema not in ("information_schema","performance_schema" ) GROUP BY ENGINE; +--------+----------+ | ENGINE | Total MB | +--------+----------+ | CSV | 0.0 | | InnoDB | 79.3 | | MyISAM | 0.7 | +--------+----------+ 3 rows in set (0.59 sec)
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

innodb_buffer_poor_size >= Total MB(數據+索引大小),如果達不到要求,設置盡可能的大就可以。

innodb_buffer_poor_instances:(mysql5.5+以上才有) 
可以控制緩沖池個數,默認情況下只有一個緩沖池,mysql中有些操作需要獨占使用緩沖池,那么就可能造成阻塞。如果分成多分可以,一般可以分為4到8份,大小=總緩沖大小/分數。 
innodb_log_buffer_size: 
innodb log 緩沖大小,由於日志最長每秒種就會刷新,所以不需要配置多大,只要能夠存下1秒鍾事務數據就可以了。 
innodb_flush_log_at_trx_commit:(據說很管用) 
關鍵參數,對innodb磁盤IO影響很大,默認為1,可以設置0,1,2三個值,一般建議為2,但如果數據安全性較高,則設置為1.

抱怨Innodb比MyISAM慢 100倍?那么你大概是忘了調整這個值。默認值1的意思是每一次事務提交或事務外的指令都需要把日志寫入(flush)硬盤,這是很費時的。特別是使用電 池供電緩存(Battery backed up cache)時。設成2對於很多運用,特別是從MyISAM表轉過來的是可以的,它的意思是不寫入硬盤而是寫入系統緩存。日志仍然會每秒flush到硬 盤,所以你一般不會丟失超過1-2秒的更新。設成0會更快一點,但安全方面比較差,即使MySQL掛了也可能會丟失事務的數據。而值2只會在整個操作系統 掛了時才可能丟數據。

innodb_read_io_threads 和 innodb_write_io_threads :(mysql5.5+以上可調整) 
決定innodb讀寫IO的進程數量,默認為4。可根據服務器具體情況設置 
innodb_file_per_table:建議設為ON。 
關鍵參數,控制innodb每個表獨立使用的表空間,默認為OFF,也就是所有表都會建立在共享表空間中。共享表空間IO成為瓶頸,順序寫入的時候,共享表空間只有一個文件,並發寫入的時候效率很低。共享表空間是無法單獨收縮的,當刪除一個很大的數據后,之后把所有數據導出后導入才可以收縮。 
innodb_stats_on_metadata:默認為ON,建議OFF 
決定mysql在什么情況下刷innodb表的統計信息。優化器在工作中需要通過統計信息獲取索引等系想你,保證優化器使用到正確的索引。但是如果設為ON,在進行show,select等操作時都會進行不必要的刷新,所以建議在修改數據庫后人為進行刷新。

第三方配置工具

PERCONA CONFIGURATION WIZARD FOR MYSQL 
http://tools.percona.com/wizard 
介紹略,大家可以自己研究

服務器硬件優化

如何選擇CPU

  1. mysql很多操作都使用單核cpu
  2. mysql對cpu核數支持並不是越多越快,建議不要超過32核。 
    建議選擇單核頻率高的cpu

Disk IO 優化

建議RAID1+0 :就是RAID1和RAID0結合,兼顧性能和數據安


免責聲明!

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



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