1. 程式人生 > 其它 >秒殺 Postman 了,Apifox 太香了!

秒殺 Postman 了,Apifox 太香了!

為何對慢SQL進行治理
從資料庫角度看:每個SQL執行都需要消耗一定I/O資源,SQL執行的快慢,決定資源被佔用時間的長短。假設總資源是100,有一條慢SQL佔用了30的資源共計1分鐘。那麼在這1分鐘時間內,其他SQL能夠分配的資源總量就是70,如此迴圈,當資源分配完的時候,所有新的SQL執行將會排隊等待。
從應用的角度看:SQL執行時間長意味著等待,在OLTP應用當中,使用者的體驗較差

  • 治理的優先順序上 :master資料庫->slave資料庫
  • 目前資料庫基本上都是讀寫分離架構,讀在從庫(slave)上執行,寫在主庫(master)上執行。
  • 由於從庫的資料都是從主庫上覆制過去的,主庫等待較多的,會加大與從庫的複製時延。
  • 執行次數多的SQL優先治理
  • 如果有一類SQL高併發集中訪問某一張表,應當優先治理。

Mysql執行原理
綠色部分為SQL實際執行部分,可以發現SQL執行2大步驟:解析,執行。
以com_query為例,dispatch_command會先呼叫alloc_query為query buffer分配記憶體,之後呼叫解析
解析:詞法解析->語法解析->邏輯計劃->查詢優化->物理執行計劃
檢查是否存在可用查詢快取結果,如果沒有或者快取失效,則呼叫mysql_execute_command執行
執行:檢查使用者、表許可權->表上加共享讀鎖->取資料到query cache->取消共享讀鎖

影響因素
如不考慮MySQL資料庫的引數以及硬體I/O的影響, 則影響SQL執行效率的因素主要是I/O和CPU的消耗量
總結:

  • 資料量:資料量越大需要的I/O次數越多
  • 取資料的方式 :資料在快取中還是在磁碟上;是否可以通過索引快速定址
  • 資料加工的方式:排序、子查詢等,需要先把資料取到臨時表中,再對資料進行加工;增加了I/O,且消耗大量CPU資源

解決思路

  1. 將資料存放在更快的地方:如果資料量不大,變化頻率不高,但訪問頻率很高,此時應該考慮將資料放在應用端的快取當中或者Redis這樣的快取當中,以提高存取速度。如果資料不做過濾、關聯、排序等操作,僅按照key進行存取,且不考慮強一致性需求,也可考慮選用NoSQL資料庫。
  2. 適當合併I/O:分別執行select c1 from t1與select c2 from t1,與執行select c1,c2 from t1相比,後者開銷更小。合併時也需要考慮執行時間的增加。
  3. 利用分散式架構:在面對海量的資料時,通常的做法是將資料和I/O分散到多臺主機上去執行。

案例 (mysql資料高CPU問題定位和優化)
開啟慢查詢

## 開關
slow_query_log=1 
## 檔案位置及名字 
slow_query_log_file=/data/mysql/slow.log
## 設定慢查詢時間
long_query_time=0.4
## 沒走索引的語句也記錄
log_queries_not_using_indexes

vim /etc/my.cnf
slow_query_log=1 
slow_query_log_file=/data/mysql/slow.log
long_query_time=0.1
log_queries_not_using_indexes

mysql> select @@long_query_time;    # 預設十秒才記錄慢日誌
  
mysql> show variables like 'slow_query_log%';
mysql>  show variables like 'long%';
mysql>  show variables like '%using_indexes%';

查詢一張沒有索引的100w資料的表
五十個併發查詢十t100w表,

mysqlslap --defaults-file=/etc/my.cnf \
--concurrency=50 --iterations=1 --create-schema='oldboy' \
--query="select * from oldboy.t_100w where k2='FGCD'" engine=innodb \
--number-of-queries=10 -uroot -pZHOUjian.22 -verbose

