MySQL不權威總結

MySQL不權威總結

歡迎閱讀

本文并非事無巨細的mysql學習資料,而是選擇其中重要、困難、易錯的部分進行系統地總結梳理,適合有一定mysql使用經驗的同學。為了避免枯燥和一葉障目,每一塊知識只進行較為抽象的總結,涉及到具體的技術細節會附上相關資料供需要時再具體研究

架構

mysql整體架構分為兩層,服務層和存儲引擎,服務層負責數據庫實例的基本功能(如多線程的連接管理、權限管理、配置管理)和部分粗粒度的查詢操作(如查詢緩存、SQL解析與優化、部分查詢結果的過濾與排序)。存儲引擎負責數據存儲和讀取,索引、事務、鎖大部分功能基本都實現在引擎層

mysql架構.png

查詢緩存
mysql的查詢緩存比較雞肋,如果兩個查詢在任何字符上有所不同(哪怕是注釋、大小寫),或者包含任何函數、變量,或者相關表發生變化,都會導致緩存失效。官方并不推薦使用查詢緩存,并會在后續版本中移除該功能

存儲引擎

本質上來說存儲引擎的功能就是維護文件的讀寫,由于提供了標準API,mysql服務層可以方便切換不同的存儲引擎,以適應不同場景

myisam與InnoDB比較
mysql有多種存儲引擎,常用的是InnoDB和MyISAM,最新版本默認存儲引擎為InnoDB,關于兩者的區別主要是

  • 鎖,InnoDB支持行鎖、表鎖,myisam支持表鎖
  • 事務,InnoDB支持事務,myisam不支持
  • 索引
    • InnoDB支持外鍵,myisam不支持
    • InnoDB數據與索引組織在一塊(聚簇索引),myisam數據與索引分離,且可以壓縮,內存中可以加載更多索引
    • myisam支持空間索引、全文索引,InnoDB則需要5.6以上版本
    • myisam允許表中不設置主鍵,InnoDB如果未顯式設置主鍵則會自動選擇非null唯一索引作為主鍵,無則自動創建不可見的6字節rowid作為主鍵
  • 大小,myisam整體文件要小一些
  • 安全,InnoDB崩潰后可以自動修復數據
  • 讀寫性能,myisam寫性能高于InnoDB,但高并發情況下差別不大;兩者讀性能無明顯差異

存儲引擎選擇
經過長期優化,InnoDB的讀性能并不弱于myisam,全文索引、空間索引也主鍵支持,而且在安全性、維護性方面也更加優秀。除非是特殊場景,如內存表、分布式、數據歸檔等可以針對性選擇適應的存儲引擎,大部分時候使用InnoDB就可以了

表結構

整數

schema byte bit min ~ max min ~ max unsigned
tinyint 1 8 -128~127 0~255
smallint 2 16 -32768~32767 0~65535
mediumint 3 24 -8388608~8388607 0~16777215
int 4 32 -2147483648~2147483647 0~4294967295
bigint 8 64 -92...(19位)~92...(19位) 0~18...(20位)

整數的表示范圍為-2^(N-1) ~ 2^(N-1)-1,如果為無符號整數(unsigned)則0 ~ 2^(N)-1

int(1) int(11)區別
不會影響存儲空間和存儲范圍, 只是規定了mysql的一些交互工具用來顯示字符的個數

字符串

  • char 定長字符串
  • varchar 變長字符串
  • text 長文本

字節長度
char(8) varchar(8)都表示8個字符, 跟編碼無關。varchar實際占用字節計算方式為:varchar長度 * 編碼字節 + 長度記錄 + null標志(如果允許null的話),varchar中記錄長度的字節在小于255為1, 大于255為2。而char則會自動補空格或者截斷

varchar最大字節為65535,即當采用latin編碼時最多存65533個字符, utf8時則為2萬多。char上限為255字節,text同樣為65535,其實當varchar長度大于500時,系統會隱式轉換為text

text同族tinytext為256Byte,mediumtext為16MB,longtext為4GB

字符集
不同的字符集表示的字符數量和占用字節都不同。比如常用的utf8可以表示所有字符,占用大小為14字節,其中常用的為13字節,坑爹的是mysql中utf8是utf8mb3的別名,即1~3字節,utf8mb4才是1~4字節,如表情

字符比較規則 collation
utf8_general_ci是一種通用的比較規則,_ci表示不區分大小寫

時間

mysql中提供了多種時間類型, year date time datetime timestamp等

datetime
表示范圍1001年到9999年

timestamp
表示范圍 1970年1月1日(格林尼治標準時間)至 2038年
mysql提供了from_unixtime()把時間戳轉換日期, 和unix_timestamp()轉換日期為時間戳
可以設置插入或更新數據時的行為
默認情況下盡量使用timestamp, 如果需要處理毫秒級數據可以使用bigint進行hack

枚舉

