1.高并發(fā)優(yōu)化點有:
如果請求過多,判定web服務器的壓力過大,增加前端的web服務器,做負載均衡
如果請求靜態(tài)界面不卡了,但是動態(tài)數(shù)據(jù)還是卡,說明MySQL處理的請求太多了,在應用層增加緩存.
數(shù)據(jù)庫層其實是最脆弱的一層,一般在應用設計時在上游就需要把請求攔截掉,數(shù)據(jù)庫層只承擔“能力范圍內(nèi)”的訪問請求,所以,我們通過在服務層引入隊列和緩存,讓最底層的數(shù)據(jù)庫高枕無憂。但是如果請求激增,還是有大量的查詢壓力到MySQL,這個時候就要想辦法解決MySQL的瓶頸了。
2.mysql執(zhí)行流程:
大致可以分為以下步驟:
1.當我們請求mysql服務器的時候,MySQL前端會有一個監(jiān)聽,請求到了之后,服務器得到相關的SQL語句,執(zhí)行之前(虛線部分為執(zhí)行),還會做權限的判斷
2.通過權限之后,SQL就到MySQL內(nèi)部,他會在查詢緩存中,看該SQL有沒有執(zhí)行過,如果有查詢過,則把緩存結果返回,說明在MySQL內(nèi)部,也有一個查詢緩存.但是這個查詢緩存,默認是不開啟的,這個查詢緩存,和我們的Hibernate,Mybatis的查詢緩存是一樣的,因為查詢緩存要求SQL和參數(shù)都要一樣,所以這個命中率是非常低的。
3.如果我們沒有開啟查詢緩存,或者緩存中沒有找到對應的結果,那么就到了解析器,解析器主要對SQL語法進行解析
4.解析結束后就變成一顆解析樹,這個解析樹其實在Hibernate里面也是有的,大家回憶一下,在以前做過Hibernate項目的時候,是不是有個一個antlr.jar。這個就是專門做語法解析的工具.因為在Hibernate里面有HQL,它就是通過這個工具轉換成SQL的,我們編程語言之所以有很多規(guī)范、語法,其實就是為了便于這個解析器解析,這個學過編譯原理的應該知道.
5.得到解析樹之后,不能馬上執(zhí)行,這還需要對這棵樹進行預處理,也就是說,這棵樹,我沒有經(jīng)過任何優(yōu)化的樹,預處理器會這這棵樹進行一些預處理,比如常量放在什么地方,如果有計算的東西,把計算的結果算出來等等...
6.預處理完畢之后,此時得到一棵比較規(guī)范的樹,這棵樹就是要拿去馬上做執(zhí)行的樹,比起之前的那棵樹,這棵得到了一些優(yōu)化的解析樹
7.查詢優(yōu)化器,是MySQL里面最關鍵的東西,我們寫任何一條SQL,比如SELECT * FROM USER WHERE USERNAME = toby AND PASSWORD = 1,它會怎么去執(zhí)行?它是先執(zhí)行username = toby還是password = 1?每一條SQL的執(zhí)行順序查詢優(yōu)化器就是根據(jù)MySQL對數(shù)據(jù)統(tǒng)計表的一些信息,比如索引,比如表一共有多少數(shù)據(jù),MySQL都是有緩存起來的,在真正執(zhí)行SQL之前,他會根據(jù)自己的這些數(shù)據(jù),進行一個綜合的判定,判斷這一次在多種執(zhí)行方式里面,到底選哪一種執(zhí)行方式,可能運行的最快.這一步是MySQL性能中,最關鍵的核心點,也是我們的優(yōu)化原則.我們平時所講的優(yōu)化SQL,其實說白了,就是想讓查詢優(yōu)化器,按照我們的想法,幫我們選擇最優(yōu)的執(zhí)行方案,因為我們比MySQL更懂我們的數(shù)據(jù).MySQL看數(shù)據(jù),僅僅只是自己收集到的信息,這些信息可能是不準確的,MySQL根據(jù)這些信息選了一個它自認為最優(yōu)的方案,但是這個方案可能和我們想象的不一樣.
8.這里的查詢執(zhí)行計劃,也就是MySQL查詢中的執(zhí)行計劃,比如要先執(zhí)行username = toby還是password = 1
9.這個執(zhí)行計劃會傳給查詢執(zhí)行引擎,執(zhí)行引擎選擇存儲引擎來執(zhí)行這一份傳過來的計劃,到磁盤中的文件中去查詢,這個時候重點來了,影響這個查詢性能最根本的原因是什么?就是硬盤的機械運動,也就是我們平時熟悉的IO,所以一條查詢語句是快還是慢,就是根據(jù)這個時間的IO來確定的.那怎么執(zhí)行IO又是什么來確定的?就是傳過來的這一份執(zhí)行計劃.
10.如果開了查詢緩存,則返回結果給客戶端,并且查詢緩存也放一份。
#要提高MySQL的更新/插入效率,應首先考慮降低鎖的競爭,減少寫操作的等待時間
3.增刪改查
一、INSERT語句:
基本:INSERT [INTO] 表名 [(字段列表)] VALUES (值列表)[, (值列表), …]
注意:
如果要插入的值列表包含所有字段并且順序一致,則可以省略字段列表。
可同時插入多條數(shù)據(jù)記錄!
REPLACE 與 INSERT 完全一樣,可互換。
優(yōu)化前例子:
優(yōu)化策略:
(1)當我們需要批量插入數(shù)據(jù)的時候,這樣的語句卻會出現(xiàn)性能問題。例如說,如果有需要插入100000條數(shù)據(jù),那么就需要有100000條insert語句,每一句都需要提交到關系引擎那里去解析,優(yōu)化,然后才能夠到達存儲引擎做真的插入工作。上述所說的同時插入多條就是一種優(yōu)化。(經(jīng)測試,大概10條同時插入是最高效的)
優(yōu)化后例子:
(2)將進程/線程數(shù)控制在2倍于CPU數(shù)目相對合適
(3)采用順序主鍵策略(例如自增主鍵,或者修改業(yè)務邏輯,讓插入的記錄盡可能順序主鍵)
(4)考慮使用replace 語句代替insert語句。(REPLACE語句請參考下文,有詳細講述)
二、DELETE語句:
DELETE FROM 表名[ 刪除條件子句](沒有條件子句,則會刪除全部)
例子:
補充:Mysql中的truncate table和delete語句都可以刪除表里面所有數(shù)據(jù),但是在一些情況下有些不同!
例子:
truncate table gag;
(1)truncate table刪除速度更快,,但truncate table刪除后不記錄mysql日志,不可以恢復數(shù)據(jù)。(謹慎使用)
(2)如果沒有外鍵關聯(lián),innodb執(zhí)行truncate是先drop table(原始表),再創(chuàng)建一個跟原始表一樣空表,速度要遠遠快于delete逐條刪除行記錄。
(3)如果使用innodb_file_per_table參數(shù),truncate table 能重新利用釋放的硬盤空間,在InnoDB Plugin中,truncate table為自動回收,如果不是用InnoDB Plugin,那么需要使用optimize table來優(yōu)化表,釋放空間。
truncate table刪除表后,optimize table尤其重要,特別是大數(shù)據(jù)數(shù)據(jù)庫,表空間可以得到釋放!
(4)表有外鍵關聯(lián),truncate table刪除表數(shù)據(jù)為逐行刪除,如果外鍵指定級聯(lián)刪除(delete cascade),關聯(lián)的子表也會會被刪除所有表數(shù)據(jù)。如果外鍵未指定級聯(lián)(cascde),truncate table逐行刪除數(shù)據(jù),如果是父行關聯(lián)子表行數(shù)據(jù),將會報錯。
注意:
一個大的 DELETE 或 INSERT 操作,要非常小心,因為這兩個操作是會鎖表的,表一鎖住,其他操作就進不來了。因此,我們要交給DBA去拆分,重整數(shù)據(jù)庫策略,比如限制處理1000條。
另外,擴展下刪除和索引的聯(lián)系,由于索引需要額外的維護成本;因為索引文件是單獨存在的文件,所以當我們對數(shù)據(jù)的增加,修改,刪除,都會產(chǎn)生額外的對索引文件的操作,這些操作需要消耗額外的IO,會降低增/改/刪的執(zhí)行效率。所以,在我們刪除數(shù)據(jù)庫百萬級別數(shù)據(jù)的時候,查詢MySQL官方手冊得知刪除數(shù)據(jù)的速度和創(chuàng)建的索引數(shù)量是成正比的。所以我們想要刪除百萬數(shù)據(jù)的時候可以先刪除索引(此時大概耗時三分多鐘),然后刪除其中無用數(shù)據(jù),此過程需要不到兩分鐘,刪除完成后重新創(chuàng)建索引(此時數(shù)據(jù)較少了)創(chuàng)建索引也非???,約十分鐘左右。與之前的直接刪除絕對是要快速很多,更別說萬一刪除中斷,一切刪除會回滾。那更是坑了。
三、UPDATE語句:
UPDATE 表名 SET 字段名=新值[, 字段名=新值] [更新條件]
例子:
優(yōu)化:更新多條記錄(往后會結合MyBatics寫個實例)
更新多條記錄的多個值
(1). 盡量不要修改主鍵字段。(廢話,反正我就從沒改過..)
(2). 當修改VARCHAR型字段時,盡量使用相同長度內(nèi)容的值代替。
(3). 盡量最小化對于含有UPDATE觸發(fā)器的表的UPDATE操作。
(4). 避免UPDATE將要復制到其他數(shù)據(jù)庫的列。
(5). 避免UPDATE建有很多索引的列。
(6). 避免UPDATE在WHERE子句條件中的列。
四、REPLACE語句:
根據(jù)應用情況可以使用replace 語句代替insert/update語句。例如:如果一個表在一個字段上建立了唯一索引,當向這個表中使用已經(jīng)存在的鍵值插入一條記錄,將會拋出一個主鍵沖突的錯誤。如果我們想用新記錄的值來覆蓋原來的記錄值時,就可以使用REPLACE語句。
使用REPLACE插入記錄時,如果記錄不重復(或往表里插新記錄),REPLACE功能與INSERT一樣,如果存在重復記錄,REPLACE就使用新記錄的值來替換原來的記錄值。使用REPLACE的最大好處就是可以將DELETE和INSERT合二為一,形成一個原子操作。這樣就可以不必考慮同時使用DELETE和INSERT時添加事務等復雜操作了。
在使用REPLACE時,表中必須有唯一有一個PRIMARY KEY或UNIQUE索引,否則,使用一個REPLACE語句沒有意義。
用法:
(1)同INSERT
含義一:與普通INSERT一樣功能
REPLACE INTO score (change_type,score,user_id) VALUES ('吃飯',10,1),('喝茶',10,1),('喝茶',10,1);
含義二:找到第一條記錄,用后面的值進行替換
REPLACE INTO score (id,change_type,score,user_id) VALUES (1,'吃飯',10,1)
此語句的作用是向表table中插入3條記錄。如果主鍵id為1或2不存在就相當于插入語句:
INSERT INTO score (change_type,score,user_id) VALUES (‘吃飯’,10,1),(‘喝茶’,10,1),(‘喝茶’,10,1);
如果存在相同的值則不會插入數(shù)據(jù)。
(2)replace(object, search, replace),把object中出現(xiàn)search的全部替換為replace。
用法一:并不是修改數(shù)據(jù),而只是單純做局部替換數(shù)據(jù)返還而已。
SELECT REPLACE('喝茶','茶','喝')//結果: 喝喝123
用法二:修改表數(shù)據(jù)啦,對應下面就是,根據(jù)change_type字段找到做任務的數(shù)據(jù),用bb來替換
UPDATE score SET change_type=REPLACE(change_type,'做任務','bb')1
在此,做下對比:UPDATE和REPLACE的區(qū)別:
1)UPDATE在沒有匹配記錄時什么都不做,而REPLACE在有重復記錄時更新,在沒有重復記錄時插入。
2)UPDATE可以選擇性地更新記錄的一部分字段。而REPLACE在發(fā)現(xiàn)有重復記錄時就將這條記錄徹底刪除,再插入新的記錄。也就是說,將所有的字段都更新了。
其實REPLACE更像INSERT與DELETE的結合。
單表查詢優(yōu)化:
(0)可以先使用EXPLAIN關鍵字可以讓你知道MySQL是如何處理你的SQL語句的。這可以幫我們分析是查詢語句或是表結構的性能瓶頸。
(1)寫sql要明確需要的字段,要多少就寫多少字段,而不是濫用 select *
(2)可以用使用連接(JOIN)來代替子查詢
(3)使用分頁語句:limit start , count 或者條件 where子句時,有什么可限制的條件盡量加上,查一條就limit一條。做到不濫用。比如說我之前做過的的p2p項目,只是需要知道有沒有一個滿標的借款,這樣的話就可以用上 limit 1,這樣mysql在找到一條數(shù)據(jù)后就停止搜索,而不是全文搜索完再停止。
(4)開啟查詢緩存:
大多數(shù)的MySQL服務器都開啟了查詢緩存。這是提高查詢有效的方法之一。當有很多相同的查詢被執(zhí)行了多次的時候,這些查詢結果會被放到一個緩存中,這樣,后續(xù)的相同的查詢就不用操作表而直接訪問緩存結果了。
查詢緩存工作流程:
A):服務器接收SQL,以SQL+DB+Query_cache_query_flags作為hash查找鍵;
B):找到了相關的結果集就將其返回給客戶端;
C):如果沒有找到緩存則執(zhí)行權限驗證、SQL解析、SQL優(yōu)化等一些列的操作;
D):執(zhí)行完SQL之后,將結果集保存到緩存
當然,并不是每種情況都適合使用緩存,衡量打開緩存是否對系統(tǒng)有性能提升是一個整體的概念。那怎么判斷要不要開啟緩存呢,如下:
1)通過緩存命中率判斷, 緩存命中率 = 緩存命中次數(shù) (Qcache_hits) / 查詢次數(shù) (Com_select)、
2)通過緩存寫入率, 寫入率 = 緩存寫入次數(shù) (Qcache_inserts) / 查詢次數(shù) (Qcache_inserts)
3)通過 命中-寫入率 判斷, 比率 = 命中次數(shù) (Qcache_hits) / 寫入次數(shù) (Qcache_inserts), 高性能MySQL中稱之為比較能反映性能提升的指數(shù),一般來說達到3:1則算是查詢緩存有效,而最好能夠達到10:1
相關參數(shù)及命令:
與緩存相關的主要參數(shù)如下表所示??梢允褂妹?b>SHOW VARIABLES LIKE '%query_cache%'查看
緩存數(shù)據(jù)失效時機
在表的結構或數(shù)據(jù)發(fā)生改變時,查詢緩存中的數(shù)據(jù)不再有效。有這些INSERT、UPDATE、 DELETE、TRUNCATE、ALTER TABLE、DROP TABLE或DROP DATABASE會導致緩存數(shù)據(jù)失效。所以查詢緩存適合有大量相同查詢的應用,不適合有大量數(shù)據(jù)更新的應用。
可以使用下面三個SQL來清理查詢緩存:
1、FLUSH QUERY CACHE; // 清理查詢緩存內(nèi)存碎片。
2、RESET QUERY CACHE; // 從查詢緩存中移出所有查詢。
3、FLUSH TABLES; //關閉所有打開的表,同時該操作將會清空查詢緩存中的內(nèi)容。
InnoDB與查詢緩存:
Innodb會對每個表設置一個事務計數(shù)器,里面存儲當前最大的事務ID.當一個事務提交時,InnoDB會使用MVCC中系統(tǒng)事務ID最大的事務ID跟新當前表的計數(shù)器.
只有比這個最大ID大的事務能使用查詢緩存,其他比這個ID小的事務則不能使用查詢緩存.
另外,在InnoDB中,所有有加鎖操作的事務都不使用任何查詢緩存
(MVCC (Multiversion Concurrency Control),即多版本并發(fā)控制技術,它使得大部分支持行鎖的事務引擎,不再單純的使用行鎖來進行數(shù)據(jù)庫的并發(fā)控制,取而代之的是把數(shù)據(jù)庫的行鎖與行的多個版本結合起來,只需要很小的開銷,就可以實現(xiàn)非鎖定讀,從而大大提高數(shù)據(jù)庫系統(tǒng)的并發(fā)性能)
多表查詢連接的選擇:
相信這內(nèi)連接,左連接什么的大家都比較熟悉了,當然還有左外連接什么的,基本用不上我就不貼出來了。這圖只是讓大家回憶一下,各種連接查詢。 然后要告訴大家的是,需要根據(jù)查詢的情況,想好使用哪種連接方式效率更高。
二、MySQL的JOIN實現(xiàn)原理
在MySQL 中,只有一種Join 算法,就是大名鼎鼎的Nested Loop Join,他沒有其他很多數(shù)據(jù)庫所提供的Hash Join,也沒有Sort Merge Join。顧名思義,Nested Loop Join 實際上就是通過驅動表的結果集作為循環(huán)基礎數(shù)據(jù),然后一條一條的通過該結果集中的數(shù)據(jù)作為過濾條件到下一個表中查詢數(shù)據(jù),然后合并結果。如果還有第三個參與Join,則再通過前兩個表的Join 結果集作為循環(huán)基礎數(shù)據(jù),再一次通過循環(huán)查詢條件到第三個表中查詢數(shù)據(jù),如此往復。 ——摘自《MySQL 性能調(diào)優(yōu)與架構設計》
三、補充:mysql對sql語句的容錯問題
即在sql語句不完全符合書寫建議的情況,mysql會允許這種情況,盡可能解釋它:
1)一般cross join后面加上where條件,但是用cross join+on也是被解釋為cross join+where;
2)一般內(nèi)連接都需要加上on限定條件,如上面場景一;如果不加會被解釋為交叉連接;
3)如果連接表格使用的是逗號,會被解釋為交叉連接;
超大型數(shù)據(jù)盡可能盡力不要寫子查詢,使用連接(JOIN)去替換它:
當然,關于這句話,也不一定就全是這樣。
1)因為在大型的數(shù)據(jù)處理中,子查詢是非常常見的,特別是在查詢出來的數(shù)據(jù)需要進一步處理的情況,無論是可讀性還是效率上,這時候的子查都是更優(yōu)。
2)然而在一些特定的場景,可以直接從數(shù)據(jù)庫讀取就可以的,比如一個表(A表 a,b,c字段,需要內(nèi)部數(shù)據(jù)交集)join自己的效率必然比放一個子查在where中快得多。
使用聯(lián)合(UNION)來代替手動創(chuàng)建的臨時表
UNION是會把結果排序的?。?!
union查詢:它可以把需要使用臨時表的兩條或更多的select查詢合并的一個查詢中(即把兩次或多次查詢結果合并起來。)。在客戶端的查詢會話結束的時候,臨時表會被自動刪除,從而保證數(shù)據(jù)庫整齊、高效。使用union來創(chuàng)建查詢的時候,我們只需要用UNION作為關鍵字把多個select語句連接起來就可以了,要注意的是所有select語句中的字段數(shù)目要想同。
要求:兩次查詢的列數(shù)必須一致(列的類型可以不一樣,但推薦查詢的每一列,相對應的類型要一樣)
可以來自多張表的數(shù)據(jù):多次sql語句取出的列名可以不一致,此時以第一個sql語句的列名為準。
如果不同的語句中取出的行,有完全相同(這里表示的是每個列的值都相同),那么union會將相同的行合并,最終只保留一行。也可以這樣理解,union會去掉重復的行。
如果不想去掉重復的行,可以使用union all。
如果子句中有order by,limit,需用括號()包起來。推薦放到所有子句之后,即對最終合并的結果來排序或篩選。
注意:
1、UNION 結果集中的列名總是等于第一個 SELECT 語句中的列名
2、UNION 內(nèi)部的 SELECT 語句必須擁有相同數(shù)量的列。列也必須擁有相似的數(shù)據(jù)類型。同時,每條 SELECT 語句中的列的順序必須相同
UNION ALL的作用和語法:
默認地,UNION 操作符選取不同的值。如果允許重復的值,請使用 UNION ALL。當 ALL 隨 UNION 一起使用時(即 UNION ALL),不消除重復行。
總結
(1)對于要求全面的結果時,我們需要使用連接操作(LEFT JOIN / RIGHT JOIN / FULL JOIN);
(2)應盡量避免在 where 子句中對字段進行 null 值判斷,否則將導致引擎放棄使用索引而進行全表掃描,如:
備注、描述、評論之類的可以設置為 NULL,其他最好不要使用NULL。
不要以為 NULL 不需要空間,比如:char(100) 型,在字段建立時,空間就固定了, 不管是否插入值(NULL也包含在內(nèi)),都是占用 100個字符的空間的,如果是varchar這樣的變長字段, null 不占用空間。
可以在num上設置默認值0,確保表中num列沒有null值,然后這樣查詢:
select id from t where num = 0
(3)in 和 not in 也要慎用,否則會導致全表掃描,如:
對于連續(xù)的數(shù)值,能用 between 就不要用 in 了:
很多時候用 exists 代替 in 是一個好的選擇:
(4)盡量使用數(shù)字型字段,若只含數(shù)值信息的字段盡量不要設計為字符型,這會降低查詢和連接的性能,并會增加存儲開銷。這是因為引擎在處理查詢和連 接時會逐個比較字符串中每一個字符,而對于數(shù)字型而言只需要比較一次就夠了。
(5)盡量使用表變量來代替臨時表。如果表變量包含大量數(shù)據(jù),請注意索引非常有限(只有主鍵索引)。
(6)不要以為使用MySQL的一些連接操作對查詢有多么大的改善,其實核心是索引