mysqlslap: [Warning] Using a password on the command line interface can be insecure.
Benchmark
    Running for engine rbose
    Average number of seconds to run all queries: 26.447 seconds
    Minimum number of seconds to run all queries: 26.447 seconds
    Maximum number of seconds to run all queries: 26.447 seconds
    Number of clients running queries: 50
    Average number of queries per client: 0

檢視系統資源消耗

mysql檢視連線執行緒
1 . 通過 show processlist; 或 show full processlist; 命令檢視當前執行的查詢,如下圖所示:

“Sending data”官網解釋:
The thread is reading and processing rows for a SELECT statement, and sending data to the client. Because operations occurring during this state tend to perform large amounts of disk access (reads), it is often the longest-running state over the lifetime of a given query.
狀態的含義,原來這個狀態的名稱很具有誤導性,所謂的“Sending data”並不是單純的傳送資料,而是包括“收集 + 傳送 資料”。
體現在:
1.沒有使用索引
2.mysql索引表結構,要是沒有使用主鍵查詢的話,需要進行回表操作,在返回客戶端。3.返回的行數太多,需要頻繁io互動
Copying to tmp table,Copying to tmp table on disk:官網解釋:
Copying to tmp table The server is copying to a temporary table in memory. Copying to tmp table on disk The server is copying to a temporary table on disk. The temporary result set has become too large
整體來說生成臨時表記憶體空間,落磁碟臨時表,臨時表使用太
體現在 多表join,buffer_size設定不合理,alter algrithem copy等方式
Sorting result:
For a SELECT statement, this is similar to Creating sort index, but for nontemporary tables.
結果集使用大的排序,基本上SQL語句上order by 欄位上沒有索引
上述的情況大量堆積,就會發現CPU飆升的情況,當然也有併發量太高的情況。
優化方向:
1.新增索引,組合索引,堅持2張表以內的join方式 這樣查詢執行成本就會大幅減少。2.隱私轉換避免,系統時間函式的呼叫避免
3.相關快取大小設定:join_buffer_size,sort_buffer_size,read_buffer_size ,read_rnd_buffer_size ,tmp_table_size。
在緊急情況下,無法改動下,通過引數控制併發度,執行時間 innodb_thread_concurrency ,max_execution_time都是有效的臨時控制手段。
檢視慢日誌

mysql> show variables like 'slow_query_log%';
+---------------------+----------------------+
| Variable_name       | Value                |
+---------------------+----------------------+
| slow_query_log      | ON                   |
| slow_query_log_file | /data/mysql/slow.log |
+---------------------+----------------------+
2 rows in set (0.00 sec)

分析慢日誌

[root@master1 ~]# mysqldumpslow -s c -t 10 /data/mysql/slow.log

Reading mysql slow query log from /data/mysql/slow.log
Count: 50  Time=27.10s (1354s)  Lock=0.42s (20s)  Rows=270.0 (13500), root[root]@localhost
  select * from oldboy.t_100w where k2='S'

Count: 3  Time=0.68s (2s)  Lock=0.00s (0s)  Rows=262.0 (786), root[root]@localhost
  select * from t_100w where k2='S'

Died at /usr/bin/mysqldumpslow line 167, <> chunk 53.

加索引

alter table t_100w add index idx(k2);

[root@master1 ~]# mysqlslap --defaults-file=/etc/my.cnf --concurrency=50 --iterations=1 --create-schema='oldboy' --query="select * from oldboy.t_100w where k2='FGCD'" engine=innodb --number-of-queries=10 -uroot -pZHOUjian.22 -verbose
mysqlslap: [Warning] Using a password on the command line interface can be insecure.
Benchmark
    Running for engine rbose
    Average number of seconds to run all queries: 0.075 seconds
    Minimum number of seconds to run all queries: 0.075 seconds
    Maximum number of seconds to run all queries: 0.075 seconds
    Number of clients running queries: 50
    Average number of queries per client: 0

五千個併發查詢一百t100w表,