enum(枚舉)和set(集合)數據類型并不常用,雖然在恰當的場景可以提高性能,但由于數據調整時需要進行DDL成本較高,而且在排序時容易產生疑惑,所以一般使用char進行替代

SQL

mysql最核心的作用就是提供SQL交互,即增刪改查,本小節主要總結mysql的SQL用法,性能優化問題會單獨總結,因為我覺得先會跑,再求快。而且很多SQL的用法本身并沒問題,所謂的“優化”只是mysql目前還不夠聰明導致的,可能在后續迭代中自然而然的消失,所以我覺得SQL使用和優化最后還是分開好一些

count

  1. count(col) 統計某個列值不為null的行數
  2. count(express) 統計表達式值不為null的行數
  3. count(*) 統計查詢結果集行數

曾經有一些廣為流傳的“神話”,比如count(col)這樣指定具體某一列的性能要好于count(*)、myisam的count非常快

其實count(*)會直接明確的統計行數,指定一個列反而需要會慢一些(可能需要檢測null等)。而myisam只記錄了全表的行數,所以count只要包含查詢條件,速度與其他存儲引擎差別并不大

distinct

distinct的作用是對結果集去重,只能用在列首,作用于所有列,也可以與count一起使用(count(disctinc col))

select disctinct col1, col2 where id > 123

group by

group by的作用是對結果集進行聚合,經常與聚合函數count min max sum avg group_concat、聚合結果過濾having一起使用

SQL的執行順序
理解group先要理解SQL的執行順序,from > where > group(含聚合)> having > order > limit > select,即先確定查詢的表,再篩選結果集,再執行聚合,再having過濾聚合結果,最后order排序。明白這個,幾個易錯地方就很好理解了

  • 聚合函數min max等只是作用于分組內
  • 由于where執行在聚合函數前,所以where條件不能包含諸如count(*)>1,having、order則可以
  • order的排序并不影響聚合,而是對聚合結果的排序,所以想通過使用order影響聚合首行行不通,聚合的首行其實是物理存儲的首行
  • select最后才執行,所以想通過min、max來影響聚合首行也是行不通的

group_concat可以獲取聚合中的某一列所有結果的拼接,個別場景非常好用

union

union的作用是將多個查詢的結果合并,要求查詢的列名、列數要一致。默認會進行去重,導致filesort,除非明確需要, 否則最好使用Union All

另一個易錯的地方是使用union時limit并不是針對union后的結果集,而是單個查詢

join

表連接查詢是常用操作,簡單說就是兩個表求笛卡爾積,然后按where條件過濾(當然,真實情況mysql不會真的先求笛卡爾積,而是邊連接邊過濾,盡可能少的掃描數據)。連接分為內連接和外連接,使用關鍵字on來限定連接條件

內連接
內連接指當連接生成的行不滿足連接條件時,過濾該行,所以內連接中on與where的效果一樣,以下是內連接語法

  • select * from t1, t2
  • select * from t1 inner join t2
  • select * from t1 cross join t2

外連接
外連接指當連接生成的行不滿足連接條件時,只保留驅動表,被連接表的字段為null。分為左外連接和右外連接

  • select * from t1 left join t2
  • select * from t1 right join t2

優化
連接查詢可以依次連接多個表,最多61個,不過建議不要超過12個,而且最好使用小表驅動大表,連接列最好有索引

索引

索引的目的為加速查詢,用好mysql的一個難點就是針對業務場景設計高效的索引。索引類型有很多種哈希索引、BTree索引、全文索引、空間索引,本小節直接以最常用的BTree索引為例說明

索引數據結構

B+Tree

索引B+Tree.png

如圖所示,多數數據庫索引使用的數據結構是B+Tree

  • 葉子節點存儲著key和data,以及下一個葉子節點
  • 非葉子節點分為多層幫助key逐級定位

這是一個非常抽象的示意圖,不過已經可以揭示索引本質,幫助理解許多索引的使用和優化了。但如果要更深入的理解mysql查詢執行原理和優化,就需要大致了解索引的真實物理結構,如下圖所示

索引B+Tree物理結構.png
  • 數據是維護在以頁為單位的數據結構中
  • 頁分為多種,這里能看到存儲節點的目錄項記錄頁,和存儲葉子節點的用戶記錄頁
  • 每個頁中數據是一個有序單鏈表,頁間是一個有序的雙鏈表
  • 頁中記錄了每個頁中節點的最大、最小值,方便檢索

那么一次真實索引查詢的大概過程就是

  1. 在目錄項記錄頁中依次檢查每個頁的最大值和最小值,判斷數據是否在該頁中,若不在則鏈表找下一頁
  2. 逐層定位到數據所在的用戶記錄頁
  3. 定位到用戶記錄頁后,遍歷頁中單鏈表,根據主鍵找到數據節點

