天天看點

Mysql性能優化

mysql的性能優化無法一蹴而就,必須一步一步慢慢來,從各個方面進行優化,最終性能就會有大的提升。

Mysql資料庫的優化技術

對mysql優化是一個綜合性的技術,主要包括

  • 表的設計合理化(符合3NF)
  • 添加适當索引(index) [四種: 普通索引、主鍵索引、唯一索引unique、全文索引]
  • 分表技術(水準分割、垂直分割)
  • 讀寫[寫: update/delete/add]分離
  • 存儲過程 [子產品化程式設計,可以提高速度]
  • 對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. 确定問題并采取相應的優化措施
-- 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)

show status like 'Connections'
show status like 'Uptime'
show status like 'Slow_queries'
      

如何查詢mysql的慢查詢時間

Show variables like 'long_query_time';
      

修改mysql 慢查詢時間

set long_query_time=2
      

SQL語句優化-定位慢查詢

問題是: 如何從一個大項目中,迅速的定位執行速度慢的語句. (定位慢查詢)

首先我們了解mysql資料庫的一些運作狀态如何查詢(比如想知道目前mysql運作的時間/一共執行了多少次select/update/delete.. / 目前連接配接)

為了便于測試,我們建構一個大表(400 萬)-> 使用存儲過程建構

預設情況下,mysql認為10秒才是一個慢查詢.

修改mysql的慢查詢.

show variables like 'long_query_time' ; //可以顯示目前慢查詢時間
set long_query_time=1 ;//可以修改慢查詢時間
      

建構大表->大表中記錄有要求, 記錄是不同才有用,否則測試效果和真實的相差大.建立:

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;
      

測試資料

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 $$

建立函數, 該函數會傳回一個指定長度的随機字元串

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 
      

建立一個存儲過程

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 檔案中配置:

[mysqld]
# The TCP/IP Port the MySQL Server will listen on
port=3306
slow-query-log
      

通過慢查詢日志定位執行效率較低的SQL語句。慢查詢日志記錄了所有執行時間超過long_query_time所設定的SQL語句。

show variables like 'long_query_time';

set long_query_time=2;

為dept表添加資料

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 ,效果很明顯慢]

select * from emp where empno=(select empno from emp where ename=' 研發部')

如果帶上order by e.empno 速度就會更慢,有時會到1min多.

測試語句

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分析問題

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可以對上面的語句修改

explain select * from emp order by ename\G
      

EXPLAIN 詳解

id

SELECT識别符。這是SELECT的查詢序列号

id 示例

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:

SELECT * FROM emp \G

完整的表掃描 通常不好

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:不用讀取表中所有資訊,僅通過索引就可以擷取所需資料;

建立适當的索引

說起提高資料庫性能,索引是最物美價廉的東西了。不用加記憶體,不用改程式,不用調sql,隻要執行個正确的'create index',查詢速度就可能提高百倍千倍,這可真有誘惑力。可是天下沒有免費的午餐,查詢速度的提高是以插入、更新、删除的速度為代價的,這些寫操作,增加了大量的I/O。

是不是建立一個索引就能解決所有的問題?ename上沒有建立索引會怎樣?

select * from emp where ename='研發部';
      

---測試案例指令如下 (最好以 select * from emp e,dept d where e.empno=123451 )

*添加主鍵

ALTER TABLE emp ADD PRIMARY KEY(empno);
      

*删除主鍵

alter table emp drop primary key;
      

索引的原理說明

沒有索引為什麼會慢?

使用索引為什麼會快?

索引的代價

1、磁盤占用

2、對dml(update delete insert)語句的效率影響

btree 方式檢索,算法複雜度: log2N 次數

哪些列上适合添加索引

1、較頻繁的作為查詢條件字段應該建立索引

select * from emp where empno = 1;
      

2、唯一性太差的字段不适合單獨建立索引,即使頻繁作為查詢條件

select * from emp where sex = '男'
      

3、更新非常頻繁的字段不适合建立索引

select * from emp where logincount = 1
      

4、不會出現在WHERE子句中的字段不該建立索引

索引的類型

  • 主鍵索引,主鍵自動的為主索引 (類型Primary)
  • 唯一索引 (UNIQUE)
  • 普通索引 (INDEX)
  • 全文索引 (FULLTEXT) [适用于MyISAM] ——》sphinx + 中文分詞    coreseek [sphinx 的中文版 ]
  • 綜合使用=>複合索引

簡述mysql四種索引的差別