[root@master1 ~]# mysqlslap --defaults-file=/etc/my.cnf --concurrency=5000 --iterations=1 --create-schema='oldboy' --query="select * from oldboy.t_100w where k2='FGCD'" engine=innodb --number-of-queries=100 -uroot -pZHOUjian.22 -verbose
mysqlslap: [Warning] Using a password on the command line interface can be insecure.
Benchmark
    Running for engine rbose
    Average number of seconds to run all queries: 6.285 seconds
    Minimum number of seconds to run all queries: 6.285 seconds
    Maximum number of seconds to run all queries: 6.285 seconds
    Number of clients running queries: 5000
    Average number of queries per client: 0

優化方向和注意點
cpu優化方向

  • 對於MySQL硬體環境資源,建議CPU起步8核開始,SSD硬碟;
  • 索引 ,合理設計表結構,優化SQL。
  • 讀寫分離,將對資料一致性不敏感的查詢轉移到只讀例項上,分擔主庫壓力。
  • 對於由應用負載高導致的 CPU 使用率高的狀況,從應用架構、例項規格等方面來解決。
  • 使用 Memcache 或者 Redis快取技術,儘量從快取中獲取常用的查詢結果,減輕資料庫的壓力。

mysql效能測試優化方向

  • 系統引數:磁碟排程算,SHELL資源限制,numa架構,檔案系統ext4,exfs
  • 重新整理mysql log相關重新整理引數:臨近頁(innodb_flush_neighbors)
  • 死鎖檢查機制(innodb_deadlock_detect),
  • 雙1重新整理:sync_binlog,innodb_flush_log_at_trx_commit
  • 併發引數: innodb_buffer_pool_instances, innodb_thread_concurrency 等
  • 因為一些伺服器的特性,導致cpu通道 和 記憶體協調存在一些問題,導致cpu效能上去得案例也存在

不走索引的情況(開發規範)
1.沒有查詢條件,或者查詢條件沒有建立索引

select * from tab;       全表掃描。
select  * from tab where 1=1;
在業務資料庫中,特別是資料量比較大的表。
是沒有全表掃描這種需求。
1、對使用者檢視是非常痛苦的。
2、對伺服器來講毀滅性的。
(1)
select * from tab;
SQL改寫成以下語句:
select  * from  tab  order by  price  limit 10 ;    需要在price列上建立索引
(2)
select  * from  tab where name='zhangsan'          name列沒有索引
改:
1、換成有索引的列作為查詢條件
2、將name列建立索引

2.查詢結果集是原表中的大部分資料,應該是25%以上

查詢的結果集,超過了總數行數25%,優化器覺得就沒有必要走索引了。

假如:tab表 id,name    id:1-100w  ,id列有(輔助)索引
select * from tab  where id>500000;
如果業務允許,可以使用limit控制。
怎麼改寫 ?
結合業務判斷,有沒有更好的方式。如果沒有更好的改寫方案
儘量不要在mysql存放這個資料了。放到redis裡面。

3.索引本身失效,統計資料不真實

索引有自我維護的能力。
對於表內容變化比較頻繁的情況下,有可能會出現索引失效。
一般是刪除重建

現象:
有一條select語句平常查詢時很快,突然有一天很慢,會是什麼原因
select?  --->索引失效,,統計資料不真實
DML ?   --->鎖衝突

4.查詢條件使用函式在索引列上,或者對索引列進行運算,運算包括(+,-,*,/,! 等)

例子:
錯誤的例子:select * from test where id-1=9;
正確的例子:select * from test where id=10;
算術運算
函式運算
子查詢

5.隱式轉換導致索引失效.這一點應當引起重視.也是開發中經常會犯的錯誤.