為什么不使用二叉樹、紅黑樹、B-Tree

  • 樹的高度決定查詢需要的隨機IO次數
  • 二叉樹出度為2,會導致樹的高度過大,增加順序IO次數,降低性能;而且二叉樹容易出現不平衡,導致平均查詢性能下降
  • 紅黑樹雖然平衡,但出度依舊為2
  • B-Tree是data保存在內部節點,這就導致頁存儲的內部節點數量減少,單頁能夠維護的出度變小,樹高增加

為什么以頁為基本單位?
這主要是目前計算機硬盤物理特性決定的,系統每次加載數據最少要讀取一個頁,而且隨機IO的性能要比順序IO差兩個數量級,所以mysql巧妙的以頁為單位維護數據,盡可能提高IO性能

索引的優點

  • 由于目錄項頁不存實際數據,所以記錄數量非常大,一般情況樹的深度不會超過4,也就是一次查詢最多需要4次隨機IO
  • 對于范圍查詢,只需要確定上下邊界,順序IO即可
  • 因為索引中數據是有序的,所以order、group都可以利用索引,指數級降低耗時

索引相關概念

聚簇索引
即數據和索引存儲在一起,InnoDB的數據表本身就是一個聚簇索引,所以它也只能有一個聚簇索引。優點是找到索引就找到了數據,不需要回表

缺點是

  • 假如能夠將所有數據放入內存中, 則聚簇索引便無意義了, 因其提供的I/O優勢不存在
  • 插入速度嚴重依賴插入順序,無序插入的話會頻繁導致裂頁操作
  • 更新代價高,可能導致數據在頁中交換
  • 由此導致產生的二級索引會比想象的大, 因為其必須包含主鍵索引
  • 二級索引查找需要兩次索引查找, 因為二級索引中保存的是主鍵而不是數據

主鍵索引、一級索引、二級索引
主鍵索引也叫一級索引,在InnoDB中就是聚簇索引,二級索引指非主鍵索引,他們同樣也是B+Tree數據結構,不同的是葉子節點中存的是主鍵key,而不是實際數據,定位數據后需要多一次回表即到聚簇索引中查詢

回表需要隨機IO,性能較差,如果二級索引需要回表的量過大的話,可能導致mysql認為全表掃描成本更低

前綴索引
索引列為字符串時,可以設置索引列的長度,用來平衡索引選擇性和索引大小。比如加入索引一個uuid,其實只需要設置字符串的前6位就能獲取較好的選擇性,沒必要把全部列納入索引

前綴索引雖然可以減小索引長度,放入更多索引至內存。但會導致不能使用索引進行order by和group by,無法使用覆蓋索引

前綴索引長度的選擇需要考慮索引選擇性, 選擇性很差還不如不使用。試探索引選擇性:

mysql> SELECT
mysql> count(distinct left(city,3))/count(*) as sel3,
mysql> count(distinct left(city,4))/count(*) as sel4,
mysql> count(distinct left(city,5))/count(*) as sel5
mysql> FROM table;

覆蓋索引
覆蓋索引指索引列包含了查找的全部列,包括where條件和select的列

聯合索引
聯合索引就是多列索引

三星索引

  1. 索引將相關的記錄放到一起則獲得一星
  2. 索引中的定義順序和查找中的排序順序一致則獲得二星
  3. 索引中列包含了查詢中需要的全部列則獲得三星

MyISAM索引比較

myisam中索引與數據是分開存儲的,即主鍵索引、二級索引葉子節點中存放的都是實際數據地址,而不是實際數據

myisam索引.png

事務、鎖

事務基本概念

ACID
事務要滿足ACID四個特性

  • 原子性 (atomicity)
  • 一致性 (consistency),事務結束后數據庫依然保持約束的一致性,如唯一索引不能重復
  • 隔離性 (isolation),事務之間要相互隔離
  • 持久性 (durability),事務執行結束后就永久保存,不會丟失

保存點
對于一個長事務,可以不斷設置保存點,防止回滾導致成本太大。由于mysql不支持事務嵌套,一般也通過保存點來模擬事務嵌套

語法

BEGIN / START TRANSACTION
update...
insert...

SAVEPOINT save_fin_1
update...

ROLLBACK TO save_fin_1 # 回退

update...
SAVEPOINT save_fin_2
RELEASE SAVEPOINT save_fin_2 # 刪除

COMMIT / ROLLBACK

自動提交
本來不想寫這段,但看到各種事務相關的文章里都人云亦云的加一個所謂的關閉自動提交,就忍不住提一下。autocommit=ON的意思是每一條語句都是一個獨立事務,包括增刪改查,顯式開啟事務后自動關閉

隱式提交
mysql中一些操作會導致當前事務隱式提交

  • 事務中再開啟事務
  • 執行DDL
  • 加載數據,如LOAD DATA
  • 主從操作

