MySQL 知識

Rakishly發表於2020-06-16

Hash索引 與 b+樹索引區別?

Hash索引結構的特殊性,檢索效率非常高,能一次定位。
B+樹索引,需要從根節點到枝節點,再到葉節點,這樣多次io訪問。

為什麼不用hash索引,而要使用 B+樹索引

1、Hash索引不能滿足範圍查詢,無法避免排序操作,因為hash值的大小與運算前的大小不一定能完全一致。
2、對於組合索引,hash索引在計算索引值的時候是合併 組合索引再一起計算hash值,而不是單獨計算hash值,所以通過一個或幾個索引鍵查詢的時候,hash索引無法被利用。
3、Hash索引遇到大量hash值相等的情況下效能不一定比 B+樹高,儲存hash衝突
4、如果是等值查詢,那麼雜湊索引絕對有優勢,因為只需要經過一次演算法就能 找到相應的鍵值,這個前提是,鍵值都是唯一的,如果鍵值不唯一,就需要先找到鍵所在的位置,然後再根據連結串列往後掃描,直到找到相應的資料。

B+樹索引

常用的innodb引擎預設使用B+樹索引,它會實時監控索引的使用情況,如果認為建立hash索引可以提高查詢效率,則自動在記憶體中的“自適應雜湊索引緩衝區”建立雜湊索引(在innodb中預設開啟自適應雜湊索引),通過觀察 查詢模式,mysql會利用index key字首建立雜湊索引,如果一個表大部分在緩衝區中,那麼建立雜湊索引能夠加快等值查詢。

innodb 引擎的普通索引和主鍵索引有什麼區別?

區別:
索引是一種資料結構,是儲存了表資料的實體地址
聚集索引既儲存了表資料key又儲存了行值,實體地址的邏輯順序和表儲存的順序一致!是唯一的
非聚簇索引:存放了表資料的實體地址和key值,可根據key值對應的實體地址再查詢具體的行值,但是實體地址存放的順序和表存放的邏輯順序沒有強一致性!

根本區別:表記錄的排列順序和索引的排列順序是否一致

普通索引:這是最基本的索引型別,而且它沒有唯一性之類的限制。
主鍵索引:就是唯一 且不能為空
主鍵索引也被稱為聚簇索引,葉子節點存放的是整行資料; 而非主鍵索引被稱為二級索引,葉子節點存放的是主鍵的值.
如果根據主鍵查詢, 只需要搜尋ID這顆B+樹
而如果通過非主鍵索引查詢, 需要先搜尋k索引樹, 找到對應的主鍵, 然後再到ID索引樹搜尋一次, 這個過程叫做回表.
總結, 非主鍵索引的查詢需要多掃描一顆索引樹, 效率相對更低.

MySQL資料庫有幾個配置選項可以幫助我們及時捕獲低效SQL語句

1,slow_query_log
這個引數設定為ON,可以捕獲執行時間超過一定數值的SQL語句。
2,long_query_time
當SQL語句執行時間超過此數值時,就會被記錄到日誌中,建議設定為1或者更短。
3,slow_query_log_file
記錄日誌的檔名。

explain 語句

type
對錶訪問方式,表示MySQL在表中找到所需行的方式,又稱“訪問型別”。
常用的型別有: ALL、index、range、 ref、eq_ref、const、system、NULL(從左到右,效能從差到好)

ALL:Full Table Scan, MySQL將遍歷全表以找到匹配的行
index: Full Index Scan,index與ALL區別為index型別只遍歷索引樹
range:只檢索給定範圍的行,使用一個索引來選擇行
ref: 表示上述表的連線匹配條件,即哪些列或常量被用於查詢索引列上的值
eq_ref: 類似ref,區別就在使用的索引是唯一索引,對於每個索引鍵值,表中只有一條記錄匹配,簡單來說,就是多表連線中使用primary key或者 unique key作為關聯條件
const、system: 當MySQL對查詢某部分進行優化,並轉換為一個常量時,使用這些型別訪問。如將主鍵置於where列表中,MySQL就能將該查詢轉換為一個常量,system是const型別的特例,當查詢的表只有一行的情況下,使用system
NULL: MySQL在優化過程中分解語句,執行時甚至不用訪問表或索引,例如從一個索引列裡選取最小值可以通過單獨索引查詢完成。

