『壹』 sql執行順序
查詢語句中select from where group by having order by的執行順序
1.查詢中用到的關鍵詞主要包含六個,並且他們的順序依次為
select--from--where--group by--having--order by
其中select和from是必須的,其他關鍵詞是可選的,這六個關鍵詞的執行順序
與sql語句的書寫順序並不是一樣的,而是按照下面的順序來執行
from--where--group by--having--select--order by,
from:需要從哪個數據表檢索數據
where:過濾表中數據的條件
group by:如何將上面過濾出的數據分組
having:對上面已經分組的數據進行過濾的條件
select:查看結果集中的哪個列,或列的計算結果
order by :按照什麼樣的順序來查看返回的數據
2.from後面的表關聯,是自右向左解析的
而where條件的解析順序是自下而上的。
也就是說,在寫SQL文的時候,盡量把數據量大的表放在最右邊來進行關聯,
而把能篩選出大量數據的條件放在where語句的最下面。
SQL Select語句完整的 執行順序 【從DBMS使用者角度】:
1、from子句組裝來自不同數據源的數據;
2、where子句基於指定的條件對記錄行進行篩選;
3、group by子句將數據劃分為多個分組;
4、使用聚集函數進行計算;
5、使用having子句篩選分組;
6、計算所有的表達式;
7、使用order by對結果集進行排序 。
from 子句--執行順序為從後往前、從右到左
表名(最後面的那個表名為驅動表,執行順序為從後往前, 所以數據量較少的表盡量放後)
oracle 的解析器按照從右到左的順序處理,FROM 子句中的表名,FROM 子句中寫在最後的表(基礎表 driving
table)將被最先處理,即最後的表為驅動表,在FROM 子句中包含多個表的情況下,你必須選擇記錄條數最少的表作為基礎表。如果有3
個以上的表連接查詢, 那就需要選擇交叉表(intersection table)作為基礎表, 交叉表是指被其他表所引用的表
多表連接時,使用表的別名並把別名前綴於每個Column上。可以減少解析的時間並減少那些由Column 歧義引起的語法錯誤.
where子句--執行順序為自下而上、從右到左
ORACLE 採用自下而上從右到左的順序解析Where 子句,根據這個原理,表之間的連接必須寫在其他Where 條件之前, 可以過濾掉最大數量記錄的條件必須寫在Where 子句的末尾。
group by--執行順序從左往右分組
提高GROUP BY 語句的效率, 可以通過將不需要的記錄在GROUP BY 之前過濾掉。即在GROUP BY前使用WHERE來過慮,而盡量避免GROUP BY後再HAVING過濾。
having 子句----很耗資源,盡量少用
避免使用HAVING 子句, HAVING 只會在檢索出所有記錄之後才對結果集進行過濾. 這個處理需要排序,總計等操作.
如果能通過Where 子句在GROUP BY前限制記錄的數目,那就能減少這方面的開銷.
(非oracle 中)on、where、having 這三個都可以加條件的子句中,on 是最先執行,where 次之,having 最後,因為on 是先把不符合條件的記錄過濾後才進行統計,它就可以減少中間運算要處理的數據,按理說應該速度是最快的,
where 也應該比having 快點的,因為它過濾數據後才進行sum,在兩個表聯接時才用on 的,所以在一個表的時候,就剩下where 跟having比較了。
在這單表查詢統計的情況下,如果要過濾的條件沒有涉及到要計算欄位,那它們的結果是一樣的,只是where 可以使用rushmore 技術,而having 就不能,在速度上後者要慢。
如果要涉及到計算的欄位,就表示在沒計算之前,這個欄位的值是不確定的,where 的作用時間是在計算之前就完成的,而having 就是在計算後才起作用的,所以在這種情況下,兩者的結果會不同。
在多表聯接查詢時,on 比where 更早起作用。系統首先根據各個表之間的聯接條件,把多個表合成一個臨時表後,再由where 進行過濾,然後再計算,計算完後再由having 進行過濾。
由此可見,要想過濾條件起到正確的作用,首先要明白這個條件應該在什麼時候起作用,然後再決定放在那裡。
select子句--少用*號,盡量取欄位名稱 。
ORACLE 在解析的過程中, 會將依次轉換成所有的列名, 這個工作是通過查詢數據字典完成的, 使用列名意味著將減少消耗時間。
sql 語句用大寫的;因為 oracle 總是先解析 sql 語句,把小寫的字母轉換成大寫的再執行
order by子句--執行順序為從左到右排序,很耗資源
『貳』 Sql語句解析過程
為了將用戶寫的SQL文本轉化為Oracle認識的且可執行的語句 這個過程就叫做解析過程 解析分為硬解析和軟解析 一條SQL語句在第一次被執行時必須進行硬解析
當客戶端發出一條SQL語句(也可以是一個存儲過程或者一個匿名PL/SQL塊)進入shared pool時(注意 我們從前面已經知道 Oracle對這些SQL不叫做SQL語句 而是稱為游標 因為Oracle在處理SQL時 需要很多相關的輔助信息 這些輔助信息與SQL語句一起組成了游標) Oracle首先將SQL文本轉化為ASCII值 然後根據hash函數計算其對應的hash值(hash_value) 根據計算出的hash值到library cache中找到對應的bucket 然後比較bucket里是否存在該SQL語句
如果不存在 則需要按照我們前面所描述的 獲得shared pool latch 然後在shared pool中的可用chunk鏈表(也就是bucket)上找到一個可用的chunk 之後釋放shared pool latch 在獲得了chunk以後 這塊chunk就可以認為是進入了library cache 接下來 進行硬解析過程 硬解析包括以下幾個步驟
對SQL語句進行文法檢查 看是否有文法錯誤 比如沒有寫from select拼寫錯誤等 如果存在文法錯誤 則退出解析過程
到數據字典里校驗SQL語句涉及的對象和列是否都存在 如果不存在 則退出解析過程 這個過程會載入dictionary cache
將對象進行名稱轉換 比如將同名詞翻譯成實際的對象等 比如select * from t中 t是一個同名詞 指向hr t 於是Oracle將t轉換為hr t 如果轉換失敗 則退出解析過程
檢查發出SQL語句的用戶是否具有訪問SQL語句里所引用的對象的許可權 如果沒有許可權 則退出解析過程
通過優化器創建一個最優的執行計劃 這個過程會根據數據字典里記錄的對象的統計信息 來計算最優的執行計劃 這一步牽涉大量數學運算 是最消耗CPU資源的
將該游標所產生的執行計劃 SQL文本等裝載進library cache的heap中
在硬解析的過程中 進程會一直持有library cache latch 直到硬解析結束為止 硬解析結束以後 會為SQL語句產生兩個游標 一個是父游標 另一個是子游標 父游標里主要包含兩種信息 SQL文本以及優化目標(optimizer goal) 父游標在第一次打開時被鎖定 直到其他所有的session都關閉該游標後才被解鎖 當父游標被鎖定的時候是不能被交換出library cache的 只有在解鎖以後才能被交換出library cache 父游標被交換出內存時 父游標對應的所有子游標也被交換出library cache 子游標包括游標所有的信息 比如具體的執行計劃 綁定變數等 子游標隨時可以被交換出library cache 當子游標被交換出library cache時 Oracle可以利用父游標的信息重新構建出一個子游標來 這個過程叫reload 可以使用下面的方式來確定reload的比率
select *sum(reloads)/sum(pins) Reload_Ratio from v$librarycache;
一個父游標可以對應多個子游標 子游標具體的個數可以從視圖v$sqlarea的version_count欄位體現出來 而每個具體的子游標則全都在視圖v$sql里體現 當具體綁定變數的值與上次綁定變數的值有較大差異(比如上次執行的綁定變數值的長度是 位 而這次執行綁定變數的值的長度是 位)時或者當SQL語句完全相同 但是所引用的表屬於不同的用戶時 都會創建一個新的子游標
如果在bucket中找到了該SQL語句 則說明該SQL語句以前運行過 於是進行軟解析 軟解析是相對於硬解析而言的 如果解析過程中 可以從硬解析的步驟中去掉一個或多個的話 這樣的解析就是軟解析 軟解析分為以下三種類型
第一種是某個session發出的SQL語句與library? cache里其他session發出的SQL語句一致 這時 該解析過程中可以去掉硬解析中的 和 但是仍然要進行硬解析過程中的 也就是表名和列名檢查 名稱轉換和許可權檢查
* 第二種是某個session發出的SQL語句是該session之前發出的曾經執行過的SQL語句 這時 該解析過程中可以去掉硬解析中的 和 這四步 但是仍然要進行許可權檢查 因為可能通過grant改變了該session用戶的許可權
* 第三種是當設置了初始化參數session_cached_cursors時 當某個session第三次執行相同的SQL時 則會把該SQL語句的游標信息轉移到該session的PGA里 這樣 該session以後再執行相同的SQL語句時 會直接從PGA里取出執行計劃 從而跳過硬解析的所有步驟 這種情況下 是最高效的解析方式 但是會消耗很大的內存
我們舉一個例子來說明解析SQL語句的過程 在該測試中 綁定變數名稱相同 但是變數類型不同時 所出現的解析情況 如下所示
首先 執行下面的命令 清空shared pool里所有的SQL語句
SQL> alter system flush shared_pool;
然後 定義一個數值型綁定變數 並為該綁定變數賦一個數值型的值以後 執行具體的查詢語句
SQL> variable v_obj_id number;
SQL> exec :v_obj_id := ;
SQL> select object_id object_name from sharedpool_test
where object_id=:v_obj_id;
OBJECT_ID OBJECT_NAME
AGGXMLIMP
接下來 定義一個字元型的綁定變數 變數名與前面相同 為該綁定變數賦一個字元型的值以後 執行相同的查詢
SQL> variable v_obj_id varchar ( );
SQL> exec :v_obj_id := ;
SQL> select object_id object_name from sharedpool_test
where object_id=:v_obj_id;
OBJECT_ID OBJECT_NAME
AGGXMLIMP
然後我們到視圖v$sqlarea里找到該SQL的父游標的信息 並到視圖v$sql里找該SQL的所有子游標的信息
SQL> select sql_text version_count from v$sqlarea where
sql_text like %sharedpool_test% ;
SQL_TEXT
VERSION_COUNT
select object_id object_name from sharedpool_test where
object_id=:v_obj_id
SQL> select sql_text child_address address from v$sql
where sql_text like %sharedpool_test% ;
SQL_TEXT
CHILD_ADDRESS ADDRESS
select object_id object_name from sharedpool_test where
object_id=:v_obj_id F
B D
select object_id object_name from sharedpool_test where
object_id=:v_obj_id FC
B D
從記錄父游標的視圖v$sqlarea的version_count列可以看到 該SQL語句有 個子游標 而從記錄子游標的視圖v$sql里可以看到 該SQL文本確實有兩條記錄 而且它們的SQL文本所處的地址(ADDRESS列)也是一樣的 但是子地址(CHILD_ADDRESS)卻不一樣 這里的子地址實際就是子游標所對應的heap 的句柄
lishixin/Article/program/Oracle/201311/18653
『叄』 使用SQL語句創建存儲過程
使用SQL語句創建存儲的具體過程如下:
1、首先,打開企業管理器,選擇【工具】-【查詢分析器】:
『肆』 SQL執行與優化
SQL優化
執行計劃,表關聯查詢順序,優化策略與思路
下面再向前走一些,容我根據自己的認識說一下查詢執行的流程是怎樣的:
1.連接
1.1客戶端發起一條Query請求,監聽客戶端的『連接管理模塊』接收請求
1.2將請求轉發到『連接進/線程模塊』
1.3調用『用戶模塊』來進行授權檢查
1.4通過檢查後,『連接進/線程模塊』從『線程連接池』中取出空閑的被緩存的連接線程和客戶端請求對接,如果失敗則創建一個新的連接請求
2.處理
2.1先查詢緩存,檢查Query語句是否完全匹配,接著再檢查是否具有許可權,都成功則直接取數據返回
2.2上一步有失敗則轉交給『命令解析器』,經過詞法分析,語法分析後生成解析樹
2.3接下來是預處理階段,處理解析器無法解決的語義,檢查許可權等,生成新的解析樹
2.4再轉交給對應的模塊處理
2.5如果是SELECT查詢還會經由『查詢優化器』做大量的優化,生成執行計劃
2.6模塊收到請求後,通過『訪問控制模塊』檢查所連接的用戶是否有訪問目標表和目標欄位的許可權
2.7有則調用『表管理模塊』,先是查看table cache中是否存在,有則直接對應的表和獲取鎖,否則重新打開表文件
2.8根據表的meta數據,獲取表的存儲引擎類型等信息,通過介面調用對應的存儲引擎處理
2.9上述過程中產生數據變化的時候,若打開日誌功能,則會記錄到相應二進制日誌文件中
3.結果
3.1Query請求完成後,將結果集返回給『連接進/線程模塊』
3.2返回的也可以是相應的狀態標識,如成功或失敗等
3.3『連接進/線程模塊』進行後續的清理工作,並繼續等待請求或斷開與客戶端的連接
接下來再走一步,讓我們看看一條SQL語句的前世今生。
首先看一下示例語句
示例語句
執行順序
SQL解析
1. FROM
當涉及多個表的時候,左邊表的輸出會作為右邊表的輸入,之後會生成一個虛擬表VT1。
(1-J1)笛卡爾積
計算兩個相關聯表的笛卡爾積(CROSS JOIN) ,生成虛擬表VT1-J1。
兩次全表掃描
哈希索引,查找復雜度都是 O(1) 。
2. WHERE
對VT1過程中生成的臨時表進行過濾,滿足WHERE子句的列被插入到VT2表中。
注意:
此時因為分組,不能使用聚合運算;也不能使用SELECT中創建的別名;
與ON的區別:
如果有外部列,ON針對過濾的是關聯表,主表(保留表)會返回所有的列;
如果沒有添加外部列,兩者的效果是一樣的;
應用:
對主表的過濾應該放在WHERE;
對於關聯表,先條件查詢後連接則用ON,先連接後條件查詢則用WHERE;
hash join 哈希連接 驅動表和被驅動表都只會訪問0次或1次
應用場景:一個大表一個小表/表上沒有索引/返回結果集比較大
3. GROUP BY
這個子句會把VT2中生成的表按照GROUP BY中的列進行分組。生成VT3表。
注意:
其後處理過程的語句,如SELECT,HAVING,所用到的列必須包含在GROUP BY中,對於沒有出現的,得用聚合函數;
原因:
GROUP BY改變了對表的引用,將其轉換為新的引用方式,能夠對其進行下一級邏輯操作的列會減少;
原作者的理解是:
根據分組欄位,將具有相同分組欄位的記錄歸並成一條記錄,因為每一個分組只能返回一條記錄,除非是被過濾掉了,而不在分組欄位裡面的欄位可能會有多個值,多個值是無法放進一條記錄的,所以必須通過聚合函數將這些具有多值的列轉換成單值;
GROUP BY 重新聚合查詢
4. HAVING
這個子句對VT3表中的不同的組進行過濾,只作用於分組後的數據,滿足HAVING條件的子句被加入到VT4表中。
7.LIMIT
LIMIT子句從上一步得到的VT6虛擬表中選出從指定位置開始的指定行數據。
注意:
offset和rows的正負帶來的影響;
當偏移量很大時效率是很低的,可以這么做:
採用子查詢的方式優化,在子查詢里先從索引獲取到最大id,然後倒序排,再取N行結果集
採用INNER JOIN優化,JOIN子句里也優先從索引獲取ID列表,然後直接關聯查詢獲得最終結果
當前未用到索引,
三次full scan , table1 AS a / table2 AS b / GROUP BY
盡量少做重復的工作
控制同一語句的多次執/減少多次的數據轉換/
杜絕不必要的子查詢和連接表,子查詢在執行計劃一般解釋成外連接,多餘的連接表帶來額外的開銷
關於臨時表和表變數的選擇
臨時表產生使用SELECT INTO和CREATE TABLE + INSERT INTO的選擇,一般情況下,SELECT INTO會比CREATE TABLE + INSERT INTO的方法快很多,但是SELECT INTO會鎖定TEMPDB的系統表SYSOBJECTS、SYSINDEXES、SYSCOLUMNS,在多用戶並發環境下,容易阻塞其他進程,所以建議,在並發系統中,盡量使用CREATE TABLE + INSERT INTO,而大數據量的單個語句使用中,使用SELECT INTO。
子查詢的用法
相關子查詢可以用IN、NOT IN、EXISTS、NOT EXISTS引入
NOT IN、NOT EXISTS的相關子查詢可以改用LEFT JOIN代替寫法
如果保證子查詢沒有重復 ,IN、EXISTS的相關子查詢可以用INNER JOIN 代替
IN``的相關子查詢用EXISTS代替
不要用 COUNT (*)的子查詢判斷是否存在記錄,最好用 LEFT` `JOIN 或者EXISTS,比如有人寫這樣的語句:
建立索引後,並不是每個查詢都會使用索引,在使用索引的情況下,索引的使用效率也會有很大的差別。只要我們在查詢語句中沒有強制指定索引,
不要對索引欄位進行運算,而要想辦法做變換
不要對索引欄位進行格式轉換
不要對索引欄位使用函數
不要對索引欄位進行多欄位連接
join關聯查詢的計算是很復雜的,特別是數據量比較大的情況下,實際情況還是拆解較快的
Join拆解的核心就是利用In關鍵字
要麼用空間換時間,要麼用時間換空間
多表連接的連接條件對索引的選擇有著重要的意義,所以我們在寫連接條件條件的時候需要特別注意。
A、多表連接的時候,連接條件必須寫全,寧可重復,不要缺漏。
B、連接條件盡量使用聚集索引
C、注意ON、WHERE和HAVING部分條件的區別
ON是最先執行, WHERE次之,HAVING最後,因為ON是先把不符合條件的記錄過濾後才進行統計,它就可以減少中間運算要處理的數據,按理說應該速度是最快的,WHERE也應該比 HAVING快點的,因為它過濾數據後才進行SUM,在兩個表聯接時才用ON的,所以在一個表的時候,就剩下WHERE跟HAVING比較了
考慮聯接優先順序:
(1)INNER JOIN
(2)LEFT JOIN (註:RIGHT JOIN 用 LEFT JOIN 替代)
(3)CROSS JOIN
索引並不適用於所有情況:a.少量數據;b.頻繁進行改動的欄位,不適合做索引;c.很少使用的欄位,不需要加索引
索引會提高數據查詢效率,但是會降低「增、刪、改」的效率。當不使用索引的時候,我們進行數據的增刪改,只需要操作源表即可,但是當我們添加索引後,不僅需要修改源表,也需要再次修改索引,很麻煩。
先執行順序, 是否走索引, 有無類型轉換
18000 字的SQL優化大全
步步深入:MySQL架構總覽->查詢執行流程->SQL解析順序
MySQL索引總結(4)——btree與hash區別
『伍』 mysql 核心內容-上
1、SQL語句執行流程
MySQL大體上可分為Server層和存儲引擎層兩部分。
Server層:
連接器:TCP握手後伺服器來驗證登陸用戶身份,A用戶創建連接後,管理員對A用戶許可權修改了也不會影響到已經創建的鏈接許可權,必須重新登陸。
查詢緩存:查詢後的結果存儲位置,MySQL8.0版本以後已經取消,因為查詢緩存失效太頻繁,得不償失。
分析器:根據語法規則,判斷你輸入的這個SQL語句是否滿足MySQL語法。
優化器:多種執行策略可實現目標,系統自動選擇最優進行執行。
執行器:判斷是否有許可權,將最終任務提交到存儲引擎。
存儲引擎層
負責數據的存儲和提取。其架構模式是插件式的,支持InnoDB、MyISAM、Memory等多個存儲引擎。現在最常用的存儲引擎是InnoDB,它從MySQL 5.5.5版本開始成為了默認存儲引擎(經常用的也是這個)。
SQL執行順序
2、BinLog、RedoLog、UndoLog
BinLog
BinLog是記錄所有資料庫表結構變更(例如create、alter table)以及表數據修改(insert、update、delete)的二進制日誌,主從資料庫同步用到的都是BinLog文件。BinLog日誌文件有三種模式。
STATEMENT 模式
內容:binlog 記錄可能引起數據變更的 sql 語句
優勢:該模式下,因為沒有記錄實際的數據,所以日誌量很少 IO 都消耗很低,性能是最優的
劣勢:但有些操作並不是確定的,比如 uuid() 函數會隨機產生唯一標識,當依賴 binlog 回放時,該操作生成的數據與原數據必然是不同的,此時可能造成無法預料的後果。
ROW 模式
內容:在該模式下,binlog 會記錄每次操作的源數據與修改後的目標數據,StreamSets就要求該模式。
優勢:可以絕對精準的還原,從而保證了數據的安全與可靠,並且復制和數據恢復過程可以是並發進行的
劣勢:缺點在於 binlog 體積會非常大,同時,對於修改記錄多、欄位長度大的操作來說,記錄時性能消耗會很嚴重。閱讀的時候也需要特殊指令來進行讀取數據。
MIXED 模式
內容:是對上述STATEMENT 跟 ROW 兩種模式的混合使用。
細節:對於絕大部分操作,都是使用 STATEMENT 來進行 binlog 沒有記錄,只有以下操作使用 ROW 來實現:表的存儲引擎為 NDB,使用了uuid() 等不確定函數,使用了 insert delay 語句,使用了臨時表
主從同步流程:
1、主節點必須啟用二進制日誌,記錄任何修改了資料庫數據的事件。
2、從節點開啟一個線程(I/O Thread)把自己扮演成 mysql 的客戶端,通過 mysql 協議,請求主節點的二進制日誌文件中的事件 。
3、主節點啟動一個線程(mp Thread),檢查自己二進制日誌中的事件,跟對方請求的位置對比,如果不帶請求位置參數,則主節點就會從第一個日誌文件中的第一個事件一個一個發送給從節點。
4、從節點接收到主節點發送過來的數據把它放置到中繼日誌(Relay log)文件中。並記錄該次請求到主節點的具體哪一個二進制日誌文件內部的哪一個位置(主節點中的二進制文件會有多個)。
5、從節點啟動另外一個線程(sql Thread ),把 Relay log 中的事件讀取出來,並在本地再執行一次。
mysql默認的復制方式是非同步的,並且復制的時候是有並行復制能力的。主庫把日誌發送給從庫後不管了,這樣會產生一個問題就是假設主庫掛了,從庫處理失敗了,這時候從庫升為主庫後,日誌就丟失了。由此產生兩個概念。
全同步復制
主庫寫入binlog後強制同步日誌到從庫,所有的從庫都執行完成後才返回給客戶端,但是很顯然這個方式的話性能會受到嚴重影響。
半同步復制
半同步復制的邏輯是這樣,從庫寫入日誌成功後返回ACK確認給主庫,主庫收到至少一個從庫的確認就認為寫操作完成。
還可以延伸到由於主從配置不一樣、主庫大事務、從庫壓力過大、網路震盪等造成主備延遲,如何避免這個問題?主備切換的時候用可靠性優先原則還是可用性優先原則?如何判斷主庫Crash了?互為主備的情況下如何避免主備循環復制?被刪庫跑路了如何正確恢復?( o )… 感覺越來越扯到DBA的活兒上去了。
RedoLog
可以先通過下面demo理解:
飯點記賬可以把賬單寫在賬本上也可以寫在粉板上。有人賒賬或者還賬的話,一般有兩種做法:
1、直接把賬本翻出來,把這次賒的賬加上去或者扣除掉。
2、先在粉板上記下這次的賬,等打烊以後再把賬本翻出來核算。
生意忙時選後者,因為前者太麻煩了。得在密密麻麻的記錄中找到這個人的賒賬總額信息,找到之後再拿出算盤計算,最後再將結果寫回到賬本上。
同樣在MySQL中如果每一次的更新操作都需要寫進磁碟,然後磁碟也要找到對應的那條記錄,然後再更新,整個過程IO成本、查找成本都很高。而粉板和賬本配合的整個過程就是MySQL用到的是Write-Ahead Logging 技術,它的關鍵點就是先寫日誌,再寫磁碟。此時賬本 = BinLog,粉板 = RedoLog。
1、 記錄更新時,InnoDB引擎就會先把記錄寫到RedoLog(粉板)裡面,並更新內存。同時,InnoDB引擎會在空閑時將這個操作記錄更新到磁碟裡面。
2、 如果更新太多RedoLog處理不了的時候,需先將RedoLog部分數據寫到磁碟,然後擦除RedoLog部分數據。RedoLog類似轉盤。
RedoLog有write pos 跟checkpoint
write pos :是當前記錄的位置,一邊寫一邊後移,寫到第3號文件末尾後就回到0號文件開頭。
check point:是當前要擦除的位置,也是往後推移並且循環的,擦除記錄前要把記錄更新到數據文件。
write pos和check point之間的是粉板上還空著的部分,可以用來記錄新的操作。如果write pos追上checkpoint,表示粉板滿了,這時候不能再執行新的更新,得停下來先擦掉一些記錄,把checkpoint推進一下。
有了redo log,InnoDB就可以保證即使資料庫發生異常重啟,之前提交的記錄都不會丟失,這個能力稱為crash-safe。 redolog兩階段提交:為了讓binlog跟redolog兩份日誌之間的邏輯一致。提交流程大致如下:
1 prepare階段 --> 2 寫binlog --> 3 commit
當在2之前崩潰時,重啟恢復後發現沒有commit,回滾。備份恢復:沒有binlog 。一致
當在3之前崩潰時,重啟恢復發現雖沒有commit,但滿足prepare和binlog完整,所以重啟後會自動commit。備份:有binlog. 一致
binlog跟redolog區別:
redo log是InnoDB引擎特有的;binlog是MySQL的Server層實現的,所有引擎都可以使用。
redo log是物理日誌,記錄的是在某個數據頁上做了什麼修改;binlog是邏輯日誌,記錄的是這個語句的原始邏輯,比如給ID=2這一行的c欄位加1。
redo log是循環寫的,空間固定會用完;binlog是可以追加寫入的。追加寫是指binlog文件寫到一定大小後會切換到下一個,並不會覆蓋以前的日誌。
UndoLog
UndoLog 一般是邏輯日誌,主要分為兩種:
insert undo log
代表事務在insert新記錄時產生的undo log, 只在事務回滾時需要,並且在事務提交後可以被立即丟棄
update undo log
事務在進行update或delete時產生的undo log; 不僅在事務回滾時需要,在快照讀時也需要;所以不能隨便刪除,只有在快速讀或事務回滾不涉及該日誌時,對應的日誌才會被purge線程統一清除
3、MySQL中的索引
索引的常見模型有哈希表、有序數組和搜索樹。
哈希表:一種以KV存儲數據的結構,只適合等值查詢,不適合范圍查詢。
有序數組:只適用於靜態存儲引擎,涉及到插入的時候比較麻煩。可以參考Java中的ArrayList。
搜索樹:按照數據結構中的二叉樹來存儲數據,不過此時是N叉樹(B+樹)。廣泛應用在存儲引擎層中。
B+樹比B樹優勢在於:
B+ 樹非葉子節點存儲的只是索引,可以存儲的更多。B+樹比B樹更加矮胖,IO次數更少。
B+ 樹葉子節點前後管理,更加方便范圍查詢。同時結果都在葉子節點,查詢效率穩定。
B+樹中更有利於對數據掃描,可以避免B樹的回溯掃描。
索引的優點:
1、唯一索引可以保證每一行數據的唯一性
2、提高查詢速度
3、加速表與表的連接
4、顯著的減少查詢中分組和排序的時間
5、通過使用索引,可以在查詢的過程中,使用優化隱藏器,提高系統的性能。
索引的缺點:
1、創建跟維護都需要耗時
2、創建索引時,需要對表加鎖,在鎖表的同時,可能會影響到其他的數據操作
3、 索引需要磁碟的空間進行存儲,磁碟佔用也很快。
4、當對表中的數據進行CRUD的時,也會觸發索引的維護,而維護索引需要時間,可能會降低數據操作性能
索引設計的原則不應該:
1、索引不是越多越好。索引太多,維護索引需要時間跟空間。
2、 頻繁更新的數據,不宜建索引。
3、數據量小的表沒必要建立索引。
應該:
1、重復率小的列建議生成索引。因為重復數據少,索引樹查詢更有效率,等價基數越大越好。
2、數據具有唯一性,建議生成唯一性索引。在資料庫的層面,保證數據正確性
3、頻繁group by、order by的列建議生成索引。可以大幅提高分組和排序效率
4、經常用於查詢條件的欄位建議生成索引。通過索引查詢,速度更快
索引失效的場景
1、模糊搜索:左模糊或全模糊都會導致索引失效,比如'%a'和'%a%'。但是右模糊是可以利用索引的,比如'a%' 。
2、隱式類型轉換:比如select * from t where name = xxx , name是字元串類型,但是沒有加引號,所以是由MySQL隱式轉換的,所以會讓索引失效 3、當語句中帶有or的時候:比如select * from t where name=『sw』 or age=14
4、不符合聯合索引的最左前綴匹配:(A,B,C)的聯合索引,你只where了C或B或只有B,C
關於索引的知識點:
主鍵索引:主鍵索引的葉子節點存的是整行數據信息。在InnoDB里,主鍵索引也被稱為聚簇索引(clustered index)。主鍵自增是無法保證完全自增的哦,遇到唯一鍵沖突、事務回滾等都可能導致不連續。
唯一索引:以唯一列生成的索引,該列不允許有重復值,但允許有空值(NULL)
普通索引跟唯一索引查詢性能:InnoDB的數據是按數據頁為單位來讀寫的,默認每頁16KB,因此這兩種索引查詢數據性能差別微乎其微。
change buffer:普通索引用在更新過程的加速,更新的欄位如果在緩存中,如果是普通索引則直接更新即可。如果是唯一索引需要將所有數據讀入內存來確保不違背唯一性,所以盡量用普通索引。
非主鍵索引:非主鍵索引的葉子節點內容是主鍵的值。在InnoDB里,非主鍵索引也被稱為二級索引(secondary index)
回表:先通過資料庫索引掃描出數據所在的行,再通過行主鍵id取出索引中未提供的數據,即基於非主鍵索引的查詢需要多掃描一棵索引樹。
覆蓋索引:如果一個索引包含(或者說覆蓋)所有需要查詢的欄位的值,我們就稱之為覆蓋索引。
聯合索引:相對單列索引,組合索引是用多個列組合構建的索引,一次性最多聯合16個。
最左前綴原則:對多個欄位同時建立的組合索引(有順序,ABC,ACB是完全不同的兩種聯合索引) 以聯合索引(a,b,c)為例,建立這樣的索引相當於建立了索引a、ab、abc三個索引。另外組合索引實際還是一個索引,並非真的創建了多個索引,只是產生的效果等價於產生多個索引。
索引下推:MySQL 5.6引入了索引下推優化,可以在索引遍歷過程中,對索引中包含的欄位先做判斷,過濾掉不符合條件的記錄,減少回表字數。
索引維護:B+樹為了維護索引有序性涉及到頁分裂跟頁合並。增刪數據時需考慮頁空間利用率。
自增主鍵:一般會建立與業務無關的自增主鍵,不會觸發葉子節點分裂。
延遲關聯:通過使用覆蓋索引查詢返回需要的主鍵,再根據主鍵關聯原表獲得需要的數據。
InnoDB存儲: * .frm文件是一份定義文件,也就是定義資料庫表是一張怎麼樣的表。*.ibd文件則是該表的索引,數據存儲文件,既該表的所有索引樹,所有行記錄數據都存儲在該文件中。
MyISAM存儲:* .frm文件是一份定義文件,也就是定義資料庫表是一張怎麼樣的表。* .MYD文件是MyISAM存儲引擎表的所有行數據的文件。* .MYI文件存放的是MyISAM存儲引擎表的索引相關數據的文件。MyISAM引擎下,表數據和表索引數據是分開存儲的。
MyISAM查詢:在MyISAM下,主鍵索引和輔助鍵索引都屬於非聚簇索引。查詢不管是走主鍵索引,還是非主鍵索引,在葉子結點得到的都是目的數據的地址,還需要通過該地址,才能在數據文件中找到目的數據。
PS:InnoDB支持聚簇索引,MyISAM不支持聚簇索引
4、SQL事務隔離級別
ACID的四個特性
原子性(Atomicity):把多個操作放到一個事務中,保證這些操作要麼都成功,要麼都不成功
一致性(Consistency):理解成一串對數據進行操作的程序執行下來,不會對數據產生不好的影響,比如憑空產生,或消失
隔離性(Isolation,又稱獨立性):隔離性的意思就是多個事務之間互相不幹擾,即使是並發事務的情況下,他們只是兩個並發執行沒有交集,互不影響的東西;當然實現中,也不一定需要這么完整隔離性,即不一定需要這么的互不幹擾,有時候還是允許有部分干擾的。所以MySQL可以支持4種事務隔離性
持久性(Durability):當某個操作操作完畢了,那麼結果就是這樣了,並且這個操作會持久化到日誌記錄中
PS:ACID中C與CAP定理中C的區別
ACID的C著重強調單資料庫事務操作時,要保證數據的完整和正確性,數據不會憑空消失跟增加。CAP 理論中的C指的是對一個數據多個備份的讀寫一致性
事務操作可能會出現的數據問題
1、臟讀(dirty read):B事務更改數據還未提交,A事務已經看到並且用了。B事務如果回滾,則A事務做錯了
2、 不可重復讀(non-repeatable read):不可重復讀的重點是修改: 同樣的條件, 你讀取過的數據, 再次讀取出來發現值不一樣了,只需要鎖住滿足條件的記錄
3、 幻讀(phantom read):事務A先修改了某個表的所有紀錄的狀態欄位為已處理,未提交;事務B也在此時新增了一條未處理的記錄,並提交了;事務A隨後查詢記錄,卻發現有一條記錄是未處理的造成幻讀現象,幻讀僅專指新插入的行。幻讀會造成語義上的問題跟數據一致性問題。
4、 在可重復讀RR隔離級別下,普通查詢是快照讀,是不會看到別的事務插入的數據的。因此,幻讀在當前讀下才會出現。要用間隙鎖解決此問題。
在說隔離級別之前,你首先要知道,你隔離得越嚴實,效率就會越低。因此很多時候,我們都要在二者之間尋找一個平衡點。SQL標準的事務隔離級別由低到高如下: 上圖從上到下的模式會導致系統的並行性能依次降低,安全性依次提高。
讀未提交:別人改數據的事務尚未提交,我在我的事務中也能讀到。
讀已提交(Oracle默認):別人改數據的事務已經提交,我在我的事務中才能讀到。
可重復讀(MySQL默認):別人改數據的事務已經提交,我在我的事務中也不去讀,以此保證重復讀一致性。
串列:我的事務尚未提交,別人就別想改數據。
標准跟實現:上面都是關於事務的標准,但是每一種資料庫都有不同的實現,比如MySQL InnDB 默認為RR級別,但是不會出現幻讀。因為當事務A更新了所有記錄的某個欄位,此時事務A會獲得對這個表的表鎖,因為事務A還沒有提交,所以事務A獲得的鎖沒有釋放,此時事務B在該表插入新記錄,會因為無法獲得該表的鎖,則導致插入操作被阻塞。只有事務A提交了事務後,釋放了鎖,事務B才能進行接下去的操作。所以可以說 MySQL的RR級別的隔離是已經實現解決了臟讀,不可重復讀和幻讀的。
5、MySQL中的鎖
無論是Java的並發編程還是資料庫的並發操作都會涉及到鎖,研發人員引入了悲觀鎖跟樂觀鎖這樣一種鎖的設計思想。
悲觀鎖:
優點:適合在寫多讀少的並發環境中使用,雖然無法維持非常高的性能,但是在樂觀鎖無法提更好的性能前提下,可以做到數據的安全性
缺點:加鎖會增加系統開銷,雖然能保證數據的安全,但數據處理吞吐量低,不適合在讀書寫少的場合下使用
樂觀鎖:
優點:在讀多寫少的並發場景下,可以避免資料庫加鎖的開銷,提高DAO層的響應性能,很多情況下ORM工具都有帶有樂觀鎖的實現,所以這些方法不一定需要我們人為的去實現。
缺點:在寫多讀少的並發場景下,即在寫操作競爭激烈的情況下,會導致CAS多次重試,沖突頻率過高,導致開銷比悲觀鎖更高。
實現:資料庫層面的樂觀鎖其實跟CAS思想類似, 通數據版本號或者時間戳也可以實現。
資料庫並發場景主要有三種:
讀-讀:不存在任何問題,也不需要並發控制
讀-寫:有隔離性問題,可能遇到臟讀,幻讀,不可重復讀
寫-寫:可能存更新丟失問題,比如第一類更新丟失,第二類更新丟失
兩類更新丟失問題:
第一類更新丟失:事務A的事務回滾覆蓋了事務B已提交的結果 第二類更新丟失:事務A的提交覆蓋了事務B已提交的結果
為了合理貫徹落實鎖的思想,MySQL中引入了雜七雜八的各種鎖:
鎖分類
MySQL支持三種層級的鎖定,分別為
表級鎖定
MySQL中鎖定粒度最大的一種鎖,最常使用的MYISAM與INNODB都支持表級鎖定。
頁級鎖定
是MySQL中鎖定粒度介於行級鎖和表級鎖中間的一種鎖,表級鎖速度快,但沖突多,行級沖突少,但速度慢。所以取了折衷的頁級,一次鎖定相鄰的一組記錄。
行級鎖定
Mysql中鎖定粒度最細的一種鎖,表示只針對當前操作的行進行加鎖。行級鎖能大大減少資料庫操作的沖突。其加鎖粒度最小,但加鎖的開銷也最大行級鎖不一定比表級鎖要好:鎖的粒度越細,代價越高,相比表級鎖在表的頭部直接加鎖,行級鎖還要掃描找到對應的行對其上鎖,這樣的代價其實是比較高的,所以表鎖和行鎖各有所長。
MyISAM中的鎖
雖然MySQL支持表,頁,行三級鎖定,但MyISAM存儲引擎只支持表鎖。所以MyISAM的加鎖相對比較開銷低,但數據操作的並發性能相對就不高。但如果寫操作都是尾插入,那還是可以支持一定程度的讀寫並發
從MyISAM所支持的鎖中也可以看出,MyISAM是一個支持讀讀並發,但不支持通用讀寫並發,寫寫並發的資料庫引擎,所以它更適合用於讀多寫少的應用場合,一般工程中也用的較少。
InnoDB中的鎖
該模式下支持的鎖實在是太多了,具體如下:
共享鎖和排他鎖 (Shared and Exclusive Locks)
意向鎖(Intention Locks)
記錄鎖(Record Locks)
間隙鎖(Gap Locks)
臨鍵鎖 (Next-Key Locks)
插入意向鎖(Insert Intention Locks)
主鍵自增鎖 (AUTO-INC Locks)
空間索引斷言鎖(Predicate Locks for Spatial Indexes)
舉個栗子,比如行鎖里的共享鎖跟排它鎖:lock in share modle 共享讀鎖:
為了確保自己查到的數據沒有被其他的事務正在修改,也就是說確保查到的數據是最新的數據,並且不允許其他人來修改數據。但是自己不一定能夠修改數據,因為有可能其他的事務也對這些數據使用了 in share mode 的方式上了S 鎖。如果不及時的commit 或者rollback 也可能會造成大量的事務等待。
for update排它寫鎖:
為了讓自己查到的數據確保是最新數據,並且查到後的數據只允許自己來修改的時候,需要用到for update。相當於一個 update 語句。在業務繁忙的情況下,如果事務沒有及時的commit或者rollback 可能會造成其他事務長時間的等待,從而影響資料庫的並發使用效率。
Gap Lock間隙鎖:
1、行鎖只能鎖住行,如果在記錄之間的間隙插入數據就無法解決了,因此MySQL引入了間隙鎖(Gap Lock)。間隙鎖是左右開區間。間隙鎖之間不會沖突。
2、間隙鎖和行鎖合稱NextKeyLock,每個NextKeyLock是前開後閉區間。
間隙鎖加鎖原則(學完忘那種):
1、加鎖的基本單位是 NextKeyLock,是前開後閉區間。
2、查找過程中訪問到的對象才會加鎖。
3、索引上的等值查詢,給唯一索引加鎖的時候,NextKeyLock退化為行鎖。
4、索引上的等值查詢,向右遍歷時且最後一個值不滿足等值條件的時候,NextKeyLock退化為間隙鎖。
5、唯一索引上的范圍查詢會訪問到不滿足條件的第一個值為止。
『陸』 MySql中Sql的執行過程
如果查詢緩存沒有命中,那麼SQL請求會進入分析器,分析器是用來分辨SQL語句的執行目的,其執行過程大致分為兩步:
表1 語法分析關鍵字然後再通過語法規則解析,判斷輸入的SQL 語句是否滿足MySQL語法,並且生成圖5的語法樹。由SQL語句生成的四個單詞中,識別出兩個關鍵字,分別是select 和from。根據MySQL的語法Select 和 from之間對應的是fields 欄位,下面應該掛接username;在from後面跟隨的是Tables欄位,其下掛接的是userinfo。
優化器的作用是對SQL進行優化,生成最有的執行方案。如圖6所示,前面提到的SQL解析器通過語法分析和語法規則生成了SQL語法樹。這個語法樹作為優化器的輸入,而優化器(黃色的部分)包含了邏輯變換和代價優化兩部分的內容。在優化完成以後會生成SQL執行計劃作為整個優化過程的輸出,交給執行器在存儲引擎上執行。
所處的位置如上圖所示,這節的重點在優化器中的邏輯變換和代價優化上。
邏輯變換也就是在關系代數基礎上進行變換,其目的是為了化簡,同時保證SQL變化前後的結果一致,也就是邏輯變化並不會帶來結果集的變化。其主要包括以下幾個方面:
這樣講概念或許有些抽象,通過圖7 來看看邏輯變化如何在SQL中執行的吧。
如圖7所示,從上往下共有4個步驟:
1. 針對存在的SQL語句,首先通過「否定消除」,去掉條件判斷中的「NOT」。語句由原來的「or」轉換成「and」,並且大於小於符號進行變號。藍色部分為修改前的SQL,紅色是修改以後的SQL。2. 等值傳遞,這一步很好理解分別降」t2.a=9」 和」t2.b=5」分別替換掉SQL中對應的值。3. 接下來就是常量表達式計算,將「5+7」計算得到「12」。4. 最後是常量表達式計算後的化簡,將」9<=10」化簡為」true」帶入到最終的SQL表達式中完成優化。
代價優化是用來確定每個表,根據條件是否應用索引,應用哪個索引和確定多表連接的順序等問題。為了完成代價優化,需要找到一個代價最小的方案。因此,優化器是通過基於代價的計算方法來決定如何執行查詢的(Cost-based Optimization)。簡化的過程如下:
這里將配置操作的代價分為MySQL 服務層和MySQL 引擎層,MySQL 服務層主要是定義CPU的代價,而MySQL 引擎層主要定義IO代價。MySQL 5.7 引入了兩個系統表mysql.server_cost和mysql.engine_cost來分別配置這兩個層的代價。如下:MySQL 服務層代價保存在表server_cost中,其具體內容如下:
由上可以看出創建臨時表的代價是很高的,尤其是內部的myisam或innodb臨時表。MySQL 引擎層代價保存在表engine_cost中,其具體內容如下:
目前io_block_read_cost和memory_block_read_cost默認值均為1,實際生產中建議酌情調大memory_block_read_cost,特別是對普通硬碟的場景。MySQL會根據SQL查詢生成的查詢計劃中對應的操作從上面兩張代價表中查找對應的代價值,並且進行累加形成最終執行SQL計劃的代價。再將多種可能的執行計劃進行比較,選取最小代價的計劃執行。
當分析器生成查詢計劃,並且經過優化器以後,就到了執行器。執行器會選擇執行計劃開始執行,但在執行之前會校驗請求用戶是否擁有查詢的許可權,如果沒有許可權,就會返回錯誤信息,否則將會去調用MySQL引擎層的介面,執行對應的SQL語句並且返回結果。例如SQL:「SELECT * FROM userinfo WHERE username = 'Tom';「假設 「username「 欄位沒有設置索引,就會調用存儲引擎從第一條開始查,如果碰到了用戶名字是」 Tom「, 就將結果集返回,沒有查找到就查看下一行,重復上一步的操作,直到讀完整個表或者找到對應的記錄。需要注意SQL語句的執行順序並不是按照書寫順序來的,順序的定義會在分析器中做好,一般是按照如下順序:
如果命中的記錄比較多,應用會從MySql Server一批批獲取數據
本文從MySQL中SQL語句的執行過程作為切入點,首先介紹了查詢請求的執行流程,其中將MySQL的處理分為MySQL Server層和MySQL存儲引擎層。通過介紹SQL語句的流轉,引出了後面要介紹的5大組件,他們分別是:連接器、查詢緩存、分析器、優化器、執行器。後面的內容中對每個組件進行了詳細的介紹。連接器,負責身份認證和許可權鑒別;查詢緩存,將查詢的結果集進行緩存,提高查詢效率;分析器,對SQL語句執行語法分析和語法規則,生成語法樹和執行計劃;優化器,包括邏輯變換和代價優化;執行器,在檢查用戶許可權以後對數據進行逐條查詢,整個過程遵守SQL語句的執行順序。