基礎篇:MySql架構與存儲引擎
邏輯架構圖:
連接層:
mysql啓動後(可以把mysql類比爲一個後臺的服務器),等待客戶端請求,當請求到來後,mysql建立一個一個線程處理(線程池則分配一個空線程,當然也可使用nio線程模型。),每個線程獨立,擁有獨自內存空間。當請求爲select請求則沒有關係,但是請求爲update時,多線程同時修改一塊內存,就會引發一系列問題,由此引出 “鎖“的概念。
查看mysql當前連接數:
show VARIABLES like '%max_connections%'
修改mysql連接數爲200
set GLOBAL max_connections = 200
同時當客戶端連接到mysql服務器時,服務器會對其進行權限驗證,包括,ip,用戶名,密碼的驗證,同時還要驗證是否有對操作某一個庫,表的權限。
SQL處理層:
show variables like '%query_cache_type%'
SET GLOBAL query_cache_size = 188888888;
是否打開mysql查詢結果緩存,默認關閉,打開後,mysql會對查詢出來的結果進行緩存,實際應用中業務數據一般不在db層緩存
mysql默認打開sql解析緩存;平時我們說的sql緩存,一般指的sql解析緩存。
解析查詢:
mysql對客戶端傳入的sql語句會按照一定的規則進行sql解析,而後進行sql優化,最後執行優化過的sql語句。而不是直接執行。
存儲引擎:
show engines;查看存儲引擎
MyISAM
進入show VARIABLES like 'datadir' 查看mysql數據文件所在路徑,發現myisam引擎會生產三個數據文件,xxx.frm存儲表結構文件,所有引擎都有此文件,xxx.MYD存儲表數據文件,xxx.MYI存儲表索引文件
特性:
併發性
支持表級鎖,
支持全文檢索,
支持數據文件壓縮
試用場景:
只讀類應用;
非事物行應用(數據倉庫,報表,數據)
空間類應用(座標)
Innodb
show VARIABLES like 'innodb_file_per_table' 查看innodb表空間類型
set global innodb_file_per_table=off 設置innodb使用系統表空間
mysql5.6以前默認使用系統表空間
系統表空間無法簡單的收縮文件大小。
獨立表空可以通過optimize table 收縮系統文件
系統表空間會產生io瓶頸
獨立表空間可以同時向多個文件刷新數據
推薦使用獨立表空間
特性:
Innodb是一種事務性存儲引擎
完全支持事物的acid特性
redo Log和Undo Log
Innodb支持行級鎖(併發度更高)
試用場景
適合大多數的oltp應用。
csv
特點:
以csv格式進行數據存儲,
所有列都不能爲空,
不支持索引,
可以直接對數據文件直接編輯(直接修改文本文件,達到修改表的目的)。
create table mycsv(id int not null,c1 VARCHAR(10) not null,c2 char(10) not null)
engine=csv;
insert into mycsv values(1,'aaa','bbb'),(2,'cccc','dddd');
修改文本數據
flush TABLES;
select * from mycsv
create index idx_id on mycsv(id)
應用場景:
需要頻繁導入導出表數據的場景,如財務報表類
Archive
應用場景:日誌以及數據採集。
Memory
show VARIABLES like 'max_heap_table_size' 查看memory引擎最大空間。
使用場景:
hash索引用於查找或者是映射表(郵編和地區對應)
用於保存數據分析中產生的中間表
用於緩存週期性聚合數據的結果表
memory數據容易丟失,所以要求數據可再生。
Ferderted
特點:提供了遠程訪問mysql服務器上表的方法
本地不存儲數據,數據全部放到遠程服務器上
本地需要保存表結構和遠程服務器的連接信息
使用場景:邊界數據庫,表 同步
該引擎默認禁止,啓用時需增加federated參數
表明,列名需要與遠程表相同
CREATE TABLE `local_fed` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`c1` varchar(10) NOT NULL DEFAULT '',
`c2` char(10) NOT NULL DEFAULT '',
PRIMARY KEY (`id`)
) ENGINE=federated CONNECTION
='mysql://root:[email protected]:3306/remote/remote_fed'
應用篇:鎖,事物,索引
鎖
MyISAM表級鎖的兩種模式:table read lock 表共享讀鎖;table write Lock 表獨佔寫鎖;
加共享表級讀鎖: lock table 表名 read
1. lock table testmysam READ
啓動另外一個session select * from
testmysam 可以查詢
2. insert into testmysam value(2);
update testmysam set id=2 where id=1;
報錯
3.在另外一個session中
insert into testmysam value(2); 等待
4.在同一個session中
insert into testdemo value(2,'2','3'); 報錯
select * from testdemo ; 報錯
5.在另外一個session中
insert into testdemo value(2,'2','3'); 成功
6.加鎖在同一個session 中 select s.* from testmysam s 報錯
lock table 表名 as 別名 read;
查看 show status LIKE 'table_locks_waited' 表被鎖過幾次
加獨佔表級寫鎖:lock table 表名 write
1.lock table testmysam WRITE
在同一個session中
insert testmysam value(3);
delete from testmysam where id = 3
select * from testmysam
2.對不同的表操作(報錯)
select s.* from testmysam s
insert into testdemo value(2,'2','3');
3.在其他session中 (等待)
select * from testmysam
Innodb行鎖
共享行鎖又稱讀鎖;當一個事務對某幾行上讀鎖時,允許其他事務對這幾行讀操作,但是不予許其進行寫操作,也不予許其他事務給這幾行上排它鎖,但允許上讀鎖。
排它鎖又稱寫鎖;當一個事務對某幾行上寫鎖時,允許其他事務對這幾行讀操作,不予許其進行寫操作,更不予許其他事務給這幾行上鎖,包括讀鎖。
注意:1.兩個事務不能鎖同一個索引。
2.insert,delete,update在事務中會默認加上排它鎖
3.行鎖必須有索引才能實現,否則會自動寫全表,那麼就不是行鎖了
1.
BEGIN
select * from testdemo where id =1 for update
在另外一個session中
update testdemo set c1 = '1' where id = 2 成功
update testdemo set c1 = '1' where id = 1 等待
2.BEGIN
update testdemo set c1 = '1' where id = 1
在另外一個session中
update testdemo set c1 = '1' where id = 1 等待
3.
BEGIN
update testdemo set c1 = '1' where c1 = '1'
在另外一個session中
update testdemo set c1 = '2' where c1 = '2' 等待
爲什麼需要事務
現在的很多軟件都是多用戶,多程序,多線程的,對同一個表可能同時有很多人在用,爲保持數據的一致性,所以提出了事務的概念。
事務的特性
事務應該具有4個屬性:原子性、一致性、隔離性、持久性。這四個屬性通常稱爲ACID特性。
原子性(atomicity)。一個事務是一個不可分割的工作單位,事務中包括的諸操作要麼都做,要麼都不做。
一致性(consistency)。事務必須是使數據庫從一個一致性狀態變到另一個一致性狀態。一致性與原子性是密切相關的。
隔離性(isolation)。一個事務的執行不能被其他事務干擾。即一個事務內部的操作及使用的數據對併發的其他事務是隔離的,併發執行的各個事務之間不能互相干擾。
持久性(durability)。持久性也稱永久性(permanence),指一個事務一旦提交,它對數據庫中數據的改變就應該是永久性的。接下來的其他操作或故障不應該對其有任何影響。
事務隔離級別
mysql默認的事務隔離級別爲repeatable-read
show variables like '%tx_isolation%';
未提交讀(READ UNCOMMITED) 解決的障礙:無; 引入的問題:髒讀
set SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
測試:
啓動兩個session
一個session中
start TRANSACTION
update account set balance = balance -50 where id = 1
另外一個session中查詢
select * from account
回到第一個session中 回滾事務
ROLLBACK
在第二個session種
update account set balance = balance -50 where id = 1
查詢結果還是 400
第二個session以爲結果是350,但前面的400數據爲髒讀數據,導致最後的結果和意料中的結果並不一致。
已提交讀 (READ COMMITED) 解決的障礙:髒讀; 引入的問題:不可重複讀
測試
show variables like '%tx_isolation%';
set SESSION TRANSACTION ISOLATION LEVEL read committed;
一個session中
start TRANSACTION
update account set balance = balance -50 where id = 1
另外一個session中查詢 (數據並沒改變)
select * from account
回到第一個session中 回滾事務
commit
在第二個session種
select * from account (數據已經改變)
可重複讀(REPEATABLE READ)解決的障礙:不可重複讀; 引入的問題:幻讀
測試
show variables like '%tx_isolation%';
set SESSION TRANSACTION ISOLATION LEVEL repeatable read;
一個session中
start TRANSACTION
update account set balance = balance -50 where id = 1
另外一個session中查詢 (數據並沒改變)
select * from account
回到第一個session中 回滾事務
commit
在第二個session種
select * from account (數據並未改變)
可串行化(SERIALIZABLE)解決的障礙:可重複讀; 引入的問題:鎖全表,性能低下
測試
show variables like '%tx_isolation%';
set SESSION TRANSACTION ISOLATION LEVEL repeatable read;
account 表有3條記錄,業務規定,最多允許4條記錄。
開啓一個事務
begin
select * from account 發現3條記錄
開啓另外一個事務
begin
select * from account 發現3條記錄 也是3條記錄
insert into account VALUES(4,'deer',500)
查詢 4條記錄
select * from account
回到第一個session
insert into account VALUES(5,'james',500)
select * from account 4條記錄
session1 與 session2 都提交事務
set SESSION TRANSACTION ISOLATION LEVEL serializable; 重新上面的測試發現插入報錯
總結:
事務隔離級別爲可重複讀時,如果有索引(包括主鍵索引)的時候,以索引列爲條件更新數據,會存在間隙鎖間、行鎖、頁鎖的問題,從而鎖住一些行;如果沒有索引,更新數據時會鎖住整張表
事務隔離級別爲串行化時,讀寫數據都會鎖住整張表
隔離級別越高,越能保證數據的完整性和一致性,但是對併發性能的影響也越大,對於多數應用程序,可以優先考慮把數據庫系統的隔離級別設爲Read Committed,它能夠避免髒讀取,而且具有較好的併發性能。
索引是什麼
MySQL官方對索引的定義爲:索引(Index)是幫助MySQL高效獲取數據的數據結構。
可以得到索引的本質:索引是數據結構。
平時我們到圖書館,首先看到的都是目錄,通過目錄去查詢想要的書籍會非常的迅速。
我們要去圖書館找一本書,這圖書館的書肯定不是線性存放的,它對不同的書籍內容進行了分類存放,整索引由於一個個節點組成,根節點有中間節點,中間節點下面又由子節點,最後一層是葉子節點,
可見,整個索引結構是一棵倒掛着的樹,其實它就是一種數據結構,這種數據結構比前面講到的線性目錄更好的增加了查詢的速度。
MySql中的索引其實也是這麼一回事,我們可以在數據庫中建立一系列的索引,比如創建主鍵的時候默認會創建主鍵索引,上圖是一種BTREE的索引。每一個節點都是主鍵的Id
當我們通過ID來查詢內容的時候,首先去查索引庫,在到索引庫後能快速的定位索引的具體位置。
索引得分類
普通索引:即一個索引只包含單個列,一個表可以有多個單列索引
唯一索引:索引列的值必須唯一,但允許有空值
複合索引:即一個索引包含多個列
聚簇索引(聚集索引):並不是一種單獨的索引類型,而是一種數據存儲方式(索引與數據放在同一個文件裏)。具體細節取決於不同的實現,InnoDB的聚簇索引其實就是在同一個結構中保存了B-Tree索引(技術上來說是B+Tree)和數據行。
非聚簇索引:不是聚簇索引,就是非聚簇索引
查看索引
SHOW INDEX FROM table_name\G
創建索引
CREATE [UNIQUE ] INDEX indexName ON mytable(columnname(length));
ALTER TABLE 表名 ADD [UNIQUE ] INDEX [indexName] ON (columnname(length))
刪除索引
DROP INDEX [indexName] ON mytable;
優化篇:慢查詢 ,執行計劃,sql優化
什麼是慢查詢
慢查詢日誌,顧名思義,就是查詢慢的日誌,是指mysql記錄所有執行超過long_query_time參數設定的時間閾值的SQL語句的日誌。該日誌能爲SQL語句的優化帶來很好的幫助。默認情況下,慢查詢日誌是關閉的,要使用慢查詢日誌功能,首先要開啓慢查詢日誌功能。
慢查詢基本配置
slow_query_log 啓動停止技術慢查詢日誌
slow_query_log_file 指定慢查詢日誌得存儲路徑及文件(默認和數據文件放一起)
long_query_time 指定記錄慢查詢日誌SQL執行時間得伐值(單位:秒,默認10秒)
log_queries_not_using_indexes 是否記錄未使用索引的SQL
log_output 日誌存放的地方【TABLE】【FILE】【FILE,TABLE】
配置了慢查詢後,它會記錄符合條件的SQL
包括:
查詢語句
數據修改語句
已經回滾得SQL
實操:
通過下面命令查看下上面的配置:
show VARIABLES like '%slow_query_log%'
show VARIABLES like '%slow_query_log_file%'
show VARIABLES like '%long_query_time%'
show VARIABLES like '%log_queries_not_using_indexes%'
show VARIABLES like 'log_output'
set global long_query_time=0; ---默認10秒,這裏爲了演示方便設置爲0
set GLOBAL slow_query_log = 1; --開啓慢查詢日誌
set global log_output='FILE,TABLE' --項目開發中日誌只能記錄在日誌文件中,不能記表中
設置完成後,查詢一些列表可以發現慢查詢的日誌文件裏面有數據了。
慢查詢解讀
從慢查詢日誌裏面摘選一條慢查詢日誌,數據組成如下
第一行:用戶名 、用戶的IP信息、線程ID號
第二行:執行花費的時間【單位:毫秒】
第三行:執行獲得鎖的時間
第四行:獲得的結果行數
第五行:掃描的數據行數
第六行:這SQL執行的具體時間
第七行:具體的SQL語句
執行計劃
使用EXPLAIN關鍵字可以模擬優化器執行SQL查詢語句,從而知道MySQL是如何處理你的SQL語句的。分析你的查詢語句或是表結構的性能瓶頸。
執行計劃作用
表的讀取順序
數據讀取操作的操作類型
哪些索引可以使用
哪些索引被實際使用
表之間的引用
每張表有多少行被優化器查詢
執行計劃的語法
執行計劃的語法其實非常簡單: 在SQL查詢的前面加上EXPLAIN關鍵字就行。
比如:EXPLAIN select * from table1
重點的就是EXPLAIN後面你要分析的SQL語句
ID列
ID列:描述select查詢的序列號,包含一組數字,表示查詢中執行select子句或操作表的順序
根據ID的數值結果可以分成一下三種情況
id相同:執行順序由上至下
id不同:如果是子查詢,id的序號會遞增,id值越大優先級越高,越先被執行
id相同不同:同時存在
分別舉例來看
如上圖所示,ID列的值全爲1,代表執行的允許從t1開始加載,依次爲t3與t2
EXPLAIN
select t2.* from t1,t2,t3 where t1.id = t2.id and t1.id = t3.id
and t1.other_column = '';
Id不同
如果是子查詢,id的序號會遞增,id值越大優先級越高,越先被執行
EXPLAIN
select t2.* from t2 where id = (
select id from t1 where id = (select t3.id from t3 where t3.other_column='')
);
Id相同又不同
id如果相同,可以認爲是一組,從上往下順序執行;
在所有組中,id值越大,優先級越高,越先執行
EXPLAIN
select t2.* from (
select t3.id
from t3 where t3.other_column = ''
) s1 ,t2 where s1.id = t2.id
select_type列
Select_type:查詢的類型,
要是用於區別:普通查詢、聯合查詢、子查詢等的複雜查詢
類型如下
SIMPLE
EXPLAIN select * from t1
簡單的 select 查詢,查詢中不包含子查詢或者UNION
PRIMARY與SUBQUERY
PRIMARY:查詢中若包含任何複雜的子部分,最外層查詢則被標記爲
SUBQUERY:在SELECT或WHERE列表中包含了子查詢
EXPLAIN
select t1.*,(select t2.id from t2 where t2.id = 1 ) from t1
DERIVED
在FROM列表中包含的子查詢被標記爲DERIVED(衍生)
MySQL會遞歸執行這些子查詢, 把結果放在臨時表裏。
.UNION RESULT 與UNION
UNION:若第二個SELECT出現在UNION之後,則被標記爲UNION;
UNION RESULT:從UNION表獲取結果的SELECT
#UNION RESULT ,UNION
EXPLAIN
select * from t1
UNION
select * from t2
table列
顯示這一行的數據是關於哪張表的
Type列
type顯示的是訪問類型,是較爲重要的一個指標,結果值從最好到最壞依次是:
system > const > eq_ref > ref > fulltext > ref_or_null > index_merge > unique_subquery > index_subquery > range > index > ALL
需要記憶的
system>const>eq_ref>ref>range>index>ALL
一般來說,得保證查詢至少達到range級別,最好能達到ref。
System與const
System:表只有一行記錄(等於系統表),這是const類型的特列,平時不會出現,這個也可以忽略不計
Const:表示通過索引一次就找到了
const用於比較primary key或者unique索引。因爲只匹配一行數據,所以很快
如將主鍵置於where列表中,MySQL就能將該查詢轉換爲一個常量
eq_ref
唯一性索引掃描,對於每個索引鍵,表中只有一條記錄與之匹配。常見於主鍵或唯一索引掃描
Ref
非唯一性索引掃描,返回匹配某個單獨值的所有行.
本質上也是一種索引訪問,它返回所有匹配某個單獨值的行,然而,它可能會找到多個符合條件的行,所以他應該屬於查找和掃描的混合體
Range
只檢索給定範圍的行,使用一個索引來選擇行。key 列顯示使用了哪個索引
一般就是在你的where語句中出現了between、<、>、in等的查詢
這種範圍掃描索引掃描比全表掃描要好,因爲它只需要開始於索引的某一點,而結束語另一點,不用掃描全部索引。
Index
當查詢的結果全爲索引列的時候,雖然也是全部掃描,但是隻查詢的索引庫,而沒有去查詢
數據。
All
Full Table Scan,將遍歷全表以找到匹配的行
possible_keys 與Key
possible_keys:可能使用的key
Key:實際使用的索引。如果爲NULL,則沒有使用索引
查詢中若使用了覆蓋索引,則該索引和查詢的select字段重疊
key_len
Key_len表示索引中使用的字節數,可通過該列計算查詢中使用的索引的長度。在不損失精確性的情況下,長度越短越好
key_len顯示的值爲索引字段的最大可能長度,並非實際使用長度,即key_len是根據表定義計算而得,不是通過表內檢索出的
key_len表示索引使用的字節數,
根據這個值,就可以判斷索引使用情況,特別是在組合索引的時候,判斷所有的索引字段是否都被查詢用到。
char和varchar跟字符編碼也有密切的聯繫,
latin1佔用1個字節,gbk佔用2個字節,utf8佔用3個字節。(不同字符編碼佔用的存儲空間不同)
字符類型
字符類型-索引字段爲char類型+不可爲Null時
name這一列爲char(10),字符集爲utf-8佔用3個字節Keylen=10*3
字符類型-索引字段爲char類型+允許爲Null時
name這一列爲char(10),字符集爲utf-8佔用3個字節,外加需要存入一個null值
Keylen=10*3+1(null) 結果爲31
索引字段爲varchar類型+不可爲Null時
Keylen=varchar(n)變長字段+不允許Null=n*(utf8=3,gbk=2,latin1=1)+2
索引字段爲varchar類型+允許爲Null時
Keylen=varchar(n)變長字段+允許Null=n*(utf8=3,gbk=2,latin1=1)+1(NULL)+2
總結
字符類型
變長字段需要額外的2個字節(VARCHAR值保存時只保存需要的字符數,另加一個字節來記錄長度(如果列聲明的長度超過255,則使用兩個字節),所以VARCAHR索引長度計算時候要加2),固定長度字段不需要額外的字節。
而NULL都需要1個字節的額外空間,所以索引字段最好不要爲NULL,因爲NULL讓統計更加複雜並且需要額外的存儲空間。
複合索引有最左前綴的特性,如果複合索引能全部使用上,則是複合索引字段的索引長度之和,這也可以用來判定複合索引是否部分使用,還是全部使用。
整數/浮點數/時間類型的索引長度
NOT NULL=字段本身的字段長度
NULL=字段本身的字段長度+1(因爲需要有是否爲空的標記,這個標記需要佔用1個字節)
datetime類型在5.6中字段長度是5個字節,datetime類型在5.5中字段長度是8個字節
Ref
顯示索引的哪一列被使用了,如果可能的話,是一個常數。哪些列或常量被用於查找索引列上的值
由key_len可知t1表的idx_col1_col2被充分使用,col1匹配t2表的col1,col2匹配了一個常量,即 'ac'
其中 【shared.t2.col1】 爲 【數據庫.表.列】
Rows
根據表統計信息及索引選用情況,大致估算出找到所需的記錄所需要讀取的行數
Extra
包含不適合在其他列中顯示但十分重要的額外信息。
Using filesort
說明mysql會對數據使用一個外部的索引排序,而不是按照表內的索引順序進行讀取。MySQL中無法利用索引完成的排序操作稱爲“文件排序”
當發現有Using filesort 後,實際上就是發現了可以優化的地方
上圖其實是一種索引失效的情況,後面會講,可以看出查詢中用到了個聯合索引,索引分別爲col1,col2,col3
當我排序新增了個col2,發現using filesort 就沒有了。
Using temporary
使了用臨時表保存中間結果,MySQL在對查詢結果排序時使用臨時表。常見於排序 order by 和分組查詢 group by。
尤其發現在執行計劃裏面有using filesort而且還有Using temporary的時候,特別需要注意
Using index
表示相應的select操作中使用了覆蓋索引(Covering Index),避免訪問了表的數據行,效率不錯!
如果同時出現using where,表明索引被用來執行索引鍵值的查找;
如果沒有同時出現using where,表明索引用來讀取數據而非執行查找動作
覆蓋索引:
覆蓋索引(Covering Index),一說爲索引覆蓋。
理解方式一:就是select的數據列只用從索引中就能夠取得,不必讀取數據行,MySQL可以利用索引返回select列表中的字段,而不必根據索引再次讀取數據文件,換句話說查詢列要被所建的索引覆蓋。
理解方式二:索引是高效找到行的一個方法,但是一般數據庫也能使用索引找到一個列的數據,因此它不必讀取整個行。畢竟索引葉子節點存儲了它們索引的數據;當能通過讀取索引就可以得到想要的數據,那就不需要讀取行了。一個索引包含了(或覆蓋了)滿足查詢結果的數據就叫做覆蓋索引
注意:
如果要使用覆蓋索引,一定要注意select列表中只取出需要的列,不可select *,
因爲如果將所有字段一起做索引會導致索引文件過大,查詢性能下降。
所以,千萬不能爲了查詢而在所有列上都建立索引,會嚴重影響修改維護的性能。
Using where 與 using join buffer
Using where
表明使用了where過濾
using join buffer
使用了連接緩存:
impossible where
where子句的值總是false,不能用來獲取任何元組