Extra
該列包含MySQL解決查詢的詳細資訊,有以下幾種情況:
1、Using filesort:說明mysql會對資料適用一個外部的索引排序。而不是按照表內的索引順序進行讀取。MySQL中無法利用索引完成排序操作稱為“檔案排序”
2、Using temporary:使用了臨時表儲存中間結果,mysql在查詢結果排序時使用臨時表。常見於排序order by和分組查詢group by。
3、Using index:表示相應的select操作用使用覆蓋索引,避免訪問了表的資料行。如果同時出現using where,表名索引被用來執行索引鍵值的查詢;如果沒有同時出現using where,表名索引用來讀取資料而非執行查詢動作。
4、Using where :表明使用where過濾
5、using join buffer:使用了連線快取
6、impossible where:where子句的值總是false,不能用來獲取任何元組
7、select tables optimized away:在沒有group by子句的情況下,基於索引優化Min、max操作或者對於MyISAM儲存引擎優化count(*),不必等到執行階段再進行計算,查詢執行計劃生成的階段即完成優化。
8、distinct:優化distinct操作,在找到第一匹配的元組後即停止找同樣值的動作。

Key
顯示MySQL實際決定使用的鍵(索引)。如果沒有選擇索引,鍵是NULL。查詢中如果使用覆蓋索引,則該索引和查詢的select欄位重疊。

possible_keys
指出MySQL能使用哪個索引在該表中找到行

事務特性

原子性、一致性、隔離性、永續性

原子性(atomicity)

一個事務必須被視為一個不可分割的最小單元,整個事務中的所有操作要麼全部提交成功,要麼全部失敗,對於一個事務來說,不可能只執行其中的一部分操作

一致性(consistency)

一致性是指事務將資料庫從一種一致性轉換到另外一種一致性狀態,在事務開始之前和事務結束之後資料庫中資料的完整性沒有被破壞

永續性(durability)

一旦事務提交,則其所做的修改就會永久儲存到資料庫中。此時即使系統崩潰,已經提交的修改資料也不會丟失

隔離性(isolation)

一個事務的執行不能被其他事務干擾。即一個事務內部的操作及使用的資料對併發的其他事務是隔離的,併發執行的各個事務之間不能互相干擾。

四種隔離級別

Read Uncommitted(讀取未提交內容)

   在該隔離級別,所有事務都可以看到其他未提交事務的執行結果。本隔離級別很少用於實際應用,因為它的效能也不比其他級別好多少。讀取未提交的資料,也被稱之為髒讀(Dirty Read)。

Read Committed(讀取提交內容)

   這是大多數資料庫系統的預設隔離級別(但不是MySQL預設的)。它滿足了隔離的簡單定義:一個事務只能看見已經提交事務所做的改變。這種隔離級別 也支援所謂的不可重複讀(Nonrepeatable Read),因為同一事務的其他例項在該例項處理其間可能會有新的commit,所以同一select可能返回不同結果。

Repeatable Read(可重讀)

   這是MySQL的預設事務隔離級別,它確保同一事務的多個例項在併發讀取資料時,會看到同樣的資料行。不過理論上,這會導致另一個棘手的問題:幻讀 (Phantom Read)。簡單的說,幻讀指當使用者讀取某一範圍的資料行時,另一個事務又在該範圍內插入了新行,當使用者再讀取該範圍的資料行時,會發現有新的“幻影” 行。InnoDB和Falcon儲存引擎通過多版本併發控制(MVCC,Multiversion Concurrency Control)機制解決了該問題。

