一.mysqlphp
學習mysql發現的一篇比較不錯的博客:html
https://blog.csdn.net/ufo___mysql
抽空閱讀一下,做爲本篇筆記的完善,本篇筆記記錄的mysql的知識點並不全程序員
1.索引web
(1)瞭解索引原理須要掌握的一些知識點算法
(i)索引爲何會增長速度spring
DB在執行一條Sql語句的時候,默認的方式是根據搜索條件進行全表掃描,sql
遇到匹配條件的就加入搜索結果集合。若是咱們對某一字段增長索引,查詢時mongodb
就會先去索引列表中一次定位到特定值的行數,大大減小遍歷匹配的行數,所數據庫
以能明顯增長查詢的速度。
(ii)瞭解索引須要知道的一些細節
索引的目的在於提升查詢效率,與咱們查閱圖書所用的目錄是一個道理:先定
位到章,而後定位到該章下的一個小節,而後找到頁數。類似的例子還有:查
字典,查火車車次,飛機航班等。本質都是:經過不斷地縮小想要獲取數據的
範圍來篩選出最終想要的結果,同時把隨機的事件變成順序的事件,也就是說,
有了這種索引機制,咱們能夠老是用同一種查找方式來鎖定數據。數據庫也是
同樣,但顯然要複雜的多,由於不只面臨着等值查詢,還有範圍查詢(>、<、
between、in)、模糊查詢(like)、並集查詢(or)等等。數據庫應該選擇怎麼樣的方
式來應對全部的問題呢?咱們回想字典的例子,能不能把數據分紅段,而後分段
查 詢呢?最簡單的若是1000條數據,1到100分紅第一段,101到200分紅第二
段,201到300分紅第三段......這樣查第250條數據,只要找第三段就能夠了,一
下子去除了90%的無效數據。但若是是1千萬的記錄呢,分紅幾段比較好?稍有
算法基礎的同窗會想到搜索樹,其平均複雜度是lgN,具備不錯的查詢性能。但
這裏咱們忽略了一個關鍵的問題,複雜度模型是基於每次相同的操做成原本考慮
的。而數據庫實現比較複雜,一方面數據是保存在磁盤上的,另一方面爲了提
高性能,每次又能夠把部分數據讀入內存來計算,由於咱們知道訪問磁盤的成本
大概是訪問內存的十萬倍左右,因此簡單的搜索樹難以知足複雜的應用場景。
(iii) 磁盤IO與預讀
考慮到磁盤IO是很是高昂的操做,計算機操做系統作了一些優化,當一次IO時,
不光把當前磁盤地址的數據,而是把相鄰的數據也都讀取到內存緩衝區內,因
爲局部預讀性原理告訴咱們,當計算機訪問一個地址的數據的時候,與其相鄰
的數據也會很快被訪問到。每一次IO讀取的數據咱們稱之爲一頁(page)。具體
一頁有多大數據跟操做系統有關,通常爲4k或8k,也就是咱們讀取一頁內的數
據時候,實際上才發生了一次IO,這個理論對於索引的數據結構設計很是有幫
助。
(2)mysql索引
mysql索引數據結構有B+Tree和Hash索引兩種,索引在mysql中也叫作鍵
(1)B+Tree
下面文章摘子以下兩個博客
https://www.jianshu.com/p/8b653423c586(B-tree,讀B樹)
http://www.javashuo.com/article/p-nxtmjxfy-kz.html(b+tree)
(a)B-Tree
B+Tree是B-tree的變種,先了解如下B-Tree。一顆m階的B-Tree有以下特徵:
-- 根結點至少有兩個子女。
-- 每一箇中間節點都包含k-1個元素和k個孩子,其中 m/2 <= k <= m
-- 每個葉子節點都包含k-1個元素,其中 m/2 <= k <= m
-- 全部的葉子結點都位於同一層。
-- 每一個節點中的元素從小到大排列,節點當中k-1個元素正好是k個孩子包含的元素的值域分劃。
例如一個3階的B-Tree
這顆樹中,咱們來看(2,6)節點,該節點有兩個元素(2,6),又有三個元素(1,(3,5),8) ,其中
1小於元素2,(3,5)在元素(2,6)之間,2大於(8,5)。正好複合剛纔所列的幾個特徵。
B-Tree主要用於文件索引和部分數據庫索引,好比著名的非關係型數據庫mongodb
(i)B-Tree的查詢過程
單行查詢:
假如咱們要查詢的數值是5
第一次磁盤IO:
在內存中定位(和9比較):
在內存中定位(和2,6比較):
第3次磁盤IO:
在內存中定位(和3,5比較):
範圍查詢,查3到11的元素
自頂向下,查找到範圍的下限(3)
中序遍歷到元素6:
中序遍歷到元素8:
中序遍歷到元素9
中序遍歷到元素11,遍歷結束
(ii)B-Tree的插入操做
假如咱們要插入的值是4
自頂向下查找4的節點位置,發現4應當插入到節點元素3,5之間。
節點3,5已是兩元素節點,沒法再增長。父親節點 2, 6 也是兩元素節點,也沒法
再增長。根節點9是單元素節點,能夠升級爲兩元素節點。因而拆分節點3,5與節點2,
6,讓根節點9升級爲兩元素節點4,9。節點6獨立爲根節點的第二個孩子。
(iii)B-Tree的刪除過程
好比咱們要刪除元素11
自頂向下的查找元素11的位置
刪除11後,節點12只有一個孩子,不符合B樹規範。所以找出12,13,15三個節點的中位數13,取
代節點12,而節點12自身下移成爲第一個孩子。(這個過程稱爲左旋)
(b)B+Tree
一個m階的B+樹具備以下幾個特徵:
-- 有k個子樹的中間節點包含有k個元素(B樹中是k-1個元素),每一個元素不保存數據,只用來索
引,全部數據都保存在葉子節點。
-- 全部的葉子結點中包含了所有元素的信息,及指向含這些元素記錄的指針,且葉子結點自己依
關鍵字的大小自小而大順序連接。
-- 全部的中間節點元素都同時存在於子節點,在子節點元素中是最大(或最小)元素。
B+Tree有以下特徵:
-- 每個父節點中的元素都出如今子節點中,是子節點中最大或最小的元素
-- 葉子節點包含所有的數據信息
-- 每個葉子節點都包含指向下一個節點的指針,造成一個有序鏈表
-- 只有葉子節點帶有衛星數據
(i)B+Tree的查詢
-- 單行查詢
單行查詢時B+Tree會自頂向下逐層查詢節點,最終找到匹配的葉子節點,好比我門要
查找的元素3。
第一次磁盤IO:
第二次磁盤IO:
第三次磁盤IO:
-- B+Tree的範圍查找
查詢3到11的元素
自頂向下查找到範圍下限3
經過鏈表指針,遍歷到元素6, 8:
過鏈表指針,遍歷到元素9, 11,遍歷結束:
擴展1:MongoDB爲何使用B-Tree而不是B+Tree(也就是說B-Tree的優點)
-- B-Tree讀作B樹
-- B+Tree更適合經過外部存儲設備獲取數據(總體IO次數少);
B-Tree單次比較的成功率高。
想看詳細看文章:https://blog.csdn.net/ahjxhy2010/article/details/80339510
擴展2:葉子節點存什麼
首先要了解彙集索引和非彙集索引
下面這些內容來源於博客:http://www.javashuo.com/article/p-vfpiinzm-em.html
mysql彙集索引
索引和數據放在同一個文件裏面,彙集索引索引樹的葉子節點直接存儲數據(所有列的
數據),這就是彙集索引(聚合索引)。若是未定義主鍵,MySQL取第一個惟一 索引並且
只含非空列做爲主鍵,InnoDB使用它做爲聚簇索引。
彙集索引只有innoDB存儲引擎支持,並且還僅限於其主鍵索引。
mysql非彙集索引
非彙集索引,也叫作二級索引,索引樹的葉子節點是個索引節點,索引樹的葉子節點
保存對應主鍵(MyISAM非彙集索引存的是數據的地址),而且包含了引用行的主鍵id值。
除去上面描述的彙集索引,剩下的都是非彙集索引(二級索引)
舉例說明彙集索引和非彙集索引
假設table表 有主鍵id和name(name列已經創建了索引):
對建立name列建立索引
create index idx_name on ti(name);
下面這張圖就表現了在InnoDB彙集索引和非彙集索引是如何工做的:
能夠看到在主鍵索引的葉子節點上直接存儲了對應的數據,數據和索引在一塊兒,聚合索
引。並且其餘的索引樹的葉子節點對應的是該行數據的主鍵id,若是以name進行查找,先在
name的索引樹上找到對應的主鍵id,再經過獲得的id進行查找。這樣是爲何innoDB查詢的
時候比MyISAM慢的緣由之一,innoDB必須涉及到主鍵索引,既佔用內存又直接多了一次主
鍵索引樹的查找。
結論一:
主鍵索引(聚餐索引)查詢效率比非主鍵索引查詢效率更高。若是能使用主鍵查找的,
就儘可能使用主鍵索引進行查找。
結論二:
主鍵定義的長度越小,二級索引的大小就越小,這樣每一個磁盤塊存儲的索引數據越多,
查詢效率就越高。
這裏順便解釋一個問題:爲何推薦用自增id做爲主鍵?
第一:數字類型的比較速度更快。
自增的id,這個id必定是數字類型,相對於字符串而言,數字的比較速度快於
字符串(字符串須要一個字母一個字母進行比較),在B+樹中,值的插入、查找、
刪除都要進行比較,這樣的話用數字做爲主鍵比用字符串作爲主鍵在比較速度上更
有優點。
第二:B樹分裂更簡單,能夠更合理的利用磁盤空間,避免形成磁盤碎片。
自增的id對比亂序的UUID有什麼優點呢,能夠看到亂序的UUID在B+樹中的插
入位置是很是隨機的,而自增id每次都往B+樹的最右邊進行插入,這樣B+樹的分裂
操做更簡單,並且底層分配空間是分配一段連續的空間,這樣每次都像往數組的最
後插入數據,能夠更合理的利用連續分配的空間,以避免形成碎片過多。
第三:數字類型的索引更節約空間。
索引也是很是佔用磁盤空間的,對字段小的列建索引更節約磁盤空間,好比
對數字型的列建索引就比字符串的列建索引更加節省磁盤空間。
下面這張圖就表現了MyISAM在非彙集索引是如何工做的:
信息量很是大,我來一一說明:這個索引樹是以創建索引的列的全部數據組織起
來的,能夠看出索引十分消耗存儲空間,並且葉子節點存儲的是對應的那行數據的地
址,查找的時候先經過索引樹找到對應數據的地址,再經過地址找到真正的數據。這
也就是爲何不把所有的索引一次性載入內存的緣由,索引太大了。
擴展:MySql複合索引是如何組織樹的
表:
下圖顯示其如何組織數據存儲的
(iii)B+Tree的性質
下面的性質得出結論索引字段要儘可能的小
先看一種簡單說法:
每次IO讀取一個磁盤塊,一個磁盤磁盤塊能夠存多項數據,數
據越大,磁盤塊存的數據項越小,因此樹的高度越高。
在看網絡上的說法,比較難懂
經過上面的分析,咱們知道IO次數取決於b+樹的高度h,假設
當前數據表的數據爲N,每一個磁盤塊的數據項的數量是m,則
有h=㏒(m+1)N,當數據量N必定的狀況下,m越大,h越小;
m = 磁盤塊的大小 / 數據項的大小,磁盤塊的大小也就是一個
數據頁的大小,是固定的,若是數據項佔的空間越小,數據項
的數量越多,樹的高度越低。這就是爲何每一個數據項,即索
引字段要儘可能的小,好比int佔4字節,要比bigint8字節少一半。
這也是爲何b+樹要求把真實的數據放到葉子節點而不是內
層節點,一旦放到內層節點,磁盤塊的數據項會大幅度降低,
致使樹增高。當數據項等於1時將會退化成線性表。
擴展,塊是什麼鬼(本節參考文章:
https://www.i3geek.com/archives/1275
還有簇的概念,有空在看)
塊、扇區的一些概念
扇區
首先搞清楚扇區一些單位大小關係
一個磁盤按層次分爲磁盤組合 -> 單個磁盤 -> 某一盤面
-> 某一磁道 -> 某一扇區
如今在說扇區是什麼
每一個磁盤有多條同心圓似的磁道,磁道被分割成多個部
分。每部分的弧長加上到圓心的兩個半徑,剛好造成一
個扇形,這就是扇區。扇區是磁盤中最小的物理存儲單
位。一般狀況下每一個扇區的大小是512字節。(因爲不
斷提升磁盤的大小,部分廠商設定每一個扇區的大小是
4096字節)
磁盤塊
磁盤塊屬於邏輯層面,是操做系統虛擬出來的, 塊是操做系
統中最小的邏輯存儲單位。操做系統與磁盤打交道的最小單
位是磁盤塊,磁盤的讀寫基本單位是塊。
扇區和磁盤塊的關係以及爲何要有磁盤塊
因爲扇區的數量比較小,數目衆多在尋址時比較困難,因此
操做系統就將相鄰的扇區組合在一塊兒,造成一個塊,再對塊
進行總體的操做。扇區和磁盤塊的關係,經過射磁盤塊來映
射。磁盤塊和扇區大小的基本關係:
一個塊大小=一個扇區大小*2的n次方(這個值只是個例子,
並非固定的,由操做系統本身決定)
複合索引爲何要知足最左匹配原則
聯合索引的內部結構,從本質上來講,聯合索引也是一棵B+樹,不一樣的是聯合索引的鍵值的數量不是l,而是大於等於
2。其結構如圖5-22
從圖5.22能夠觀察到多個鍵值的B+樹狀況。其實和以前討論的單個鍵值的B+樹並無什麼不一樣,鍵值都是排序的,通
過葉子節點能夠邏輯上順序地讀出全部數據,就上面的例子來講,即(1,1)、(1,2)、(2,1)、(2,4)、(3,1)、(3,2)。
數據按(a,b)的順序進行了存放。
所以,對於查詢SELECT * FROM TABLE WHERE a=xxx and b=xxx,顯然是能夠使用(a,b)這個聯合索引的(由於索
引是經過排序確認查找方向)。對於單個的a列查詢SELECT * FROM TABLE WHERE a=xxx,也能夠使用這個(a,b)索引。
但對於b列的查詢SELECT* FROM TABLE WHERE b=xxx,則不能夠使用這棵B+樹索引。能夠發現葉子節點上的b值爲一、二、
一、四、一、2,顯然不是排序的,所以對於b列的查詢使用不到(a,b)的索引。
爲了加深理解,在囉嗦一段,當複合索引是(name,age,sex)的時候,b+樹是按照從左到右的順序來創建搜索樹的,好比
當(張三,20,F)這樣的數據來檢索的時候,b+樹會優先比較name來肯定下一步的所搜方向,若是name相同再依次比較age和
sex,最後獲得檢索的數據;但當(20,F)這樣的沒有name的數據來的時候,b+樹就不知道下一步該查哪一個節點,由於創建搜索
樹的時候name就是第一個比較因子,必需要先根據name來搜索才能知道下一步去哪裏查詢。好比當(張三,F)這樣的數據來檢
索時,b+樹能夠用name來指定搜索方向,但下一個字段age的缺失,因此只能把名字等於張三的數據都找到,而後再匹配性
別是F的數據了,這個是很是重要的性質,即索引的最左匹配特性。
學習最左匹配原則的時候複習一下這幅個表格
複合索引的第二個好處是已經對第二個鍵值進行了排序處理。例如,在不少狀況下應用程序都須要查詢某個用戶的購物情
況,並按照時間進行排序,最後取出最近三次的購買記錄,這時使用聯合索引能夠避免多一次的排序操做,由於索引自己在葉
子節點已經排序了。
語句 | 索引是否發揮做用 |
where a=3 | 是 |
where a=3 and b=5 | 是 |
where a=3 and b=5 and c=4 | 是 |
select * from mytable where c=4 and b=6 and a=3; |
原本按理說是不能夠的,可是網上說mysql會進行優化,因此和上一句同樣,用到了索引 |
where b=3 | 否 |
where c=4 | 否 |
where a=3 and c=4 | a列能用到索引,c不能 |
where a=3 and b>10 and c=7 | a能,b能,c不能,這個的地方b用的是範圍查詢,也算斷點,不過自身能用到索引 |
where a>4 and b=7 and c=9 | a能、b不能、c不能,理由同上 |
select * from mytable where a=3 order by b; | a用到了索引,b在結果排序中也用到了索引的效果 |
select * from mytable where a=3 order by c; | a用到了索引,可是這個地方c沒有發揮排序效果,由於中間斷點了 |
where a=3 and b like 'xxx%' and c=7 | a能,b能,c不能 |
(iv)覆蓋索引(索引覆蓋,重點,全看)
咱們知道MySQL的B+Tree索引是用咱們字段的數據來創建索引的,比
如說咱們的主鍵id字段,就是用全部的id來組織這顆索引樹,若是咱們再對
name字段創建索引的話,這個二級索引就是用name字段的數據來組織這
顆索引樹。那麼問題就來了,咱們知道對於二級索引而言他的葉子節點存
儲了對應數據行的id,也就是說最後咱們的查詢仍是要經過主鍵id來進行
查詢獲取數據。若是咱們只須要name這個字段呢?好比說
select name from table where name>'aaa'; 咱們這個二級索引上保存了的
name字段的全部數據,那麼就沒有必要再經過id去訪問數據行了,直接從
索引上獲取數據便可。稱之爲覆蓋索引,有的也翻譯爲索引覆蓋。
咱們能夠經過expalin來判斷查詢是否覆蓋索引,主要看extra字段是
否有using index 。
好比說:咱們有如下表:
對name字段創建索引:
執行sql 【explain select name from tb where name>'w'】;
能夠看到咱們使用上了索引idx_name,並且extra字段也出現了using index,
說明咱們此次查詢是覆蓋索引的。
把sql稍微修改一下:【explain select id,name from tb where name>'w'】;
能夠看到此次查詢仍是覆蓋索引,由於咱們二級索引樹的葉子節點就保存了
對應數據行的id,因此對於二級索引而言,查詢的時候加不加id都不影響是
否是覆蓋索引,id也在二級索引樹上。
咱們再把sql修改一下:【explain select name,age from tb where name>'w'】;
咱們對name字段創建的索引上並無保存age這個字段的數據,也就沒法從
索引上來獲取數據了,只能訪問數據表了。extra字段也沒有出現
using index(MySQL查詢優化器會在執行查詢前判斷是否有一個索引可以
進行覆蓋。若是索引覆蓋了where條件中的字段,但不是整個查詢的字段,
則會使用回表方式讀取數據 ) 。那麼咱們的業務需求就是常常的查詢name
和age字段咋辦呢?
爲了再次提升效率咱們能夠對name和age字段創建複合索引。
例如:
咱們再次執行【explain select name,age from tb where name>'w'】
發現咱們的新索引並無使用上,由於咱們MySQL優化器認爲不值得調用
這個複合索引,由於索引也佔空間,在當前就10條數據量,表就三個字段
(id,name,age)的狀況下,他認爲多一次主鍵查詢比從複合索引上獲
取數據的效果更好,並且也使用上了對name字段創建的單值索引。
咱們把索引idx_name刪掉,在次執行:
【explain select name,age from tb where name>'w'】
這個時候MySQL乖乖用上了咱們創建的複合索引,並且extra字段也出現
了using index 。在一個系統應用當中除非特別狀況,不然咱們也通常也
是創建複合索引。
注:可以使用索引覆蓋的情形
-- 查詢二級索引中的字段+主鍵字段。由於二級索引的葉子節點包含了主鍵的值
-- 查詢語句的全部字段都包括在一個複合索引裏面,且與字段順序無關,好比我
有一個複合索引
idx_payment_date(paymnet_date,amount,last_update)
則SQL語句
select amount,last_update,payment_date from payment
select amount,payment_date from payment
都將進行索引覆蓋查詢
而SQL語句
select customer_id,payment_date from payment
將不使用覆蓋索引,由於customer_id字段不在那個索引裏面
在舉一個列子,延時關聯
原Sql:
SELECT store_ids,film_id FROM sakila.inventory where actor='SEAN CARREY' and title like '%APOLLO%'
對(actor, title, prod_id),創建索引,而後按以下方式進行查詢
EXPLAIN SELECT * FROM products join ( SELECT prod_id from products WHERE actor='SEAN CARREY' and title like '%APOLLO%' ) tl ON (T1.prod_id-products.prod_id )
咱們把這種方式叫作延遲關聯(deferredjoin),由於延遲了對列的訪
問。在查詢的第一階段MySQL能夠使用覆蓋索引,在FROM子句
的子查詢中找到匹配的prod_id,而後根據這些prod_id值在外層
查詢匹配獲取須要的全部列值。雖然沒法使用索引覆蓋整個查詢,
但總算比徹底沒法利用索引覆蓋的好。
(v)mysql鎖會經過索引鎖定
-- 行鎖有時候會鎖大於返回記錄的行
InnoDB只有在訪問行的時候纔會對其加鎖,而索引可以減
少 InnoDB訪問的行數,從而減小鎖的數量。但這隻有當
InnoDB在存儲引擎層能夠過濾掉全部不須要的行時纔有效。如
果索引沒法過濾掉無效的行,那麼在InnoDB檢索到數據並返回
給服務器層之後,MySQL服務器才能應用WHERE子句。這時
已經沒法避免鎖定行了:InnoDB已經鎖住了這 些行,到適當的
時候才釋放。在MySQL5.1和更新的版本中,InnoDB可以在服
務器端過濾掉行後就釋放鎖,可是在早期的MySQL版本中,
InnoDB只有在事務提交後才能釋放鎖。
這個查詢僅僅會返回2~4之間的行,可是實際上獲取了1~4之
間的行的排他鎖。InnoDB會鎖住第1行,這是由於MySQL爲該查
詢選擇的執行計劃是索引範圍掃描:
換句話說,底層存儲引擎的操做是「從索引的開頭開始獲取
知足條件actor_id < 5的記錄「,服務器並無告訴InnoDB能夠
過濾第1行的WHERE條件。注意到EXPLAIN的Extra列出現了
」Using where「,這表示 MySQL服務器將存儲引擎返回行之後
再應用WHERE過濾條件。下面的第2個查詢就能證實第1行確
實已經被鎖定了,儘管第1個查詢的結果中並無這個第1行。
保持第1個鏈接打開,而後開啓第2個鏈接並執行以下查詢:
就像這個例子顯示的,即便使用了索引,InnoDB也可能鎖
定一些不須要的數據。若是不能使用索引查找和鎖定行的話問題
可能會更糟糕,MySQL會作全表掃描並鎖住全部的行,而無論
是否是須要。關於InnoDB、索引和鎖有一些不多有人知道的細
節:
InnoDB在二級索引上使用共享(讀)鎖,但訪問主鍵索引
須要排他(寫)鎖。這消除了使用覆蓋索引的可能性,而且使
得SELECT FOR UPDATE比LOCK IN SHAREMODE或非鎖定
查詢要慢得多。
-- 由於鎖索引引發的死鎖
案例一:
tab_test 結構以下:
id:主鍵;
state:狀態;
time:時間;
索引:idx_1(state,time)
出現死鎖的2條sql語句:
update tab_test set state=1064,time=now() where
state=1061
and time < date_sub(now(), INTERVAL 30 minute);
update tab_test set state=1067,time=now ()
where id in (9921180)
緣由分析:
當「update tab_test set state=1064,time=now() where
state=1061 and time < date_sub(now(), INTERVAL 30 minute)」
執行時,MySQL會使用idx_1索引,所以首先鎖定相關
的索引記錄,由於idx_1是非主鍵索引,爲執行該語句,
MySQL還會鎖定主鍵索引。假設
「update tab_test set state=1067,time=now () where
id in (9921180)」幾乎同時執行時,本語句首先鎖定主鍵
索引,因爲須要更新state的值,因此還須要鎖定idx_1
的某些索引記錄。這樣第一條語句鎖定了idx_1的記錄,
等待主鍵索引,而第二條語句則鎖定了主鍵索引記錄,
而等待idx_1的記錄,這樣死鎖就產生了。在第一條語
句給主鍵加鎖前,第二條語句已經給主鍵加了鎖,因此
在高併發的數據操做時,死鎖的狀況就容易產生InnoDB
會自動檢測一個事務的死鎖並回滾一個或多個事務來防
止死鎖。Innodb會選擇代價比較小的事務回滾,這次
事務(1)解鎖並回滾,語句(2)繼續運行直至事務結
束。
解決方案:
拆分第一條sql,先查出符合條件的主鍵值,再按照
主鍵更新記錄:
select id from tab_test where state=1061 and time <
date_sub(now(), INTERVAL 30 minute);
update tab_test state=1064,time=now() where id in(......);
其它死鎖狀況參見:https://www.cnblogs.com/phpfans/p/4649883.html
(2)Hash索引
簡單地說,哈希索引就是採用必定的哈希算法,把鍵值換算成新的哈希值,檢
索時不須要相似B+樹那樣從根節點到葉子節點逐級查找,只需一次哈希算法
便可馬上定位到相應的位置,速度很是快。
Hash索引的示意圖以下:
(a)Hash 索引結構的特殊性,其檢索效率很是高,索引的檢索能夠一次定位,
不像B-Tree 索引須要從根節點到枝節點,最後才能訪問到頁節點這樣多
次的IO訪問,因此 Hash 索引的查詢效率要遠高於 B-Tree 索引。可是當
Hash 索引遇到大量Hash值相等的狀況後性能並不必定就會比B-Tree索
引高。對於選擇性比較低的索引鍵,若是建立 Hash 索引,那麼將會存
在大量記錄指針信息存於同一個 Hash值相關聯。這樣要定位某一條記
錄時就會很是麻煩,會浪費屢次表數據的訪問,而形成總體性能低下。
(c)Hash 索引僅僅能知足"=","IN"和"<=>"查詢,不能使用範圍查詢。
因爲 Hash 索引比較的是進行 Hash 運算以後的 Hash 值,因此它只能
用於等值的過濾,不能用於基於範圍的過濾,由於通過相應的Hash算
法處理以後的Hash 值的大小關係,並不能保證和Hash運算前徹底一
樣。
(d)Hash 索引沒法被用來避免數據的文件排序操做。
因爲 Hash 索引中存放的是通過 Hash 計算以後的 Hash 值,並且Hash
值的大小關係並不必定和 Hash 運算前的鍵值徹底同樣,因此數據庫無
法利用索引的數據來避免任何排序運算;
(e)Hash 索引不能利用組合索引部分索引鍵查詢。
對於組合索引,Hash 索引在計算Hash 值的時候是組合索引鍵合併後再
一塊兒計算 Hash 值,而不是單獨計算 Hash 值,因此經過組合索引的前
面一個或幾個索引鍵進行查詢的時候,Hash 索引也沒法被利用。
(f)有大量鍵重複,hash碰撞頻繁,效率也不高
(3)B+Tree索引和Hash索引比較
(a)若是是等值查詢,那麼哈希索引明顯有絕對優點,由於只須要通過一次
算法便可找到相應的鍵值;固然了,這個前提是,鍵值都是惟一的。如
果鍵值不是惟一的,就須要先找到該鍵所在位置,而後再根據鏈表日後
掃描,直到找到相應的數據。
(b)從示意圖中也能看到,若是是範圍查詢檢索,這時候哈希索引就毫無用
武之地了,由於原先是有序的鍵值,通過哈希算法後,有可能變成不連
續的了,就沒辦法再利用索引完成範圍查詢檢索;
(c)同理,哈希索引也沒辦法利用索引完成排序,以及like ‘xxx%’ 這樣的部
分模糊查詢(這種部分模糊查詢,其實本質上也是範圍查詢);
(d)哈希索引也不支持多列聯合索引的最左匹配規則;
(e)B+樹索引的關鍵字檢索效率比較平均,不像B樹那樣波動幅度大,在有
大量重複鍵值狀況下,哈希索引的效率也是極低的,由於存在所謂的哈
希碰撞問題。
2.mysql索引的分類
(1)普通索引(normal)
這是最基本的索引,它沒有任何限制。它有如下幾種建立方式:
(a)直接建立索引
CREATE INDEX index_name ON table(column(length))
(b)修改表結構的方式添加索引
ALTER TABLE table_name ADD INDEX index_name ON (column(length))
(c)建立表的時候同時建立索引
CREATE TABLE `table` (
`id` int(11) NOT NULL AUTO_INCREMENT ,
`title` char(255) CHARACTER NOT NULL ,
`content` text CHARACTER NULL ,
`time` int(10) NULL DEFAULT NULL ,
PRIMARY KEY (`id`),
INDEX index_name (title(length))
)
(d)刪除索引
DROP INDEX index_name ON table
(2)惟一索引(unique)
它與前面的普通索引相似,不一樣的就是:索引列的值必須惟一,但容許有
空值。若是是組合索引,則列值的組合必須惟一。
(a)建立惟一索引
CREATE UNIQUE INDEX indexName ON table(column(length))
(b)修改表結構
ALTER TABLE table_name ADD UNIQUE indexName ON (column(length))
(c)建立表的時候直接指定
CREATE TABLE `table` (
`id` int(11) NOT NULL AUTO_INCREMENT ,
`title` char(255) CHARACTER NOT NULL ,
`content` text CHARACTER NULL ,
`time` int(10) NULL DEFAULT NULL ,
UNIQUE indexName (title(length))
);
(3)主鍵索引
是一種特殊的惟一索引,一個表只能有一個主鍵,不容許有空值。通常是
在建表的時候同時建立主鍵索引:
CREATE TABLE `table` (
`id` int(11) NOT NULL AUTO_INCREMENT ,
`title` char(255) NOT NULL ,
PRIMARY KEY (`id`)
);
(4)組合索引
指多個字段上建立的索引,只有在查詢條件中使用了建立索引時的第一個
字段,索引纔會被使用。使用組合索引時遵循最左前綴集合。
ALTER TABLE `table` ADD INDEX name_city_age (name,city,age);
組合索引的生效規則:
多列索引起揮做用,須要知足左前綴要求。
以index(a,b,c)爲例:
語句 | 索引是否發揮做用 |
where a=3 | 是 |
where a=3 and b=5 | 是 |
where a=3 and b=5 and c=4 | 是 |
where b=3 | 否 |
where c=4 | 否 |
where a=3 and c=4 | a列能用到索引,c不能 |
where a=3 and b>10 and c=7 | a能,b能,c不能 |
where a=3 and b like 'xxx%' and c=7 | a能,b能,c不能 |
注意:對複合索引的非最左前綴字段進行 OR 運算,是沒法使用到複合索
引的。
好比:
SELECT * FROM tbl_name WHERE (key_col1 > 10 OR key_col2 = 20) AND nonkey_col=30;
其中key_col1和key_col2創建複合索引,可是卻沒法使用到索引(
由於key_col2不是最左前綴,key_col2是否生效不知道,有環境測
試一下)。
其緣由是,MySQL中的索引,使用的是B+tree, 也就是說他是:
先按照複合索引的 第一個字段的大小來排序,插入到 B+tree
中的,當第一個字段值相同時,在按照第二個字段的值比較來
插入的。那麼若是咱們須要對:OR key_col2 = 20 這樣的條件
也使用複合索引,那麼該怎麼操做呢?應該要對複合索引進行
全掃描,找出全部 key_col2 =20 的項,而後還要回表去判斷
nonkey_col=30,顯然代價太大了。因此通常而言
OR key_col2 = 20 這樣的條件是沒法使用到複合索引的。若是
必定要使用索引,那麼能夠在 col2 上單獨創建一個索引。
(5)全文搜索索引(full textl)
從3.23.23版開始支持全文索引和全文檢索,FULLTEXT 用於搜索很長一
篇文章的時候,效果最好。主要用來查找文本中的關鍵字,而不是直接與
索引中的值相比較。fulltext索引跟其它索引大不相同,它更像是一個搜索
引擎,而不是簡單的where語句的參數匹配。fulltext索引配合
match against操做使用,而不是通常的where語句加like。它能夠在
create table,alter table ,create index使用,不過目前只有char、varchar,
text 列上能夠建立全文索引。值得一提的是,在數據量較大時候,現將數
據放入一個沒有全局索引的表中,而後再用CREATE index建立fulltext索
引,要比先爲一張表創建fulltext而後再將數據寫入的速度快不少。
(a)建立表的適合添加全文索引
CREATE TABLE `table` (
`id` int(11) NOT NULL AUTO_INCREMENT ,
`title` char(255) CHARACTER NOT NULL ,
`content` text CHARACTER NULL ,
`time` int(10) NULL DEFAULT NULL ,
PRIMARY KEY (`id`),
FULLTEXT (content)
);
(b)修改表結構添加全文索引
ALTER TABLE article ADD FULLTEXT index_content(content)
(c)直接建立索引
CREATE FULLTEXT INDEX index_content ON article(content)
3.索引優化,避免索引失效
(1)哪些列上適合建索引
(a)索引應該建在小字段上,對於大的文本字段甚至超長字段,不要建索引
(b)在常常須要搜索的列上,能夠加快搜索的速度;
(c)主鍵和外鍵
(d)在常常用於鏈接兩張表的列上
(e)在常用在WHERE子句中的列上面建立索引
(f)在常常須要根據範圍進行搜索的列上建立索引,由於索引已經排序,其
指定的範圍是連續的;
(g)在常常須要排序(order by)的列上建立索引,由於索引已經排序,
這樣查詢能夠利用索引的排序,加快排序查詢時間;
(h)數據量超過300的表應該有索引
(i)更新頻繁的字段不適合建立索引,不會出如今 where 子句中的字段不
應該建立索引
(2)避免索引失效和寫出高性能sql總結
(a)應儘可能避免在 where 子句中對字段進行 null 值判斷
(b)應儘可能避免在 where 子句中使用!=或<>操做符
(c)應儘可能避免在 where 子句中使用 or 來鏈接條件
(d)in 和 not in 也要慎用,不然可能會致使全表掃描(in字段少的時候會走索引,
因此不是in的就必定不走索引)
(e)下面的查詢也將致使全表掃描:
select id from t where name like '%abc%'
可是若是是這樣: select id from t where name like 'abc%'
索引是能夠生效的
(f)若是在 where 子句中使用參數,也會致使全表掃描。由於SQL只有在
運行時纔會解析局部變量,但優化程序不能將訪問計劃的選擇推遲到
運行時;它必須在編譯時進行選擇。然 而,若是在編譯時創建訪問計
劃,變量的值仍是未知的,於是沒法做爲索引選擇的輸入項。以下面
語句將進行全表掃描:
select id from t where num=@num
能夠改成強制查詢使用索引:
select id from t with(index(索引名)) where num=@num
(g)應儘可能避免在 where 子句中對字段進行運算、表達式、函數這將導
致引擎放棄使用索引而進行全表掃描。如:
select id from t where num/2=100
應改成:
select id from t where num=100*2
(h)應儘可能避免在where子句中對字段進行函數操做,這將致使引擎放棄
使用索引而進行全表掃描
(i)隱式轉換致使索引失效,例如表的字段tu_mdn定義爲varchar2(20),
但在查詢時把該字段做爲number類型以where條件傳給Oracle,這樣
會致使索引失效。
錯誤的例子:select * from test where tu_mdn=13333333333;
正確的例子:select * from test where tu_mdn='13333333333';
(j)並非全部索引對查詢都有效,SQL是根據表中數據來進行查詢優化
的,當索引列有大量數據重複時,SQL查詢可能不會去利用索引,如
一表中有字段sex,male、female幾乎各一半,那麼即便在sex上建
了索引也對查詢效率起不了做用。
關於性別字段不適合建索引緣由:
由於你訪問索引須要付出額外的IO開銷,你從索引中拿到的只
是地址,要想真正訪問到數據仍是要對錶進行一次IO。假如你要從表
的100萬行數據中取幾個數據,
那麼利用索引迅速定位,訪問索引的這IO開銷就很是值了。但若是你
是從100萬行數
據中取50萬行數據,就好比性別字段,那你相對須要訪問50萬次索引,
再訪問50萬次表,加起來的開銷並不會比直接對錶進行一次完整掃描
小。
(k)不少時候用 exists 代替 in 是一個好的選擇
例子:
select a.id, a.workflowid,a.operator,a.stepid from dbo.[[zping.com]]] a inner join workflowbase b on a.workflowid=b.id and operator='4028814111ad9dc10111afc134f10041' 替換爲 select a.id,a.workflowid,a.operator ,a.stepid from dbo.[[zping.com]]] a where exists (select 'X' from workflowbase b where a.workflowid=b.id) and operator='4028814111ad9dc10111afc134f10041'
(l)用exists替換DISTINCT
適用於相似這種狀況:當提交一個包含一對多表信息(好比部門表和僱
員表)的查詢時,避免在SELECT子句中使用DISTINCT。通常能夠考
慮用EXIST替換,EXISTS使查詢更爲迅速。
4.mysql語句的執行過程
此節內容摘自文章:http://www.javashuo.com/article/p-hpflcnvm-cq.html
(1)SQL語句的執行順序
(8) SELECT (9) DISTINCT (11) <TOP_specification> <select_list>
(1) FROM <left_table>
(3) <join_type> JOIN <right_table>
(2) ON <join_condition>
(4) WHERE <where_condition>
(5) GROUP BY <group_by_list>
(6) WITH {CUBE | ROLLUP}(avg,sum,mysql、oracle貌似沒有,先不用關注)
(7) HAVING <having_condition>
(10) ORDER BY <order_by_list>
即:from->on->join->where->group by->with->having->select->order by
以上每一個步驟都會產生一個虛擬表,該虛擬表被用做下一個步驟的輸
入。這些虛擬表對調用者(客戶端應用程序或者外部查詢)不可用。只有最
後一步生成的表纔會會給調用者。若是沒有在查詢中指定某一個子句,將
跳過相應的步驟。
由此可間,join的效率要好於where,網上查mysql是這樣,可是有點
不敢輕信,有空本身測試一下
對寫sql的一個現象解釋:
group by開始才能夠使用select中的別名,他返回的是一個遊標,而
不是一個虛擬表,因此在where中不能夠使用select中的別名,而
having卻能夠使用。
sql處理的詳細介紹:
(a)FROM:對FROM子句中的前兩個表執行笛卡爾積(交叉聯接),生成
虛擬表VT1。表名執行順序是從後往前,因此數據較少的表
儘可能放後。
(b)ON:對VT1應用ON篩選器,只有那些使爲真才被插入到TV2。
(c)JOIN(OUTER):若是指定了OUTER JOIN(Left Outer join、
Right Outer join),保留表中未找到匹配的行將做
爲外部行添加到VT2,生成TV3;若是是InnerJoin,
則不添加行,直接生成TV3;若是FROM子句包含
兩個以上的表,則對上一個聯接生成的結果表和下
一個表重複執行步驟1到步驟3,直處處理完全部的
表位置。
(d) WHERE:對TV3應用WHERE篩選器,只有使爲true的行才插入TV4。
執行順序爲從前日後或者說從左到右。
(e)GROUP BY:按GROUP BY子句中的列列表對TV4中的行進行分組,
生成TV5。執行順序從左往右分組。
(f) CUTE|ROLLUP:把超組插入VT5,生成VT6。
(g)HAVING:對VT6應用HAVING篩選器,只有使爲true的組插入到VT7。
Having語句很耗資源,儘可能少用。
(h)SELECT:處理SELECT列表,產生VT8。
(i)DISTINCT:將重複的行從VT8中刪除,產品VT9。
(j)ORDER BY:將VT9中的行按ORDER BY子句中的列列表順序,生成一
個遊標(VC10)。執行順序從左到右,是一個很耗資源的語
句。
(h)TOP:從VC10的開始處選擇指定數量或比例的行,生成表TV11,並返
回給調用者。
例子:
select 考生姓名, max(總成績) as max總成績 from tb_Grade where 考生姓名 is not null group by 考生姓名 having max(總成績) > 600 order by max總成績
在上面的示例中 SQL 語句的執行順序以下:
(a)首先執行 FROM 子句, 從 tb_Grade 表組裝數據源的數據
(b) 執行 WHERE 子句, 篩選 tb_Grade 表中全部數據不爲 NULL 的數據
(c)執行 GROUP BY 子句, 把 tb_Grade 表按 "學生姓名" 列進行分組(注:
這一步開始才能夠使用select中的別名,他返回的是一個遊標,而不
是一個表,因此在where中不能夠使用select中的別名,而having卻可
以使用)
(d) 計算 max() 彙集函數, 按 "總成績" 求出總成績中最大的一些數值
(e) 執行 HAVING 子句, 篩選課程的總成績大於 600 分的.
(f) 執行 ORDER BY 子句, 把最後的結果按 "Max 成績" 進行排序.
(g)mysql和sql執行順序基本是同樣的, 標準順序的 SQL 語句爲:
(2)Mysql語句的執行順序
Mysql語句的執行順序和sql語句的執行順序基本相同。
5.Mysql的執行計劃
什麼是執行計劃:
執行計劃是數據庫根據SQL語句和相關表的統計信息做出的一個查詢方
案,這個方案是由查詢優化器自動分析產生的,好比一條SQL語句若是
用來從一個10萬條記錄的表中查1條記錄,那查詢優化器會選擇「索引查
找」方式,若是該表進行了歸檔,當前只剩下5000條記錄了,那查詢優
化器就會改變方案,採用「全表掃描」方式。同屬的說:「模擬Mysql優化
器是如何執行SQL查詢語句的,從而知道Mysql是如何處理你的SQL語
句的、分析你的查詢語句或是表結構的性能瓶頸」。可見,執行計劃並不
是固定的,它是「個性化的」。產生一個正確的「執行計劃」有兩點很重要。
執行計劃須要注意的事項:
* 統一SQL語句的寫法
執行計劃是能夠被重用的,越簡單的SQL語句被重用的可能性越高。而複雜的
SQL語句只要有一個字符發生變化就必須從新解析,而後再把這一大堆垃圾塞
在內存裏。可想而知,數據庫的效率會何等低下。
例如,對於如下兩句SQL語句,程序員認爲是相同的,數據庫查詢優化器認爲
是不一樣的:
select*from dual
select*From dual
其實就是大小寫不一樣,查詢分析器就認爲是兩句不一樣的SQL語句,必須進行兩
次解析。生成2個執行計劃。因此做爲程序員,應該保證相同的查詢語句在任
何地方都一致,多一個空格都不行。
* 不要把SQL語句寫得太複雜
我常常看到,從數據庫中捕捉到的一條SQL語句打印出來有2張A4紙這麼
長。通常來講這麼複雜的語句一般都是有問題的。我拿着這2頁長的SQL語句
去請教原做者,結果他說時間太長,他一時也看不懂了。可想而知,連原做者
都有可能看糊塗的SQL語句,數據庫也同樣會看糊塗。
通常,將一個Select語句的結果做爲子集,而後從該子集中再進行查詢,
這種一層嵌套語句仍是比較常見的,可是根據經驗,超過3層嵌套,查詢優化
器就很容易給出錯誤的執行計劃。由於它被繞暈了。像這種相似人工智能的東
西,終究比人的分辨力要差些,若是人都看暈了,我能夠保證數據庫也會暈的。
* OLTP系統SQL語句必須採用綁定變量
select*from orderheader where changetime >'2010-10-20 00:00:01'
select*from orderheader where changetime >'2010-09-22 00:00:01'
以上兩句語句,查詢優化器認爲是不一樣的SQL語句,須要解析兩次。若是採用
綁定變量:
select*from orderheader where changetime >@chgtime
@chgtime變量能夠傳入任何值,這樣大量的相似查詢能夠重用該執行計劃了,
這能夠大大下降數據庫解析SQL語句的負擔。一次解析,屢次重用,是提升數
據庫效率的原則。
綁定變量的注意事項:
事物都存在兩面性,綁定變量對大多數OLTP處理是適用的,可是也有例
外。好比在where條件中的字段是「傾斜字段」的時候。「傾斜字段」指該列中的絕
大多數的值都是相同的,好比一張人口調查表,其中「民族」這列,90%以上都
是漢族。那麼若是一個SQL語句要查詢30歲的漢族人口有多少,那「民族」這列
必然要被放在where條件中。這個時候若是採用綁定變量@nation會存在很大
問題。試想若是@nation傳入的第一個值是「漢族」,那整個執行計劃必然會選
擇表掃描。而後,第二個值傳入的是「布依族」,按理說「布依族」佔的比例可能只
有萬分之一,應該採用索引查找。可是,因爲重用了第一次解析的「漢族」的那
個執行計劃,那麼第二次也將採用表掃描方式。這個問題就是著名的「綁定變量
窺測」,建議對於「傾斜字段」不要採用綁定變量。
注:這裏不一樣性別字段,性別字段不適合創建索引,是由於它大約要取一半的
數據,而民族字段卻不一樣,它有時候取大多數數據,有時候取少許數據,
因此它能夠加索引
本節參考文章:
http://www.javashuo.com/article/p-gfxgxizs-bb.html
http://www.javashuo.com/article/p-kfabrzdh-k.html
https://www.cnblogs.com/magialmoon/archive/2013/11/23/3439042.html#Extra
http://www.javashuo.com/article/p-djdeatzx-cc.html
https://segmentfault.com/q/1010000014908005/a-1020000014913547
(1)id:id是一組數字,表示查詢中執行select子句或操做表的順序,若是id相同,則執行順序
從上至下,若是是子查詢,id的序號會遞增,id越大則優先級越高,越先會被執行。
分三種狀況:
(i)id相同:執行順序由上至下
select查詢的序列號,包含一組數字,表示查詢中執行select子句或操做表的順序。
(ii)id不一樣:若是是子查詢,id的序號會遞增,id值越大優先級越高,越先被執行,自
己推測,有子查詢先執行子查詢。
(iii)id相同又不一樣(兩種狀況同時存在):id若是相同,能夠認爲是一組,從上往下順
序執行;在全部組中,id值越大,優先級越高,越先執行
關於derived的說明:
詳見select_type中的union
(2)select_type
查詢的類型,主要是用於區分普通查詢、聯合查詢、子查詢等複雜的查詢。
(i)SIMPLE:簡單的select查詢,查詢中不包含子查詢或者union
(ii)PRIMARY:查詢中包含任何複雜的子部分,最外層查詢則被標記爲primary
(iii)SUBQUERY:在select 或 where列表中包含了子查詢
(iv)DERIVED:在from列表中包含的子查詢被標記爲derived(衍生),mysql
或遞歸執行這些子查詢,把結果放在臨時表裏
(v)UNION:若第二個select出如今union以後,則被標記爲union;若union包含
在from子句的子查詢中,外層select將被標記爲derived。
<derived N>就表示這個是臨時表,後邊的N就是執行計劃中的id,
表示臨時表的結果來自於這個查詢產生。若是是尖括號括起來的
<union M,N>,與<derived N>相似,也是一個臨時表,表示這個臨時表
的結果來自於union查詢的id爲M和N的結果集。
(vi)UNION RESULT:從union表獲取結果的select(通俗的說就是union的結果,
本身猜想,就是若是是select * from (select1 union selet2)
那麼就有個序號(id),若是是select1 union select2 因爲
最外層沒有select,因此就是NULL,沒有id。具體什麼樣
子,有機會裝了mysql執行計劃在測試)
(3)type
訪問類型,sql查詢優化中一個很重要的指標,結果值從好到壞依次是:
system > const > eq_ref > ref > fulltext > ref_or_null > index_merge >
unique_subquery > index_subquery > range > index > ALL
通常來講,好的sql查詢至少達到range級別,最好能達到ref
system:表只有一行記錄(等於系統表),這是const類型的特例,平時不會出
現,能夠忽略不計。
const:使用惟一索引或者主鍵,返回記錄必定是1行記錄的等值where條件時,
一般type是const。其餘數據庫也叫作惟一索引掃描
eq_ref:惟一性索引掃描(主鍵或惟一索引),對於每一個索引鍵,表中只有一條
記錄與之匹配。常見於主鍵或惟一索引掃描。
注,與const的區別:
eq_ref是表示的是表和表關聯的一行,而const是單表中的一行且一般條
件是個常數。
注意:ALL全表掃描的表記錄最少的表如t1表
ref:非惟一性索引掃描,返回匹配某個單獨值的全部行。本質是也是一種索引訪
問,它返回全部匹配某個單獨值的行,所以他可能會找到多個符合條件的行,
因此它應該屬於查找和掃描的混合體
fulltext:全文索引檢索,要注意,全文索引的優先級很高,若全文索引和普通索引
同時存在時,mysql無論代價,優先選擇使用全文索引。
ref_or_null :這種鏈接類型相似 ref,不一樣的是增長了對null值的查詢。這種鏈接
類型的優化是從mysql4.1.1開始的,它常常用於子查詢。在如下的
例子中,mysql使用ref_or_null 類型來處理ref_table:
index merge: 此節內容內容參考文章:
https://www.cnblogs.com/digdeep/archive/2015/11/18/4975977.html
中文稱爲「索引合併」。表示查詢使用了兩個以上的索引,最後取
交集或者並集,常見and ,or的條件使用了不一樣的索引,官方排
序這個在ref_or_null以後,可是實際上因爲要讀取所個索引,性
能可能大部分時間都不如range。其執行過程簡單的說,其實就
是:對多個索引分別進行條件掃描,而後將它們各自的結果進行
合併。從MySQL5.1開始支持,合併的算法有:intersect,union,
sort_union(交集和並集的各類組合)。
注:索引合併(Index Merge)不適應與全文索引(full-text indexes)
對index merge優化之MySQL 5.6.7:
MySQL在5.6.7以前,使用index merge有一個重要的前提條件:
沒有range能夠使用。這個限制下降了 MySQL index merge 可
以使用的場景。理想狀態是同時評估成本後而後作出選擇。如
下例子:
SELECT * FROM t1 WHERE (goodkey1 < 10 OR goodkey2 < 20) AND badkey < 30;
對於這個查詢,有兩種處理方案:
第一種,索引合併使用條件(goodkey1 < 10 OR goodkey2 < 20)
第二種,範圍掃描(range scan)使用條件 badkey < 30優化器可
以選擇使用goodkey1和goodkey2作index merge,也
能夠使用badkey作range。
由於上面的原則,不管goodkey1和 goodkey2的選擇度如何,
MySQL都只會考慮range,而不會使用index merge的訪問方式。
5.6.7版本針對此有修復。
索引合併開關配置:
索引合併(Index Merge)的使用取決於optimizer_switch系統變
量的index_merge,index_merge_intersection,
index_merge_union和index_merge_sort_union標誌的值。默
認狀況下,全部這些標誌都打開。 要僅啓用特定算法,請將
index_merge設置爲關閉,並僅啓用其餘應容許的其餘算法。
* intersect:索引合併Intersection訪問算法對全部使用的索引執行同
時掃描,將其結果進行交集運算,即AND。例子以下:
結果進行交集運算,即AND。例子以下:
--例一:條件使用到複合索引中的全部字段或者左前綴字段(對單字段索引也適用) key_part1=const1 AND key_part2=const2 ... AND key_partN=constN
--例二:主鍵上的任何範圍條件 SELECT * FROM innodb_table WHERE primary_key < 10 AND key_col1=20; SELECT * FROM tbl_name WHERE (key1_part1=1 AND key1_part2=2) AND key2=2;
上面只說到複合索引,可是其實單字段索引顯然也是一
樣的。好比:
select * from tab where key1=xx and key2 =xxx; 也是
有可能進行index intersect merge的。另外上面兩種情
況的AND組合也同樣可能會進行index intersect merge。
若是查詢中使用的全部列都被使用的索引覆蓋,則
不會檢索完整的錶行(EXPLAIN輸出包含在這種狀況下
在Extra字段中 Using index)。索引合併Intersection訪
問算法對全部使用的索引執行同時掃描,併產生從合併
索引掃描中接收到的行序列的交集。若是使用的索引不
包括查詢中使用的全部列,則只有知足全部使用的鍵的
範圍條件時才檢索完整的行。若是其中一個合併條件是
InnoDB表的主鍵條件,則不用於行檢索,而是用於過
濾使用其餘條件檢索的行。
* union:簡單而言,index uion merge就是多個索引條件掃描,對
獲得的結果進行並集運算,即OR運算。下面幾種類型的
where 條件,以及他們的組合
可能會使用到 index union merge算法:
第一,條件使用到複合索引中的全部字段或者左前綴字段
(對單字段索引也適用)
第二,主鍵上的任何範圍條件
第三,任何符合 index intersect merge 的where條件
SELECT * FROM t1 WHERE key1=1 OR key2=2 OR key3=3; SELECT * FROM innodb_table WHERE (key1=1 AND key2=2) OR (key3='foo' AND key4='bar') AND key5=5;
解說:第一個例子,就是三個 單字段索引 進行 OR 運算,
因此他們可能會使用 index union merge算法。
第二個例子,複雜一點。(key1=1 AND key2=2) 是
符合index intersect merge;
(key3='foo' AND key4='bar') AND key5=5 也是符合
index intersect merge,因此 兩者之間進行 OR 運算,
天然可能會使用 index union merge算法。
* sort_union:多個條件掃描進行 OR 運算,可是不符合
index union merge算法的,此時可能會使用
sort_union算法。官方給出以下兩個例子:
SELECT * FROM tbl_name WHERE key_col1 < 10 OR key_col2 < 20; SELECT * FROM tbl_name WHERE (key_col1 > 10 OR key_col2 = 20) AND nonkey_col=30;
索引合併Sort-Union訪問算法和索引合併Union訪問
算法的區別在於,索引合併Sort-Union訪問算法必須
首先爲全部行提取行ID,而後在返回任何行以前對其
進行排序(表示沒看懂)。
對 index merge 的進一步優化(表示不是很理解,複合索引不就
是intersect嗎?有空搞明白):
index merge使得咱們能夠使用到多個索引同時進行掃描,然
後將結果進行合併。聽起來好像是很好的功能,可是若是出現
了 index intersect merge,那麼通常同時也意味着咱們的索引
創建得不太合理,由於 index intersect merge 是能夠經過創建
複合索引進行更一步優化的。
index merge的侷限:
若是咱們的條件比較複雜,用到多個 and / or 條件運算,而
MySQL沒有使用最優的執行計劃,那麼能夠使用以下的兩個
等式將條件進行轉換一下:
(x AND y) OR z = (x OR z) AND (y OR z)
(x OR y) AND z = (x AND z) OR (y AND z)
unique_subquery:該類型替換了下面形式的IN子查詢的ref:
value IN (SELECT primary_key FROM single_table
WHERE some_expr)
unique_subquery是一個索引查找函數,能夠徹底替換子查詢,
效率更高。
index_subquery:該聯接類型相似於unique_subquery。能夠替換IN子查詢,但只適
合下列形式的子查詢中的非惟一索引(子查詢可能返回重複值):
value IN (SELECT key_column FROM single_table WHERE
some_expr)
range: 索引的範圍查詢。只檢索給定範圍的行,使用一個索引來選擇行。key
列顯示使用了那個索引。通常就是在where語句中出現了bettween、
<、>、in等的查詢。這種索引列上的範圍掃描比全索引掃描要好。只
須要開始於某個點,結束於另外一個點,不用掃描所有索引。
index:Full Index Scan,index與ALL區別爲index類型只遍歷索引樹。這一般爲
ALL塊,由於索引文件一般比數據文件小。(Index與ALL雖然都是讀全
表,但index是從索引中讀取,而ALL是從硬盤讀取,出現這個若是能優
化也儘可能優化,可是以下例,確實是沒法優化)
ALL:Full Table Scan,遍歷全表以找到匹配的行
(4)possible_keys
查詢可能使用到的索引都會在這裏列出來
(5)key
查詢真正使用到的索引,若是沒有索引被選擇,鍵是NULL;查詢
中若是使用了覆蓋索引,則該索引僅出如今key列表中;若是
select_type爲index_merge時,這裏可能出現兩個以上的索引,其
他的select_type這裏只會出現一個。想要讓mysql強行使用或者忽
略在 possible_keys字段中的索引列表,能夠在查詢語句中使用關
鍵字force index, use index,或 ignore index。若是是myisam 和 bdb
類型表,能夠使用 analyzetable 來幫助分析使用使用哪一個索引更好。
若是是 myisam類型表,運行命令 myisamchk --analyze也是同樣的
效果。詳細的能夠查看章節"14.5.2.1 analyze tablesyntax"和
"5.7.2 tablemaintenance and crash recovery"(這裏沒細究,詳
情請百度搜索標黑的章節)。
(6)key_len
表示索引中使用的字節數,查詢中使用的索引的長度(最大可能長
度),並不是實際使用長度,理論上長度越短越好。key_len是根據
表定義計算而得的,不是經過表內檢索出的。當key字段的值爲
null時,索引就是null。
(7)ref
顯示索引的哪一列被使用了,即「哪些字段或者常量被用來和key
配合從表中查詢記錄出來「。
若是是使用的常數等值查詢,這裏會顯示const,若是是連
接查詢,被驅動表的執行計劃這裏會顯示驅動表的關聯字段,
若是是條件使用了表達式或者函數,或者條件列發生了內部隱
式轉換,這裏可能顯示爲func
補充,什麼是驅動表?
通俗的講就是先從哪一個表開始檢索,找到好的驅動表語句
的優化就成功一半了。例如:
select * from a,b where a.id = b.id and a.姓名 = '美格瑞恩'
and b.性別 = '女';在a,b表同等數量級的狀況下顯然用a表作
爲驅動表比較好由於姓名相對於性別來講能夠過濾掉更多
的數據,因此想辦法使你的執行計劃掃描a表先再經過
nest loop與b表關連比較理想。
驅動表的定義:
wwh999 在 2006年總結說,當進行多表鏈接查詢時, 驅動表的
定義爲:
* 指定了聯接條件時,知足查詢條件的記錄行數少的表爲「驅動表」。
* 未指定聯接條件時,行數少的表爲「驅動表」。
(8)rows
根據表統計信息及索引選用狀況,大體估算出找到所需的記錄
所須要讀取的行數。
(9)Extra
本字段顯示了查詢中mysql的附加信息
關於Extra的一些例子參看以下博客:
https://www.cnblogs.com/wy123/p/7366486.html
Extra的中只有出現Using filesort, Using temporary才須要優化
* Distinct
一旦MYSQL找到了與行相聯合匹配的行,就再也不搜索了
* Not exists
MYSQL優化了LEFT JOIN,一旦它找到了匹配LEFT JOIN標準
的行,就再也不搜索了
* Range checked for each
Record(index map:#)
沒有找到理想的索引,所以對於從前面表中來的每個行組合,
MYSQL檢查使用哪一個索引,並用它來從表中返回行。這是使用
索引的最慢的鏈接之一
* Using filesort(須要回表查詢)
mysql對數據使用一個外部的索引排序,而不是按照表內的索引
進行排序讀取。也就是說mysql沒法利用索引完成的排序操做稱
爲「文件排序」。看到這個的時候,查詢就須要優化了。
因爲索引是先按email排序、再按address排序,因此查詢時若是
直接按address排序,索引就不能知足要求了,mysql內部必須再
實現一次「文件排序」。
MySql中的排序:
MySql中有兩種排序,一種是「索引排序」,一種是「文件排
序」,索引排序效率比較高,因此排序字段須要創建索引。
MySql中ORDER BY、GROUP BY都會用到排序,
ORDER BY 必定要創建索引;GROUP BY是否創建索引尚
有爭議,有的地方說「須要創建」,有的地方說「視狀況而定」,
有空在研究一下。關於MySql的排序,
詳見:
https://www.cnblogs.com/cchust/p/5304594.html。
該篇文章由於時間關係我沒有看
Group By不加索引的理由:
https://blog.csdn.net/epee/article/details/83169669
* Using temporary
看到這個的時候,查詢須要優化了。表示 MySQL 在對查詢結果
排序時使用臨時表。常見於排序 order by 和分組查詢 group by上(這只是
一個比較典型的例子,並非只有order by和group by纔會出現)。
例一:
例二:
這是爲何呢?他倆之間只是一個order by不一樣,MySQL 表關聯的算法是
Nest Loop Join,是經過驅動表的結果集做爲循環基礎數據,而後一條一條
地經過該結果集中的數據做爲過濾條件到下一個表中查詢數據,而後合併結
果。EXPLAIN 結果中,第一行出現的表就是驅動表(Important!)以上兩個
查詢語句,驅動表都是 city,如上面的執行計劃所示!對驅動表能夠直接排
序,對非驅動表(的字段排序)須要對循環查詢的合併結果(臨時表)進行
排序所以,order by ads.id desc 時,就要先 using temporary 了!
* Using where(須要回表查詢)
Extra列出現Using where表示MySQL服務器將存儲引擎
返回服務層之後再應用WHERE條件過濾。
* Using index(不須要回表查詢)
表示相應的select操做中使用了覆蓋索引(Covering Index),
避免了訪問表的數據行,效率高若是同時出現Using where,表
明索引被用來執行索引鍵值的查找(參考 Using temporary中的
圖)若是沒用同時出現Using where,代表索引用來讀取數據而
非執行查找動做
* Using index condition
這是MySQL 5.6出來的新特性,叫作「索引條件推送」。簡單說一
點就是MySQL原來在索引上是不能執行如like這樣的操做的,但
是如今能夠了,這樣減小了沒必要要的IO操做,可是隻能用在二級
索引上,詳情點這裏。
SQL優化的例子:
例一,根據Type和Extra優化SQL
建立一張表:
CREATE TABLE IF NOT EXISTS `article` (`id` int(10) unsigned NOT NULL AUTO_INCREMENT, `author_id` int(10) unsigned NOT NULL, `category_id` int(10) unsigned NOT NULL, `views` int(10) unsigned NOT NULL, `comments` int(10) unsigned NOT NULL, `title` varbinary(255) NOT NULL, `content` text NOT NULL, PRIMARY KEY (`id`) );
插幾條數據:
CREATE TABLE IF NOT EXISTS `class` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `card` int(10) unsigned NOT NULL, PRIMARY KEY (`id`) ); CREATE TABLE IF NOT EXISTS `book` ( `bookid` int(10) unsigned NOT NULL AUTO_INCREMENT, `card` int(10) unsigned NOT NULL, PRIMARY KEY (`bookid`) ); CREATE TABLE IF NOT EXISTS `phone` ( `phoneid` int(10) unsigned NOT NULL AUTO_INCREMENT, `card` int(10) unsigned NOT NULL, PRIMARY KEY (`phoneid`) ) engine = innodb;
而後再分別插入大量數據。插入數據的php腳本:
explain select * from class left join book on class.card = book.card\G
執行結果:
*************************** 1. row *************************** id: 1 select_type: SIMPLE table: class type: ALL possible_keys: NULL key: NULL key_len: NULL ref: NULL rows: 20000 Extra: *************************** 2. row *************************** id: 1 select_type: SIMPLE table: book type: ALL possible_keys: NULL key: NULL key_len: NULL ref: NULL rows: 20000 Extra: 2 rows in set (0.00 sec)
顯然第二個 ALL 是須要咱們進行優化的。
創建索引:
ALTER TABLE `book` ADD INDEX y ( `card`);
*************************** 1. row *************************** id: 1 select_type: SIMPLE table: class type: ALL possible_keys: NULL key: NULL key_len: NULL ref: NULL rows: 20000 Extra: *************************** 2. row *************************** id: 1 select_type: SIMPLE table: book type: ref possible_keys: y key: y key_len: 4 ref: test.class.card rows: 1000 Extra: 2 rows in set (0.00 sec)
能夠看到第二行的 type 變爲了 ref,rows 也變成了
1741*18,優化比較明顯。
Left Join的反向測試,左表創建索引,右表不建:
刪除原索引:
DROP INDEX y ON book;
創建新索引:
explain select * from class right join book on class.card = book.card;
執行結果:
創建新索引:
ALTER TABLE `book` ADD INDEX y ( `card`);
執行以下代碼:
explain select * from class inner join book on class.card = book.card;
執行結果以下:
*************************** 1. row *************************** id: 1 select_type: SIMPLE table: book type: ALL possible_keys: NULL key: NULL key_len: NULL ref: NULL rows: 20000 Extra: *************************** 2. row *************************** id: 1 select_type: SIMPLE table: class type: ref possible_keys: x key: x key_len: 4 ref: test.book.card rows: 1000 Extra: 2 rows in set (0.00 sec)
執行結果:
*************************** 1. row *************************** id: 1 select_type: SIMPLE table: class type: ALL possible_keys: NULL key: NULL key_len: NULL ref: NULL rows: 20000 Extra: *************************** 2. row *************************** id: 1 select_type: SIMPLE table: book type: ref possible_keys: y key: y key_len: 4 ref: test.class.card rows: 1000 Extra: *************************** 3. row *************************** id: 1 select_type: SIMPLE table: phone type: ref possible_keys: z key: z key_len: 4 ref: test.book.card rows: 260 Extra: Using index 3 rows in set (0.00 sec)
後 2 行的 type 都是 ref 且總 rows 優化很好,效果不錯。
4.MyISAM和InnoDB的區別
參考文章:https://blog.csdn.net/UFO___/article/details/80519309
可見,MyISAM主要用做查詢,MyISAM和InnoDB的區別總結以下:
(1)MyISAM不支持事務,而InnoDB支持
(2)InnoDB支持數據行鎖定,MyISAM不支持行鎖定,只支持鎖定整個表
(3)InnoDB支持外鍵,MyISAM不支持
(4)InnoDB的主鍵範圍更大,最大是MyISAM的2倍。
(5)InnoDB不支持全文索引,而MyISAM支持
(6)沒有where的count(*)使用MyISAM要比InnoDB快得多。由於MyISAM內置了一
個計數器,count(*)時它直接從計數器中讀,而InnoDB必須掃描全表。
(7)MyISAM支持GIS數據,InnoDB不支持
5. Mysql MVCC
(1)MySql實現MVCC的策略
在每一行數據中額外保存兩個隱藏的列:當前行建立時的版本號和刪除時的版本號(可能爲
空,其實還有一列稱爲回滾指針,用於事務回滾,不在本文範疇)。每開始新的事務,系統版本
號都會自動遞增。事務開始時刻的系統版本號會做爲事務的版本號,用來和查詢每行記錄的版本
號進行比較。每一個事務又有本身的版本號,這樣事務內執行CRUD操做時,就經過版本號的比較
來達到數據版本控制的目的。
(2)MVCC下CRUD是怎麼工做的?
(a)insert
記錄的版本號即當前事務的版本號執行一條數據語句:
insert into testmvcc values(1,"test");
假設事務id爲1,那麼插入後的數據行以下:
(b)update
在更新操做的時候,採用的是先標記舊的那行記錄爲已刪除,而且刪除版本號是事務版本號,
而後插入一行新的記錄的方式。
好比,針對上面那行記錄,事務Id爲2 要把name字段更新
update table set name= 'new_value' where id=1
(c)delete
刪除操做的時候,就把事務版本號做爲刪除版本號。好比
delete from table where id=1;
(d)select
從上面的描述能夠看到,在查詢時要符合如下兩個條件的記錄才能被事務查詢出來:
(i)刪除版本號未指定或者大於當前事務版本號,即查詢事務開啓後確保讀取的行未被刪
除。(即上述事務id爲2的事務查詢時,依然能讀取到事務id爲3所刪除的數據行)
(ii)建立版本號小於或者等於當前事務版本號 ,就是說記錄建立是在當前事務中(等於的
狀況)或者在當前事務啓動以前的其餘事物進行的insert。(即事務id爲2的事務只能
讀取到create version<=2的已提交的事務的數據集)
5.mysql和oracle比較
(1)簡單版
http://www.javashuo.com/article/p-orgibpsk-br.html
(2)詳細瞭解
http://www.javashuo.com/article/p-hkbuuspe-bc.html
http://www.javashuo.com/article/p-twmviqrx-ba.html
而後在百度一些東西。
6.mysql大數據量翻頁
(1)問題的產生
假設有一個千萬量級的表,取1到10條數據;
select * from table limit 0,10; select * from table limit 1000,10;
這兩條語句查詢時間應該在毫秒級完成。可是,下面這條語句執行時間
大約要5秒
select * from table limit 3000000,10;
爲何相差這麼大?這是由於mysql並無你想的那麼智能,好比你要查
詢 300w開始後面10條數據,mysql會讀取300w加10條這麼多的數據,
只不過過濾後返回最後10條而已。
(2)解決方案
(i)業務解決,好比百度,你翻76頁就不讓你翻了。
(ii)若是你的table的主鍵id是自增的,而且中間沒有刪除和斷點,能夠
使用以下這種方式——在查詢下一頁的的時候把上一頁的行做爲參數
傳遞給客戶端。以下例:
select * from table where id>3000000 limit 10;
這條語句執行也是在毫秒級完成的,id>300w其實就是讓mysql直接跳到這
裏了,不用依次在掃描全面全部的行。
若是你的table的主鍵id是自增的,而且中間沒有刪除和斷點,好比100頁的
10條數據,以下
select * from table where id>100*10 limit 10;
這種方式由於索引掃描,因此速度會很快。有朋友提出: 由於數據
查詢出來並非按照pk_id排序的,因此會有漏掉數據的狀況,只
能用以下方法:
基於索引的在排序:
SELECT * FROM 表名稱 WHERE id_pk > (pageNum*10) ORDER BY id_pk ASC LIMIT M
(iii)延遲關聯(基於子查詢)
咱們在來分析一下這條語句爲何慢,慢在哪裏
select * from table limit 3000000,10;
玄機就處在這個 * 裏面,這個表除了id主鍵確定還有其餘字段 好比 name、
age之類的,由於select * 因此mysql在沿着id主鍵走的時候要回行拿數據,
走一下拿一下數據。
若是把語句改爲:
select id from table limit 3000000,10;
你會發現時間縮短了一半;而後咱們在拿id分別去取10條數據就好了。
語句就改爲這樣了:
select table.* from table inner join ( select id from table limit 3000000,10 ) as tmp on tmp.id=table.id;
這三種方法最早考慮第一種 其次第二種,第三種是別無選擇
(iv)基於索引使用prepare(第一個問號表示pageNum,第二個?表
示每頁元組數)
PREPARE stmt_name FROM SELECT * FROM 表名稱 WHERE id_pk > (?* ?) ORDER BY id_pk ASC LIMIT M
mysql處理prepare的原理:
http://www.javashuo.com/article/p-sihbbfaz-k.html
7.truncate和delete的區別
(1)DELETE語句執行刪除的過程是每次從表中刪除一行,而且同時將該
行的刪除操做做爲事務記錄在日誌中保存以便進行進行回滾操做;
TRUNCATE TABLE 則一次性地從表中刪除全部的數據並不把單獨的
刪除操做記錄記入日誌保存,刪除行是不能恢復的,而且在刪除的過
程中不會激活與表有關的刪除觸發器。執行速度快。
(2)當表被TRUNCATE 後,這個表和索引所佔用的空間會恢復到初始大
小;DELETE操做不會減小表或索引所佔用的空間。
(3)TRUNCATE 只能對TABLE; DELETE能夠是table和view
9.數據庫鎖
(1)共享鎖和排它鎖(原文:http://www.cnblogs.com/boblogsbo/p/5602122.html)
mysql鎖機制分爲表級鎖和行級鎖
共享鎖又稱爲讀鎖,簡稱S鎖,顧名思義,共享鎖就是多個事務對於
同一數據能夠共享一把鎖,都能訪問到數據,可是隻能讀不能修改。
排他鎖又稱爲寫鎖,簡稱X鎖,顧名思義,排他鎖就是不能與其餘所
並存,如一個事務獲取了一個數據行的排他鎖,其餘事務就不能再獲
取該行的其餘鎖,包括共享鎖和排他鎖,可是獲取排他鎖的事務是可
以對數據就行讀取和修改。
共享鎖:又稱爲讀鎖,簡稱S鎖,顧名思義,共享鎖就是多個事務對於同一
數據能夠共享一把鎖,都能訪問到數據,可是隻能讀不能修改。
排它鎖:又稱爲寫鎖,獨佔鎖,簡稱X鎖,顧名思義,排他鎖就是不能與其
他所並存,如一個事務獲取了一個數據行的排他鎖,其餘事務就不
能再獲取該行的其餘鎖,包括共享鎖和排他鎖,可是獲取排他鎖的
事務是能夠對數據就行讀取和修改。
對於共享鎖你們可能很好理解,就是多個事務只能讀數據不能改數據,對於
排他鎖你們的理解可能就有些差異,我當初就犯了一個錯誤,覺得排他鎖鎖
住一行數據後,其餘事務就不能讀取和修改該行數據,其實不是這樣的。排
他鎖指的是一個事務在一行數據加上排他鎖後,其餘事務不能再在其上加其
他的鎖,mysql InnoDB引擎默認的修改數據語句,update,delete,insert
都會自動給涉及到的數據加上排他鎖,select語句默認不會加任何鎖類型(
網上說默認會加S鎖,取決於隔離級別),若是加排他鎖能夠使用
select ...for update語句,加共享鎖能夠使用select ... lock in share mode
語句。因此加過排他鎖的數據行在其餘事務種是不能修改數據的,也不能通
過for update和lock in share mode鎖的方式查詢數據,但能夠直接經過
select ...from...查詢數據,由於普通查詢沒有任何鎖機制;在加了共享鎖的
語句上,能夠在加共享鎖,可是不能加排它鎖。實驗以下圖:
咱們看到是能夠查詢數據的,但加排他鎖就查不到,由於排他鎖與共享鎖不
能存在同一數據上。
(2)悲觀鎖和樂觀鎖
悲觀鎖:在關係數據庫管理系統裏,悲觀併發控制(又名「悲觀鎖」,
Pessimistic Concurrency Control,縮寫「PCC」)是一種併發控制
的方法。它能夠阻止一個事務以影響其餘用戶的方式來修改數據。
若是一個事務執行的操做都某行數據應用了鎖,那只有當這個事
務把鎖釋放,其餘事務纔可以執行與該鎖衝突的操做。悲觀併發
控制主要用於數據爭用激烈的環境,以及發生併發衝突時使用鎖
保護數據的成本要低於回滾事務的成本的環境中。悲觀鎖的實現,
每每依靠數據庫提供的鎖機制。悲觀鎖的執行流程以下:
(i)在對任意記錄進行修改前,先嚐試爲該記錄加上排他鎖
(exclusive locking)。
(ii)若是加鎖失敗,說明該記錄正在被修改,那麼當前查詢
可能要等待或者拋出異常。 具體響應方式由開發者根據
實際須要決定。
(iii)若是成功加鎖,那麼就能夠對記錄作修改,事務完成後
就會解鎖了。
(iv)其間若是有其餘對該記錄作修改或加排他鎖的操做,都
會等待咱們解鎖或直接拋出異常。
優勢與不足:
悲觀併發控制其實是「先取鎖再訪問」的保守策略,爲數據處
理的安全提供了保證。可是在效率方面,處理加鎖的機制會讓
數據庫產生額外的開銷,還有增長產生死鎖的機會;另外,在
只讀型事務處理中因爲不會產生衝突,也不必使用鎖,這樣
作只能增長系統負載;還有會下降了並行性,一個事務若是鎖
定了某行數據,其餘事務就必須等待該事務處理完才能夠處理
那行數據。
MySQL InnoDB中使用悲觀鎖:
要使用悲觀鎖,咱們必須關閉mysql數據庫的自動提交屬性,
由於MySQL默認使用autocommit模式,也就是說,當你執行
一個更新操做後,MySQL會馬上將結果進行提交。
set autocommit=0;
//0.開始事務 begin;/begin work;/start transaction; (三者選一就能夠) //1.查詢出商品信息 select status from t_goods where id=1 for update; //2.根據商品信息生成訂單 insert into t_orders (id,goods_id) values (null,1); //3.修改商品status爲2 update t_goods set status=2; //4.提交事務 commit;/commit work;
上面的查詢語句中,咱們使用了select…for update的方式,這樣就經過
開啓排他鎖的方式實現了悲觀鎖。此時在t_goods表中,id爲1的 那條數
據就被咱們鎖定了,其它的事務必須等本次事務提交以後才能執行。這
樣咱們能夠保證當前的數據不會被其它事務修改。
上面咱們提到,使用select…for update會把數據給鎖住,不過咱們
須要注意一些鎖的級別,MySQL InnoDB默認行級鎖。行級鎖都是基於
索引的,若是一條SQL語句用不到索引是不會使用行級鎖的,會使用表
級鎖把整張表鎖住,這點須要注意。
樂觀鎖:在關係數據庫管理系統裏,樂觀併發控制(又名「樂觀鎖」,
Optimistic Concurrency Control,縮寫「OCC」)是一種併發控制的方法。
它假設多用戶併發的事務在處理時不會彼此互相影響,各事務可以在不
產生鎖的狀況下處理各自影響的那部分數據。在提交數據更新以前,每
個事務會先檢查在該事務讀取數據後,有沒有其餘事務又修改了該數據。
若是其餘事務有更新的話,正在提交的事務會進行回滾。樂觀鎖
( Optimistic Locking ) 是相對悲觀鎖而言,樂觀鎖假設認爲數據通常情
況下不會形成衝突,因此在數據進行提交更新的時候,纔會正式對數據的
衝突與否進行檢測,若是發現衝突了,則讓返回用戶錯誤的信息,讓用戶
決定如何去作。相對於悲觀鎖,在對數據庫進行處理的時候,樂觀鎖並不
會使用數據庫提供的鎖機制。通常的實現樂觀鎖的方式就是記錄數據版本。
數據版本,爲數據增長的一個版本標識。當讀取數據時,將版本標識
的值一同讀出,數據每更新一次,同時對版本標識進行更新。當咱們提交
更新的時候,判斷數據庫表對應記錄的當前版本信息與第一次取出來的版
本標識進行比對,若是數據庫表當前版本號與第一次取出來的版本標識值
相等,則予以更新,不然認爲是過時數據(有點像CAS)。
實現數據版本有兩種方式,第一種是使用版本號,第二種是使用時間
戳。
使用版本號實現樂觀鎖:
1.查詢出商品信息 select (status,status,version) from t_goods where id=#{id} 2.根據商品信息生成訂單 3.修改商品status爲2 update t_goods set status=2,version=version+1 where id=#{id} and version=#{version};
使用時間戳實現樂觀鎖:有時間在查。
優勢與不足
(3)行鎖死鎖
死鎖分行鎖死鎖和表鎖死鎖,常見的是行鎖死鎖,本節介紹行鎖死鎖
下一節介紹表鎖死鎖。
(a)在學習行鎖死鎖以前要補充兩個知識點:
(i)在MySQL中,行級鎖並非直接鎖記錄,而是鎖索引。
索引分爲主鍵索引和非主鍵索引兩種,若是一條sql語句操做
了主鍵索引,MySQL就會鎖定這條主鍵索引;若是一條語句操
做了非主鍵索引,MySQL會先鎖該非主鍵索引,而後在定再鎖
定相關的主鍵索引。
(ii)行鎖是逐行獲取的。
好比表A有50行記錄,語句一逐行獲取鎖,獲取了1到50條
的鎖,語句2逐條獲取鎖,獲取了100到51條的鎖,當語句1要
獲取第51條記錄的鎖,語句2要獲取第50條記錄 的鎖,這個時
候就出現了互相等待的情況。
(iii)除了單個SQL組成的事務外,鎖是逐步獲取的
(b)四種死鎖狀況
(i)不一樣表的相同記錄行鎖衝突
案例:兩個表、兩行記錄,交叉得到和申請互斥鎖
條件:
- 兩事務分別操做兩個表、相同表的同一行記錄
- 申請的鎖互斥
- 申請的順序不一致
(ii)主鍵索引鎖衝突
案例:本文案例,產生衝突在主鍵索引鎖上
條件:
- 兩sql語句即兩事務操做同一個表、使用不一樣索引
- 申請的鎖互斥
- 操做多行記錄
- 查找到記錄的順序不一致
案例:
tab_test 結構以下:
id:主鍵;
state:狀態;
time:時間;
索引:idx_1(state,time)
出現死鎖的2條sql語句:
update tab_test set state=1064,time=now() where state=1061 and time < date_sub(now(), INTERVAL 30 minute); update tab_test set state=1067,time=now () where id in (9921180)
緣由分析:
當「update tab_test set state=1064,time=now()
where state=1061 and
time < date_sub(now(), INTERVAL 30 minute)」
執行時,MySQL會使用idx_1索引,所以首先鎖定相關
的索引記錄,由於idx_1是非主鍵索引,爲執行該語句,
MySQL還會鎖定主鍵索引。假設「update tab_test set
state=1067,time=now () where id in (9921180)」幾乎同
時執行時,本語句首先鎖定主鍵索引,因爲須要更新
state的值,因此還須要鎖定idx_1的某些索引記錄。這
樣第一條語句鎖定了idx_1的記錄,等待主鍵索引,而
第二條語句則鎖定了主鍵索引記錄,而等待idx_1的記
錄,這樣死鎖就產生了。在第一條語句給主鍵加鎖前,
第二條語句已經給主鍵加了鎖,因此在高併發的數據
操做時,死鎖的狀況就容易產生InnoDB 會自動檢測一
個事務的死鎖並回滾一個或多個事務來防止死鎖。Innodb
會選擇代價比較小的事務回滾,這次事務。
解決方案:
拆分第一條sql,先查出符合條件的主鍵值,再按
照主鍵更新記錄:
select id from tab_test where state=1061 and time <
date_sub(now(), INTERVAL 30 minute);
update tab_test state=1064,time=now() where id in(......);
(iii)主鍵索引鎖與非聚簇索引鎖衝突
直接看案例:
teamUser表的表結構以下:
PRIMARY KEY (`uid`,`Id`), KEY `k_id_titleWeight_score` (`Id`,`titleWeight`,`score`), ENGINE=InnoDB
出現死鎖的兩條SQL語句以下:
insert into teamUser_20110121 select * from teamUser DELETE FROM teamUser WHERE teamId=$teamId AND titleWeight<32768 AND joinTime<'$daysago_1week'
緣由分析:
在innodb默認的事務隔離級別下,普通的SELECT是不
須要加行鎖的,只有LOCK IN SHARE MODE、
FOR UPDATE及高串行化級別中的SELECT都要加鎖。但
是此案例中的,第一條語句這種狀況
insert into teamUser_20110121 select * from teamUser會
對錶teamUser_20110121(ENGINE= MyISAM)加表鎖,
並對teamUser表全部行的主鍵索引(即聚簇索引)加共享
鎖。默認對其使用主鍵索引。而第二條語句
DELETE FROM teamUser WHERE teamId=$teamId
AND titleWeight<32768 AND joinTime<'$daysago_1week'
爲刪除操做,會對選中行的主鍵索引加排他鎖。因爲此語句
還使用了非聚簇索引
KEY `k_teamid_titleWeight_score` (`teamId`,`titleWeight`,`score`)
的前綴索引,因而,還會對相關行的此非聚簇索引加排他鎖。
因爲共享鎖與排他鎖是互斥的,當一方擁有了某行記錄的
排他鎖後,另外一方就不能其擁有共享鎖,一樣,一方擁有了某
行共享鎖後,另外一方也沒法獲得其排他鎖(可見,元兇是鎖是
逐行獲取的,好比teamUser表有50行記錄,語句一逐行獲取鎖,
獲取了1到50條的鎖,語句2逐條獲取鎖,獲取了100到51條的
鎖,當語句1要獲取第51條記錄的鎖,語句2要獲取第50條記錄
的鎖,這個時候就出現了互相等待的情況)。因此兩條語句同
時行時,至關於兩個事務會同時申請某相同記錄行的鎖資源,
因而會產生鎖衝突。因爲兩個事務都會申請主鍵索引,鎖衝突
只會發生在主鍵索引上。
解決方案:
InnoDB給MySQL提供了具備提交,回滾和崩潰恢復能力
的事務安全(ACID兼容)存儲引擎。InnoDB鎖定在行級而且
也在SELECT語句提供非鎖定讀。這些特點增長了多用戶部署
和性能。但其行鎖的機制也帶來了產生死鎖的風險,這就需
要在應用程序設計時避免死鎖的發生。以單個SQL語句組成的
隱式事務來講,建議的避免死鎖的方法以下:
-- 若是使用insert…select語句備份表格且數據量較大,在單
獨的時間點操做,避免與其餘sql語句爭奪資源,或使用
select into outfile加上load data infile代替 insert…select,
這樣不只快,並且不會要求鎖定
-- 一個鎖定記錄集的事務,其操做結果集應儘可能簡短,以避免
一次佔用太多資源,與其餘事務處理的記錄衝突。
-- 更新或者刪除表格數據,sql語句的where條件都是主鍵或
都是索引,避免兩種狀況交叉,形成死鎖。對於where子
句較複雜的狀況,將其單獨經過sql獲得後,再在更新語句
中使用。
-- sql語句的嵌套表格不要太多,能拆分就拆分,避免佔有資
源同時等待資源,致使與其餘事務衝突。
-- 對定點運行腳本的狀況,避免在同一時間點運行多個對同
一表進行讀寫的腳本,特別注意加鎖且操做數據量比較大的
語句。
-- 應用程序中增長對死鎖的判斷,若是事務意外結束,從新運
行該事務,減小對功能的影響。
(iv)鎖升級形成鎖隊列阻塞
提交:
- 兩事務操做同一行記錄
- 一事務對某一記錄先申請共享鎖,再升級爲排他鎖
- 另外一事務在過程當中申請這一記錄的排他鎖
例如,用戶A查詢一條紀錄,而後修改該條紀錄;這時用戶B修
改該條紀錄,這時用戶A的事務裏鎖的性質由查詢的共享鎖企圖上升
到獨佔鎖,而用戶B裏的獨佔鎖因爲A有共享鎖存在因此必須等A釋
放掉共享鎖,而A因爲B的獨佔鎖而沒法上升的獨佔鎖也就不可能釋
放共享鎖,因而出現了死鎖。這種死鎖因爲比較隱蔽,但在稍大點
的項目中常常發生。
通常更新模式由一個事務組成,此事務讀取記錄,獲取資源
(頁或行)的共享 (S) 鎖,而後修改行,此操做要求鎖轉換爲排它 (X)
鎖。若是兩個事務得到了資源上的共享模式鎖,而後試圖同時更新
數據,則一個事務嘗試將鎖轉換爲排它 (X) 鎖。共享模式到排它鎖
的轉換必須等待一段時間,由於一個事務的排它鎖與其它事務的共
享模式鎖不兼容;發生鎖等待。第二個事務試圖獲取排它 (X) 鎖以
進行更新。因爲兩個事務都要轉換爲排它 (X) 鎖,而且每一個事務都
等待另外一個事務釋放共享模式鎖,所以發生死鎖。
解決方法:
-- 使用樂觀鎖進行控制
-- 使用悲觀鎖進行控制
(4)表鎖死鎖
出現緣由:
一個用戶A 訪問表A(鎖住了表A),而後又訪問表B;另外一個用戶B訪
問表B(鎖住了表B),而後企圖訪問表A;這時用戶A因爲用戶B已經
鎖住表B,它必須等待用戶B釋放表B才能繼續,一樣用戶B要等用戶A
釋放表A才能繼續,這就死鎖就產生了。
解決方法:
這種死鎖比較常見,是因爲程序的BUG產生的,除了調整的程序的
邏輯沒有其它的辦法。仔細分析程序的邏輯,對於數據庫的多表操做時,
儘可能按照相同的順序進行處理,儘可能避免同時鎖定兩個資源,如操做A
和B兩張表時,老是按先A後B的順序處理, 必須同時鎖定兩個資源時,
要保證在任什麼時候刻都應該按照相同的順序來鎖定資源。
(b)併發修改同一記錄
(c)參見4中的(2)
(4)mysql行級鎖、表級鎖、頁級鎖
表級鎖:開銷小,加鎖快;不會出現死鎖;鎖定粒度大,發生鎖衝突的機率最
高,併發度最低。
行級鎖:開銷大,加鎖慢;會出現死鎖;鎖定粒度最小,發生鎖衝突的機率最
低,併發度也最高。
行鎖的死鎖:參見4中的(2)
頁面鎖:開銷和加鎖時間界於表鎖和行鎖之間;會出現死鎖;鎖定粒度界於表
鎖和行鎖之間,併發度通常。
mysql行鎖、表鎖一些須要注意的點:
(i)在不經過索引條件查詢的時候,InnoDB 確實使用的是表鎖,而不是行鎖。
(ii)因爲 MySQL 的行鎖是針對索引加的鎖,不是針對記錄加的鎖,因此雖
然是訪問不一樣行的記錄,可是若是是使用相同的索引鍵,是會出現鎖衝
突的。應用設計的時候要注意這一點。
(iii)當表有多個索引的時候,不一樣的事務能夠使用不一樣的索引鎖定不一樣的行,
另外,不管是使用主鍵索引、惟一索引或普通索引,InnoDB 都會使用行
鎖來對數據加鎖。
(iv)即使在條件中使用了索引字段,可是否使用索引來檢索數據是由 MySQL
經過判斷不一樣執行計劃的代價來決定的,若是 MySQL 認爲全表掃效率
更高,好比對一些很小的表,它就不會使用索引,這種狀況下 InnoDB
將使用表鎖,而不是行鎖。所以,在分析鎖衝突時,別忘了檢查SQL的
執行計劃,以確認是否真正使用了索引。
何時使用表鎖?
對於InnoDB表,在絕大部分狀況下都應該使用行級鎖,由於事務和行鎖往
往是咱們之因此選擇InnoDB表的理由。但在個別特殊事務中,也能夠考慮使用
表級鎖。
第一種狀況是:事務須要更新大部分或所有數據,表又比較大,若是使用默認
的行鎖,不只這個事務執行效率低,並且可能形成其餘事務長
時間鎖等待和鎖衝突,這種狀況下能夠考慮使用表鎖來提升該
事務的執行速度。
第二種狀況是:事務涉及多個表,比較複雜,極可能引發死鎖,形成大量事務
回滾。這種狀況也能夠考慮一次性鎖定事務涉及的表,從而避
免死鎖、減小數據庫因事務回滾帶來的開銷。
表鎖和行鎖應用場景:
表級鎖使用與併發性不高,以查詢爲主,少許更新的應用,好比小型的
web應用;而行級鎖適用於高併發環境下,對事務完整性要求較高的系
統,如在線事務處理系統。
10.數據庫三範式和五大約束
(1)三範式
第一範式(1NF):當關系模式R的全部屬性都不能在分解爲更基本的數據單
位時,稱R是知足第一範式的,簡記爲1NF。知足第一範
式是關係模式規範化的最低要求,不然,將有不少基本操
做在這樣的關係模式中實現不了。通俗的說就是:
(a)數據表中的每一列(每一個字段)必須是不可拆分的最
小單元,也就是確保每一列的原子性;
(b)兩列的屬性相近或類似或同樣,儘可能合併屬性同樣的
列,確保不產生冗餘數據。
第二範式(2NF):若是關係模式R知足第一範式,而且R得全部非主屬性都
徹底依賴於R的每個候選關鍵屬性,稱R知足第二範式,
簡記爲2NF。通俗的說是每一行的數據只能與其中一列
相關,即一行數據只作一件事。只要數據列中出現數據重
復,就要把表拆分開來。例如,一我的同時訂幾個房間,
就會出來一個訂單號多條數據,這樣子聯繫人都是重複的,
就會形成數據冗餘。咱們應該把他拆開來。
第三範式(3NF):設R是一個知足第一範式條件的關係模式,X是R的任意屬
性集,若是X非傳遞依賴於R的任意一個候選關鍵字,稱R
知足第三範式,簡記爲3NF。通俗的說就是「數據不能存在
傳遞關係,即每一個屬性都跟主鍵有直接關係而不是間接關
系」。例如,Student表(學號,姓名,年齡,性別,所在
院校,院校地址,院校電話)這樣一個表結構,就存在上
述關係。 學號--> 所在院校 --> (院校地址,院校電話)這樣
的表結構,咱們應該拆開來,以下:(學號,姓名,年齡,
性別,所在院校)--(所在院校,院校地址,院校電話)
(2)五大約束
(a)主鍵約束(Primay Key Coustraint) 惟一性,非空性
添加主鍵約束(將stuNo做爲主鍵)
alter table stuInfo
add constraint PK_stuNo primary key (stuNo)
(b)惟一約束 (Unique Counstraint)惟一性,能夠空,但只能有一個
添加惟一約束(身份證號惟一,由於每一個人的都不同)
alter table stuInfo
add constraint UQ_stuID unique(stuID)
(c)檢查約束 (Check Counstraint) 對該列數據的範圍、格式的限制(如:年
齡、性別等)
添加檢查約束 (對年齡加以限定 15-40歲之間)
alter table stuInfo
add constraint CK_stuAge check (stuAge between 15 and 40)
(d)默認約束 (Default Counstraint) 該數據的默認值
添加默認約束(若是地址不填 默認爲「地址不詳」)
alter table stuInfo
add constraint DF_stuAddress default (‘地址不詳’) for stuAddress
(e)外鍵約束 (Foreign Key Counstraint) 須要創建兩表間的關係並引用主表
的列
添加外鍵約束 (主表stuInfo和從表stuMarks創建關係,關聯字段stuNo)
alter table stuInfo
add constraint FK_stuNo foreign key(stuNo)references stuinfo(stuNo)
二:數據庫的分庫分表
1.互聯網行業傳統架構中分庫分表(水平分庫分表)
(1)水平分庫分表產生的業務場景
對於大型的互聯網應用來講,數據庫單表的記錄行數可能達到千萬
級甚至是億級,而且數據庫面臨着極高的併發訪問。採用Master-Slave
複製模式的MySQL架構,只可以對數據庫的讀進行擴展,而對數據庫的
寫入操做仍是集中在Master上,而且單個Master掛載的Slave也不可能無
限制多,Slave的數量受到Master能力和負載的限制。
(2)分表
對於訪問極爲頻繁且數據量巨大的單表來講,咱們首先要作的就是
減小單表的記錄條數,以便減小數據查詢所須要的時間,提升數據庫的
吞吐,這就是所謂的分表!
在分表以前,首先須要選擇適當的分表策略,使得數據可以較爲均
衡地分不到多張表中,而且不影響正常的查詢!
對於互聯網企業來講,大部分數據都是與用戶關聯的,所以,用戶
id是最經常使用的分表字段。由於大部分查詢都須要帶上用戶id,這樣既不
影響查詢,又可以使數據較爲均衡地分佈到各個表中(固然,有的場景
也可能會出現冷熱數據分佈不均衡的狀況),以下圖:
注:拆分後表的數量通常爲2的n次方(爲何是2的n次方呢?由於
2的n次方是最小的拆分,最小的正整數是1,1的n次方是1,因此
使用2的n次方分表)。
假設有一張表記錄用戶購買信息的訂單表order,因爲order表記
錄條數太多,將被拆分紅256張表。拆分的記錄根據user_id%256取
得對應的表進行存儲,前臺應用則根據對應的user_id%256,找到對
應訂單存儲的表進行訪問。這樣一來,user_id便成爲一個必需的查詢
條件,不然將會因爲沒法定位數據存儲的表而沒法對數據進行訪問。
這樣一來,user_id便成爲一個必需的查詢條件,不然將會因爲無
法定位數據存儲的表而沒法對數據進行訪問。
舉例說明user_id爲必須查詢的字段
假設表結構爲:
create table order_( order_id bigint(20) primary key auto_increment, user_id bigint(20), user_nick varchar(50), auction_id bigint(20), auction_title bigint(20), price bigint(20), auction_cat varchar(200), seller_id bigint(20), seller_nick varchar(50) )
那麼分表之後,假設user_id = 257,而且auction_id = 100,須要根
據auction_id來查詢對應的訂單信息,則對應的SQL語句以下:
select * from order_1 where user_id=257 and auction_id = 100;
其中,order_1是根據257%256計算得出,表示分表以後的第一
張order表。
(3)分庫
分表可以解決單表數據量過大帶來的查詢效率降低的問題,可是,
卻沒法給數據庫的併發處理能力帶來質的提高。面對高併發的讀寫訪
問,當數據庫master服務器沒法承載寫操做壓力時,無論如何擴展
slave服務器,此時都沒有意義了。所以,咱們必須換一種思路,對數
據庫進行拆分,從而提升數據庫寫入能力,這就是所謂的分庫!
與分表策略類似,分庫能夠採用經過一個關鍵字取模的方式,來
對數據訪問進行路由,以下圖所示:
仍是以前的訂單表,假設user_id 字段的值爲258,將原有的單庫
分爲256個庫,那麼應用程序對數據庫的訪問請求將被路由到第二個
庫(258%256 = 2)。
(3)分庫分表
(a)爲何要分庫分表?
有時數據庫可能既面臨着高併發訪問的壓力,又須要面
對海量數據的存儲問題,這時須要對數據庫既採用分表策略,
又採用分庫策略,以便同時擴展系統的發處理能力,以及提高
單表的查詢性能,這就是所謂的分庫分表。
(b)分庫分表策略
注:下面案例中,庫的編號是一個天然序列,且從0開始
中間變量=userid%(庫的數量*每一個庫表的數量)
庫=「中間變量/每一個庫的表的數量」 而後在取整 。
表=中間變量%庫中的表數量
注意:上面公式的第一步,求的是全部表的天然序列;
第二步,若是庫的下標是從0開始,向下取整,
若是庫的編號從1開始像上取整。
假設將原來的單庫單表order拆分紅256個庫,每一個庫包含1024
個表,那麼按照前面所提到的路由策略,對於user_id=262145
的訪問,路由的計算過程以下:
* 中間變量 = 262145 % (256 * 1024) = 1
* 庫 = 取整 (1/1024) = 0
* 表 = 1 % 1024 = 1
結論,user_id=262145 的訂單記錄的查詢和修改,將被路由到
第0個庫的第1個order_1表中執行
(4)垂直分表
上面介紹的分表方式是水平的,下面介紹垂直分表。
(a)爲何要垂直分表?
若是一個表的字段很是多,有可能形成數據庫的跨頁存儲,
這就致使了性能的降低,垂直分表正式爲了解決這個問題產生
的。
(b)垂直分表
垂直分表就是將一張表中不經常使用的字段拆分到另外一張表中,
從而保證第一章表中的字段較少,避免出現數據庫跨頁存儲的
問題,從而提高查詢效率。而另外一張表中的數據經過外鍵與第
一張表進行關聯,以下圖所示:
2. 微服務架構中的分庫(垂直分庫)
垂直分庫便是將一個完整的數據庫根據業務功能拆分紅多個獨立的數據
庫,這些數據庫能夠運行在不一樣的服務器上,從而提高數據庫總體的數
據讀寫性能。這種方式在微服務架構中很是經常使用。微服務架構的核心思
想是將一個完整的應用按照業務功能拆分紅多個可獨立運行的子系統,
這些子系統稱爲「微服務」,各個服務之間經過RPC接口通訊,這樣的結
構使得系統耦合度更低、更易於擴展。垂直分庫的理念與微服務的理念
不謀而合,能夠將本來完整的數據按照微服務拆分系統的方式,拆分紅
多個獨立的數據庫,使得每一個微服務系統都有各自獨立的數據庫,從而
能夠避免單個數據庫節點壓力過大,影響系統的總體性能,以下圖所示
3. 微服務分庫(垂直分庫)跨庫join的幾種解決方案
(1)全局表
所謂全局表,就是有可能系統中全部模塊均可能會依賴到的一些
表。比較相似咱們理解的「數據字典」。爲了不跨庫join查詢,咱們
能夠將這類表在其餘每一個數據庫中均保存一份。同時,這類數據一般
也不多發生修改(甚至幾乎不會),因此也不用太擔憂「一致性」問題。
(2)字段冗餘
這是一種典型的反範式設計,在互聯網行業中比較常見,一般是
爲了性能來避免join查詢。
舉個電商業務中很簡單的場景:
「訂單表」中保存「賣家Id」的同時,將賣家的「Name」字段也冗
餘,這樣查詢訂單詳情的時候就不須要再去查詢「賣家用戶表」。
字段冗餘能帶來便利,是一種「空間換時間」的體現。但其適用場
景也比較有限,比較適合依賴字段較少的狀況。最複雜的仍是數
據一致性問題,這點很難保證,能夠藉助數據庫中的觸發器或者
在業務代碼層面去保證。固然,也須要結合實際業務場景來看一
致性的要求。就像上面例子,若是賣家修改了Name以後,是否
須要在訂單信息中同步更新呢?
(3)數據同步
定時A庫中的tab_a表和B庫中tbl_b有關聯,能夠定時將指定
的表作同步。固然,同步原本會對數據庫帶來必定的影響,須要
性能影響和數據時效性中取得一個平衡。這樣來避免複雜的跨庫
查詢。筆者曾經在項目中是經過ETL工具來實施的。
(4)新建連接表
(4)系統層組裝
(原文出自博客:https://www.open-open.com/lib/view/open1473820694158.html#articleHeader4
的系統層組裝章節中,若是博客被刪了,百度「跨庫 系統層組裝」。
其實(1)、(2)、(3)、(4)點都來自這篇博客)
在系統層面,經過調用不一樣模塊的組件或者服務,獲取到數據並
進行字段拼裝。提及來很容易,但實踐起來可真沒有這麼簡單,尤爲
是數據庫設計上存在問題但又沒法輕易調整的時候。具體狀況一般會
比較複雜。下面筆者結合以往實際經驗,並經過僞代碼方式來描述。
(a)簡單的列表查詢的狀況
僞代碼很容易理解,先獲取「個人提問列表」數據,而後再根
據列表中的UserId去循環調用依賴的用戶服務獲取到用戶的
RealName,拼裝結果並返回。有經驗的讀者一眼就能看出上訴
僞代碼存在效率問題。循環調用服務,可能會有循環RPC,循
環查詢數據庫…不推薦使用。再看看改進後的:
這種實現方式,看起來要優雅一點,其實就是把循環調用改爲
一次調用。固然,用戶服務的數據庫查詢中極可能是In查詢,效率
方面比上一種方式更高。(坊間流傳In查詢會全表掃描,存在性能
問題,傳聞不可全信。其實查詢優化器都是基本成本估算的,通過
測試,在In語句中條件字段有索引的時候,條件較少的狀況是會走
索引的。這裏不細展開說明,感興趣的朋友請自行測試)。
小結
簡單字段組裝的狀況下,咱們只須要先獲取「主表」數據,
而後再根據關聯關係,調用其餘模塊的組件或服務來獲取
依賴的其餘字段(如例中依賴的用戶信息),最後將數據
進行組裝。一般,咱們都會經過緩存來避免頻繁RPC通訊
和數據庫查詢的開銷。
(b)列表查詢帶條件過濾的狀況
- 查出全部的問答數據,而後調用用戶服務進行拼裝數據,再根據過濾
字段state字段進行過濾,最後進行排序和分頁並返回。這種方式可以
保證數據的準確性和完整性,可是性能影響很是大,不建議使用。
- 查詢出state字段符合/不符合的UserId,在查詢問答數據的時候使用
in/not in進行過濾,排序,分頁等。過濾出有效的問答數據後,再調
用用戶服務獲取數據進行組裝。
(c)跨庫事務(分佈式事務)的問題
按業務拆分數據庫以後,不可避免的就是「分佈式事務」的問題。以
往在代碼中經過spring註解簡單配置就能實現事務的,如今則須要花很
大的成本去保證一致性。這裏不展開介紹。
4. 跨庫翻頁
參考文章:http://www.javashuo.com/article/p-rlfxuign-ho.html
(1)先介紹萬能的終極武器「二次查詢法」
「二次查詢法」即可以知足業務的精確須要,無需業務折衷,又高性能的
方法。該方法比較複雜,經過例子講解。
假設一頁只有5條數據,查詢第200頁的SQL語句爲
select * from T order by time offset 1000 limit 5;
步驟一:查詢改寫
將select * from T order by time offset 1000 limit 5
改寫爲select * from T order by time offset 333 limit 5
並投遞給全部的分庫。注意,這個offset的333,來自於全局offset
的總偏移量1000,除以水平切分數據庫個數3。若是是2個分庫,
則能夠改寫爲select * from T order by time offset 500limit 5
假設這三個分庫返回的數據(time, uid)以下:
能夠看到,每一個分庫都是返回的按照time排序的一頁數據。
步驟二:找到所返回3頁所有數據的最小值
第一個庫,5條數據的time最小值是1487501123
第二個庫,5條數據的time最小值是1487501133
第三個庫,5條數據的time最小值是1487501143
故,三頁數據中,time最小值來自第一個庫,time_min=1487501123,
這個過程只須要比較各個分庫第一條數據,時間複雜度很低。
步驟三:查詢二次改寫
上面第一次改寫後的SQL爲:
是select * from T order by time offset 333 limit 5
那麼,第二次要改寫成一個between語句,between的起點是
time_min,between的終點是原來每一個分庫各自返回數據的最
大值。
第一個分庫,第一次返回數據的最大值是1487501523
因此查詢改寫爲select * from T order by time where time
between time_min and 1487501523
第二個分庫,第一次返回數據的最大值是1487501323
因此查詢改寫爲select * from T order by time where time
between time_min and 1487501323
第三個分庫,第一次返回數據的最大值是1487501553
因此查詢改寫爲select * from T order by time where time
between time_min and 1487501553
相對第一次查詢,第二次查詢條件放寬了,故第二次查詢會返回比
第一次查詢結果集更多的數據,假設這三個分庫返回的數據
(time, uid)以下:
能夠看到:
因爲time_min來自原來的分庫一,因此分庫一的返回結果
集和第一次查詢相同(因此其實此次訪問是能夠省略的);分
庫二的結果集,比第一次多返回了1條數據,頭部的1條記錄
(time最小的記錄)是新的(上圖中粉色記錄);分庫三的結
果集,比第一次多返回了2條數據,頭部的2條記錄(time最小
的2條記錄)是新的(上圖中粉色記錄);
步驟四:找到全局offset
咱們在每一個結果集中虛擬一個time_min記錄,找到time_min在全局
的offset
在第一個庫中,time_min在第一個庫的offset是333
在第二個庫中,(1487501133, uid_aa)的offset是333(根據第一次
查詢條件得出的),由於按第二次改寫多了一條數據,因此故虛擬
time_min在第二個庫的offset是331.
在第三個庫中,(1487501143, uid_aaa)的offset是333(根據第一次
查詢條件得出的),由於按第二次改寫多了兩條數據,故虛擬
time_min在第三個庫的offset是330綜上,time_min在全局的offset是
333+331+330=994。
綜上,time_min在全局的offset是333+331+330=994
4. mysql SSL鏈接
MySQL默認的數據通道是不加密的,在一些安全性要求特別高的場景下,咱們需
要配置MySQL端口爲SSL,使得數據通道加密處理,避免敏感信息泄漏和被篡改。
固然,啓用MySQL SSL以後,因爲每一個數據包都須要加密和解密,這個對MySQL
的性能是有不小影響的,讀者們在使用的時候,要根據實際狀況斟酌。
SSL證書:https正是使用的SSL證書,http+ssl=https
MySQL客戶端登陸服務器時候的密碼不是明文傳輸,有加密策略處理
1.本身比較懵懂的概念
(1)數據庫分區
查看:https://blog.csdn.net/xhf852963/article/details/78896427
(2)數據庫分片
僞代碼很容易理解,先獲取「個人提問列表」數據,而後再根
據列表中的UserId去循環調用依賴的用戶服務獲取到用戶的
RealName,拼裝結果並返回。有經驗的讀者一眼就能看出上訴
僞代碼存在效率問題。循環調用服務,可能會有循環RPC,循
環查詢數據庫…不推薦使用。再看看改進後的:
這種實現方式,看起來要優雅一點,其實就是把循環調用改爲
一次調用。固然,用戶服務的數據庫查詢中極可能是In查詢,效率
方面比上一種方式更高。(坊間流傳In查詢會全表掃描,存在性能
問題,傳聞不可全信。其實查詢優化器都是基本成本估算的,通過
測試,在In語句中條件字段有索引的時候,條件較少的狀況是會走
索引的。這裏不細展開說明,感興趣的朋友請自行測試)。