數據庫基礎知識html
爲何要使用數據庫java
數據保存在內存mysql
優勢: 存取速度快算法
缺點: 數據不能永久保存sql
數據保存在文件數據庫
優勢: 數據永久保存緩存
缺點:1)速度比內存操做慢,頻繁的IO操做。2)查詢數據不方便安全
數據保存在數據庫性能優化
1)數據永久保存服務器
2)使用SQL語句,查詢方便效率高。
3)管理數據方便
結構化查詢語言(Structured Query Language)簡稱SQL,是一種數據庫查詢語言。
做用:用於存取數據、查詢、更新和管理關係數據庫系統。
MySQL是一個關係型數據庫管理系統,由瑞典MySQL AB 公司開發,屬於 Oracle 旗下產品。MySQL 是最流行的關係型數據庫管理系統之一,在 WEB 應用方面,MySQL是最好的 RDBMS (Relational Database Management System,關係數據庫管理系統) 應用軟件之一。在Java企業級開發中很是經常使用,由於 MySQL 是開源免費的,而且方便擴展。
第一範式:每一個列都不能夠再拆分。
第二範式:在第一範式的基礎上,非主鍵列徹底依賴於主鍵,而不能是依賴於主鍵的一部分。
第三範式:在第二範式的基礎上,非主鍵列只依賴於主鍵,不依賴於其餘非主鍵。
在設計數據庫結構的時候,要儘可能遵照三範式,若是不遵照,必須有足夠的理由。好比性能。事實上咱們常常會爲了性能而妥協數據庫的設計。
MySQL服務器經過權限表來控制用戶對數據庫的訪問,權限表存放在mysql數據庫裏,由mysql_install_db腳本初始化。這些權限表分別user,db,table_priv,columns_priv和host。下面分別介紹一下這些表的結構和內容:
user權限表:記錄容許鏈接到服務器的用戶賬號信息,裏面的權限是全局級的。
db權限表:記錄各個賬號在各個數據庫上的操做權限。
table_priv權限表:記錄數據表級的操做權限。
columns_priv權限表:記錄數據列級的操做權限。
host權限表:配合db權限表對給定主機上數據庫級操做權限做更細緻的控制。這個權限表不受GRANT和REVOKE語句的影響。
有三種格式,statement,row和mixed。
statement模式下,每一條會修改數據的sql都會記錄在binlog中。不須要記錄每一行的變化,減小了binlog日誌量,節約了IO,提升性能。因爲sql的執行是有上下文的,所以在保存的時候須要保存相關的信息,同時還有一些使用了函數之類的語句沒法被記錄複製。
row級別下,不記錄sql語句上下文相關信息,僅保存哪條記錄被修改。記錄單元爲每一行的改動,基本是能夠所有記下來可是因爲不少操做,會致使大量行的改動(好比alter table),所以這種模式的文件保存的信息太多,日誌量太大。
mixed,一種折中的方案,普通操做使用statement記錄,當沒法使用statement的時候使用row。
此外,新版的MySQL中對row級別也作了一些優化,當表結構發生變化的時候,會記錄語句而不是逐行記錄。
mysql有哪些數據類型
一、整數類型,包括TINYINT、SMALLINT、MEDIUMINT、INT、BIGINT,分別表示1字節、2字節、3字節、4字節、8字節整數。任何整數類型均可以加上UNSIGNED屬性,表示數據是無符號的,即非負整數。
長度:整數類型能夠被指定長度,例如:INT(11)表示長度爲11的INT類型。長度在大多數場景是沒有意義的,它不會限制值的合法範圍,只會影響顯示字符的個數,並且須要和UNSIGNED ZEROFILL屬性配合使用纔有意義。
例子,假定類型設定爲INT(5),屬性爲UNSIGNED ZEROFILL,若是用戶插入的數據爲12的話,那麼數據庫實際存儲數據爲00012。
二、實數類型,包括FLOAT、DOUBLE、DECIMAL。
DECIMAL能夠用於存儲比BIGINT還大的整型,能存儲精確的小數。
而FLOAT和DOUBLE是有取值範圍的,並支持使用標準的浮點進行近似計算。
計算時FLOAT和DOUBLE相比DECIMAL效率更高一些,DECIMAL你能夠理解成是用字符串進行處理。
三、字符串類型,包括VARCHAR、CHAR、TEXT、BLOB
VARCHAR用於存儲可變長字符串,它比定長類型更節省空間。
VARCHAR使用額外1或2個字節存儲字符串長度。列長度小於255字節時,使用1字節表示,不然使用2字節表示。
VARCHAR存儲的內容超出設置的長度時,內容會被截斷。
CHAR是定長的,根據定義的字符串長度分配足夠的空間。
CHAR會根據須要使用空格進行填充方便比較。
CHAR適合存儲很短的字符串,或者全部值都接近同一個長度。
CHAR存儲的內容超出設置的長度時,內容一樣會被截斷。
使用策略:
對於常常變動的數據來講,CHAR比VARCHAR更好,由於CHAR不容易產生碎片。
對於很是短的列,CHAR比VARCHAR在存儲空間上更有效率。
使用時要注意只分配須要的空間,更長的列排序時會消耗更多內存。
儘可能避免使用TEXT/BLOB類型,查詢時會使用臨時表,致使嚴重的性能開銷。
四、枚舉類型(ENUM),把不重複的數據存儲爲一個預約義的集合。
有時能夠使用ENUM代替經常使用的字符串類型。
ENUM存儲很是緊湊,會把列表值壓縮到一個或兩個字節。
ENUM在內部存儲時,其實存的是整數。
儘可能避免使用數字做爲ENUM枚舉的常量,由於容易混亂。
排序是按照內部存儲的整數
五、日期和時間類型,儘可能使用timestamp,空間效率高於datetime,
用整數保存時間戳一般不方便處理。
若是須要存儲微妙,能夠使用bigint存儲。
看到這裏,這道真題是否是就比較容易回答了。
MySQL存儲引擎MyISAM與InnoDB區別
存儲引擎Storage engine:MySQL中的數據、索引以及其餘對象是如何存儲的,是一套文件系統的實現。
經常使用的存儲引擎有如下:
Innodb引擎:Innodb引擎提供了對數據庫ACID事務的支持。而且還提供了行級鎖和外鍵的約束。它的設計的目標就是處理大數據容量的數據庫系統。
MyIASM引擎(本來Mysql的默認引擎):不提供事務的支持,也不支持行級鎖和外鍵。
MEMORY引擎:全部的數據都在內存中,數據的處理速度快,可是安全性不高。
MyISAM與InnoDB區別
若是沒有特別的需求,使用默認的Innodb便可。
MyISAM:以讀寫插入爲主的應用程序,好比博客系統、新聞門戶網站。
Innodb:更新(刪除)操做頻率也高,或者要保證數據的完整性;併發量高,支持事務和外鍵。好比OA自動化辦公系統。
什麼是索引?
索引是一種特殊的文件(InnoDB數據表上的索引是表空間的一個組成部分),它們包含着對數據表裏全部記錄的引用指針
索引是一種數據結構。數據庫索引,是數據庫管理系統中一個排序的數據結構,以協助快速查詢、更新數據庫表中數據。索引的實現一般使用B樹及其變種B+樹。
更通俗的說,索引就至關於目錄。爲了方便查找書中的內容,經過對內容創建索引造成目錄。索引是一個文件,它是要佔據物理空間的。
索引有哪些優缺點?
索引的優勢
能夠大大加快數據的檢索速度,這也是建立索引的最主要的緣由。
經過使用索引,能夠在查詢的過程當中,使用優化隱藏器,提升系統的性能。
索引的缺點
時間方面:建立索引和維護索引要耗費時間,具體地,當對錶中的數據進行增長、刪除和修改的時候,索引也要動態的維護,會下降增/改/刪的執行效率;
空間方面:索引須要佔物理空間。
索引使用場景(重點)
where
上圖中,根據id
查詢記錄,由於id
字段僅創建了主鍵索引,所以此SQL執行可選的索引只有主鍵索引,若是有多個,最終會選一個較優的做爲檢索的依據。
-- 增長一個沒有創建索引的字段 alter table innodb1 add sex char(1); -- 按sex檢索時可選的索引爲null EXPLAIN SELECT * from innodb1 where sex='男';
能夠嘗試在一個字段未創建索引時,根據該字段查詢的效率,而後對該字段創建索引(
alter table 表名 add index(字段名)
),一樣的SQL執行的效率,你會發現查詢效率會有明顯的提高(數據量越大越明顯)。
order by
當咱們使用order by將查詢結果按照某個字段排序時,若是該字段沒有創建索引,那麼執行計劃會將查詢出的全部數據使用外部排序(將數據從硬盤分批讀取到內存使用內部排序,最後合併排序結果),這個操做是很影響性能的,由於須要將查詢涉及到的全部數據從磁盤中讀到內存(若是單條數據過大或者數據量過多都會下降效率),更不管讀到內存以後的排序了。
可是若是咱們對該字段創建索引alter table 表名 add index(字段名),那麼因爲索引自己是有序的,所以直接按照索引的順序和映射關係逐條取出數據便可。並且若是分頁的,那麼只用取出索引表某個範圍內的索引對應的數據,而不用像上述那取出全部數據進行排序再返回某個範圍內的數據。(從磁盤取數據是最影響性能的)
join
對join
語句匹配關係(on
)涉及的字段創建索引可以提升效率
索引覆蓋
若是要查詢的字段都創建過索引,那麼引擎會直接在索引表中查詢而不會訪問原始數據(不然只要有一個字段沒有創建索引就會作全表掃描),這叫索引覆蓋。所以咱們須要儘量的在select後只寫必要的查詢字段,以增長索引覆蓋的概率。
這裏值得注意的是不要想着爲每一個字段創建索引,由於優先使用索引的優點就在於其體積小。
主鍵索引: 數據列不容許重複,不容許爲NULL,一個表只能有一個主鍵。
惟一索引:數據列不容許重複,容許爲NULL值,一個表容許多個列建立惟一索引。
能夠經過 ALTER TABLE table_name ADD UNIQUE (column); 建立惟一索引
能夠經過 ALTER TABLE table_name ADD UNIQUE (column1,column2); 建立惟一組合索引
普通索引: 基本的索引類型,沒有惟一性的限制,容許爲NULL值。
能夠經過ALTER TABLE table_name ADD INDEX index_name (column);建立普通索引
能夠經過ALTER TABLE table_name ADD INDEX index_name(column1, column2, column3);建立組合索引
全文索引: 是目前搜索引擎使用的一種關鍵技術。
能夠經過ALTER TABLE table_name ADD FULLTEXT (column);建立全文索引
索引的數據結構(b樹,hash)
索引的數據結構和具體存儲引擎的實現有關,在MySQL中使用較多的索引有Hash索引,B+樹索引等,而咱們常用的InnoDB存儲引擎的默認索引實現爲:B+樹索引。對於哈希索引來講,底層的數據結構就是哈希表,所以在絕大多數需求爲單條記錄查詢的時候,能夠選擇哈希索引,查詢性能最快;其他大部分場景,建議選擇BTree索引。
1)B樹索引
mysql經過存儲引擎取數據,基本上90%的人用的就是InnoDB了,按照實現方式分,InnoDB的索引類型目前只有兩種:BTREE(B樹)索引和HASH索引。B樹索引是Mysql數據庫中使用最頻繁的索引類型,基本全部存儲引擎都支持BTree索引。一般咱們說的索引不出意外指的就是(B樹)索引(實際是用B+樹實現的,由於在查看錶索引時,mysql一概打印BTREE,因此簡稱爲B樹索引)
查詢方式:
主鍵索引區:PI(關聯保存的時數據的地址)按主鍵查詢,
普通索引區:si(關聯的id的地址,而後再到達上面的地址)。因此按主鍵查詢,速度最
B+tree性質:
2)哈希索引
簡要說下,相似於數據結構中簡單實現的HASH表(散列表)同樣,當咱們在mysql中用哈希索引時,主要就是經過Hash算法(常見的Hash算法有直接定址法、平方取中法、摺疊法、除數取餘法、隨機數法),將數據庫字段數據轉換成定長的Hash值,與這條數據的行指針一併存入Hash表的對應位置;若是發生Hash碰撞(兩個不一樣關鍵字的Hash值相同),則在對應Hash鍵下以鏈表形式存儲。固然這只是簡略模擬圖。
索引用來快速地尋找那些具備特定值的記錄。若是沒有索引,通常來講執行查詢時遍歷整表。
索引的原理很簡單,就是把無序的數據變成有序的查詢
索引算法有哪些?
索引算法有 BTree算法和Hash算法
BTree算法
BTree是最經常使用的mysql數據庫索引算法,也是mysql默認的算法。由於它不只能夠被用在=,>,>=,<,<=和between這些比較操做符上,並且還能夠用於like操做符,只要它的查詢條件是一個不以通配符開頭的常量, 例如:
-- 只要它的查詢條件是一個不以通配符開頭的常量 select * from user where name like 'jack%'; -- 若是一通配符開頭,或者沒有使用常量,則不會使用索引,例如: select * from user where name like '%jack';
Hash算法
Hash Hash索引只能用於對等比較,例如=,<=>(至關於=)操做符。因爲是一次定位數據,不像BTree索引須要從根節點到枝節點,最後才能訪問到頁節點這樣屢次IO訪問,因此檢索效率遠高於BTree索引。
索引設計的原則?
建立索引的原則(重中之重)
索引雖好,但也不是無限制的使用,最好符合一下幾個原則
1) 最左前綴匹配原則,組合索引很是重要的原則,mysql會一直向右匹配直到遇到範圍查詢(>、<、between、like)就中止匹配,好比a = 1 and b = 2 and c > 3 and d = 4 若是創建(a,b,c,d)順序的索引,d是用不到索引的,若是創建(a,b,d,c)的索引則均可以用到,a,b,d的順序能夠任意調整。
2)較頻繁做爲查詢條件的字段纔去建立索引
3)更新頻繁字段不適合建立索引
4)如果不能有效區分數據的列不適合作索引列(如性別,男女未知,最多也就三種,區分度實在過低)
5)儘可能的擴展索引,不要新建索引。好比表中已經有a的索引,如今要加(a,b)的索引,那麼只須要修改原來的索引便可。
6)定義有外鍵的數據列必定要創建索引。
7)對於那些查詢中不多涉及的列,重複值比較多的列不要創建索引。
8)對於定義爲text、image和bit的數據類型的列不要創建索引。
建立索引的三種方式,刪除索引
第一種方式:在執行CREATE TABLE時建立索引
CREATE TABLE user_index2 ( id INT auto_increment PRIMARY KEY, first_name VARCHAR (16), last_name VARCHAR (16), id_card VARCHAR (18), information text, KEY name (first_name, last_name), FULLTEXT KEY (information), UNIQUE KEY (id_card) );
第二種方式:使用ALTER TABLE命令去增長索引
ALTER TABLE table_name ADD INDEX index_name (column_list);
ALTER TABLE用來建立普通索引、UNIQUE索引或PRIMARY KEY索引。
其中table_name是要增長索引的表名,column_list指出對哪些列進行索引,多列時各列之間用逗號分隔。
索引名index_name可本身命名,缺省時,MySQL將根據第一個索引列賦一個名稱。另外,ALTER TABLE容許在單個語句中更改多個表,所以能夠在同時建立多個索引
第三種方式:使用CREATE INDEX命令建立
CREATE INDEX index_name ON table_name (column_list);
CREATE INDEX可對錶增長普通索引或UNIQUE索引。(可是,不能建立PRIMARY KEY索引)
刪除索引
根據索引名刪除普通索引、惟一索引、全文索引:alter table 表名 drop KEY 索引名
alter table user_index drop KEY name; alter table user_index drop KEY id_card; alter table user_index drop KEY information;
刪除主鍵索引:alter table 表名 drop primary key(由於主鍵只有一個)。這裏值得注意的是,若是主鍵自增加,那麼不能直接執行此操做(自增加依賴於主鍵索引)
須要取消自增加再行刪除:
alter table user_index -- 從新定義字段 MODIFY id int, drop PRIMARY KEY
但一般不會刪除主鍵,由於設計主鍵必定與業務邏輯無關。
建立索引時須要注意什麼?
非空字段:應該指定列爲NOT NULL,除非你想存儲NULL。在mysql中,含有空值的列很難進行查詢優化,由於它們使得索引、索引的統計信息以及比較運算更加複雜。你應該用0、一個特殊的值或者一個空串代替空值;
取值離散大的字段:(變量各個取值之間的差別程度)的列放到聯合索引的前面,能夠經過count()函數查看字段的差別值,返回值越大說明字段的惟一值越多字段的離散程度高;
索引字段越小越好:數據庫的數據存儲以頁爲單位一頁存儲的數據越多一次IO操做獲取的數據越大效率越高。
使用索引查詢必定能提升查詢的性能嗎?爲何
一般,經過索引查詢數據比全表掃描要快。可是咱們也必須注意到它的代價。
百萬級別或以上的數據如何刪除
關於索引:因爲索引須要額外的維護成本,由於索引文件是單獨存在的文件,因此當咱們對數據的增長,修改,刪除,都會產生額外的對索引文件的操做,這些操做須要消耗額外的IO,會下降增/改/刪的執行效率。因此,在咱們刪除數據庫百萬級別數據的時候,查詢MySQL官方手冊得知刪除數據的速度和建立的索引數量是成正比的。
前綴索引
語法:index(field(10)),使用字段值的前10個字符創建索引,默認是使用字段的所有內容創建索引。
前提:前綴的標識度高。好比密碼就適合創建前綴索引,由於密碼幾乎各不相同。
實操的難度:在於前綴截取的長度。
咱們能夠利用select count(*)/count(distinct left(password,prefixLen));,經過從調整prefixLen的值(從1自增)查看不一樣前綴長度的一個平均匹配度,接近1時就能夠了(表示一個密碼的前prefixLen個字符幾乎能肯定惟一一條記錄)
什麼是最左前綴原則?什麼是最左匹配原則
B樹和B+樹的區別
使用B樹的好處
B樹能夠在內部節點同時存儲鍵和值,所以,把頻繁訪問的數據放在靠近根節點的地方將會大大提升熱點數據的查詢效率。這種特性使得B樹在特定數據重複屢次查詢的場景中更加高效。
使用B+樹的好處
因爲B+樹的內部節點只存放鍵,不存放值,所以,一次讀取,能夠在內存頁中獲取更多的鍵,有利於更快地縮小查找範圍。 B+樹的葉節點由一條鏈相連,所以,當須要進行一次全數據遍歷的時候,B+樹只須要使用O(logN)時間找到最小的一個節點,而後經過鏈進行O(N)的順序遍歷便可。而B樹則須要對樹的每一層進行遍歷,這會須要更多的內存置換次數,所以也就須要花費更多的時間
Hash索引和B+樹全部有什麼區別或者說優劣呢?
首先要知道Hash索引和B+樹索引的底層實現原理:
hash索引底層就是hash表,進行查找時,調用一次hash函數就能夠獲取到相應的鍵值,以後進行回表查詢得到實際數據。B+樹底層實現是多路平衡查找樹。對於每一次的查詢都是從根節點出發,查找到葉子節點方能夠得到所查鍵值,而後根據查詢判斷是否須要回表查詢數據。
那麼能夠看出他們有如下的不一樣:
所以,在大多數狀況下,直接選擇B+樹索引能夠得到穩定且較好的查詢速度。而不須要使用hash索引。
數據庫爲何使用B+樹而不是B樹
B樹只適合隨機檢索,而B+樹同時支持隨機檢索和順序檢索;
B+樹在知足聚簇索引和覆蓋索引的時候不須要回表查詢數據
在B+樹的索引中,葉子節點可能存儲了當前的key值,也可能存儲了當前的key值以及整行的數據,這就是聚簇索引和非聚簇索引。 在InnoDB中,只有主鍵索引是聚簇索引,若是沒有主鍵,則挑選一個惟一鍵創建聚簇索引。若是沒有惟一鍵,則隱式的生成一個鍵來創建聚簇索引。
當查詢使用聚簇索引時,在對應的葉子節點,能夠獲取到整行數據,所以不用再次進行回表查詢。
什麼是聚簇索引?什麼時候使用聚簇索引與非聚簇索引
聚簇索引:將數據存儲與索引放到了一塊,找到索引也就找到了數據
非聚簇索引:將數據存儲於索引分開結構,索引結構的葉子節點指向了數據的對應行,myisam經過key_buffer把索引先緩存到內存中,當須要訪問數據時(經過索引訪問數據),在內存中直接搜索索引,而後經過索引找到磁盤相應數據,這也就是爲何索引不在key buffer命中時,速度慢的緣由
澄清一個概念:innodb中,在聚簇索引之上建立的索引稱之爲輔助索引,輔助索引訪問數據老是須要二次查找,非聚簇索引都是輔助索引,像複合索引、前綴索引、惟一索引,輔助索引葉子節點存儲的再也不是行的物理位置,而是主鍵值
什麼時候使用聚簇索引與非聚簇索引
非聚簇索引必定會回表查詢嗎?
不必定,這涉及到查詢語句所要求的字段是否所有命中了索引,若是所有命中了索引,那麼就沒必要再進行回表查詢。
舉個簡單的例子,假設咱們在員工表的年齡上創建了索引,那麼當進行select age from employee where age < 20的查詢時,在索引的葉子節點上,已經包含了age信息,不會再次進行回表查詢。
聯合索引是什麼?爲何須要注意聯合索引中的順序?
MySQL能夠使用多個字段同時創建一個索引,叫作聯合索引。在聯合索引中,若是想要命中索引,須要按照創建索引時的字段順序挨個使用,不然沒法命中索引。
具體緣由爲:
MySQL使用索引時須要索引有序,假設如今創建了"name,age,school"的聯合索引,那麼索引的排序爲: 先按照name排序,若是name相同,則按照age排序,若是age的值也相等,則按照school進行排序。
當進行查詢時,此時索引僅僅按照name嚴格有序,所以必須首先使用name字段進行等值查詢,以後對於匹配到的列而言,其按照age字段嚴格有序,此時能夠使用age字段用作索引查找,以此類推。所以在創建聯合索引的時候應該注意索引列的順序,通常狀況下,將查詢需求頻繁或者字段選擇性高的列放在前面。此外能夠根據特例的查詢或者表結構進行單獨的調整。
事務
什麼是數據庫事務?
事務是一個不可分割的數據庫操做序列,也是數據庫併發控制的基本單位,其執行的結果必須使數據庫從一種一致性狀態變到另外一種一致性狀態。事務是邏輯上的一組操做,要麼都執行,要麼都不執行。
事務最經典也常常被拿出來講例子就是轉帳了。
假如小明要給小紅轉帳1000元,這個轉帳會涉及到兩個關鍵操做就是:將小明的餘額減小1000元,將小紅的餘額增長1000元。萬一在這兩個操做之間忽然出現錯誤好比銀行系統崩潰,致使小明餘額減小而小紅的餘額沒有增長,這樣就不對了。事務就是保證這兩個關鍵操做要麼都成功,要麼都要失敗。
事物的四大特性(ACID)介紹一下?
關係性數據庫須要遵循ACID規則,具體內容以下:
什麼是髒讀?幻讀?不可重複讀?
什麼是事務的隔離級別?MySQL的默認隔離級別是什麼?
爲了達到事務的四大特性,數據庫定義了4種不一樣的事務隔離級別,由低到高依次爲Read uncommitted、Read committed、Repeatable read、Serializable,這四個級別能夠逐個解決髒讀、不可重複讀、幻讀這幾類問題。
SQL 標準定義了四個隔離級別:
這裏須要注意的是:Mysql 默認採用的 REPEATABLE_READ隔離級別 Oracle 默認採用的 READ_COMMITTED隔離級別
事務隔離機制的實現基於鎖機制和併發調度。其中併發調度使用的是MVVC(多版本併發控制),經過保存修改的舊版本信息來支持併發一致性讀和回滾等特性。
由於隔離級別越低,事務請求的鎖越少,因此大部分數據庫系統的隔離級別都是READ-COMMITTED(讀取提交內容):,可是你要知道的是InnoDB 存儲引擎默認使用 **REPEATABLE-READ(可重讀)**並不會有任何性能損失。
InnoDB 存儲引擎在 分佈式事務 的狀況下通常會用到**SERIALIZABLE(可串行化)**隔離級別。
咱們的程序或者工具要操做數據庫,第一步要作什麼事情?
跟數據庫創建鏈接。
首先,MySQL 必需要運行一個服務,監聽默認的 3306 端口。
在咱們開發系統跟第三方對接的時候,必需要弄清楚的有兩件事。
第一個就是通訊協議,好比咱們是用 HTTP 仍是 WebService 仍是 TCP?
第二個是消息格式,好比咱們用 XML 格式,仍是 JSON 格式,仍是定長格式?報文頭長度多少,包含什麼內容,每一個字段的詳細含義。好比咱們以前跟銀聯對接,銀聯的銀行卡聯網規範,約定了一種比較複雜的通信協議叫作:四進四出單工異步長鏈接(爲了保證穩定性和性能)。
MySQL 是支持多種通訊協議的,能夠使用同步/異步的方式,支持長鏈接/短鏈接。這裏咱們拆分來看。第一個是通訊類型
通訊類型: 同步或者異步
同步通訊的特色:
一、同步通訊依賴於被調用方,受限於被調用方的性能。也就是說,應用操做數據庫,線程會阻塞,等待數據庫的返回。
二、通常只能作到一對一,很難作到一對多的通訊。
異步跟同步相反:
一、異步能夠避免應用阻塞等待,可是不能節省 SQL 執行的時間。
二、若是異步存在併發,每個 SQL 的執行都要單獨創建一個鏈接,避免數據混亂。可是這樣會給服務端帶來巨大的壓力(一個鏈接就會建立一個線程,線程間切換會佔用大量 CPU 資源)。另外異步通訊還帶來了編碼的複雜度,因此通常不建議使用。若是要異步,必須使用鏈接池,排隊從鏈接池獲取鏈接而不是建立新鏈接。
通常來講咱們鏈接數據庫都是同步鏈接。
鏈接方式: 長鏈接或者短鏈接
MySQL 既支持短鏈接,也支持長鏈接。短鏈接就是操做完畢之後,立刻 close 掉。長鏈接能夠保持打開,減小服務端建立和釋放鏈接的消耗,後面的程序訪問的時候還能夠使用這個鏈接。通常咱們會在鏈接池中使用長鏈接。
保持長鏈接會消耗內存。長時間不活動的鏈接,MySQL 服務器會斷開。
show global variables like 'wait_timeout'; -- 非交互式超時時間, 如 JDBC 程序 show global variables like 'interactive_timeout'; -- 交互式超時時間, 如數據庫工具
默認都是 28800 秒,8 小時。
咱們怎麼查看 MySQL 當前有多少個鏈接?
能夠用 show status 命令:
show global status like 'Thread%';
Threads_cached:緩存中的線程鏈接數。
Threads_connected:當前打開的鏈接數。
Threads_created:爲處理鏈接建立的線程數。
Threads_running:非睡眠狀態的鏈接數,一般指併發鏈接數。
每產生一個鏈接或者一個會話,在服務端就會建立一個線程來處理。反過來,若是要殺死會話,就是 Kill 線程。
有了鏈接數,怎麼知道當前鏈接的狀態?
也能夠使用 SHOW PROCESSLIST;(root 用戶)查看 SQL 的執行狀態。
一些常見的狀態:
MySQL 服務容許的最大鏈接數是多少呢?
在 5.7 版本中默認是 151 個,最大能夠設置成 16384(2^14)。
show variables like 'max_connections';
show 的參數說明:
一、級別:會話 session 級別(默認);全局 global 級別
二、動態修改:set,重啓後失效;永久生效,修改配置文件/etc/my.conf
set global max_connections = 1000;
通訊協議
MySQL 支持哪些通訊協議呢?
第一種是 Unix Socket。
好比咱們在 Linux 服務器上,若是沒有指定-h 參數,它就用 socket 方式登陸(省略了-S /var/lib/mysql/mysql.sock)。
它不用經過網絡協議,也能夠鏈接到 MySQL 的服務器,它須要用到服務器上的一個物理文件(/var/lib/mysql/mysql.sock)。
select @@socket;
若是指定-h 參數,就會用第二種方式,TCP/IP 協議。
mysql -h192.168.8.211 -uroot -p123456
我 們 的 編 程 語 言 的 連 接 模 塊 都 是 用 TCP 協 議 連 接 到 MySQL 服 務 器 的 , 比 如
mysql-connector-java-x.x.xx.jar。
另外還有命名管道(Named Pipes)和內存共享(Share Memory)的方式,這兩種通訊方式只能在 Windows 上面使用,通常用得比較少。
1.1.2.通訊方式
第二個是通訊方式。
單工:
在兩臺計算機通訊的時候,數據的傳輸是單向的。生活中的類比:遙控器。
半雙工:
在兩臺計算機之間,數據傳輸是雙向的,你能夠給我發送,我也能夠給你發送,
可是在這個通信鏈接裏面,同一時間只能有一臺服務器在發送數據,也就是你要給我發
的話,也必須等我發給你完了以後才能給我發。生活中的類比:對講機。
全雙工:
數據的傳輸是雙向的,而且能夠同時傳輸。生活中的類比:打電話。
MySQL 使用了半雙工的通訊方式?
要麼是客戶端向服務端發送數據,要麼是服務端向客戶端發送數據,這兩個動做不能同時發生。因此客戶端發送 SQL 語句給服務端的時候,(在一次鏈接裏面)數據是不能分紅小塊發送的,無論你的 SQL 語句有多大,都是一次性發送。
好比咱們用 MyBatis 動態 SQL 生成了一個批量插入的語句,插入 10 萬條數據,values後面跟了一長串的內容,或者 where 條件 in 裏面的值太多,會出現問題。
這個時候咱們必需要調整 MySQL 服務器配置 max_allowed_packet 參數的值(默認是 4M),把它調大,不然就會報錯。
另外一方面,對於服務端來講,也是一次性發送全部的數據,不能由於你已經取到了想要的數據就中斷操做,這個時候會對網絡和內存產生大量消耗。
因此,咱們必定要在程序裏面避免不帶 limit 的這種操做,好比一次把全部知足條件的數據所有查出來,必定要先 count 一下。若是數據量的話,能夠分批查詢。
執行一條查詢語句,客戶端跟服務端創建鏈接以後呢?下一步要作什麼?
MySQL 內部自帶了一個緩存模塊。
緩存的做用咱們應該很清楚了,把數據以 KV 的形式放到內存裏面,能夠加快數據的讀取速度,也能夠減小服務器處理的時間。可是 MySQL 的緩存咱們好像比較陌生,歷來沒有去配置過,也不知道它何時生效?
好比 user_innodb 有 500 萬行數據,沒有索引。咱們在沒有索引的字段上執行一樣的查詢,你們以爲第二次會快嗎?
select * from user_innodb where name='javaHuang';
爲什麼緩存沒有生效,爲何?MySQL 的緩存默認是關閉的。
show variables like 'query_cache%';
默認關閉的意思就是不推薦使用,爲何 MySQL 不推薦使用它自帶的緩存呢?
主要是由於 MySQL 自帶的緩存的應用場景有限,第一個是它要求 SQL 語句必須如出一轍,中間多一個空格,字母大小寫不一樣都被認爲是不一樣的的 SQL。
第二個是表裏面任何一條數據發生變化的時候,這張表全部緩存都會失效,因此對於有大量數據更新的應用,也不適合。
因此緩存這一塊,咱們仍是交給 ORM 框架(好比 MyBatis 默認開啓了一級緩存),或者獨立的緩存服務,好比 Redis 來處理更合適。
在 MySQL 8.0 中,查詢緩存已經被移除了。
咱們沒有使用緩存的話,就會跳過緩存的模塊,下一步咱們要作什麼呢?
OK,這裏我會有一個疑問,爲何個人一條 SQL 語句可以被識別呢?假如我隨便執行一個字符串 penyuyan,服務器報了一個 1064 的錯:
[Err] 1064 - You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for theright syntax to use near 'penyuyan' at line 1
它是怎麼知道我輸入的內容是錯誤的?
這個就是 MySQL 的 Parser 解析器和 Preprocessor 預處理模塊。
這一步主要作的事情是對語句基於 SQL 語法進行詞法和語法分析和語義的解析。
1.3.1.詞法解析
詞法分析就是把一個完整的 SQL 語句打碎成一個個的單詞。
好比一個簡單的 SQL 語句:
select name from user where id = 1;
它會打碎成 8 個符號,每一個符號是什麼類型,從哪裏開始到哪裏結束。
1.3.2.語法解析
第二步就是語法分析,語法分析會對 SQL 作一些語法檢查,好比單引號有沒有閉合,而後根據 MySQL 定義的語法規則,根據 SQL 語句生成一個數據結構。這個數據結構咱們把它叫作解析樹(select_lex)。
任何數據庫的中間件,好比 Mycat,Sharding-JDBC(用到了 Druid Parser),都必需要有詞法和語法分析功能,在市面上也有不少的開源的詞法解析的工具(好比 LEX,Yacc)。
1.3.3.預處理器
問題:若是我寫了一個詞法和語法都正確的 SQL,可是表名或者字段不存在,會在哪裏報錯?是在數據庫的執行層仍是解析器?好比:
select * from penyuyan;
解析器能夠分析語法,可是它怎麼知道數據庫裏面有什麼表,表裏面有什麼字段呢?
實際上仍是在解析的時候報錯,解析 SQL 的環節裏面有個預處理器。
它會檢查生成的解析樹,解決解析器沒法解析的語義。好比,它會檢查表和列名是否存在,檢查名字和別名,保證沒有歧義。
預處理以後獲得一個新的解析樹。
1.4.2. 優化器能夠作什麼?
MySQL 的優化器能處理哪些優化類型呢?
舉兩個簡單的例子:
一、當咱們對多張表進行關聯查詢的時候,以哪一個表的數據做爲基準表。
二、有多個索引能夠使用的時候,選擇哪一個索引。
實際上,對於每一種數據庫來講,優化器的模塊都是必不可少的,他們經過複雜的算法實現儘量優化查詢效率的目標。
若是對於優化器的細節感興趣,能夠看看《數據庫查詢優化器的藝術-原理解析與SQL性能優化》。
可是優化器也不是萬能的,並非再垃圾的 SQL 語句都能自動優化,也不是每次都能選擇到最優的執行計劃,你們在編寫 SQL 語句的時候仍是要注意。
若是咱們想知道優化器是怎麼工做的,它生成了幾種執行計劃,每種執行計劃的 cost是多少,應該怎麼作?
1.4.3.優化器是怎麼獲得執行計劃的?
首先咱們要啓用優化器的追蹤(默認是關閉的):
SHOW VARIABLES LIKE 'optimizer_trace'; set optimizer_trace='enabled=on';
注意開啓這開關是會消耗性能的,由於它要把優化分析的結果寫到表裏面,因此不要輕易開啓,或者查看完以後關閉它(改爲 off)。
注意:參數分爲 session 和 global 級別。
接着咱們執行一個 SQL 語句,優化器會生成執行計劃:
select t.tcid from teacher t,teacher_contact tc where t.tcid = tc.tcid;
這個時候優化器分析的過程已經記錄到系統表裏面了,咱們能夠查詢:
select * from information_schema.optimizer_trace\G
它是一個 JSON 類型的數據,主要分紅三部分,準備階段、優化階段和執行階段。
expanded_query 是優化後的 SQL 語句。
considered_execution_plans 裏面列出了全部的執行計劃。
分析完記得關掉它:
set optimizer_trace="enabled=off"; SHOW VARIABLES LIKE 'optimizer_trace';
1.4.4. 優化器獲得的結果
優化完以後,獲得一個什麼東西呢?
優化器最終會把解析樹變成一個查詢執行計劃,查詢執行計劃是一個數據結構。
固然,這個執行計劃是否是必定是最優的執行計劃呢?不必定,由於 MySQL 也有可能覆蓋不到全部的執行計劃。
咱們怎麼查看 MySQL 的執行計劃呢?好比多張表關聯查詢,先查詢哪張表?在執行查詢的時候可能用到哪些索引,實際上用到了什麼索引?
MySQL 提供了一個執行計劃的工具。咱們在 SQL 語句前面加上 EXPLAIN,就能夠看到執行計劃的信息。
EXPLAIN select name from user where id=1
注意 Explain 的結果也不必定最終執行的方式。
獲得執行計劃之後,SQL 語句是否是終於能夠執行了?
問題又來了:
一、從邏輯的角度來講,咱們的數據是放在哪裏的,或者說放在一個什麼結構裏面?
二、執行計劃在哪裏執行?是誰去執行?
1.5.1. 存儲引擎基本介紹
咱們先回答第一個問題:在關係型數據庫裏面,數據是放在什麼結構裏面的?(放在表 Table 裏面的)
咱們能夠把這個表理解成 Excel 電子表格的形式。因此咱們的表在存儲數據的同時,還要組織數據的存儲結構,這個存儲結構就是由咱們的存儲引擎決定的,因此咱們也能夠把存儲引擎叫作表類型。
在 MySQL 裏面,支持多種存儲引擎,他們是能夠替換的,因此叫作插件式的存儲引擎。爲何要搞這麼多存儲引擎呢?一種還不夠用嗎?
這個問題先留着。
1.5.2. 查看存儲引擎
好比咱們數據庫裏面已經存在的表,咱們怎麼查看它們的存儲引擎呢?
show table status from `ToBeTopJavaer`;
或者經過 DDL 建表語句來查看。
在 MySQL 裏面,咱們建立的每一張表均可以指定它的存儲引擎,而不是一個數據庫只能使用一個存儲引擎。存儲引擎的使用是以表爲單位的。並且,建立表以後還能夠修改存儲引擎。
咱們說一張表使用的存儲引擎決定咱們存儲數據的結構,那在服務器上它們是怎麼存儲的呢?咱們先要找到數據庫存放數據的路徑:
show variables like 'datadir';
默認狀況下,每一個數據庫有一個本身文件夾,以 ToBeTopJavaer數據庫爲例。
任何一個存儲引擎都有一個 frm 文件,這個是表結構定義文件。
不一樣的存儲引擎存放數據的方式不同,產生的文件也不同,innodb 是 1 個,memory 沒有,myisam 是兩個。
這些存儲引擎的差異在哪呢?
1.5.3. 存儲引擎比較
常見存儲引擎
MyISAM 和 InnoDB 是咱們用得最多的兩個存儲引擎,在 MySQL 5.5 版本以前,默認的存儲引擎是 MyISAM,它是 MySQL 自帶的。咱們建立表的時候不指定存儲引擎,它就會使用 MyISAM 做爲存儲引擎。
MyISAM 的前身是 ISAM(Indexed Sequential Access Method:利用索引,順序存取數據的方法)。
5.5 版本以後默認的存儲引擎改爲了 InnoDB,它是第三方公司爲 MySQL 開發的。
爲何要改呢?最主要的緣由仍是 InnoDB 支持事務,支持行級別的鎖,對於業務一致性要求高的場景來講更適合。
這個裏面又有 Oracle 和 MySQL 公司的一段恩怨情仇。
InnoDB 原本是 InnobaseOy 公司開發的,它和 MySQL AB 公司合做開源了 InnoDB的代碼。可是沒想到 MySQL 的競爭對手 Oracle 把 InnobaseOy 收購了。後來 08 年 Sun 公司(開發 Java 語言的 Sun)收購了 MySQL AB,09 年 Sun 公司又被 Oracle 收購了,因此 MySQL,InnoDB 又是一家了。有人以爲 MySQL 愈來愈像Oracle,其實也是這個緣由。
那麼除了這兩個咱們最熟悉的存儲引擎,數據庫還支持其餘哪些經常使用的存儲引擎呢?
數據庫支持的存儲引擎
咱們能夠用這個命令查看數據庫對存儲引擎的支持狀況:
show engines ;
其中有存儲引擎的描述和對事務、XA 協議和 Savepoints 的支持。XA 協議用來實現分佈式事務(分爲本地資源管理器,事務管理器)。
Savepoints 用來實現子事務(嵌套事務)。建立了一個 Savepoints 以後,事務就能夠回滾到這個點,不會影響到建立 Savepoints 以前的操做。
這些數據庫支持的存儲引擎,分別有什麼特性呢?
MyISAM( 3 個文件)
These tables have a small footprint. Table-level locking limits the performance in read/write workloads, so it is often used in read-only or read-mostly workloads in Web and data warehousing configurations.
應用範圍比較小。表級鎖定限制了讀/寫的性能,所以在 Web 和數據倉庫配置中,它一般用於只讀或以讀爲主的工做。
特色:
1.支持表級別的鎖(插入和更新會鎖表)。不支持事務。
2.擁有較高的插入(insert)和查詢(select)速度。
3.存儲了表的行數(count 速度更快)。
(怎麼快速向數據庫插入 100 萬條數據?咱們有一種先用 MyISAM 插入數據,而後修改存儲引擎爲 InnoDB 的操做。)
適合:只讀之類的數據分析的項目。
InnoDB( 2 個文件)
The default storage engine in MySQL 5.7. InnoDB is a transaction-safe (ACID compliant) storage engine for MySQL that has commit, rollback, and crash-recovery capabilities to protect user data. InnoDB row-level locking (without escalation to coarser granularity locks) and Oracle-style consistent nonlocking reads increase multi-user concurrency and performance.
InnoDB stores user data in clustered indexes to reduce I/O for common queries based on primary keys. To maintain data integrity, InnoDB also supports FOREIGN KEY referential-integrity constraints.
mysql 5.7 中的默認存儲引擎。InnoDB 是一個事務安全(與 ACID 兼容)的 MySQL存儲引擎,它具備提交、回滾和崩潰恢復功能來保護用戶數據。InnoDB 行級鎖(不升級爲更粗粒度的鎖)和 Oracle 風格的一致非鎖讀提升了多用戶併發性和性能。InnoDB 將用戶數據存儲在彙集索引中,以減小基於主鍵的常見查詢的 I/O。爲了保持數據完整性,InnoDB 還支持外鍵引用完整性約束。
特色:
1.支持事務,支持外鍵,所以數據的完整性、一致性更高。
2.支持行級別的鎖和表級別的鎖。
3.支持讀寫併發,寫不阻塞讀(MVCC)。
4.特殊的索引存放方式,能夠減小 IO,提高查詢效率。
適合:常常更新的表,存在併發讀寫或者有事務處理的業務系統。
Memory( 1 個文件)
Stores all data in RAM, for fast access in environments that require quick lookups of non-critical data. This engine was formerly known as the HEAP engine. Its use cases are decreasing; InnoDB with its buffer pool memory area provides a general-purpose and durable way to keep most or all data in memory, and NDBCLUSTER provides fast key-value lookups for huge distributed data sets.
將全部數據存儲在 RAM 中,以便在須要快速查找非關鍵數據的環境中快速訪問。這個引擎之前被稱爲堆引擎。其使用案例正在減小;InnoDB 及其緩衝池內存區域提供了一種通用、持久的方法來將大部分或全部數據保存在內存中,而 ndbcluster 爲大型分佈式數據集提供了快速的鍵值查找。
特色:
1.把數據放在內存裏面,讀寫的速度很快,可是數據庫重啓或者崩潰,數據會所有消
失。只適合作臨時表。
2.將表中的數據存儲到內存中。
CSV( 3 個文件)
Its tables are really text files with comma-separated values. CSV tables let you import or dump data in CSV format, to exchange data with scripts and applications that read and write that same format. Because CSV tables are not indexed, you typically keep the data in InnoDB tables during normal operation, and only use CSV tables during the import or export stage.
它的表其實是帶有逗號分隔值的文本文件。csv表容許以csv格式導入或轉儲數據,以便與讀寫相同格式的腳本和應用程序交換數據。由於 csv 表沒有索引,因此一般在正常操做期間將數據保存在 innodb 表中,而且只在導入或導出階段使用 csv 表。
特色:不容許空行,不支持索引。格式通用,能夠直接編輯,適合在不一樣數據庫之間導入導出。
Archive( 2 個文件)
These compact, unindexed tables are intended for storing and retrieving large amounts of seldom-referenced historical,archived, or security audit information.
這些緊湊的未索引的表用於存儲和檢索大量不多引用的歷史、存檔或安全審計信息。
特色:不支持索引,不支持 update delete。
這是 MySQL 裏面常見的一些存儲引擎,咱們看到了,不一樣的存儲引擎提供的特性都不同,它們有不一樣的存儲機制、索引方式、鎖定水平等功能。
咱們在不一樣的業務場景中對數據操做的要求不一樣,就能夠選擇不一樣的存儲引擎來知足咱們的需求,這個就是 MySQL 支持這麼多存儲引擎的緣由。
1.5.4. 如何選擇存儲引擎?
若是對數據一致性要求比較高,須要事務支持,能夠選擇 InnoDB。
若是數據查詢多更新少,對查詢性能要求比較高,能夠選擇 MyISAM。
若是須要一個用於查詢的臨時表,能夠選擇 Memory。
若是全部的存儲引擎都不能知足你的需求,而且技術能力足夠,能夠根據官網內部手冊用 C 語言開發一個存儲引擎:https://dev.mysql.com/doc/internals/en/custom-eng
OK,存儲引擎分析完了,它是咱們存儲數據的形式,繼續第二個問題,是誰使用執行計劃去操做存儲引擎呢?
這就是咱們的執行引擎,它利用存儲引擎提供的相應的 API 來完成操做。
爲何咱們修改了表的存儲引擎,操做方式不須要作任何改變?由於不一樣功能的存儲引擎實現的 API 是相同的。
最後把數據返回給客戶端,即便沒有結果也要返回。
基於上面分析的流程,咱們一塊兒來梳理一下 MySQL 的內部模塊。
一、 Connector:用來支持各類語言和 SQL 的交互,好比 PHP,Python,Java 的JDBC;
二、 Management Serveices & Utilities:系統管理和控制工具,包括備份恢復、MySQL 複製、集羣等等;
三、 Connection Pool:鏈接池,管理須要緩衝的資源,包括用戶密碼權限線程等等;
四、 SQL Interface:用來接收用戶的 SQL 命令,返回用戶須要的查詢結果
五、 Parser:用來解析 SQL 語句;
六、 Optimizer:查詢優化器;
七、 Cache and Buffer:查詢緩存,除了行記錄的緩存以外,還有表緩存,Key 緩存,權限緩存等等;
八、 Pluggable Storage Engines:插件式存儲引擎,它提供 API 給服務層使用,跟具體的文件打交道。
整體上,咱們能夠把 MySQL 分紅三層,跟客戶端對接的鏈接層,真正執行操做的服務層,和跟硬件打交道的存儲引擎層(參考 MyBatis:接口、核心、基礎)。
2.1.1.鏈接層
咱們的客戶端要鏈接到 MySQL 服務器 3306 端口,必需要跟服務端創建鏈接,那麼管理全部的鏈接,驗證客戶端的身份和權限,這些功能就在鏈接層完成。
2.1.2.服務層
鏈接層會把 SQL 語句交給服務層,這裏面又包含一系列的流程:
好比查詢緩存的判斷、根據 SQL 調用相應的接口,對咱們的 SQL 語句進行詞法和語法的解析(好比關鍵字怎麼識別,別名怎麼識別,語法有沒有錯誤等等)。而後就是優化器,MySQL 底層會根據必定的規則對咱們的 SQL 語句進行優化,最後再交給執行器去執行。
2.1.3.存儲引擎
存儲引擎就是咱們的數據真正存放的地方,在 MySQL 裏面支持不一樣的存儲引擎。再往下就是內存或者磁盤。
講完了查詢流程,咱們是否是再講講更新流程、插入流程和刪除流程?
在數據庫裏面,咱們說的 update 操做其實包括了更新、插入和刪除。若是你們有看過 MyBatis 的源碼,應該知道 Executor 裏面也只有 doQuery()和 doUpdate()的方法,沒有 doDelete()和 doInsert()。
更新流程和查詢流程有什麼不一樣呢?
基本流程也是一致的,也就是說,它也要通過解析器、優化器的處理,最後交給執行器。
區別就在於拿到符合條件的數據以後的操做。
3.1. 緩衝池 Buffer Pool
首先,InnnoDB 的數據都是放在磁盤上的,InnoDB 操做數據有一個最小的邏輯單位,叫作頁(索引頁和數據頁)。咱們對於數據的操做,不是每次都直接操做磁盤,由於磁盤的速度太慢了。InnoDB 使用了一種緩衝池的技術,也就是把磁盤讀到的頁放到一塊內存區域裏面。這個內存區域就叫 Buffer Pool。下一次讀取相同的頁,先判斷是否是在緩衝池裏。
下一次讀取相同的頁,先判斷是否是在緩衝池裏面,若是是,就直接讀取,不用再次訪問磁盤。
修改數據的時候,先修改緩衝池裏面的頁。內存的數據頁和磁盤數據不一致的時候,咱們把它叫作髒頁。InnoDB 裏面有專門的後臺線程把 Buffer Pool 的數據寫入到磁盤,每隔一段時間就一次性地把多個修改寫入磁盤,這個動做就叫作刷髒。
Buffer Pool 是 InnoDB 裏面很是重要的一個結構,它的內部又分紅幾塊區域。這裏咱們趁機到官網來認識一下 InnoDB 的內存結構和磁盤結構。
3.3.1.內存結構
Buffer Pool 主要分爲 3 個部分: Buffer Pool、Change Buffer、Adaptive Hash Index,另外還有一個(redo)log buffer。
一、 Buffer Pool
Buffer Pool 緩存的是頁面信息,包括數據頁、索引頁。
查看服務器狀態,裏面有不少跟 Buffer Pool 相關的信息:
SHOW STATUS LIKE '%innodb_buffer_pool%';
這些狀態均可以在官網查到詳細的含義,用搜索功能。
Buffer Pool 默認大小是 128M(134217728 字節),能夠調整。
查看參數(系統變量):
SHOW VARIABLES like '%innodb_buffer_pool%';
這些參數均可以在官網查到詳細的含義,用搜索功能。
內存的緩衝池寫滿了怎麼辦?(Redis 設置的內存滿了怎麼辦?)InnoDB 用 LRU算法來管理緩衝池(鏈表實現,不是傳統的 LRU,分紅了 young 和 old),通過淘汰的數據就是熱點數據。
內存緩衝區對於提高讀寫性能有很大的做用。思考一個問題:
當須要更新一個數據頁時,若是數據頁在 Buffer Pool 中存在,那麼就直接更新好了。不然的話就須要從磁盤加載到內存,再對內存的數據頁進行操做。也就是說,若是沒有命中緩衝池,至少要產生一次磁盤 IO,有沒有優化的方式呢?
二、 Change Buffer 寫緩衝
若是這個數據頁不是惟一索引,不存在數據重複的狀況,也就不須要從磁盤加載索引頁判斷數據是否是重複(惟一性檢查)。這種狀況下能夠先把修改記錄在內存的緩衝池中,從而提高更新語句(Insert、Delete、Update)的執行速度。
這一塊區域就是 Change Buffer。5.5 以前叫 Insert Buffer 插入緩衝,如今也能支持 delete 和 update。
最後把 Change Buffer 記錄到數據頁的操做叫作 merge。何時發生 merge?有幾種狀況:在訪問這個數據頁的時候,或者經過後臺線程、或者數據庫 shut down、redo log 寫滿時觸發。
若是數據庫大部分索引都是非惟一索引,而且業務是寫多讀少,不會在寫數據後馬上讀取,就能夠使用 Change Buffer(寫緩衝)。寫多讀少的業務,調大這個值:
SHOW VARIABLES LIKE 'innodb_change_buffer_max_size';
表明 Change Buffer 佔 Buffer Pool 的比例,默認 25%。
三、 Adaptive Hash Index
索引應該是放在磁盤的,爲何要專門把一種哈希的索引放到內存?好好思考。
四、 ( redo) Log Buffer
思考一個問題:若是 Buffer Pool 裏面的髒頁尚未刷入磁盤時,數據庫宕機或者重啓,這些數據丟失。若是寫操做寫到一半,甚至可能會破壞數據文件致使數據庫不可用。
爲了不這個問題,InnoDB 把全部對頁面的修改操做專門寫入一個日誌文件,而且在數據庫啓動時從這個文件進行恢復操做(實現 crash-safe)——用它來實現事務的持久性。
這個文件就是磁盤的 redo log(叫作重作日誌),對應於/var/lib/mysql/目錄下的ib_logfile0 和 ib_logfile1,每一個 48M。
這 種 日 志 和 磁 盤 配 合 的 整 個 過 程 , 其 實 就 是 MySQL 裏 的 WAL 技 術(Write-Ahead Logging),它的關鍵點就是先寫日誌,再寫磁盤。
show variables like 'innodb_log%';
問題:
一樣是寫磁盤,爲何不直接寫到 db file 裏面去?爲何先寫日誌再寫磁盤?
咱們先來了解一下隨機 I/O 和順序 I/O 的概念。
磁盤的最小組成單元是扇區,一般是 512 個字節。
操做系統和內存打交道,最小的單位是頁 Page。
操做系統和磁盤打交道,讀寫磁盤,最小的單位是塊 Block。
若是咱們所須要的數據是隨機分散在不一樣頁的不一樣扇區中,那麼找到相應的數據須要等到磁臂旋轉到指定的頁,而後盤片尋找到對應的扇區,才能找到咱們所須要的一塊數據,一次進行此過程直到找完全部數據,這個就是隨機 IO,讀取數據速度較慢。
假設咱們已經找到了第一塊數據,而且其餘所需的數據就在這一塊數據後邊,那麼就不須要從新尋址,能夠依次拿到咱們所需的數據,這個就叫順序 IO。
刷盤是隨機 I/O,而記錄日誌是順序 I/O,順序 I/O 效率更高。所以先把修改寫入日誌,能夠延遲刷盤時機,進而提高系統吞吐。
固然 redo log 也不是每一次都直接寫入磁盤,在 Buffer Pool 裏面有一塊內存區域(Log Buffer)專門用來保存即將要寫入日誌文件的數據,默認 16M,它同樣能夠節省磁盤 IO。
SHOW VARIABLES LIKE 'innodb_log_buffer_size';
須要注意:redo log 的內容主要是用於崩潰恢復。磁盤的數據文件,數據來自 buffer pool。redo log 寫入磁盤,不是寫入數據文件。
那麼,Log Buffer 何時寫入 log file?
在咱們寫入數據到磁盤的時候,操做系統自己是有緩存的。flush 就是把操做系統緩衝區寫入到磁盤。
log buffer 寫入磁盤的時機,由一個參數控制,默認是 1。
SHOW VARIABLES LIKE 'innodb_flush_log_at_trx_commit';
這是內存結構的第 4 塊內容,redo log,它又分紅內存和磁盤兩部分。redo log 有什麼特色?
一、redo log 是 InnoDB 存儲引擎實現的,並非全部存儲引擎都有。
二、不是記錄數據頁更新以後的狀態,而是記錄這個頁作了什麼改動,屬於物理日誌。
三、redo log 的大小是固定的,前面的內容會被覆蓋。
check point 是當前要覆蓋的位置。若是 write pos 跟 check point 重疊,說明 redo log 已經寫滿,這時候須要同步 redo log 到磁盤中。
這是 MySQL 的內存結構,總結一下,分爲:
Buffer pool、change buffer、Adaptive Hash Index、 log buffer。
磁盤結構裏面主要是各類各樣的表空間,叫作Table space。
3.3.2.磁盤結構
表空間能夠看作是 InnoDB 存儲引擎邏輯結構的最高層,全部的數據都存放在表空間中。InnoDB 的表空間分爲 5 大類。
系統表空間 system tablespace
在默認狀況下 InnoDB 存儲引擎有一個共享表空間(對應文件/var/lib/mysql/ibdata1),也叫系統表空間。
InnoDB 系統表空間包含 InnoDB 數據字典和雙寫緩衝區,Change Buffer 和 Undo Logs),若是沒有指定 file-per-table,也包含用戶建立的表和索引數據。
一、undo 在後面介紹,由於有獨立的表空間。
二、數據字典:由內部系統表組成,存儲表和索引的元數據(定義信息)。
三、雙寫緩衝(InnoDB 的一大特性):
InnoDB 的頁和操做系統的頁大小不一致,InnoDB 頁大小通常爲 16K,操做系統頁大小爲 4K,InnoDB 的頁寫入到磁盤時,一個頁須要分 4 次寫。
若是存儲引擎正在寫入頁的數據到磁盤時發生了宕機,可能出現頁只寫了一部分的狀況,好比只寫了 4K,就宕機了,這種狀況叫作部分寫失效(partial page write),可能會致使數據丟失。
show variables like 'innodb_doublewrite';
咱們不是有 redo log 嗎?可是有個問題,若是這個頁自己已經損壞了,用它來作崩潰恢復是沒有意義的。因此在對於應用 redo log 以前,須要一個頁的副本。若是出現了寫入失效,就用頁的副原本還原這個頁,而後再應用 redo log。這個頁的副本就是 double write,InnoDB 的雙寫技術。經過它實現了數據頁的可靠性。
跟 redo log 同樣,double write 由兩部分組成,一部分是內存的 double write,一個部分是磁盤上的 double write。由於 double write 是順序寫入的,不會帶來很大的開銷。
在默認狀況下,全部的表共享一個系統表空間,這個文件會愈來愈大,並且它的空間不會收縮。
獨佔表空間 file-per-table tablespaces
咱們可讓每張表獨佔一個表空間。這個開關經過 innodb_file_per_table 設置,默認開啓。
SHOW VARIABLES LIKE 'innodb_file_per_table';
開啓後,則每張表會開闢一個表空間,這個文件就是數據目錄下的 ibd 文件(例如/var/lib/mysql/gupao/user_innodb.ibd),存放表的索引和數據。
可是其餘類的數據,如回滾(undo)信息,插入緩衝索引頁、系統事務信息,二次寫緩衝(Double write buffer)等仍是存放在原來的共享表空間內。
通用表空間 general tablespaces
通用表空間也是一種共享的表空間,跟 ibdata1 相似。
能夠建立一個通用的表空間,用來存儲不一樣數據庫的表,數據路徑和文件能夠自定義。語法:
create tablespace ts2673 add datafile '/var/lib/mysql/ts2673.ibd' file_block_size=16K engine=innodb;
在建立表的時候能夠指定表空間,用 ALTER 修改表空間能夠轉移表空間。
create table t2673(id integer) tablespace ts2673;
不一樣表空間的數據是能夠移動的。
刪除表空間須要先刪除裏面的全部表:
drop table t2673; drop tablespace ts2673;
臨時表空間 temporary tablespaces
存儲臨時表的數據,包括用戶建立的臨時表,和磁盤的內部臨時表。對應數據目錄下的 ibtmp1 文件。當數據服務器正常關閉時,該表空間被刪除,下次從新產生。
Redo log
磁盤結構裏面的 redo log,在前面已經介紹過了。
undo log tablespace
undo log(撤銷日誌或回滾日誌)記錄了事務發生以前的數據狀態(不包括 select)。若是修改數據時出現異常,能夠用 undo log 來實現回滾操做(保持原子性)。
在執行 undo 的時候,僅僅是將數據從邏輯上恢復至事務以前的狀態,而不是從物理頁面上操做實現的,屬於邏輯格式的日誌。
redo Log 和 undo Log 與事務密切相關,統稱爲事務日誌。
undo Log 的數據默認在系統表空間 ibdata1 文件中,由於共享表空間不會自動收縮,也能夠單首創建一個 undo 表空間。
show global variables like '%undo%';
有了這些日誌以後,咱們來總結一下一個更新操做的流程,這是一個簡化的過程。
name 原值是 javaHuang。
update user set name = 'penyuyan' where id=1;
一、事務開始,從內存或磁盤取到這條數據,返回給 Server 的執行器;
二、執行器修改這一行數據的值爲 penyuyan;
三、記錄 name=qingshan 到 undo log;
四、記錄 name=penyuyan 到 redo log;
五、調用存儲引擎接口,在內存(Buffer Pool)中修改 name=penyuyan;
六、事務提交。
內存和磁盤之間,工做着不少後臺線程。
3.3.3.後臺線程
(供瞭解)
後臺線程的主要做用是負責刷新內存池中的數據和把修改的數據頁刷新到磁盤。後臺線程分爲:master thread,IO thread,purge thread,page cleaner thread。
master thread 負責刷新緩存數據到磁盤並協調調度其它後臺進程。
IO thread 分爲 insert buffer、log、read、write 進程。分別用來處理 insert buffer、重作日誌、讀寫請求的 IO 回調。
purge thread 用來回收 undo 頁。
page cleaner thread 用來刷新髒頁。
除了 InnoDB 架構中的日誌文件,MySQL 的 Server 層也有一個日誌文件,叫作binlog,它能夠被全部的存儲引擎使用。
binlog 以事件的形式記錄了全部的 DDL 和 DML 語句(由於它記錄的是操做而不是數據值,屬於邏輯日誌),能夠用來作主從複製和數據恢復。
跟 redo log 不同,它的文件內容是能夠追加的,沒有固定大小限制。
在開啓了 binlog 功能的狀況下,咱們能夠把 binlog 導出成 SQL 語句,把全部的操做重放一遍,來實現數據的恢復。
binlog 的另外一個功能就是用來實現主從複製,它的原理就是從服務器讀取主服務器的 binlog,而後執行一遍。
配置方式和主從複製的實現原理在後續會有專文詳解。
有了這兩個日誌以後,咱們來看一下一條更新語句是怎麼執行的:
例如一條語句:update teacher set name='盆魚宴' where id=1;
一、先查詢到這條數據,若是有緩存,也會用到緩存。
二、把 name 改爲盆魚宴,而後調用引擎的 API 接口,寫入這一行數據到內存,同時記錄 redo log。這時 redo log 進入 prepare 狀態,而後告訴執行器,執行完成了,能夠隨時提交。
三、執行器收到通知後記錄 binlog,而後調用存儲引擎接口,設置 redo log爲 commit狀態。
四、更新完成。
這張圖片的重點:
一、先記錄到內存,再寫日誌文件。
二、記錄 redo log 分爲兩個階段。
三、存儲引擎和 Server 記錄不一樣的日誌。
三、先記錄 redo,再記錄 binlog。
咱們知道,數據也是一種供許多用戶共享訪問的資源。如何保證數據併發訪問的一致性、有效性,是全部數據庫必須解決的一個問題,鎖的衝突也是影響數據庫併發訪問性能的一個重要因素。從這一角度來講,鎖對於數據庫而言就顯得尤其重要。本文將帶領你們一塊兒深刻領略Mysql鎖的各類風采。
表鎖
表級鎖是mysql鎖中粒度最大的一種鎖,表示當前的操做對整張表加鎖,資源開銷比行鎖少,不會出現死鎖的狀況,可是發生鎖衝突的機率很大。
該鎖定機制最大的特色是實現邏輯很是簡單,帶來的系統負面影響最小。因此獲取鎖和釋放鎖的速度很快。因爲表級鎖一次會將整個表鎖定,因此能夠很好的避免困擾咱們的死鎖問題。表鎖被大部分的mysql引擎支持,MyISAM和InnoDB都支持表級鎖。
MyISAM只是支持表鎖,所以性能相對Innodb來講相對下降,而Innodb也支持表鎖,可是默認的行鎖,並且只有在查詢或者其餘SQL語句經過索引纔會使用行鎖。
行鎖
行鎖的是mysql鎖中粒度最小的一種鎖,由於鎖的粒度很小,因此發生資源爭搶的機率也最小,併發性能最大,可是也會形成死鎖,每次加鎖和釋放鎖的開銷也會變大。
目前主要是Innodb使用行鎖,Innodb也是mysql在5.5.5版本以後默認使用的存儲引擎。
行鎖按照使用方式也分爲共享鎖(S鎖或者讀鎖)和排它鎖(X鎖或者寫鎖)
共享鎖(S鎖,讀鎖)
使用說明:若事務A對數據對象1加上S鎖,則事務A能夠讀數據對象1但不能修改,其餘事務只能再對數據對象1加S鎖,而不能加X鎖,直到事務A釋放數據對象1上的S鎖。這保證了其餘事務能夠讀數據對象1,但在事務A釋放數據對象1上的S鎖以前不能對數據對象1作任何修改。
用法:
select ... lock in share mode;
共享鎖就是多個事務對於同一數據能夠共享一把鎖,都能訪問到數據,可是隻能讀不能修改。
排它鎖(X鎖,寫鎖)
使用說明:若事務A對數據對象1加上X鎖,事務A能夠讀數據對象1也能夠修改數據對象1,其餘事務不能再對數據對象1加任何鎖,直到事務A釋放數據對象1上的鎖。這保證了其餘事務在事務A釋放數據對象1上的鎖以前不能再讀取和修改數據對象1。
select ... for update
排他鎖就是不能與其餘所並存,如一個事務獲取了一個數據行的排他鎖,其餘事務就不能再獲取該行的其餘鎖
意向共享鎖(IS)和意向排它鎖(IX)
釋義:
意向共享鎖(IS):事務想要在得到表中某些記錄的共享鎖,須要在表上先加意向共享鎖。
意向互斥鎖(IX):事務想要在得到表中某些記錄的互斥鎖,須要在表上先加意向互斥鎖。
意向共享鎖和意向排它鎖總稱爲意向鎖。意向鎖的出現是爲了支持Innodb支持多粒度鎖。
首先,意向鎖是表級別鎖。
理由:當咱們須要給一個加表鎖的時候,咱們須要根據意向鎖去判斷表中有沒有數據行被鎖定,以肯定是否能加成功。若是意向鎖是行鎖,那麼咱們就得遍歷表中全部數據行來判斷。若是意向鎖是表鎖,則咱們直接判斷一次就知道表中是否有數據行被鎖定了。因此說將意向鎖設置成表級別的鎖的性能比行鎖高的多。
有了意向鎖以後,前面例子中的事務A在申請行鎖(寫鎖)以前,數據庫會自動先給事務A申請表的意向排他鎖。當事務B去申請表的寫鎖時就會失敗,由於表上有意向排他鎖以後事務B申請表的寫鎖時會被阻塞。
因此,意向鎖的做用就是:
當一個事務在須要獲取資源的鎖定時,若是該資源已經被排他鎖佔用,則數據庫會自動給該事務申請一個該表的意向鎖。若是本身須要一個共享鎖定,就申請一個意向共享鎖。若是須要的是某行(或者某些行)的排他鎖定,則申請一個意向排他鎖。
樂觀鎖
樂觀鎖不是數據庫自帶的,須要咱們本身去實現。樂觀鎖是指操做數據庫時(更新操做),想法很樂觀,認爲此次的操做不會致使衝突,在操做數據時,並不進行任何其餘的特殊處理(也就是不加鎖),而在進行更新後,再去判斷是否有衝突了。
一般實現是這樣的:在表中的數據進行操做時(更新),先給數據表加一個版本(version)字段,每操做一次,將那條記錄的版本號加1。也就是先查詢出那條記錄,獲取出version字段,若是要對那條記錄進行操做(更新),則先判斷此刻version的值是否與剛剛查詢出來時的version的值相等,若是相等,則說明這段期間,沒有其餘程序對其進行操做,則能夠執行更新,將version字段的值加1;若是更新時發現此刻的version值與剛剛獲取出來的version的值不相等,則說明這段期間已經有其餘程序對其進行操做了,則不進行更新操做。
使用實例:
1. SELECT data AS old_data, version AS old_version FROM …; 2. 根據獲取的數據進行業務操做,獲得new_data和new_version 3. UPDATE SET data = new_data, version = new_version WHERE version = old_version if (updated row > 0) { // 樂觀鎖獲取成功,操做完成 } else { // 樂觀鎖獲取失敗,回滾並重試 }
優勢:從上面的例子能夠看出,樂觀鎖機制避免了長事務中的數據庫加鎖開銷,大大提高了大併發量下的系統總體性能表現。
缺點:樂觀鎖機制每每基於系統中的數據存儲邏輯,所以也具有必定的侷限性,如在上例中,因爲樂觀鎖機制是在咱們的系統中實現,來自外部系統的更新操做不受咱們系統的控制,所以可能會形成髒數據被更新到數據庫中。在系統設計階段,應該充分考慮到這些狀況出現的可能性,並進行相應調整(如將樂觀鎖策略在數據庫存儲過程當中實現,對外只開放基於此存儲過程的數據更新途徑,而不是將數據庫表直接對外公開)。
總結:讀用樂觀鎖,寫用悲觀鎖。
悲觀鎖
悲觀鎖介紹(引自百科):
悲觀鎖,正如其名,它指的是對數據被外界(包括本系統當前的其餘事務,以及來自外部系統的事務處理)修改持保守態度,所以,在整個數據處理過程當中,將數據處於鎖定狀態。悲觀鎖的實現,每每依靠數據庫提供的鎖機制(也只有數據庫層提供的鎖機制才能真正保證數據訪問的排他性,不然,即便在本系統中實現了加鎖機制,也沒法保證外部系統不會修改數據)
悲觀鎖的實現:首先實現悲觀鎖時,咱們必須先使用
set autocommit=0; 關閉 mysql的autoCommit屬性。
由於咱們查詢出數據以後就要將該數據鎖定。
關閉自動提交後,咱們須要手動開啓事務。
//1.開始事務 begin; 或者 start transaction; //2.查詢出商品信息,而後經過for update鎖定數據防止其餘事務修改 select status from t_goods where id=1 for update; //3.根據商品信息生成訂單 insert into t_orders (id,goods_id) values (null,1); //4.修改商品status爲2 update t_goods set status=2; //4.提交事務 commit; --執行完畢,提交事務
上述就實現了悲觀鎖,悲觀鎖就是悲觀主義者,它會認爲咱們在事務A中操做數據1的時候,必定會有事務B來修改數據1,因此,在第2步咱們將數據查詢出來後直接加上排它鎖(X)鎖,防止別的事務來修改事務1,直到咱們commit後,才釋放了排它鎖。
優勢:保證了數據處理時的安全性。
缺點:加鎖形成了開銷增長,而且增長了死鎖的機會。下降了併發性。
樂觀鎖更新有可能會失敗,甚至是更新幾回都失敗,這是有風險的。
因此若是寫入居多,對吞吐要求不高,可以使用悲觀鎖。
下面三種鎖都是innodb的行鎖,前面咱們說過行鎖是基於索引實現的,一旦加鎖操做沒有操做在索引上,就會退化成表鎖。
間隙鎖(Next-Key鎖)
間隙鎖,做用於非惟一索引上,主要目的,就是爲了防止其餘事務在間隔中插入數據,以致使「不可重複讀」。
若是把事務的隔離級別降級爲讀提交(Read Committed, RC),間隙鎖則會自動失效。
如圖:(1,4),(4,7),(7,11),(11,∞)即爲間隙鎖要鎖定的位置。
舉例說明:
SELECT * FROM table WHERE id = 8 FOR UPDATE; ----此時,(7,11)就會被鎖定 SELECT * FROM table WHERE id BETWEN 2 AND 5 FOR UPDATE; ----此時,(1,4)和(4,7)就會被鎖定
記錄鎖
記錄鎖,它封鎖索引記錄,做用於惟一索引上,以下圖所示:
select * from t where id=4 for update;
它會在id=4的索引記錄上加鎖,以阻止其餘事務插入,更新,刪除id=1的這一行。
須要說明的是:
select * from t where id=4;
則是快照讀(SnapShot Read),它並不加鎖,不影響其餘事務操做該數據。
臨鍵鎖
臨鍵鎖,做用於非惟一索引上,是記錄鎖與間隙鎖的組合,以下圖所示:
它的封鎖範圍,既包含索引記錄,又包含索引以前的區間,即(負無窮大,1],(2,4],(5,7],(8,11],(12,無窮大]。
在事務A中執行:
UPDATE table SET name = 'javaHuang' WHERE age = 4; SELECT * FROM table WHERE age = 4 FOR UPDATE;
這兩個語句都會鎖定(2,4],(4,7)這兩個區間。
即, InnoDB 會獲取該記錄行的 臨鍵鎖 ,並同時獲取該記錄行下一個區間的間隙鎖。
臨鍵鎖的出現是爲了innodb在rr隔離級別下,解決幻讀問題(如何解決幻讀問題,後續會出文章詳細解答,也能夠關注公衆號【ToBeTopJavaer】,查看)。
若是把事務的隔離級別降級爲RC,臨鍵鎖則也會失效。
死鎖
釋義:死鎖是指兩個或兩個以上事務在執行過程當中因爭搶鎖資源而形成的互相等待的現象
上圖所示,即爲死鎖產生的常規情景。
那麼如何解決死鎖?
1.等待事務超時,主動回滾。
2.進行死鎖檢查,主動回滾某條事務,讓別的事務能繼續走下去。
下面提供一種方法,解決死鎖的狀態:
SELECT * FROM INFORMATION_SCHEMA.INNODB_TRX;--查看正在被鎖的事務
kill trx_mysql_thread_id;--(上圖trx_mysql_thread_id列的值)
死鎖是一個很複雜的話題,此處只能簡而言之,後續會寫一篇專門講解死鎖的文章。
咱們大體瞭解了mysql大部分鎖的功能,做用,實現以及解決方法,我想作爲了一個java開發工程師,瞭解到這個程度應該已經夠了,畢竟咱們不是DBA,否則瞭解太深,搶了DBA的飯碗可就不太好了,開個玩笑,畢竟學無止境。
咱們知道一條sql語句的執行過程當中會通過優化器進行優化。優化器就是對咱們的 SQL 語句進行分析,生成執行計劃。問題:在咱們作項目的時候,有時會收到 DBA 的郵件,裏面列出了咱們項目上幾個耗時比較長的查詢語句,讓咱們去優化,這些語句是從哪裏來的呢?咱們的服務層天天執行了這麼多 SQL 語句,它怎麼知道哪些 SQL 語句比較慢呢?第一步,咱們要把 SQL 執行狀況記錄下來。
1.1.1 打開慢日誌開關由於開啓慢查詢日誌是有代價的(跟 bin log、optimizer-trace 同樣),因此它默認是關閉的:
show variables like 'slow_query%';
除了這個開關,還有一個參數,控制執行超過多長時間的 SQL 才記錄到慢日誌,默認是 10 秒。
show variables like '%slow_query%';
能夠直接動態修改參數(重啓後失效)。
set @@global.slow_query_log=1; set @@global.long_query_time=3; show variables like '%long_query%';-- 1 開啓, 0 關閉, 重啓後失效-- mysql 默認的慢查詢時間是 10 秒, 另開一個窗口後纔會查到最新值 show variables like '%slow_query%';
或者修改配置文件 my.cnf。如下配置定義了慢查詢日誌的開關、慢查詢的時間、日誌文件的存放路徑。
slow_query_log = ON long_query_time=2slow_query_log_file =/var/lib/mysql/localhost-slow.log
模擬慢查詢:
select sleep(10);
查詢 user_innodb 表的 500 萬數據(檢查是否是沒有索引)。
SELECT * FROM `user_innodb` where phone = '136';
一、 日誌內容
show global status like 'slow_queries'; -- 查看有多少慢查詢 show variables like '%slow_query%'; -- 獲取慢日誌目錄 --日誌路徑 cat /var/lib/mysql/ localhost-slow.log
有了慢查詢日誌,怎麼去分析統計呢?好比 SQL 語句的出現的慢查詢次數最多,平均每次執行了多久?
二、 mysqldumpslowhttps://dev.mysql.com/doc/refman/5.7/en/mysqldumpslow.htmlMySQL提供了 mysqldumpslow 的工具,在 MySQL 的 bin 目錄下。
mysqldumpslow --help
例如:查詢用時最多的 20 條慢 SQL:
mysqldumpslow -s t -t 20 -g 'select' /var/lib/mysql/localhost-slow.log
Count表明這個 SQL 執行了多少次;Time表明執行的時間,括號裏面是累計時間;Lock表示鎖定的時間,括號是累計;Rows表示返回的記錄數,括號是累計。除了慢查詢日誌以外,還有一個SHOW PROFILE工具能夠使用。
SHOW PROFILE 是谷歌高級架構師 Jeremy Cole 貢獻給 MySQL 社區的,能夠查看SQL 語句執行的時候使用的資源,好比 CPU、IO 的消耗狀況。在 SQL 中輸入 help profile 能夠獲得詳細的幫助信息。4.2.1 查看是否開啓
select @@profiling;set @@profiling=1;
4.2.2 查看 profile 統計(命令最後帶一個 s)
show profiles;
查看最後一個 SQL 的執行詳細信息,從中找出耗時較多的環節(沒有 s)。
show profile;
6.2E-5,小數點左移 5 位,表明 0.000062 秒。也能夠根據 ID 查看執行詳細信息,在後面帶上 for query + ID。
show profile for query 1;
除了慢日誌和 show profile,若是要分析出當前數據庫中執行的慢的 SQL,還能夠經過查看運行線程狀態和服務器運行信息、存儲引擎信息來分析。4.2.3 其餘系統命令show processlist 運行線程
show processlist;
這是很重要的一個命令,用於顯示用戶運行線程。能夠根據 id 號 kill 線程。也能夠查表,效果同樣:
select * from information_schema.processlist;
show status 服務器運行狀態https://dev.mysql.com/doc/refman/5.7/en/show-status.htmlSHOWSTATUS 用於查看 MySQL 服務器運行狀態(重啓後會清空),有 session和 global 兩種做用域,格式:參數-值。能夠用 like 帶通配符過濾。
SHOW GLOBAL STATUS LIKE 'com_select'; -- 查看 select 次數
show engine 存儲引擎運行信息https://dev.mysql.com/doc/refman/5.7/en/show-engine.htmlshowengine 用來顯示存儲引擎的當前運行信息,包括事務持有的表鎖、行鎖信息;事務的鎖等待狀況;線程信號量等待;文件 IO 請求;buffer pool 統計信息。例如:
show engine innodb status;
若是須要將監控信息輸出到錯誤信息 error log 中(15 秒鐘一次),能夠開啓輸出。
show variables like 'innodb_status_output%';-- 開啓輸出: SET GLOBAL innodb_status_output=ON;SET GLOBAL innodb_status_output_locks=ON;
咱們如今已經知道了這麼多分析服務器狀態、存儲引擎狀態、線程運行信息的命令,若是讓你去寫一個數據庫監控系統,你會怎麼作?其實不少開源的慢查詢日誌監控工具,他們的原理其實也都是讀取的系統的變量和狀態。
如今咱們已經知道哪些 SQL 慢了,爲何慢呢?慢在哪裏?MySQL 提供了一個執行計劃的工具(在架構中咱們有講到,優化器最終生成的就是一個執行計劃),其餘數據庫,例如 Oracle 也有相似的功能。經過 EXPLAIN 咱們能夠模擬優化器執行 SQL 查詢語句的過程,來知道 MySQL 是怎麼處理一條 SQL 語句的。經過這種方式咱們能夠分析語句或者表的性能瓶頸。explain 能夠分析 update、delete、insert 麼?MySQL 5.6.3之前只能分析 SELECT;MySQL5.6.3之後就能夠分析update、delete、insert 了。
寫SQL語句的時候咱們每每關注的是SQL的執行結果,可是是否真的關注了SQL的執行效率,是否注意了SQL的寫法規範?
如下的乾貨分享是在實際開發過程當中總結的,但願對你們有所幫助!
當偏移量特別大時,limit效率會很是低。
SELECT id FROM A LIMIT 1000,10 很快
SELECT id FROM A LIMIT 90000,10 很慢
方案一:
select id from A order by id limit 90000,10;
若是咱們結合order by使用。很快,0.04秒就OK。 由於使用了id主鍵作索引!固然,是否可以使用索引還須要根據業務邏輯來定,這裏只是爲了提醒你們,在分頁的時候還需謹慎使用!
方案二
select id from A order by id between 90000 and 90010;
有些業務邏輯進行查詢操做時(特別是在根據某一字段DESC,取最大一筆).能夠使用limit 1 或者 top 1 來終止[數據庫索引]繼續掃描整個表或索引。
反例
SELECT id FROM A LIKE 'abc%'
正例
SELECT id FROM A LIKE 'abc%' limit 1
反例
SELECT * FROM A
正例
SELECT id FROM A
反例
INSERT into person(name,age) values('A',24) INSERT into person(name,age) values('B',24) INSERT into person(name,age) values('C',24)
正例
INSERT into person(name,age) values('A',24),('B',24),('C',24),
sql語句的優化主要在於對索引的正確使用,而咱們在開發中常常犯的錯誤即是對錶進行全盤掃描,一來影響性能,而來耗費時間!
反例
SELECT id FROM A WHERE name like '%abc%'
因爲abc前面用了「%」,所以該查詢必然走全表查詢,除非必要(模糊查詢須要包含abc),不然不要在關鍵詞前加%
正例
SELECT id FROM A WHERE name like 'abc%'
實例
mysql版本:5.7.26
select nick_name from member where nick_name like '%小明%'
like'%小明%'並未使用索引!
select nick_name from member where nick_name like '小明%'
like'小明%'成功使用索引!
一般使用 union all 或 union 的方式替換「or」會獲得更好的效果。where子句中使用了or關鍵字,索引將被放棄使用。
反例
SELECT id FROM A WHERE num = 10 or num = 20
正例
SELECT id FROM A WHERE num = 10 union all SELECT id FROM A WHERE num=20
反例
SELECT id FROM A WHERE num IS NULL
在where子句中使用 IS NULL 或 IS NOT NULL 判斷,索引將被放棄使用,會進行全表查詢。
正例
優化成num上設置默認值0,確保表中num沒有null值, IS NULL 的用法在實際業務場景下SQL使用率極高,咱們應注意避免全表掃描
SELECT id FROM A WHERE num=0
不要在where子句中的「=」左邊進行函數、算數運算或其餘表達式運算,不然系統將可能沒法正確使用索引。
SELECT id FROM A WHERE datediff(day,createdate,'2019-11-30')=0
優化爲
SELECT id FROM A WHERE createdate>='2019-11-30' and createdate<'2019-12-1' SELECT id FROM A WHERE year(addate) <2020
優化爲
SELECT id FROM A where addate<'2020-01-01'
mysql查詢只是用一個索引,所以若是where子句中已經使用了索引的話,那麼order by中的列是不會使用索引。所以數據庫默認排序能夠符合要求狀況下不要使用排序操做;
儘可能不要包含多個列的排序,若是須要最好給這些列建立複合索引。
union和union all的差別主要是前者須要將兩個(或者多個)結果集合並後再進行惟一性過濾操做,這就會涉及到排序,增長大量的cpu運算,加大資源消耗及延遲。因此當咱們能夠確認不可能出現重複結果集或者不在意重複結果集的時候,儘可能使用union all而不是union
SELECT A.id,A.name,B.id,B.name FROM A LEFT JOIN B ON A.id =B.id; SELECT A.id,A.name,B.id,B.name FROM A RIGHT JOIN ON B A.id= B.id; SELECT A.id,A.name,B.id,B.name FROM A INNER JOIN ON A.id =B.id;
通過來之多方面的證明 inner join性能比較快,由於inner join是等值鏈接,或許返回的行數比較少。可是咱們要記得有些語句隱形的用到了等值鏈接,如:
SELECT A.id,A.name,B.id,B.name FROM A,B WHERE A.id = B.id;
推薦:能用inner join鏈接儘可能使用inner join鏈接
反例
mysql是先對外表A執行全表查詢,而後根據uuid逐次執行子查詢,若是外層表是一個很大的表,咱們能夠想象查詢性能會表現比這個更加糟糕。
Select* from A where exists (select * from B where id>=3000 and A.uuid=B.uuid);
執行時間:2s左右
正例
Select* from A inner join B ON A.uuid=B.uuid where b.uuid>=3000; 這個語句執行測試不到一秒;
執行時間:1s不到
left join 左邊表結果儘可能小,若是有條件應該放到左邊先處理,right join同理反向。如:
反例
Select * from A left join B A.id=B.ref_id where A.id>10
正例
select * from (select * from A wehre id >10) T1 left join B on T1.id=B.ref_id;
SELECT * from A WHERE id in ( SELECT id from B ) SELECT * from A WHERE id EXISTS ( SELECT 1 from A.id= B.id )
分析:
in 是在內存中遍歷比較
exist 須要查詢數據庫,因此當B的數據量比較大時,exists效率優於in**
in()只執行一次,把B表中的全部id字段緩存起來,以後檢查A表的id是否與B表中的id相等,若是id相等則將A表的記錄加入到結果集中,直到遍歷完A表的全部記錄。
In 操做的流程原理如同一下代碼
List resultSet={}; Array A=(select * from A); Array B=(select id from B); for(int i=0;i<A.length;i++) { for(int j=0;j<B.length;j++) { if(A[i].id==B[j].id) { resultSet.add(A[i]); break; } } } return resultSet;
能夠看出,當B表數據較大時不適合使用in(),由於會把B表數據所有遍歷一次
如:A表有10000條記錄,B表有1000000條記錄,那麼最多有可能遍歷10000*1000000次,效率不好。
再如:A表有10000條記錄,B表有100條記錄,那麼最多有可能遍歷10000*100次,遍歷次數大大減小,效率大大提高。
結論:in()適合B表比A表數據小的狀況
exist()會執行A.length()次,執行過程代碼以下
List resultSet={}; Array A=(select * from A); for(int i=0;i<A.length;i++) { if(exists(A[i].id) { //執行select 1 from B where B.id=A.id是否有記錄返回 resultSet.add(A[i]); } }return resultSet;
當B表比A表數據大時適合使用exists(),由於它沒有那麼多遍歷操做,只須要再執行一次查詢就行。
如:A表有10000條記錄,B表有1000000條記錄,那麼exists()會執行10000次去判斷A表中的id是否與B表中的id相等。
如:A表有10000條記錄,B表有100000000條記錄,那麼exists()仍是執行10000次,由於它只執行A.length次,可見B表數據越多,越適合exists()發揮效果。
再如:A表有10000條記錄,B表有100條記錄,那麼exists()仍是執行10000次,還不如使用in()遍歷10000*100次,由於in()是在內存裏遍歷比較,而exists()須要查詢數據庫,
咱們都知道查詢數據庫所消耗的性能更高,而內存比較很快。
結論:exists()適合B表比A表數據大的狀況