Serializable(可序列化)

   這是最高的隔離級別,它通過強制事務排序,使之不可能相互衝突,從而解決幻讀問題。簡言之,它是在每個讀的資料行上加上共享鎖。在這個級別,可能導致大量的超時現象和鎖競爭。

     這四種隔離級別採取不同的鎖型別來實現,若讀取的是同一個資料的話,就容易發生問題。例如:

髒讀(Drity Read):某個事務已更新一份資料,另一個事務在此時讀取了同一份資料,由於某些原因,前一個RollBack了操作,則後一個事務所讀取的資料就會是不正確的。(事務A讀取了事務B更新的資料,然後B回滾操作,那麼A讀取到的資料是髒資料)

不可重複讀(Non-repeatable read):在一個事務的兩次查詢之中資料不一致,這可能是兩次查詢過程中間插入了一個事務更新的原有的資料。(事務 A 多次讀取同一資料,事務 B 在事務A多次讀取的過程中,對資料作了更新並提交,導致事務A多次讀取同一資料時,結果 不一致。)

幻讀(Phantom Read):在一個事務的兩次查詢中資料筆數不一致,例如有一個事務查詢了幾列(Row)資料,而另一個事務卻在此時插入了新的幾列資料,先前的事務在接下來的查詢中,就會發現有幾列資料是它先前所沒有的。(系統管理員A將資料庫中所有學生的成績從具體分數改為ABCDE等級,但是系統管理員B就在這個時候插入了一條具體分數的記錄,當系統管理員A改結束後發現還有一條記錄沒有改過來,就好像發生了幻覺一樣,這就叫幻讀。)

在MySQL中,實現了這四種隔離級別,分別有可能產生問題如下所示:
Mysql 知識

Sql語句優化原則:

原則1:避免在列上進行運算 select * from t where YEAR(d) >= ‘2020’ 優化為 : select * from t where d >= ‘2020’
原則2:使用join時,應該用小結果集驅動大結果集
原則3:使用 like 模糊查詢時避免 %%
原則4:避免select *
原則5:使用批量插入語句節省互動 insert into t (id,name) values(1,’a’),(2,’b’),(3,’c’)
原則6:limit基數比較大的時候使用between(有缺陷,id斷行時,讀取的數量會小於預計數量)在取比較後面的資料時,通過desc方式把資料方向查詢,可以減少對前段資料的掃描,讓limit的基數越小越好
原則7:不要使用rand函式獲取多條隨機資料
原則8:避免使用null
原則9:使用count(*) 而不是count(id)
原則10:不要做無謂的排序操作,而應儘可能在索引中完成排序
Text型別 查詢會使用臨時表,導致嚴重的效能開銷,分開查

利用表的覆蓋索引來加速分頁查詢

我們都知道,利用了索引查詢的語句中如果只包含了那個索引列(覆蓋索引),那麼這種情況會查詢很快。
因為利用索引查詢有優化演算法,且資料就在查詢索引上面,不用再去找相關的資料地址了,這樣節省了很多時間。
另外Mysql中也有相關的索引快取,在併發高的時候利用快取就效果更好了。
在我們的例子中,我們知道id欄位是主鍵,自然就包含了預設的主鍵索引。現在讓我們看看利用覆蓋索引的查詢效果如何:
這次我們之間查詢最後一頁的資料(利用覆蓋索引,只包含id列),如下:
select id from product limit 866613, 20
查詢時間為0.2秒,相對於查詢了所有列的37.44秒,提升了大概100多倍的速度。
那麼如果我們也要查詢所有列,有兩種方法,
id>=的形式:
SELECT * FROM product
WHERE ID > =(select id from product limit 866613, 1) limit 20

mysql防注入

