為何對慢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資源
解決思路
- 將數據存放在更快的地方。
- 如果數據量不大,變化頻率不高,但訪問頻率很高,此時應該考慮將數據放在應用端的緩存當中或者Redis這樣的緩存當中,以提高存取速度。如果數據不做過濾、關聯、排序等操作,僅按照key進行存取,且不考慮強一致性需求,也可考慮選用NoSQL數據庫。
- 適當合並I/O
- 分別執行select c1 from t1與select c2 from t1,與執行select c1,c2 from t1相比,后者開銷更小。
- 合並時也需要考慮執行時間的增加。
- 利用分布式架構
- 在面對海量的數據時,通常的做法是將數據和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 專門做搜索服務的數據庫產品
建立外鍵的規則
- 父子表中建立外鍵的字段數據類型需要一致
- 關聯父表時,父表的字段需要為父表
- 如果父表為聯合主鍵需要從第一個字段開始關聯
- 書寫問題
- 存儲引擎 只有innodb才支持外鍵,其他不行,否則外鍵建立不成功
建立有外鍵的父子表中不允許使用truncate table 只能使用delete進行刪除數據
父子表寫入數據時,如果想給子表中的外鍵寫入數據,需要保證寫入的數據在父表的主鍵列擁有該數據才能進行添加是否添加失敗,用來保證數據的一致性
外鍵在進行建立的過程中需要重新寫一行進行添加,不能跟在數據類型的后面進行建立
自增
# 自增,如果為某列設置自增列,插入數據時無需設置此列的值,默認將自增(表中只能有一個自增列)
create table tb1(
id int auto_increment primary key,
age int not null
)
show variables like '%auto_increment_%';
auto_increment_increment | 1 # 每次按照指定的數量自增
auto_increment_offset | 1 # 自增量的初始量
set auto_increment_increment=2;
創建表定義一對多關系
create table student(
id1 int auto_increment primary key,
name varchar(12) not null,
age int not null,
phone char(11)
);
create table student2(
id int auto_increment primary key,
class_id int,
foreign key(class_id) REFERENCES student(id1)
);
添加主鍵
alter table 表名 add primary key(列名);
alter table students add id int not null auto_increment, add primary key (id);
刪除主鍵
alter table 表名 drop primary key;
# 刪除主鍵屬性,保留原值和列
alter table 表名 modify 列名 int, drop primary key;
數據庫注意事項
1、重要的sql必須被索引,例如:
1)select、update、delete語句的where條件列;
2)order by、group by、distinct字段
2、mysql索引的限制:
1)mysql目前不支持函數索引
2)使用不等於(!=或者<>)的時候,mysql無法使用索引,單獨的>,<,in 有可能走,也有可能不走,和結果集有關,盡量結合業務添加limitor或in 盡量改成union
3)過濾字段使用單行函數 (如 abs (column)) 后, MYSQL無法使用索引。
4) join語句中join條件字段類型不一致的時候MYSQL 無法使用索引
5)使用 LIKE 操作的時候如果條件以通配符開始 (如 ‘%abc…’)時, MYSQL無法使用索引。
6)使用非等值查詢的時候, MYSQL 無法使用 Hash 索引。
7)BLOB 和 TEXT 類型的列只能創建前綴索引
3、mysql常見sql規范:
1)SQL語句盡可能簡單 大SQL語句盡可能拆成小SQL語句,MySQL對復雜SQL支持不好。
2)事務要簡單,整個事務的時間長度不要太長,SQL結束后及時提交。
3)限制單個事務所操作的數據集大小,不能超過 10000 條記錄
4)禁止使用觸發器、函數、存儲過程。
5)降低業務耦合度,為scale out、sharding留有余地
6)避免在數據庫中進行數學運算(數據庫不擅長數學運算和邏輯判斷)
*7)避免使用select ,需要查詢哪幾個字段就select這幾個字段,避免buffer pool被無用數據填充。
8)條件中使用到OR的SQL語句必須改寫成用IN()(OR的效率比IN低很多)
9)IN()里面的數據個數建議控制在 500 以內,可以用exist代替in,exist在某些場景比in效率高,盡量不使
用not in。
10)limit分頁注意效率。 limit越大,效率越低。可以改寫limit,例如:
select id from test limit 10000,10 可以改寫為 select id from test where id > 10000 limit 10
11)當只要一行數據時使用LIMIT 1 。
12)獲取大量數據時,建議分批次獲取數據,每次獲取數據少於 10000 條,結果集應小於 1M
13)避免使用大表做 JOIN,使用group by分組、自動排序
14)SQL語句禁止出現隱式轉換,例如:select id from test where id=’1’,其中 id 列為 int 等數字
類型。
15)在SQL中,盡量不使用like,且禁止使用前綴是%的like匹配。
16)合理選擇union all與union
17)禁止在OLTP類型系統中使用沒有where條件的查詢。
18)使用 prepared statement 語句,只傳參數,比傳遞 SQL 語句更高效;一次解析,多次使用;降低SQL
注入概率。
19)禁止使用 order by rand().
20)禁止單條 SQL 語句同時更新多個表。
21)不在業務高峰期批量更新或查詢數據庫,避免在業務高峰期alter表。
22)禁止在主庫上執行 sum,count 等復雜的統計分析語句,可以使用從庫來執行。