redo日志
要實現事務的持久性,則要在事務提交之前,現將數據寫入硬盤。mysql為了提高IO性能,數據庫的寫操作會先寫入內存中的數據頁,再異步刷新到硬盤。這樣每當事務提交,相關的頁就需要刷新到硬盤,寫性能壓力會非常大,redo日志便是為了解決該問題

其實不需要把整個頁刷新,只需要把該頁的位級別diff刷新到硬盤即可,這就是redo日志。redo日志很小,大約幾mb,一直循環覆蓋順序寫入,每當事務提交就刷新內存中的redo臟頁到硬盤的redo日志,每當表的臟頁刷新到硬盤中時,redo中對應的事務部分就標記為可以覆蓋

undo日志
undo日志與redo日志類似,不過是為了實現事務的回滾,每當事務產生一條更新,undo日志就會記錄一個對應的撤銷sql

鎖是實現并發寫,和事務隔離的重要機制

鎖分類

行鎖、表鎖
鎖按照鎖粒度可以分為表級鎖和行級鎖(還有個頁級鎖),表鎖實現在mysql服務層,與存儲引擎無關;行鎖實現在存儲引擎層,不同存儲引擎的支持、實現都有所不同。粒度越小開銷越大,并發性越好,出現死鎖的概率也越大

橫向按照排他性可劃分為讀鎖(共享鎖)和寫鎖(排他鎖),寫鎖的優先級更高,在鎖等待隊列中可以插隊

行鎖實現
行鎖按照實現算法,又可以分為

  • record lock 記錄鎖
  • gap lock 間隙鎖
  • next-key lock 記錄鎖+間隙鎖

簡而言之,記錄鎖只會鎖住記錄本身;間隙鎖則鎖住兩個記錄的間隙,唯一作用就是防止幻讀(mvcc也可解決幻讀);next-key lock則是鎖住記錄本身以及其前緊挨的間隙。為了覆蓋所有間隙,mysql有兩個特殊的隱藏記錄分別為最大和最小,用于實現表中最大值之后與最小值之前的間隙

意向鎖
這個知道就好,一般用不到。意向鎖屬于表鎖,作用是為了兼容行鎖與表鎖,試想一千萬行記錄中有一個行寫鎖,這時候要加表鎖難道要先遍歷檢查每行是否有寫鎖?不是的,在加行鎖的時候就加了對應的表級意向寫/讀鎖,這樣就可以快速判斷是否可以加表鎖

另外間隙鎖實現阻塞插入是通過插入意向鎖,insert之前要先獲取間隙的插入意向鎖,而其與間隙鎖互斥。插入意向鎖之間并不互斥

悲觀鎖、樂觀鎖
悲觀鎖、樂觀鎖屬于加鎖策略,悲觀鎖思想為寫沖突會有很多,所以要對操作對象加排他鎖;樂觀鎖則認為寫沖突并不多,所以通過更新條件來實現軟性的寫鎖,比如常用update...where version=3,并不加寫鎖,更新失敗則知道出現沖突。需要根據實際業務場景來確定到底使用哪種策略才可以最大化系統并發

加鎖

顯式加鎖
可以顯式的加表鎖和行鎖(讀鎖與寫鎖),其中行鎖必須在一個事務中,而且行鎖的具體算法根據存儲引擎、事務隔離級別自動選擇

lock table tab_name read|write
flush tables with read|write lock # 全局表加鎖
unlock tables

begin
select * from table where ... for update
select * from table where ... lock in share mode
commit / rollback

隱式加鎖
DDL語句都會導致表鎖;任何的delete、update都會自動添加寫鎖;可串行隔離級別的事務中會自動加讀鎖

鎖升級
由于InnoDB的行鎖是實現在索引上的,假如行鎖沒有索引,或者索引選擇性差導致加鎖過多時,有可能會導致行鎖升級為表鎖。其實是mysql認為使用表鎖cost更小,對使用者透明,不過出現這種現象往往意味著sql設計存在缺陷,需要優化

死鎖

死鎖指兩個事務互相等待對方釋放所需資源的鎖, 導致事務阻塞。解決辦法一般有

  • 回滾其中一個事務(可能只需回滾到上個savepoint,而非全部回滾)
  • 對資源加鎖的按照相同順序
  • 悲觀鎖策略, 事先對可能用到的資源全部加鎖

事務隔離級別

由于事務可以回滾,如果不進行適當隔離,回滾的數據就有可能造成錯誤。由于隔離主要通過鎖實現,隔離越嚴格則并發性就越差,但出問題的概率就越小,我們需要根據實際業務場景設置mysql的隔離級別。標準的隔離級別分為4層,mysql全部支持,默認為可重復讀

  1. 未提交讀,read uncommitted。允許事務讀到其他事務未提交的修改
  2. 已提交讀,read committed。只允許事務讀取其他事務已提交的修改
  3. 可重復讀,repeatable read。保證事務相同的讀取得到的結果是一致的
  4. 可串行化,serializable。事務串行執行,雖然部分讀還可以并行,但并發性降至最低