1、如果是整形變數或欄位,使用intval()函式把所有傳入的引數轉化為一個數值,比如翻頁,按id瀏覽文章
2、對於字元型變數,用addslashes()會把所有單引號(‘),雙引號(“),反斜槓()和空字元轉為含有反斜槓的溢位字元,或者使用pdo引數繫結來提高安全性
3、轉義或過濾一些特殊字元,如%等
4、保護表結構等關鍵資訊(對於開源程式此條不成立,這僅是無奈之舉,例如有人會故意把欄位名命名搞得古怪,這是不可取的,最可靠的還是在程式碼級別上把好關。)
5、任何情況下都要做好資料備份,以防萬一。

Mysql各種鎖的總結

1.共享鎖(又稱讀鎖)、排它鎖(又稱寫鎖):

InnoDB引擎的鎖機制:InnoDB支援事務,支援行鎖和表鎖用的比較多,Myisam不支援事務,只支援表鎖。
共享鎖(S):允許一個事務去讀一行,阻止其他事務獲得相同資料集的排他鎖。
排他鎖(X):允許獲得排他鎖的事務更新資料,阻止其他事務取得相同資料集的共享讀鎖和排他寫鎖。
意向共享鎖(IS):事務打算給資料行加行共享鎖,事務在給一個資料行加共享鎖前必須先取得該表的IS鎖。
意向排他鎖(IX):事務打算給資料行加行排他鎖,事務在給一個資料行加排他鎖前必須先取得該表的IX鎖。

說明:
1)共享鎖和排他鎖都是行鎖,意向鎖都是表鎖,應用中我們只會使用到共享鎖和排他鎖,意向鎖是mysql內部使用的,不需要使用者干預。

2)對於UPDATE、DELETE和INSERT語句,InnoDB會自動給涉及資料集加排他鎖(X);對於普通SELECT語句,InnoDB不會加任何鎖,事務可以通過以下語句顯示給記錄集加共享鎖或排他鎖。
共享鎖(S):SELECT * FROM table_name WHERE … LOCK IN SHARE MODE。
排他鎖(X):SELECT * FROM table_name WHERE … FOR UPDATE。

**對於鎖定行記錄後需要進行更新操作的應用,應該使用Select…For update 方式,獲取排它鎖。(用共享鎖,在讀了之後再寫會阻塞,會導致死鎖)

這裡說說Myisam:MyISAM在執行查詢語句(SELECT)前,會自動給涉及的所有表加讀鎖,在執行更新操作(UPDATE、DELETE、INSERT等)前,會自動給涉及的表加寫鎖。

3)InnoDB行鎖是通過給索引上的索引項加鎖來實現的,因此InnoDB這種行鎖實現特點意味著:只有通過索引條件檢索資料,InnoDB才使用行級鎖,否則,InnoDB將使用表鎖!

2.樂觀鎖、悲觀鎖:

悲觀鎖:悲觀鎖,正如其名,它指的是對資料被外界(包括本系統當前的其他事務,以及來自外部系統的事務處理)修改持保守態度,因此,在整個資料處理過程中,將資料處於鎖定狀態。悲觀鎖的實現,往往依靠資料庫提供的鎖機制(也只有資料庫層提供的鎖機制才能真正保證資料訪問的排他性,否則,即使在本系統中實現了加鎖機制,也無法保證外部系統不會修改資料)

1)使用悲觀鎖,我們必須關閉mysql資料庫的自動提交屬性,採用手動提交事務的方式,因為MySQL預設使用autocommit模式,也就是說,當你執行一個更新操作後,MySQL會立刻將結果進行提交。

2)需要注意的是,在事務中,只有SELECT … FOR UPDATE 或LOCK IN SHARE MODE 同一筆資料時會等待其它事務結束後才執行,一般SELECT … 則不受此影響。對於UPDATE、DELETE和INSERT語句,InnoDB會自動給涉及資料集加排他鎖(X)。