lPRIMARY 索引 =》在主鍵上自動建立

lUNIQUE 索引=> 隻要是UNiQUE 就是Unique索引.(隻能在字段内容不重複的情況下,才能建立唯一索引)

lINDEX 索引=>就是普通索引

lFULLTEXT => 隻在MYISAM 存儲引擎支援, 目的是全文索引,在内容系統中用的多, 在全英文網站用多(英文詞獨立). 中文資料不常用,意義不大,國内全文索引通常使用 sphinx來完成,全文索引隻能在 char varchar text字段建立.

全文索引案例

1.建立表

create table news(id int , title varchar(32),con varchar(1024)) engine=MyISAM;
      

2.建立全文索引

create fulltext index ful_inx on news (con);
      

3.插入資料

這裡要注意,對于常見的英文 fulltext 不會比對,而且插入的語句本身是正确的.

'but it often happens that they are not above supporting themselves by dishonest means.which should be more disreputable.Cultivate poverty like a garden herb'

4.看看比對度

mysql> select match(con) against('poverty') from news;

+-------------------------------+

| match(con) against('poverty') |

+-------------------------------+

|                             0 |

|                             0 |

|                             0 |

|            0.9853024482727051 |

+-------------------------------+
      

0表示沒有比對到,或者你的詞是停止詞,是不會建立索引的.

使用全文索引,不能使用like語句,這樣就不會使用到全文索引了.

複合索引

create index 索引名 on 表名(列1,列2);
      

索引的使用

建立索引

create [UNIQUE|FULLTEXT]  index index_name on tbl_name (col_name [(length)] [ASC | DESC] , …..);
alter table table_name ADD INDEX [index_name]  (index_col_name,...)
      

添加主鍵(索引) ALTER TABLE 表名 ADD PRIMARY KEY(列名,..); 聯合主鍵

删除索引

DROP INDEX index_name ON tbl_name;
alter table table_name drop index index_name;
      

删除主鍵(索引)比較特别: alter table t_b drop primary key;

查詢索引(均可)

show index(es) from table_name;
show keys from table_name;
desc table_Name;
      

修改索引,我們一般是先删除在重新建立.

查詢要使用索引最重要的條件是查詢條件中需要使用索引。

下列幾種情況下有可能使用到索引:

1,對于建立的多列索引,隻要查詢條件使用了最左邊的列,索引一般就會被使用。

2,對于使用like的查詢,查詢如果是  '%aaa' 不會使用到索引, 'aaa%' 會使用到索引。

下列的表将不使用索引:

1,如果條件中有or,即使其中有條件帶索引也不會使用。

2,對于多列索引,不是使用的第一部分,則不會使用索引。

3,like查詢是以%開頭

4,如果列類型是字元串,那一定要在條件中将資料使用引号引用起來。否則不使用索引。(添加時,字元串必須'')

5,如果mysql估計使用全表掃描要比使用索引快,則不使用索引。

測試案例(就在前面的dept表上做示範.)

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 ;
      

--放入資料,前面應該已經添加了,如果沒有則需要重新添加

--測試開始.

添加一個主鍵索引

alter table dept add primary key (deptno)
      

--測試語句

explain select * from dept where deptno=1;
      

結果是:

mysql> explain select * from dept where deptno=1;
*************************** 1. row ***************************
           id: 1
  select_type: SIMPLE
        table: dept
         type: const
possible_keys: PRIMARY
          key: PRIMARY
      key_len: 3
          ref: const
         rows: 1
        Extra:
1 row in set (0.00 sec)
      

--建立多列索引

alter table dept add index myind (dname,loc);
      

--證明對于建立的多列索引,隻要查詢條件使用了最左邊的列,索引一般就會被使用

explain select * from dept where dname='研發部'; 會顯示使用到了索引myind

explain select * from dept where loc='MsBDpMRX'; 不會顯示使用到了索引myind

--對于使用like的查詢

explain select * from dept where dname like '%研發部'; 不會顯示使用到了索引myind

explain select * from dept where dname like '研發部%'; 會顯示使用到了索引myind

--如果條件中有or,即使其中有條件帶索引也不會使用

--為了示範,我們把複合索引删除,然後隻在dname上加入索引.

alter table dept drop index myind
alter table dept add index myind (dname)
explain select * from dept where dname='研發部' or loc='aa';-- 就不會使用到dname列上的
      

--如果列類型是字元串,那一定要在條件中将資料使用引号引用起來。否則不使用索引