未提交讀相當于基本沒有隔離,讀到其他事務未提交的修改叫做臟讀,如果發生回滾則極易出錯。通過MVCC和加讀鎖,已提交讀可以避免臟讀,但由于該隔離級別允許讀到已提交的修改,所以會出現相同的查詢,前后讀取結果不同的情況,叫做不可重復讀(其中由于insert導致讀取結果變多的特殊情況叫做幻讀)。可重復讀級別利用MVCC實現了可重復讀,而且在標準允許出現幻讀的情況下,超前在該級別解決了幻讀。最高級別的可串行化通過默認為所有讀加讀鎖,來解決之前所說的問題,但依然存在事務先后提交而導致的邏輯錯誤的可能

MVCC
多版本并發控制。mysql為每一條記錄維護了一個鏈表,記錄了當前活躍事務對其進行的修改歷史鏈表。嗯,知道這么多就能夠理解許多問題了

非鎖定一致性讀
update、delete會默認加寫鎖,而select除非明確加鎖,或者可串行隔離級別,默認是無鎖讀的。實現隔離級別并非完全依賴鎖,利用MVCC便可以實現非鎖定一致性讀,如RC級別通過讀取MVCC中的最新已提交版本實現已提交讀,RR級別通過管理先后關系控制事務讀取版本從而實現可重復讀。由于不需要加鎖,mysql的并發性得到保障

可重復讀
該隔離級別在SQL標準中是允許出現幻讀的,但mysql通過MVCC實現了可重復讀,同時解決了幻讀。但又不是徹底的解決了幻讀,select式的幻讀解決了,但insert導致的幻讀卻未被解決,如唯一索引插入相同數據,明明select沒有該值,但卻出現插入阻塞。這時需要明確加讀行鎖來解決,這主要是因為RR級別下,行鎖的默認算法為next-key,可以鎖定間隙

有時候RR級別也需要讀已提交,此時可以通過加讀鎖來明確讀取記錄最新版本

優化

優化的目的是減少查詢時間,這個查詢時間其實包括了查詢請求發送、數據庫查詢、結果返回,通常情況主要時間消耗在數據庫查詢環節。所以優化的大原則是

  1. 最好不需要查詢,即通過緩存或產品改進,避免數據庫查詢
  2. 查詢執行的越快越好,涉及的方面比較多
    1. 列盡量少、結構盡量簡單,內存加載的更多、掃描更快
    2. 查詢盡可能利用索引,減少掃描的行數、避免排序、避免回表
    3. 結果集盡量精簡行數和列
    4. 避免大事務、大批量、大SQL

優化的主要思路是結合業務實際需要,分析慢查詢日志,對慢sql進行分類,再用explain分析,最后通過調整sql、表設計解決慢查詢問題

庫表優化

表設計原則

  • 平衡范式和冗余,適當的冗余和違反范式可以降低sql復雜度
  • 單表列數最好小于20,行數控制在千萬級別
  • 盡量把大列分拆到子表中,需要時連接查詢
  • 更小的類型通常更好,滿足需求的情況下盡量使用最小數據類型,不過也要考慮未來擴展維護
  • 盡量使用簡單的數據類型,如能用date就別用varchar、能用varchar就別用text
  • 盡量使用not null定義列

varchar越小越好
varchar(6) varchar(200)用于存儲 hello 時的耗費是一致的, 那么使用短列有優勢嗎?
事實證明是有很大優勢的,因為開辟內存時是以200字節進行的,遇到需要filesort或tmp table作業可能會帶來不利影響

InnoDB主鍵類型選擇
優先使用合適大小的自增整數類型

  • 合適大小可以減小空間, 加快計算速度
  • 自增由于是順序插入,可以盡可能的減少裂頁,插入速度更快。假如使用md5為主鍵,由于散列均勻,會大量裂頁
  • 由于二級索引會包含主鍵,所以主鍵越小越好

enum
雖然enum更簡單更小,但由于維護成本較高,大多數情況都可以使用char來代替

alter table
mysql執行大部分DDL的方法都是創建一個新表,導入數據后,刪除舊表,非常耗時、耗內存,要非常慎重

索引設計

索引設計原則

  • 盡量使用選擇性高的列
  • 索引并非越多越好,盡量擴展索引,而非新增,使用聯合索引來減少索引數量

查詢優化

查詢優化原則

  • sql盡量精簡,拒絕大事務、大批量、大SQL,復雜的sql需求要進行拆分
  • 避免mysql承擔過多計算任務

查詢成本估算
一個查詢可能有多種執行方案,mysql會計算每個方案的cost(成本)然后選擇成本最低的。因為實時性要求高,這個cost只是估算,比如加載一個頁耗時1.0,讀取并檢測一條記錄耗時0.2,再根據mysql的統計數據與執行計劃,便能大概估算個cost。所以有時候即便有索引可用,mysql也可能認為全表掃描綜合成本更低