3)補充:MySQL select…for update的Row Lock與Table Lock
使用select…for update會把資料給鎖住,不過我們需要注意一些鎖的級別,MySQL InnoDB預設Row-Level Lock,所以只有「明確」地指定主鍵(或有索引的地方),MySQL 才會執行Row lock (只鎖住被選取的資料) ,否則MySQL 將會執行Table Lock (將整個資料表單給鎖住)。

樂觀鎖:
樂觀鎖( Optimistic Locking ) 相對悲觀鎖而言,樂觀鎖假設認為資料一般情況下不會造成衝突,所以在資料進行提交更新的時候,才會正式對資料的衝突與否進行檢測,如果發現衝突了,則讓返回使用者錯誤的資訊,讓使用者決定如何去做(一般是回滾事務)。那麼我們如何實現樂觀鎖呢,一般來說有以下2種方式:

1).使用資料版本(Version)記錄機制實現,這是樂觀鎖最常用的一種實現方式。何謂資料版本?即為資料增加一個版本標識,一般是通過為資料庫表增加一個數字型別的 “version” 欄位來實現。當讀取資料時,將version欄位的值一同讀出,資料每更新一次,對此version值加一。當我們提交更新的時候,判斷資料庫表對應記錄的當前版本資訊與第一次取出來的version值進行比對,如果資料庫表當前版本號與第一次取出來的version值相等,則予以更新,否則認為是過期資料。

2).樂觀鎖定的第二種實現方式和第一種差不多,同樣是在需要樂觀鎖控制的table中增加一個欄位,名稱無所謂,欄位型別使用時間戳(timestamp), 和上面的version類似,也是在更新提交的時候檢查當前資料庫中資料的時間戳和自己更新前取到的時間戳進行對比,如果一致則OK,否則就是版本衝突。

總結:兩種鎖各有優缺點,不可認為一種好於另一種,像樂觀鎖適用於寫比較少的情況下,即衝突真的很少發生的時候,這樣可以省去了鎖的開銷,加大了系統的整個吞吐量。但如果經常產生衝突,上層應用會不斷的進行retry,這樣反倒是降低了效能,所以這種情況下用悲觀鎖就比較合適。
另外,高併發情況下個人認為樂觀鎖要好於悲觀鎖,因為悲觀鎖的機制使得各個執行緒等待時間過長,極其影響效率,樂觀鎖可以在一定程度上提高併發度。

3.表鎖、行鎖

表級鎖(table-level locking):MyISAM和MEMORY儲存引擎
行級鎖(row-level locking) :InnoDB儲存引擎
頁面鎖(page-level-locking):BDB儲存引擎
表級鎖:開銷小,加鎖快;不會出現死鎖;鎖定粒度大,發生鎖衝突的概率最高,併發度最低。
行級鎖:開銷大,加鎖慢;會出現死鎖;鎖定粒度最小,發生鎖衝突的概率最低,併發度也最高。
頁面鎖:開銷和加鎖時間界於表鎖和行鎖之間;會出現死鎖;鎖定粒度界於表鎖和行鎖之間,併發度一般。

資料庫的主從複製和讀寫分離**:**

原理:

MySQL之間資料複製的基礎是二進位制日誌檔案(binary log file)。一臺MySQL資料庫一旦啟用二進位制日誌後,其作為master,它的資料庫中所有操作都會以“事件”的方式記錄在二進位制日誌中,其他資料庫作為slave通過一個I/O執行緒與主伺服器保持通訊,並監控master的二進位制日誌檔案的變化,如果發現master二進位制日誌檔案發生變化,則會把變化複製到自己的中繼日誌(relay log)中,然後slave的一個SQL執行緒會把相關的“事件”執行到自己的資料庫中,以此實現從資料庫和主資料庫的一致性,也就實現了主從複製。

主伺服器配置