select * from dept from dname=1234; //不會使用到索引

select * from dept from dname='1234'; //會使用到索引

檢視索引的使用情況

show status like 'Handler_read%';

大家可以注意:

handler_read_key:這個值越高越好,越高表示使用索引查詢到的次數。

handler_read_rnd_next:這個值越高,說明查詢低效。

* 這時我們會看到handler_read_rnd_next值很高,為什麼,這是因為我們前面沒有加索引的時候,做過多次查詢的原因.

常用SQL優化

大批量插入資料(MySql管理者) 了解對于MyISAM:

alter table table_name disable keys;
loading data//insert語句;
alter table table_name enable keys;
      

對于Innodb:

1,将要導入的資料按照主鍵排序

2,set unique_checks=0,關閉唯一性校驗。

3,set autocommit=0,關閉自動送出。

優化group by 語句

預設情況,MySQL對所有的group by col1,col2進行排序。這與在查詢中指定order by col1, col2類似。如果查詢中包括group by但使用者想要避免排序結果的消耗,則可以使用order by null禁止排序

有些情況下,可以使用連接配接來替代子查詢。因為使用join,MySQL不需要在記憶體中建立臨時表。(講解)

如果想要在含有or的查詢語句中利用索引,則or之間的每個條件列都必須用到索引,如果沒有索引,則應該考慮增加索引(與環境相關 講解)

select * from 表名 where 條件1='' or 條件2='tt'

explaine select * from dept group by dname; =>這時顯示 extra: using filesort 說明會進行排序

explaine select * from dept group by dname order by null =>這時不含有顯示 extra: using filesort 說明不會進行排序

***有些情況下,可以使用連接配接來替代子查詢。因為使用join,MySQL不需要在記憶體中建立臨時表。

explain select * from emp , dept where emp.deptno=dept.deptno;

和下面比較就可以說明問題!!

explain select * from emp left join dept on emp.deptno=dept.deptno;

選擇合适的存儲引擎

MyISAM:預設的MySQL存儲引擎。如果應用是以讀操作和插入操作為主,隻有很少的更新和删除操作,并且對事務的完整性要求不是很高。其優勢是通路的速度快。

InnoDB:提供了具有送出、復原和崩潰恢複能力的事務安全。但是對比MyISAM,寫的處理效率差一些并且會占用更多的磁盤空間。

Memory:資料存在記憶體中,服務重新開機時,資料丢失

MyISAM:在插入資料時,預設放在最後. ,删除資料後,空間不回收.(不支援事務和外鍵)

InnoDB 支援事務和外鍵

對應我們程式員說,常用的存儲引擎主要是 myisam / innodb / memory,heap 表

如果選用小原則:

1.如果追求速度,不在乎資料是否一直把儲存,也不考慮事務,請選擇 memory 比如存放使用者線上狀态.

2.如果表的資料要持久儲存,應用是以讀操作和插入操作為主,隻有很少的更新和删除操作,并且對事務的完整性要求不是很高。選用MyISAM

3.如果需要資料持久儲存,并提供了具有送出、復原和崩潰恢複能力的事務安全,請選用Innodb

選擇合适的資料類型

在精度要求高的應用中,建議使用定點數來存儲數值,以保證結果的準确性。deciaml 不要用float

對于存儲引擎是MyISAM的資料庫,如果經常做删除和修改記錄的操作,要定時執行optimize table table_name;功能對表進行碎片整理。

日期類型要根據實際需要選擇能夠滿足應用的最小存儲的早期類型

create table bbs(id int ,con varchar(1024) , pub_time int);

date('Ymd',時間-3*24*60*60); 2038年-1-19

對于使用浮點數和定點數的案例說明

create table temp1( t1 float(10,2), t2 decimal(10,2));

insert into temp1 values(1000000.32,1000000,32); 發現 t1 成了 1000000.31 是以有問題.

對于optimize table 表名 示範

create table temp2( id int) engine=MyISAM;
insert into temp2 values(1); insert into temp2 values(2); insert into temp2 values(3);
insert into temp2 select * from temp2;--複制
delete from temp2 where id=1; 發現 該表對于的資料檔案沒有變小
      

定期執行 optimize table temp2 發現表大小變化,碎片整理完畢

&&對于InnoDB它的資料會存在data/ibdata1目錄下,在data/資料庫/隻有一個 *.frm表結構檔案.

熬夜不易,點選請老王喝杯烈酒!!!!!!!