參考 http://www.jb51.net/article/82254.htmmysql
今天,數據庫的操做愈來愈成爲整個應用的性能瓶頸了,這點對於Web應用尤爲明顯。關於數據庫的性能,這並不僅是DBA才須要擔憂的事,而這更是咱們程序員須要去關注的事情。當咱們去設計數據庫表結構,對操做數據庫時(尤爲是查表時的SQL語句),咱們都須要注意數據操做的性能。這裏,咱們不會講過多的SQL語句的優化,而只是針對MySQL這一Web應用最多的數據庫。程序員
mysql的性能優化沒法一蹴而就,必須一步一步慢慢來,從各個方面進行優化,最終性能就會有大的提高。sql
Mysql數據庫的優化技術數據庫
對mysql優化是一個綜合性的技術,主要包括編程
•表的設計合理化(符合3NF)緩存
•添加適當索引(index) [四種: 普通索引、主鍵索引、惟一索引unique、全文索引]性能優化
•分表技術(水平分割、垂直分割)服務器
•讀寫[寫: update/delete/add]分離session
•存儲過程 [模塊化編程,能夠提升速度]mysql優化
•對mysql配置優化 [配置最大併發數my.ini, 調整緩存大小 ]
•mysql服務器硬件升級
•定時的去清除不須要的數據,定時進行碎片整理(MyISAM)
數據庫優化工做
對於一個以數據爲中心的應用,數據庫的好壞直接影響到程序的性能,所以數據庫性能相當重要。通常來講,要保證數據庫的效率,要作好如下四個方面的工做:
① 數據庫設計
② sql語句優化
③ 數據庫參數配置
④ 恰當的硬件資源和操做系統
此外,使用適當的存儲過程,也能提高性能。
這個順序也表現了這四個工做對性能影響的大小
數據庫表設計
通俗地理解三個範式,對於數據庫設計大有好處。在數據庫設計中,爲了更好地應用三個範式,就必須通俗地理解三個範式(通
俗地理解是夠用的理解,並非最科學最準確的理解):
第一範式:1NF是對屬性的原子性約束,要求屬性(列)具備原子性,不可再分解;(只要是關係型數據庫都知足1NF)
第二範式:2NF是對記錄的唯一性約束,要求記錄有唯一標識,即實體的唯一性;
第三範式:3NF是對字段冗餘性的約束,它要求字段沒有冗餘。 沒有冗餘的數據庫設計能夠作到。
可是,沒有冗餘的數據庫未必是最好的數據庫,有時爲了提升運行效率,就必須下降範式標準,適當保留冗餘數據。具體作法是: 在概念數據模型設計時遵照第三範式,下降範式標準的工做放到物理數據模型設計時考慮。下降範式就是增長字段,容許冗餘。
☞ 數據庫的分類
關係型數據庫: mysql/oracle/db2/informix/sysbase/sql server
非關係型數據庫: (特色: 面向對象或者集合)
NoSql數據庫: MongoDB(特色是面向文檔)
舉例說明什麼是適度冗餘,或者說有理由的冗餘!
上面這個就是不合適的冗餘,緣由是:
在這裏,爲了提升學生活動記錄的檢索效率,把單位名稱冗餘到學生活動記錄表裏。單位信息有500條記錄,而學生活動記錄在
一年內大概有200萬數據量。 若是學生活動記錄表不冗餘這個單位名稱字段,只包含三個int字段和一個timestamp字段,只佔用了16字節,是一個很小的表。而冗餘了一個 varchar(32)的字段後則是原來的3倍,檢索起來相應也多了這麼多的I/O。並且記錄數相差懸殊,500 VS 2000000 ,致使更新一個單位名稱還要更新4000條冗餘記錄。因而可知,這個冗餘根本就是拔苗助長。
訂單表裏面的Price就是一個冗餘字段,由於咱們能夠從訂單明細表中統計出這個訂單的價格,可是這個冗餘是合理的,也能提高查詢性能。
從上面兩個例子中能夠得出一個結論:
1---n 冗餘應當發生在1這一方.
SQL語句優化
SQL優化的通常步驟
1.經過show status命令瞭解各類SQL的執行頻率。
2.定位執行效率較低的SQL語句-(重點select)
3.經過explain分析低效率的SQL
4.肯定問題並採起相應的優化措施
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
-- select語句分類
Select
Dml數據操做語言( insert update delete )
dtl 數據事物語言( commit rollback savepoint)
Ddl數據定義語言( create alter drop ..)
Dcl(數據控制語言) grant revoke
-- Show status 經常使用命令
--查詢本次會話
Show session status like 'com_%' ; //show session status like 'Com_select'
--查詢全局
Show global status like 'com_%' ;
-- 給某個用戶受權
grant all privileges on *.* to 'abc' @ '%' ;
--爲何這樣受權 'abc'表示用戶名 '@' 表示host, 查看一下mysql->user表就知道了
--回收權限
revoke all on *.* from 'abc' @ '%' ;
--刷新權限[也能夠不寫]
flush privileges ;
|
SQL語句優化-show參數
MySQL客戶端鏈接成功後,經過使用show [session|global] status 命令能夠提供服務器狀態信息。其中的session來表示當前的鏈接的統計結果,global來表示自數據庫上次啓動至今的統計結果。默認是session級別的。
下面的例子:
show status like 'Com_%';
其中Com_XXX表示XXX語句所執行的次數。
重點注意:Com_select,Com_insert,Com_update,Com_delete經過這幾個參數,能夠容易地瞭解到當前數據庫的應用是以插入更新爲主仍是以查詢操做爲主,以及各種的SQL大體的執行比例是多少。
還有幾個經常使用的參數便於用戶瞭解數據庫的基本狀況。
Connections:試圖鏈接MySQL服務器的次數
Uptime:服務器工做的時間(單位秒)
Slow_queries:慢查詢的次數 (默認是慢查詢時間10s)
1
2
3
|
show status like 'Connections'
show status like 'Uptime'
show status like 'Slow_queries'
|
如何查詢mysql的慢查詢時間
1
|
Show variables like 'long_query_time' ;
|
修改mysql 慢查詢時間
1
|
set long_query_time=2
|
SQL語句優化-定位慢查詢
問題是: 如何從一個大項目中,迅速的定位執行速度慢的語句. (定位慢查詢)
首先咱們瞭解mysql數據庫的一些運行狀態如何查詢(好比想知道當前mysql運行的時間/一共執行了多少次
select/update/delete.. / 當前鏈接)
爲了便於測試,咱們構建一個大表(400 萬)-> 使用存儲過程構建
默認狀況下,mysql認爲10秒纔是一個慢查詢.
修改mysql的慢查詢.
1
2
|
show variables like 'long_query_time' ; //能夠顯示當前慢查詢時間
set long_query_time=1 ;//能夠修改慢查詢時間
|
構建大表->大表中記錄有要求, 記錄是不一樣纔有用,不然測試效果和真實的相差大.建立:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
CREATE TABLE dept( /*部門表*/
deptno MEDIUMINT UNSIGNED NOT NULL DEFAULT 0, /*編號*/
dname VARCHAR (20) NOT NULL DEFAULT "" , /*名稱*/
loc VARCHAR (13) NOT NULL DEFAULT "" /*地點*/
) ENGINE=MyISAM DEFAULT CHARSET=utf8 ;
CREATE TABLE emp
(empno MEDIUMINT UNSIGNED NOT NULL DEFAULT 0, /*編號*/
ename VARCHAR (20) NOT NULL DEFAULT "" , /*名字*/
job VARCHAR (9) NOT NULL DEFAULT "" ,/*工做*/
mgr MEDIUMINT UNSIGNED NOT NULL DEFAULT 0,/*上級編號*/
hiredate DATE NOT NULL ,/*入職時間*/
sal DECIMAL (7,2) NOT NULL ,/*薪水*/
comm DECIMAL (7,2) NOT NULL ,/*紅利*/
deptno MEDIUMINT UNSIGNED NOT NULL DEFAULT 0 /*部門編號*/
)ENGINE=MyISAM DEFAULT CHARSET=utf8 ;
CREATE TABLE salgrade
(
grade MEDIUMINT UNSIGNED NOT NULL DEFAULT 0,
losal DECIMAL (17,2) NOT NULL ,
hisal DECIMAL (17,2) NOT NULL
)ENGINE=MyISAM DEFAULT CHARSET=utf8;
|
測試數據
1
2
3
4
5
|
INSERT INTO salgrade VALUES (1,700,1200);
INSERT INTO salgrade VALUES (2,1201,1400);
INSERT INTO salgrade VALUES (3,1401,2000);
INSERT INTO salgrade VALUES (4,2001,3000);
INSERT INTO salgrade VALUES (5,3001,9999);
|
爲了存儲過程可以正常執行,咱們須要把命令執行結束符修改delimiter $$
建立函數,該函數會返回一個指定長度的隨機字符串
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
create function rand_string(n INT )
returns varchar (255) #該函數會返回一個字符串
begin
#chars_str定義一個變量 chars_str,類型是 varchar (100),默認值 'abcdefghijklmnopqrstuvwxyzABCDEFJHIJKLMNOPQRSTUVWXYZ' ;
declare chars_str varchar (100) default
'abcdefghijklmnopqrstuvwxyzABCDEFJHIJKLMNOPQRSTUVWXYZ' ;
declare return_str varchar (255) default '' ;
declare i int default 0;
while i < n do
set return_str =concat(return_str, substring (chars_str,floor(1+rand()*52),1));
set i = i + 1;
end while;
return return_str;
end
|
建立一個存儲過程
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
create procedure insert_emp( in start int (10), in max_num int (10))
begin
declare i int default 0;
# set autocommit =0 把autocommit設置成0
set autocommit = 0;
repeat
set i = i + 1;
insert into emp values ((start+i) ,rand_string(6), 'SALESMAN' ,0001,curdate(),2000,400,rand());
until i = max_num
end repeat;
commit ;
end
#調用剛剛寫好的函數, 1800000條記錄,從100001號開始
call insert_emp(100001,4000000);
|
這時咱們若是出現一條語句執行時間超過1秒中,就會統計到.
若是把慢查詢的sql記錄到咱們的一個日誌中
在默認狀況下,低版本的mysql不會記錄慢查詢,須要在啓動mysql時候,指定記錄慢查詢才能夠
bin\mysqld.exe - -safe-mode - -slow-query-log [mysql5.5 能夠在my.ini指定]
bin\mysqld.exe –log-slow-queries=d:/abc.log [低版本mysql5.0能夠在my.ini指定]
該慢查詢日誌會放在data目錄下[在mysql5.0這個版本中時放在 mysql安裝目錄/data/下],在 mysql5.5.19下是須要查看
my.ini 的 datadir="C:/Documents and Settings/All Users/Application Data/MySQL/MySQL Server 5.5/Data/「來肯定.
在mysql5.6中,默認是啓動記錄慢查詢的,my.ini的所在目錄爲:C:\ProgramData\MySQL\MySQL Server 5.6,其中有一個配置項
slow-query-log=1
針對 mysql5.5啓動慢查詢有兩種方法
bin\mysqld.exe - -safe-mode - -slow-query-log
也能夠在my.ini 文件中配置:
1
2
3
4
|
[mysqld]
# The TCP/IP Port the MySQL Server will listen on
port=3306
slow-query-log
|
經過慢查詢日誌定位執行效率較低的SQL語句。慢查詢日誌記錄了全部執行時間超過long_query_time所設置的SQL語句。
1
2
|
show variables like 'long_query_time' ;
set long_query_time=2;
|
爲dept表添加數據
1
2
3
4
5
6
7
|
desc dept;
ALTER table dept add id int PRIMARY key auto_increment;
CREATE PRIMARY KEY on dept(id);
create INDEX idx_dptno_dptname on dept(deptno,dname);
INSERT into dept(deptno,dname,loc) values (1, '研發部' , '康和盛大廈5樓501' );
INSERT into dept(deptno,dname,loc) values (2, '產品部' , '康和盛大廈5樓502' );
INSERT into dept(deptno,dname,loc) values (3, '財務部' , '康和盛大廈5樓503' ); UPDATE emp set deptno=1 where empno=100002;
|
****測試語句***[對emp表的記錄能夠爲3600000 ,效果很明顯慢]
1
|
select * from emp where empno=( select empno from emp where ename= '研發部' )
|
若是帶上order by e.empno 速度就會更慢,有時會到1min多.
測試語句
1
|
select * from emp e,dept d where e.empno=100002 and e.deptno=d.deptno;
|
查看慢查詢日誌:默認爲數據目錄data中的host-name-slow.log。低版本的mysql須要經過在開啓mysql時使用- -log-slow-queries[=file_name]來配置
SQL語句優化-explain分析問題
1
|
Explain select * from emp where ename=「wsrcla」
|
會產生以下信息:
select_type:表示查詢的類型。
table:輸出結果集的表
type:表示表的鏈接類型
possible_keys:表示查詢時,可能使用的索引
key:表示實際使用的索引
key_len:索引字段的長度
rows:掃描出的行數(估算的行數)
Extra:執行狀況的描述和說明
explain select * from emp where ename='JKLOIP'
若是要測試Extra的filesort能夠對上面的語句修改
1
|
explain select * from emp order by ename\G
|
EXPLAIN詳解
id
SELECT識別符。這是SELECT的查詢序列號
id 示例
1
|
SELECT * FROM emp WHERE empno = 1 and ename = ( SELECT ename FROM emp WHERE empno = 100001) \G;
|
select_type
PRIMARY :子查詢中最外層查詢
SUBQUERY : 子查詢內層第一個SELECT,結果不依賴於外部查詢
DEPENDENT SUBQUERY:子查詢內層第一個SELECT,依賴於外部查詢
UNION :UNION語句中第二個SELECT開始後面全部SELECT,
SIMPLE
UNION RESULT UNION 中合併結果
Table
顯示這一步所訪問數據庫中表名稱
Type
對錶訪問方式
ALL:
1
|
SELECT
*
FROM
emp \G
|
完整的表掃描 一般很差
1
|
SELECT
*
FROM
(
SELECT
*
FROM
emp
WHERE
empno = 1) a ;
|
system:表僅有一行(=系統表)。這是const聯接類型的一個特
const:表最多有一個匹配行
Possible_keys
該查詢能夠利用的索引,若是沒有任何索引顯示 null
Key
Mysql 從 Possible_keys 所選擇使用索引
Rows
估算出結果集行數
Extra
查詢細節信息
No tables :Query語句中使用FROM DUAL 或不含任何FROM子句
Using filesort :當Query中包含 ORDER BY 操做,並且沒法利用索引完成排序,
Impossible WHERE noticed after reading const tables: MYSQL Query Optimizer
經過收集統計信息不可能存在結果
Using temporary:某些操做必須使用臨時表,常見 GROUP BY ; ORDER BY
Using where:不用讀取表中全部信息,僅經過索引就能夠獲取所需數據;