mysql 自身参照自身_MySQL入门
1.SQL查詢操作
select的“另類”用法
我們通常習慣select + from從數據表中讀取數據,不過實際上select并不一定要去讀取數據庫中的內容。
比如:
select 1+1; 返回2
select now(); 返回當前時間
select本身代表要返回的內容,至于與數據庫表中存的數據是否有關并不重要。
同時MySQL支持基本四則運算,所以可以利用這兩個特性來實現:
統計A隊列的案件數量比B隊列的案件數量差異:
select
(select count(id) from assignment where queue='A' and `status`='ACTIVE' and active=1)
-
(select count(id) from assignment where queue='B' and `status`='ACTIVE' and active=1);
where和having
where和having都有對數據進行篩選的功能,通常的使用習慣是where跟在from后面,而having跟在group by后面,那么是不是非得這么用呢?二者的區別在哪里呢?
where表示基于數據表的列參數的限制條件,即where a=b語句中的a必須是數據表中的列。
having表示基于select語句返回的變量的限制條件,即having a=b語句中的a必須在select中出現。
來看下面幾種場景:
select * from table where a=XXX;
select * from table having a=XXX;
// 二者均可使用,效果一樣
select a,b,c from table where d=xxx; // ok
select a,b,c from table having d=xxx; // 錯誤,d不在select選擇之中,無法用來having
select a, count(b) from table where count(b)>100; // 錯誤,where只能跟表中存在的列,無法對聚合、函數操作后的變量進行篩選
select a, count(b) from table group by a having count(b)>100; // ok
distinct和group by
distinct()函數和group by 語句都有用來去重的效果,但對應的使用場景有些差異。
distinct的作用就是單純的去重,必須緊跟著select的后面,否則會報錯。當select中涉及的到所有元素都重復時,只返回1條記錄。有時和count連用,統計不重復的數量。
select distinct(a) from table; // 返回1,2,3,5,6,7,9...
select distinct(a),b from table; // 返回 (1,2),(1,3),(2,1),(3,5),(3,6)....
select a,distinct(b) from table; // error
select count(distinct(a)) from table; // 統計a不重復的記錄數量
group by的作用是做分組匯總統計,必須配合聚合函數(count、sum、avg等)使用,否則無意義。返回結果中,被group by的參數每個只有1行。
select avg(b) from table group by a //對每一個a的條件查詢b的平均值
select count(distinct(c)) from table group by a //對每個a的分組統計不同的c列值的數量
關聯查詢
當from語句選擇了2張或以上的表時,返回內容是兩張表的笛卡爾積,總數是二者數據量的乘積!
因此為了避免數據量的爆炸,關聯查詢多表時必須要加聯結條件。
基本句式是:
select a ·聯結方式· b on ·聯結條件·
聯結方式有內聯結、外聯結、左聯結、右聯結等,詳見下圖:
關聯查詢
修改操作涉及自身查詢
有時候在做update、delete操作時需要結合select子句確定修改的范圍。當select子句查詢涉及要修改的表格本身時,要特別注意:不能select出數據,再在同一個表中做update、delete操作。
來看一個例子:a表中現在出現了b列同一個值有多條ACTIVE的記錄的錯誤情況,現在要將這些記錄全部設為EXPIRE。
下面是錯誤的示范:
UPDATE a SET `status` = 'EXPIRE'
WHERE b IN (
SELECT b FROM a
WHERE `status` = 'ACTIVE'
GROUP BY b
HAVING count(*) > 1
);
執行報錯: You can't specify target table 'a' for update in FROM clause
要正常實現該功能,需要在update和select操作直接加一層臨時表,臨時表里存放從原表select出來的數據,update原表時,子查詢讀取臨時表的數據,從而避免報錯。
正確寫法:
UPDATE a SET `status` = 'EXPIRE'
WHERE b IN (
SELECT temp.b from (
SELECT b from a
WHERE `status` = 'ACTIVE'
GROUP BY b
HAVING count(*) > 1
) as temp
);
這里從a表中查詢出來的b全部放進了temp表中,update的時候IN語句讀取temp表中的值,避免了兩個操作指向同一張表產生報錯。
2.InnoDB引擎和索引的技巧
InnoDB引擎的b+樹索引
如果留心過數據庫表信息的話肯定能發現,我們用的數據庫默認都是InnoDB引擎的。MySQL建立數據庫表時,除非顯示聲明,否則建立的都是InnoDB引擎。InnoDB的優勢在于事務安全性和更細粒度的行級鎖,詳見下一章。
InnoDB引擎的索引機制是b+樹,簡稱b樹索引。至于什么是b+樹,請自行百度。
.
.
.
算了,還是講下吧。。。。
查詢一般有三類:順序查詢(慢)、二分查詢(快)和hash查詢(最快,但是費空間,除了內存機制的應用,一般不會使用)。順序查詢和二分查詢有著數量級的性能差距,對于數據量大的情況差異尤其明顯。
為了更快的定位查詢到需要的數據,就要想辦法把慢的順序查詢變為快的二分查詢,這就是索引的本質。
InnoDB中的索引分為主鍵索引(聚簇索引)和二級索引,其中主鍵索引和其他數據是存放在一起的,二級索引有單獨的索引樹。
先來看主鍵索引。
為了實現二分查詢,MySQL規定,主鍵索引必須唯一并按照升序排列(否則沒法二分)。
數據存儲的的基本單位是“頁”,一頁大小為16k,為連續的存儲空間。
連續就好辦了,由于規定了主鍵唯一升序,要在一頁內查找某個主鍵,就可以先讀取中間地址(更準確的說法是槽位)存儲的數據,拿主鍵來比較,如果比中間的主鍵值大,那在后半空間里再去一半位置地址的數據比較,以此類推。
當一頁存不下的時候,就需要開一個新的頁,但頁和頁之間的地址未必是連續的,二者通過雙向鏈表關聯。所以當數據量大,不止一頁的時候如何實現二分呢?
答案是建立一個目錄頁,目錄頁和存儲數據的頁結構上其實是一樣一樣的,存儲的內容是主鍵和其對應的頁的地址。這樣查詢的過程變成:先在目錄頁中二分查找主鍵,找到對應數據頁的地址,進入數據頁再次進行二分查找。
若數據量很大,目錄頁也不止一個怎么辦?那就再建立一個指向目錄頁的二級目錄頁。
整個過程就像是根據頁碼查目錄,先搜索章節、再搜索小節、再搜索小小節。
詳見下圖:
比如我要搜id=26的數據,首先<62找到目錄頁17,然后<50找到數據頁37,在頁37中最后二分法找到26的記錄。
主鍵索引樹
這個形狀不就是樹結構嗎?來看一下這個樹有什么特點:
首先,所有的具體數據都存在葉子節點
非葉子節點只存儲Key值和通往下一級節點的地址
我們管具有這種特點的樹叫做b+樹。InnoDB引擎通過b+樹實現索引的二分查詢,從而提升查詢性能。
那手動建立的其他索引呢?
答案是,每建立一個索引,就建一顆對應的b+樹,并按照該索引字段升序排列(MySQL不止是數值可以排序,任何類型的值都可以,比如字符串是根據字母排列順序來排序)。
在按索引查詢時,就在對應的索引樹上進行二分查詢操作,和主鍵樹一樣。
除主鍵外的所有索引都是二級索引,其b+樹和主鍵b+樹唯一的區別在于葉子節點:主鍵樹的葉子節點存放主鍵和具體記錄的數據;二級索引樹的葉子節點存放索引和主鍵。
還有一種比較特別的二級索引,里面包括多個列的值,稱為聯合索引。一個聯合索引也是一個b+數,內部的順序先按照建立索引的第一個字段排序,相同情況下再按照第二個字段排序,以此類推。
二級索引是怎么查詢數據的呢?首先通過二級索引樹二分查詢找到對應的主鍵id,再拿這些主鍵id去主鍵索引樹上查詢對應的數據,這個過程稱為“回表”。
所以,根據主鍵id查數據走一次索引,根據普通索引查數據要走兩次索引。
如何使用索引
索引將順序查詢升級為二分查詢,使得性能可以大大提升,但索引也不是萬能的,要用索引之前首先得知道使用的代價:
每建一個索引都要建一個b+樹,占空間是肯定的了,如果索引的值很大的話,更容易產生分頁現象,b+樹就需要更多的層,占空間就更大;
不能“管殺不管埋”,建了索引樹還得維護,當表內插入、變更、刪除數據時,對應的索引樹可能也需要同時更新,這樣會拖慢數據庫操作的性能;
對于二級索引的使用,可能查出多個主鍵id,而這些主鍵id未必是連續的,所以回表查詢時是隨機I/O,比順序I/O的性能會差很多,可能得不償失;
所以,一個表上建的索引越多,占用空間就越大,增刪改記錄時的時間性能就越差。因此要建立索引,就要保證其能被充分的利用。
網上對于什么情況下可以命中索引,什么情況下不行的列舉較為零碎,不好理解,但事實上只要理解了索引的b+樹結構原理,判斷能否命中索引其實不難。
先來看幾個這輩子也別想命中索引的操作:
select * from table where a!=xxx // !=, not in 這種操作顯然是沒法通過二分查找來快速定位的
select * from table where a*2=xxx // 對索引變量做運算會導致mysql把a*2作為整體來篩選,無法使用b+樹,函數處理同理,所以對于字符串類型的變量保存數字的列要特別小心。
select * from table order by a // 排序本身不是問題,問題在于沒有where子句,是全部數據的排序,走索引只會增加回表操作開銷,不如全表搜索。
select * from table order by a, b desc limit 10 // 如果有多個字段做排序,排序的方式需要一樣,才能方便的從b+樹上正著取或者倒著取數據。
select * from table where b like '%xxx%' // 字符串按照字母順序來排序,前綴做通配會導致無法判斷其大小范圍。
聯合索引(a,b,c)的情況:
select * from table where b=xxx // 聯合索引按建立索引的順序在b+樹上進行升序排列,所以不使用a的情況下直接用b,由于a的值不確定,無法命中
select * from table where a=xxx and c=xxx // 只能命中索引a,原理同上
select * from table where a>xxx and b=xxx // 還是只能命中索引a,因為a進行的是范圍操作,會篩選出多個值,無法在同一顆索引樹上方便的匹配第二個字段,系統會把b字段的匹配變為順序搜索
理解了索引基于b+樹的搜索方式,就比較容易找正確的使用姿勢了:
表本身的數據量要大,否則二分查詢節約的時間不如回表操作費的時間;
索引盡量小,節約b+樹空間和搜索層次數,降低維護成本;
索引的取值盡可能多(唯一就更好了),避免索引樹上查到大量主鍵id,在回表過程中浪費時間;
基于二分查詢的原理,表達式應該是等值匹配、IN匹配、大小判斷或者用于排序、分組操作,如果是字符串匹配,前綴要準確;
索引列要單獨出現,不要做任何算術、函數運算;
若有多個字段會同時使用,可以建立聯合索引,一是減少b+樹的數量節約空間;二是因為一次查詢只會用一個索引樹,聯合索引效率更高;
對于聯合索引,建立時要按照使用頻率順序來建立,避免出現前一個未使用而使用后一個的情況;
排序情況下一定要限制數量或者條件,不用全表排序;
如果可以,只搜索索引字段,避免select *,索引覆蓋的情況下只需要搜索索引樹,免去了回表操作;
對于IN操作,可以拆解成多個等值匹配,用UNION算符并連結果;
等等.....
MySQL自帶的explain是分析查詢性能的好工具。
explain
這里主要看type和key這倆字段。
key表示這個查詢用到的索引。
type表示這個查詢的索引使用效率。type大概有以下幾種:
const 使用主鍵索引匹配,或者唯一索引匹配非null值。精確匹配一條,速度最快。
ref 使用普通二級索引匹配。會匹配出多個主鍵進行回表查詢。
range 使用索引進行范圍查找。
index 使用了不帶索引的字段進行條件篩選。會在索引匹配后順序判斷每一條是否符合條件。
all 無法命中索引,全表掃描。
3.事務&數據庫鎖
事務和MVCC
數據庫的變更操作是個“危險”的事情,尤其是對于需要幾個語句共同合作完成的操作(例如案件的出催),可能受到硬件性故障例如斷網斷電、并發影響,導致執行一半中斷或者中途數據錯亂的問題。
所以我們要確保這樣一個操作場景,要么一次性執行完成不受影響,要么干脆別執行。這種操作系列稱之為事務。
事務具備四個基本特性,稱為ACID:
Atomicity 原子性,該操作不可分割打斷,保障可以一次性執行完成
Consistency 一致性,所有的操作要滿足約束條件(唯一、not null等)
Isolation 隔離性,事務之間不能相互干擾,引起數據錯亂(臟寫)
Durability 持久性, 已經完成的事務所修改的數據不會因任何故障丟失
InnoDB引擎支持事務操作,以begin開始,以commit提交結束。
在高并發情況下,為了性能考慮,事務間的隔離性有時會做出一點取舍。引起數據錯亂的臟寫肯定是要杜絕的,但還有幾種情況有時要求就沒有那么嚴格:
臟讀:事務A讀到了事務B修改過的數據,但此時事務B未提交
不可重復讀:事務A以同樣的查詢語句查詢,而過程中有其他事務修改了數據,引起每次查詢的結果不同
幻讀:事務A以同樣的查詢語句查詢,而過程中有其他事務插入了滿足查詢條件的新數據,導致A查出了“不該查出”的新數據
MySQL支持4種隔離性級別的設置:
MySQL事務隔離級別
隔離性最差的read uncommitted 會直接讀取最新的版本,而serializable會使用加鎖的方式。
剩下倆會通過MVCC(multi-version concurrency control)來實現,其主要依賴版本鏈和ReadView。
可以對比參照git的代碼版本控制和tag,事務對數據的修改就可以看成是各種分支,每一個事務有自己對應的版本位置,ReadView是在某個時刻對版本鏈打的tag,兩個級別的區別主要在于打tag的時機要求。
鎖的性質:共享鎖 pk 排他鎖
加鎖是隔離性最強的方法。對于讀和寫操作,我們對隔離性的要求又有一些不同:
由于讀操作不改變數據,所以A在讀的時候,并不排斥B也來讀;
寫操作會直接影響數據,所以A在寫的時候,即不能讓B來寫,也不能讓B來讀。
所以,讀操作的事務是可以同時進行的,而寫操作事務之間、寫操作和讀操作則是互斥排他的。
我們把讀操作加的鎖稱為共享鎖(S),寫操作加的鎖稱為排他鎖(X)。(p.s 讀操作也可以顯示聲明加排它鎖,select.....for update,一般不會這么干)
S鎖和X鎖的兼容性
加鎖、解鎖操作:
一個讀操作事務開始時,先判斷是否有X鎖,如果有則進入S鎖等待隊列等待,沒有則加上S鎖并開始執行;
一個寫操作事務開始是,先判斷是否有鎖,如有則進入X鎖等待隊列等待,沒有則加上X鎖并開始執行;
當前事務完成并釋放鎖后,先把X等待隊列中的X鎖出隊列執行操作(防止寫操作“饑餓”)。
鎖的顆粒度:表級鎖 pk 行級鎖
顧名思義,表級鎖是對整張表上鎖,而行級鎖是對一條記錄上鎖。一般情況下,行級鎖對并發的支持會好于表級所,比如有兩個寫任務,針對不同的記錄,行級鎖可以并行而表級鎖會阻塞。
不過說句公道話,表級鎖并不是一無是處,因為其更省內存。尤其是一個事務需要訪問表中大量數據或者做group by的時候,用表鎖只要維護一個鎖,而用行鎖要維護N個鎖。
此外,大量insert操作時,表鎖性能也會優于行鎖。InnoDB主鍵自增就是通過表級鎖完成的。
InnoDB引擎同時支持表級鎖和行級鎖,而其他引擎如MyISAM只支持表級鎖。
原因還是在于b+索引樹,InnoDB引擎可以對索引上鎖,當然同時也反過來要求使用行級鎖必須要用索引。
有行鎖和表鎖共存時就會引出一個問題,如果要對表加鎖,就需要判斷表中的記錄是否被加了行鎖,總不能遍歷查詢判斷每一行有沒有加鎖吧?
遍歷是不可能遍歷的,這輩子都不可能。為了解決這個問題,需要引入一個“意向”鎖。
意向鎖也分為共享和排他兩類,簡稱IS和IX。有IS鎖意味著表中有行級別S鎖;有IX鎖意味著表中有行級別X鎖。
意向鎖是表級別的,但其本身不鎖定任何表或者行,只是用來快速判斷表內是否有記錄被鎖著,所以意向鎖之間是兼容的。有10個意向鎖就說明里面有10個行級操作正在進行。
InnoDB表級別鎖的兼容性:
完整的鎖兼容性
完整版的上鎖過程:
行級讀操作,先判斷是否有表級X鎖或對應行的X鎖,若有則等待;沒有則給表加IS鎖,給對應行加S鎖并執行操作。
行級寫操作,先判斷是否有表鎖或者對應行鎖,若有則等待;沒有則給表加IX鎖,給對應行加X鎖并執行操作。
行級操作完成后,釋放行鎖及其加上的表意向鎖。
表級讀操作,先判斷是否有表級X鎖或IX鎖,若有則等待;沒有則給表加S鎖并執行操作。
表級寫操作,先判斷是否有表級鎖或意向鎖,若有則等待;沒有則給表加X鎖執行操作。
表級操作完成后,釋放表級鎖。
行級鎖的功能:記錄鎖 pk 間隙鎖
這二者還是比較好理解的。
記錄鎖鎖定的是一條數據記錄本身,對于臟讀、不可重復讀的情況,是某個特定的記錄被其他事務修改引起的,所以只要鎖定這提條數據防止其他事務來更新就可以。
對于幻讀,是由于新插入的記錄引起的,由于無法預測哪些記錄會插入,所以依靠鎖數據是無法避免的。這時需要再加一個間隙鎖。
間隙鎖的作用是鎖定上鎖行和主鍵前一條數據之間的空間,防止有插入操作。例如目前表中兩條連續記錄id分別是10和15,那在15的數據上加間隙鎖,可以防止插入id在11-14范圍內的數據。
supermum是mysql里的虛擬最大行記錄,對于當前表里主鍵最大的記錄,在supermum上加間隙鎖可以防止后續更大主鍵的記錄插入。
總結
以上是生活随笔為你收集整理的mysql 自身参照自身_MySQL入门的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 字节跳动测试开发4轮面试_字节跳动测试开
- 下一篇: linux cmake编译源码,linu