1. 開啟二進位制日誌
2. 配置唯一的server-id
3. 獲得master二進位制日誌檔名及位置 show master status;
4. 建立一個用於slave和master通訊的使用者賬號
實現:
1. 找到資料卷對映的mysqld.cnf檔案(例如:/var/lib/docker/volumes/mysql_mysql-conf/_data/mysql.conf.d)
2. 修改配置
3. 連線mysql,建立使用者並賦予許可權
如(GRANT REPLICATION SLAVE ON . TO ‘wzh‘@’%’ IDENTIFIED BY ‘123456’)

從伺服器配置:

1. 配置唯一的server-id
2. 使用master分配的使用者賬號讀取master二進位制日誌
3. 啟用slave服務
實現:
slave庫CHANGE MASTER TO MASTER_HOST=’192.168.79.130’,MASTER_USER=’wzh’,MASTER_PASSWORD=’123456’,MASTER_LOG_FILE=’mysql-bin.000003’,MASTER_LOG_POS=1329;
start slave;
show slave status;
兩者都為yes即可成功

注意事項**:**

1. docker-compose複製過來修改配置後需要重啟 docker-compose restart
2. 複製從庫後,需要修改mysql的uuid,find -name auto.cnf找到位置後修改,使主從不一致即可
3. 預設主從複製所有庫,可以配置指定讀寫庫
4. 如果主從表結構不同,修改主表後,slave-sql-running會變為No,需要stop slave後,修改表結構,重新建立連線啟動。
因此,為了保持同步, 可以給從庫新增一個只讀許可權的使用者,從庫不寫只讀,實現讀寫分離。

Mysql分表

分表是分散資料庫壓力的好方法。
分表,最直白的意思,就是將一個表結構分為多個表,然後,可以再同一個庫裡,也可以放到不同的庫。
當然,首先要知道什麼情況下,才需要分表。個人覺得單表記錄條數達到百萬到千萬級別時就要使用分表了。

1,分表的分類

1>縱向分表
將本來可以在同一個表的內容,人為劃分為多個表。(所謂的本來,是指按照關係型資料庫的第三正規化要求,是應該在同一個表的。)
分表理由:根據資料的活躍度進行分離,(因為不同活躍的資料,處理方式是不同的)
案例
對於一個部落格系統,文章標題,作者,分類,建立時間等,是變化頻率慢,查詢次數多,而且最好有很好的實時性的資料,我們把它叫做冷資料。而部落格的瀏覽量,回覆數等,類似的統計資訊,或者別的變化頻率比較高的資料,我們把它叫做活躍資料。所以,在進行資料庫結構設計的時候,就應該考慮分表,首先是縱向分表的處理。

這樣縱向分表後:
首先儲存引擎的使用不同,冷資料使用MyIsam 可以有更好的查詢資料。活躍資料,可以使用Innodb ,可以有更好的更新速度。
其次,對冷資料進行更多的從庫配置,因為更多的操作時查詢,這樣來加快查詢速度。對熱資料,可以相對有更多的主庫的橫向分表處理。
其實,對於一些特殊的活躍資料,也可以考慮使用memcache ,redis
之類的快取,等累計到一定量再去更新資料庫。或者mongodb 一類的nosql 資料庫,這裡只是舉例,就先不說這個。

2>橫向分表
字面意思,就可以看出來,是把大的表結構,橫向切割為同樣結構的不同表,如,使用者資訊表,user_1,user_2 等。表結構是完全一樣,但是,根據某些特定的規則來劃分的表,如根據使用者ID來取模劃分。
分表理由:根據資料量的規模來劃分,保證單表的容量不會太大,從而來保證單表的查詢等處理能力。
案例:同上面的例子,部落格系統。當部落格的量達到很大時候,就應該採取橫向分割來降低每個單表的壓力,來提升效能。例如部落格的冷資料表,假如分為100個表,當同時有100萬個使用者在瀏覽時,如果是單表的話,會進行100萬次請求,而現在分表後,就可能是每個表進行1萬個資料的請求(因為,不可能絕對的平均,只是假設),這樣壓力就降低了很多很多。

本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章