這樣會導致索引失效. 錯誤的例子:
mysql> alter table tab add index inx_tel(telnum);
Query OK, 0 rows affected (0.03 sec)
Records: 0  Duplicates: 0  Warnings: 0
mysql>
mysql> desc tab;
+--------+-------------+------+-----+---------+-------+
| Field  | Type        | Null | Key | Default | Extra |
+--------+-------------+------+-----+---------+-------+
| id    | int(11)    | YES  |    | NULL    |      |
| name  | varchar(20) | YES  |    | NULL    |      |
| telnum | varchar(20) | YES  | MUL | NULL    |      |
+--------+-------------+------+-----+---------+-------+
3 rows in set (0.01 sec)
mysql> select * from tab where telnum='1333333';
+------+------+---------+
| id  | name | telnum  |
+------+------+---------+
|    1 | a    | 1333333 |
+------+------+---------+
1 row in set (0.00 sec)
mysql> select * from tab where telnum=1333333;
+------+------+---------+
| id  | name | telnum  |
+------+------+---------+
|    1 | a    | 1333333 |
+------+------+---------+
1 row in set (0.00 sec)
mysql> explain  select * from tab where telnum='1333333';
+----+-------------+-------+------+---------------+---------+---------+-------+------+-----------------------+
| id | select_type | table | type | possible_keys | key    | key_len | ref  | rows | Extra                |
+----+-------------+-------+------+---------------+---------+---------+-------+------+-----------------------+

|  1 | SIMPLE      | tab  | ref  | inx_tel      | inx_tel | 63      | const |    1 | Using index condition |
+----+-------------+-------+------+---------------+---------+---------+-------+------+-----------------------+
1 row in set (0.00 sec)
mysql> explain  select * from tab where telnum=1333333;
+----+-------------+-------+------+---------------+------+---------+------+------+-------------+
| id | select_type | table | type | possible_keys | key  | key_len | ref  | rows | Extra      |
+----+-------------+-------+------+---------------+------+---------+------+------+-------------+
|  1 | SIMPLE      | tab  | ALL  | inx_tel      | NULL | NULL    | NULL |    2 | Using where |
+----+-------------+-------+------+---------------+------+---------+------+------+-------------+
1 row in set (0.00 sec)
mysql> explain  select * from tab where telnum=1555555;
+----+-------------+-------+------+---------------+------+---------+------+------+-------------+
| id | select_type | table | type | possible_keys | key  | key_len | ref  | rows | Extra      |
+----+-------------+-------+------+---------------+------+---------+------+------+-------------+
|  1 | SIMPLE      | tab  | ALL  | inx_tel      | NULL | NULL    | NULL |    2 | Using where |
+----+-------------+-------+------+---------------+------+---------+------+------+-------------+
1 row in set (0.00 sec)
mysql> explain  select * from tab where telnum='1555555';
+----+-------------+-------+------+---------------+---------+---------+-------+------+-----------------------+
| id | select_type | table | type | possible_keys | key    | key_len | ref  | rows | Extra                |
+----+-------------+-------+------+---------------+---------+---------+-------+------+-----------------------+
|  1 | SIMPLE      | tab  | ref  | inx_tel      | inx_tel | 63      | const |    1 | Using index condition |
+----+-------------+-------+------+---------------+---------+---------+-------+------+-----------------------+
1 row in set (0.00 sec)
mysql>

6.<> ,not in 不走索引(輔助索引)

EXPLAIN  SELECT * FROM teltab WHERE telnum  <> '110';
EXPLAIN  SELECT * FROM teltab WHERE telnum  NOT IN ('110','119');

mysql> select * from tab where telnum <> '1555555';
+------+------+---------+
| id  | name | telnum  |
+------+------+---------+
|    1 | a    | 1333333 |
+------+------+---------+
1 row in set (0.00 sec)
mysql> explain select * from tab where telnum <> '1555555';

單獨的>,<,in 有可能走,也有可能不走,和結果集有關,儘量結合業務新增limit
or或in  儘量改成union
EXPLAIN  SELECT * FROM teltab WHERE telnum  IN ('110','119');
改寫成:
EXPLAIN SELECT * FROM teltab WHERE telnum='110'
UNION ALL
SELECT * FROM teltab WHERE telnum='119'

7.like "%_" 百分號在最前面不走

EXPLAIN SELECT * FROM teltab WHERE telnum LIKE '31%'  走range索引掃描
EXPLAIN SELECT * FROM teltab WHERE telnum LIKE '%110'  不走索引
%linux%類的搜尋需求,可以使用elasticsearch+mongodb 專門做搜尋服務的資料庫產品
郭慕榮部落格園