當然有時候這個估算也會出錯,此時需要明確指定執行計劃,如force index

不能使用索引的場景

  • 不是按照索引的最左列開始查找
  • 不能跳過索引中的列
  • 如果查詢中的某個列是范圍查詢, 則其右邊的所有列都無法使用索引,比如like between
  • 索引列上使用函數, 或者算數運算
  • 少用or
  • 列類型是字符串的話,查詢時一定要給值加引號,否則索引失效
  • 表連接時列字符集不同

select
盡量少使用select *,不僅是因為對索引不友好,還因為查出不需要的數據會侵占寶貴的緩存資源,導致緩存頻繁變化,命中率下降

where

  1. 直接在做索引中使用where條件來過濾,存儲引擎層便可以完成,最為高效
  2. 假如無法使用索引篩選數據,如`col like '%a',但若col列在索引中,則可以通過全量掃描索引過濾數據
  3. 使用了索引中沒有的列為過濾條件,則需要把數據讀出到服務層進行掃描過濾

order
避免使用order by rand()
盡量使用升序

limit
limit一個常見問題是大表的分頁,由于limit在服務層進行,所以要把結果集全部查出來,再掃描獲取所需結果,性能慢在大量的回表,所以解決思路有

  1. 保證查詢為覆蓋索引,從而避免回表
  2. 假如無法滿足覆蓋索引,則先使用某列如主鍵通過覆蓋索引確定結果集起始點,再通過范圍查詢獲取結果

延伸

join
小表驅動大表

explain

優化之前先要確定sql的執行計劃,explain中常用字段的含義為

  • id 包括子查詢在內每個查詢的唯一id
  • type 針對單表的訪問方法
  • possible_keys 可能用到的索引
  • key 實際使用的索引
  • key_len 實際使用的索引長度
  • ref 表連接方式
  • rows 預估的掃描行數
  • extra 額外補充信息

type
單表的訪問方法,表明mysql如何從單表獲取數據

  • system 表中只有一行數據或者是空表,只能用于myisam和memory表,Innodb引擎是all或者index
  • const 基于主鍵或者非null唯一索引掃描, 結果最多返回一條數據
  • eq_ref 表連接時,基于主鍵或者唯一索引掃描
  • ref 普通索引掃描
  • fulltext 全文檢索
  • index_merge 可以利用index merge特性用到多個索引,提高查詢效率
  • range 索引范圍掃描
  • index 全索引掃描,即可以通過索引掃描完成數據讀取和過濾,避免回表
  • All 全表掃描

extra

  • Using index 覆蓋索引,不需要回表
  • Using index condition 使用了索引進行結果過濾
  • Using where 使用了where進行結果過濾,也即在服務層完成
  • Using filesort 在服務層進行了排序操作,盡量避免
  • Using temporary 創建了臨時表

key_len
這個要特別注意,能幫助我們了解聯合索引中有多少個列被實際使用到。列長度大概計算方法見1.4小節

延伸

  • 其他調試工具mysqldumpslow、show profile

運維管理

啟動關閉

# mysql安裝目錄bin中有許多實用工具
# mysqld,可啟動mysql服務器進程,但并不常用
mysqld

# mysqld_safe,是一個腳本,調用了mysqld,但同時會啟動一個監控進程,守護mysql進程
mysqld_safe

# mysql.server,也是一個腳本,調用了mysqld_safe,但可以添加參數進行管理。注意bin中的mysql.server默認為鏈接文件,源在support-files/mysql.server
mysql.server stop
mysql.server start

# 如何開啟/關閉mysql服務
mysqladmin shutdown -uroot -p***

# 如何登陸mysql數據庫
mysql -u username -p

配置管理

# 查看配置文件位置
mysql --help | grep cnf

mysqld --verbose --help | grep -A 1 'Default options'
# Default options are read from the following files in the given order:
# /etc/mysql/my.cfn ~/.my.cnf /usr/etc/my.cnf

默認配置
mysql可配置性太強也可以說是一個弱點, 其實大多數配置的默認值已經是最佳配置了, 所以最好不要改動太多。以下創建了一個完整的最小示例配置文件, 可以作為一個良好的起點, 不要以自帶配置文件作為起點。此配置文件可能比你見過的其他配置文件太少了, 但實際上已經超過了許多人的需要, 請確保基本了解這些配置的意義

[mysqld]
# General
datadir                 = /var/lib/mysql
socket                  = /var/lib/mysql/mysql.sock
pid_file                = /var/lib/mysql/mysql.pid
user                    = mysql
port                    = 3306
default_storage_engine  = InnoDB

# InnoDB
innodb_buffer_pool_size = <value>
innodb_log_file_size    = <value>
innodb_file_per_table   = 1
innodb_flush_method     = 0_DIRECT

# myisam
key_bufffer_size        = <value>

# Logging
log_error               = /var/lib/mysql/mysql-error.log
slow_query_log          = /var/lib/mysql/mysql-slow.log

# Other
tmp_table_size          = 32M
max_heap_table_size     = 32M
query_cache_type        = 0
query_cache_size        = 0
max_connections         = <value>
thread_cache            = <value>
table_cache             = <value>
thread_cache            = <value>
open_files_limit        = 65535

[client]
socket                  = /var/lib/mysql/mysql.sock
port                    = 3306

用戶管理

# 查看系統用戶
select * from mysql.user;

# 創建 & 刪除用戶
create user 'USERNAME'@'HOSTNAME' identified by 'PASSWORD';
drop user 'USERNAME'@'HOSTNAME';

# 修改密碼
set password for 'USERNAME'@'HOSTNAME' = password('NEW_PASSWD');
set password = password('NEW_PASSWD'); # 修改當前用戶密碼

權限管理

# 模式: grant 權限 on 數據庫對象 to 用戶
# 權限包括:
# select delete update insert 增刪改查, select(col1, col2) 限制到列
# create drop alter 表管理權限
# 注意:用戶需要重新登錄才能權限生效
# FLUSH PRIVILEGES;

# 查看權限
show grants; # 查看當前用戶
show grants for 'USERNAME'@'HOSTNAME'; # 查看指定用戶

# 授予全部權限
grant all on DB.TABLE to 'USERNAME'@'HOSTNAME';

# 允許用戶再授權別人
gran select on DB.TABLE to 'USERNAME'@'HOSTNAME' with grant option;

# 回收權限
# 注意這里有個坑:revoke的權限要和grant的時候一樣! e.g.
# grant all on *.* to user@'%'; 則必須
# revoke all on *.* from user@'%';
# 所以需求如:100個表,其中97個授予delete權限,3個不給,不能簡單的 grant delete on db.* .. 然后再revoke
revoke 權限 on 數據庫對象 from 用戶

查看表信息

# 顯示表格創建sql
show create table table_name;

# 簡要列出表格的字段信息
describe table_name;

# 查看表大小
# 進入information_schema 數據庫(存放了其他的數據庫的信息)
use information_schema;

# 查詢所有數據的大小
SELECT concat(round(sum(DATA_LENGTH/1024/1024),2),'MB') as data FROM information_schema.TABLES;

# 查看數據庫所有表大小
SELECT
    table_name,
    round(sum(DATA_LENGTH / 1024 / 1024) , 2) AS 'mb'
FROM
    information_schema. TABLES
WHERE
    table_schema = 'lbsugc_ifix_before_2017_06_01'
group by table_name
order by mb DESC;

# 查看指定數據庫的大小:比如查看數據庫home的大小
SELECT concat(round(sum(DATA_LENGTH/1024/1024),2),'MB') as data FROM information_schema.TABLES WHERE table_schema='home';

# 查看指定數據庫的某個表的大小,比如查看數據庫home中 members 表的大小
SELECT concat(round(sum(DATA_LENGTH/1024/1024),2),'MB') as data FROM information_schema.TABLES WHERE table_schema='home' AND table_name='members';

查詢管理

# 查看所有連接
# mysql的查詢可能處于的狀態包括
# Sleep 線程正在等待客戶端發送新的請求
# Query 線程正在執行查詢或者正在將結果發送給客戶端
# Locked mysql服務器層, 該線程正在等待表鎖(_存儲引擎實現的鎖并不會在此反映如行鎖_). 對于myisam來說這是一個比較典型的狀態, 但在其他沒有行鎖的引擎中也經常出現
# Analyzing and statistics 線程正在收集存儲引擎的統計信息, 并生成查詢的執行計劃
# Copying to tmp table [on disk] 線程正在執行查詢, 并且將結果集都復制到一個臨時表中, 這種狀態一般要么是在做group by操作, 要么是在file sort, 或者union;on disk標記表示mysql正在將一個內存臨時表放到磁盤上
# Sorting result 線程正在對結果集進行排序
# Sending data 多種情況: 可能是線程在多個狀態之間傳送數據;或者生成結果;或者向客戶端返回數據
show full processlist

# 關閉所有連接
for i in $(mysql -uroot -p537ce49da46a -Bse "show processlist" | awk '{print $1}');do mysql -uroot -p537ce49da46a -e "kill $i";done

事務

# 隔離級別設置
# 查看隔離級別
select @@tx_isolation
# 設置讀未提交級別
set transaction isolation level read uncommitted
# 設置讀提交級別
set transaction isolation level read committed
# 設置可重復讀(缺省),保證每次讀的結果是一樣的
set transaction isolation level repeatable read
# 設置成序列化
set transaction isolation level serializable

# 查看鎖競爭統計
show status like 'innodb_row_lock%';
+-------------------------------+--------+
| Variable_name                | Value  |
+-------------------------------+--------+
| Innodb_row_lock_current_waits | 0      |
| Innodb_row_lock_time          | 150751 |
| Innodb_row_lock_time_avg      | 21535  |
| Innodb_row_lock_time_max      | 51158  |
| Innodb_row_lock_waits        | 7      |
+-------------------------------+--------+

導入、導出

# 導出
mysqldump -h$DB_HOST -Pprot -u$DB_USER -pPwd --skip-lock-tables -q \
DB table1 table2 > $DATA_PATH/itest.sql

# -w 添加搜索條件

mysqldump -h$DB_HOST -Pport -u$DB_USER -ppwd --skip-lock-tables -q \
-w "id < 1000 and createTime>unix_timestamp('20170101')" \
DB table1 table2 > $DATA_PATH/itest.sql

# -d 只導出表結構
# -q 不緩沖查詢,直接導出至stdout。(默認打開,用--skip-quick來關閉)該選項用于轉儲大的表
# -t 只導出數據
# -c 附帶列名
# --lock-all-tables 全局加讀鎖
# --lock-tables 當前表加讀鎖
# --add-locks 導入, sql文件時鎖表
# --skip-lock-tables 導出, 時不加讀鎖
# --ignore-table=db_name.t2 排除表
mysqldump -h$DB_HOST -Pport -u$DB_USER -pPwd --skip-lock-tables -q -d DB table > $DATA_PATH/itest.sql

導入

# 導入 或者登陸mysql,使用source命令
mysql -h... -P3306 -ulbsugc -p... lbsugc_hard < taskCenter.sql

跨表導入數據

INSERT INTO SELECT
-- 形式為:Insert into Table2(field1,field2,...) select value1,value2,... from Table1
-- 要求目標表Table2必須存在,由于目標表Table2已經存在,所以我們除了插入源表Table1的字段外,
-- 還可以插入常量

SELECT INTO FROM
-- 語句形式為:SELECT vale1, value2 into Table2 from Table1
-- 要求目標表Table2不存在,因為在插入時會自動創建表Table2,
-- 并將Table1中指定字段數據復制到Table2中

binlog

# 查看binlog狀態
show variables like '%log_bin%'; # log_bin ON 開啟

# 開啟binlog,編輯my.cnf,重啟mysql
server-id=1 # 隨機設定的節點id
log_bin=/home/lbsugc/.jumbo/var/lib/mysql/mysql-bin # binlog保存路徑
expire_logs_days=7 # binlog保留日期 0表示永不過期

# 查看binlog文件
show binary logs; # 查看當前服務器使用的全部binlog,每次重啟服務器或者binlog文件過大,則分割新的
show master status; # 當前mysql實例正在使用的binlog文件
show binlog events in “mysql-bin.000005”; # 查看binlog內容

# 使用mysqlbinlog工具
mysqlbinlog mysql-bin.000005; # 查看binlog內容
mysqlbinlog -d lbsugc_rocket mysql-bin.000005 -r out.sql # -d 指定db -r 指定輸出文件
mysqlbinlog mysql-bin.000005 --start-position=100 --stop-position=100 # 精確指定輸出事件位置
mysqlbinlog mysql-bin.000005 --start-datetime='2018-05-18 12:00'--stop-datetime='2018-05-18 14:00'# 指定輸出事件起始時間

# 使用binlog恢復數據
# 原理很簡單,mysqlbinlog輸出的是sql文件,當做普通的.sql輸入數據庫即可
# 比如在99號事件誤刪了數據庫,則可這樣恢復
mysqlbinlog mysql-bin.000005 --stop-position=98 | mysql -uroot -p*** DB
mysqlbinlog mysql-bin.000005 --start-position=100 --end-position=目標位置 | mysql -uroot -p*** DB

常用命令

# 直接執行sql
mysql -h10.57.27.37 -P5020 -uliu******* -pFb9F39SrpS lbsugc -e ""

# 漢字拼音排序
select poiName
from userProblem
order by convert(left(poiName,1) using gbk) desc;

# 交換一個表中兩列值
update table a,table b set a.column1 = b.column2, a.column2=b.column1 where a.id=b.id;

集群

經典問題

mysql server has gone away 錯誤原因

  • mysql宕機
  • client連接時間過長
  • 查詢sql過大被斷開
  • 多進程場景,錯誤復用了連接,導致socket混淆

學習資料

  • 掘金平臺付費《MySQL是怎樣運行的:從根兒上理解MySQL》
  • 《MySQL技術內幕 InnoDB存儲引擎》
  • 《高性能MySQL》
  • MySQL學習筆記
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,967評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,273評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,870評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,742評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,527評論 6 407
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,010評論 1 322
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,108評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,250評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,769評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,656評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,853評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,371評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,103評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,472評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,717評論 1 281
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,487評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,815評論 2 372

推薦閱讀更多精彩內容