MySQL技術內(nèi)幕:SQL編程
姜承堯
第1章 SQL編程
>> B是由MySQL創(chuàng)始人之一Monty分支的一個版本。在MySQL數(shù)據(jù)庫被Oracle公司收購后,Monty擔心MySQL數(shù)據(jù)庫發(fā)展的未來,從而分支出一個版本。這個版本和其他分支有很大的不同,其默認使用嶄新的Maria存儲引擎,是原MyISAM存儲引擎的升級版本
>> 在這里,我把SQL編程分為三個階段,當然不是每個人都必須同意筆者的觀點。
第一階段是面向過程化的SQL編程階段。
>> 在這一階段,經(jīng)常會有濫用各種工具(如游標、臨時表、動態(tài)SQL語句等)的情況,而程序員自己通常意識不到他們正在引起破壞。
>> 第二階段是面向集合的SQL編程階段。
>> 從這一階段開始,程序員開始相信那些說游標、臨時表、動態(tài)SQL有害而永遠不應該使用的“專家”。
>> 第三階段是融合的SQL編程階段
>> 在這一階段,SQL程序員不再迷戀所謂的專家,他們可能意識到即使是游標,也并不是在所有情況下都是無用和有害的。
>> 對于表中數(shù)據(jù)的存儲,InnoDB存儲引擎采用了聚集(clustered)的方式,每張表都是按主鍵的順序進行存儲的,如果沒有顯式地在表定義時指定主鍵,InnoDB存儲引擎會為每一行生成一個6字節(jié)的ROWID,并以此作為主鍵。
第2章 數(shù)據(jù)類型
>> 如果在數(shù)據(jù)
>> UNSIGNED屬性就是將數(shù)字類型無符號化,與C、C++這些程序語言中的unsigned含義相同。
>> 看起來這是一個不錯的屬性選項,特別是對于主鍵是自增長的類型,因為一般來說,用戶都希望主鍵是非負數(shù)。然而在實際使用中,UNSIGNED可能會帶來一些負面的影響,示例如下:
>> 和其他數(shù)據(jù)庫一樣,MySQL的確存在一些Bug,其實并不是MySQL數(shù)據(jù)庫的Bug比較多,去看一下Oracle RAC的Bug,那可能就更多了,它可是Oracle的一款旗艦產(chǎn)品。
>> 筆者個人的看法是盡量不要使用UNSIGNED,因為可能會帶來一些意想不到的效果。另外,對于INT類型可能存放不了的數(shù)據(jù),INT UNSIGNED同樣可能存放不了,與其如此,還不如在數(shù)據(jù)庫設計
>> 階段將INT類型提升為BIGINT類型。
>> 可以看到int(10),這代表什么意思呢?整型不就是4字節(jié)的嗎?這10又代表什么呢?其實如果沒有ZEROFILL這個屬性,括號內(nèi)的數(shù)字是毫無意義的
>> 就是ZEROFILL屬性的作用,如果寬度小于設定的寬度(這里的寬度為4),則自動填充0。要注意的是,這只是最后顯示的結果,在MySQL中實際存儲的還是1。
>> SQL_MODE可以設置的選項。
STRICT_TRANS_TABLES:
>> ALLOW_INVALID_DATES:該選項并不完全對日期的合法性進行檢查,只檢查月份是否在1~12之間,日期是否在1~31之間
>> ANSI_QUOTES:啟用ANSI_QUOTES后,不能用雙引號來引用字符串,因為它將被解釋為識別符,
>> 然而從MySQL 5.6.4版本開始,MySQL增加了對秒的小數(shù)部分(fractional second)的支持,具體語法為:
>> 其中,type_name的類型可以是TIME、DATETIME和TIMESTAMP。fsp表示支持秒的小數(shù)部分的精度,最大為6,表示微秒(microseconds);默認為0,表示沒有小數(shù)部分,同時也是為了兼容之前版本中的TIME、DATETIME和TIMESTAMP類型。對于時間函數(shù),如CURTIME()、SYSDATE()和UTC_TIMESTAMP()也增加了對fsp的支持,例如:
>> TIMESTAMP占用4字節(jié),顯示的范圍為“1970-01-0100:00:00”UTC到“2038-01-19 03:14:07”UTC。其實際存儲的內(nèi)容為“1970-01-0100:00:00”到當前時間的毫秒數(shù)。
>> UTC協(xié)調(diào)世界時,又稱世界統(tǒng)一時間、世界標準時間和國際協(xié)調(diào)時間。它從英文Coordinated Universal Time和法文Temps Universel Cordonné而來。
>> CURRENT_TIMESTAMP是NOW的同義詞,也就是說兩者是相同的。
SYSDATE函數(shù)返回的是執(zhí)行到當前函數(shù)時的時間,而NOW返回的是執(zhí)行SQL語句時的時間。
>> 一般來說表中都會有一個對日期類型的索引,如果使用上述的語句,優(yōu)化器絕對不會使用索引,也不可能通過索引來查詢數(shù)據(jù),因此上述查詢的執(zhí)行效率可能非常低。
>> 演示前,需要確認已經(jīng)安裝了MySQL官方的示例數(shù)據(jù)庫employees
>> 一旦啟用ZEROFILL屬性,MySQL數(shù)據(jù)庫為列自動添加UNSIGNED屬性,
>> MySQL數(shù)據(jù)庫支持兩種浮點類型:單精度的FLOAT類型及雙精度的DOUBLE PRECISION類型。這兩種類型都是非精確的類型,經(jīng)過一些操作后并不能保證運算的正確性,例如M*G/G不一定等于M,雖然數(shù)據(jù)庫內(nèi)部算法已經(jīng)使其盡可能的正確,但是結果還會有偏差
>> 為了保證最大的可移植性,需要使用近似數(shù)值數(shù)據(jù)值存儲的代碼,使用FLOAT或DOUBLE PRECISION,并不規(guī)定精度或位數(shù)
>> DECIMAL和NUMERIC類型在MySQL中被視為相同的類型,用于保存必須為確切精度的值。
>> DECIMAL或NUMERIC的最大位數(shù)是65,但具體的DECIMAL或NUMERIC列的實際范圍受具體列的精度或標度約束。如果分配給此類列的值的小數(shù)點后位數(shù)超過指定的標度允許的范圍,值將按該標度進行轉(zhuǎn)換。(具體操作與操作系統(tǒng)有關,一般結果均被截取到允許的位數(shù))。
>> 位類型,即BIT數(shù)據(jù)類型可用來保存位字段的值。BIT(M)類型表示允許存儲M位數(shù)值,M范圍為1到64,占用的空間為(M+7)/8字節(jié)。
>> 因為采用位的存儲方式,所以不能直接查看
>> 數(shù)字輔助表是一個只包含從1到N的N個整數(shù)的簡單表,N通常很大。因為數(shù)字輔助表是一個非常強大的工具,可能經(jīng)常需要在解決方案中用到它,筆者建議創(chuàng)建一個持久的數(shù)字輔助表,并根據(jù)需要填充一定數(shù)據(jù)量的值。
>> 實際上如何填充數(shù)字輔助表無關緊要,因為只需要運行這個過程一次。不過還可以對填充語句進行優(yōu)化。
>> utf8目前被視為utf8mb3,即最大占用3個字節(jié)空間,而utf8mb4可以視做utf8mb3的擴展。
>> 對BMP(Basic Multilingual Plane)字符的存儲,utf8mb3和utf8mb4
>> 兩者是完全一樣的,區(qū)別只是utf8mb4對擴展字符的支持。
>> 對于Unicode編碼的字符集,強烈建議將所有的CHAR字段設置為VARCHAR字段,因為對于CHAR字段,數(shù)據(jù)庫會保存最大可能的字節(jié)數(shù)。例如,對于CHAR(30),數(shù)據(jù)庫可能存儲90字節(jié)的數(shù)據(jù)。
>> 要查看當前使用的字符集,可以使用STATUS命令:
>> MySQL數(shù)據(jù)庫一個比較“強悍”的地方是,可以細化每個對象字符集的設置
>> 排序規(guī)則(Collation)是指對指定字符集下不同字符的比較規(guī)則。
>> 兩個不同的字符集不能有相同的排序規(guī)則。
每個字符集有一個默認的排序規(guī)則。
有一些常用的命名規(guī)則。如_ci結尾表示大小寫不敏感(case insensitive),_cs表示大小寫敏感(case sensitive),_bin表示二進制的比較(binary)。
>> 另外,排序規(guī)則不僅影響大小寫的比較問題,也影響著索引
>> 可以看到,不能在a列上創(chuàng)建一個唯一索引,報錯中提示有重復數(shù)據(jù)。索引是B+樹,同樣需要對字符進行比較,因此在建立唯一索引時由于排序規(guī)則對大小寫不敏感而導致了錯誤。
>> CHAR(N)和VARCHAR(N)中的N都代表字符長度,而非字節(jié)長度。
>> 對于CHAR類型的字符串,MySQL數(shù)據(jù)庫會自動對存儲列的右邊進行填充(Right Padded)操作,直到字符串達到指定的長度N。而在讀取該列時,MySQL數(shù)據(jù)庫會自動將填充的字符刪除
>> LENGTH函數(shù)返回的是字節(jié)長度,而不是字符長度。
>> VARCHAR類型存儲變長字段的字符類型,與CHAR類型不同的是,其存儲時需要在前綴長度列表加上實際存儲的字符,該字符占用1~2字節(jié)的空間。當存儲的字符串長度小于255字節(jié)時,其需要1字節(jié)的空間,當大于255字節(jié)時,需要2字節(jié)的空間。所以,對于單字節(jié)的latin1來說,CHAR(10)和VARCHAR(10)最大占用的存儲空間是不同的, CHAR(10)占用10個字節(jié)這是毫無疑問的,
>> 而VARCHAR(10)的最大占用空間數(shù)是11字節(jié),因為其需要1字節(jié)來存放字符長度。
>> BINARY和VARBINARY與前面介紹的CHAR和VARCHAR類型有點類似,不同的是BINARY和VARBINARY存儲的是二進制的字符串,而非字符型字符串。也就是說,BINARY和VARBINARY沒有字符集的概念,對其排序和比較都是按照二進制值進行對比。
>> BINARY和VARBINARY對比CHAR和VARCHAR,第一個不同之處就是BINARY (N)和VARBINARY(N)中的N值代表的是字節(jié)數(shù),而非
>> 字符長度;第二個不同點是, CHAR和VARCHAR在進行字符比較時,比較的只是字符本身存儲的字符,忽略字符后的填充字符,而對于BINARY和VARBINARY來說,由于是按照二進制值來進行比較的,因此結果會非常不同
>> 第三個不同的是,對于BINARY字符串,其填充字符是0x00,而CHAR的填充字符為0x20。可能是因為BINARY的比較需要,0x00顯然是比較的最小字符
>> 而,BLOB和TEXT在以下幾個方面又不同于VARBINARY和VARCHAR:[插圖]在BLOB和TEXT類型的列上創(chuàng)建索引時,必須制定索引前綴的長度。而VARCHAR和VARBINARY的前綴長度是可選的。
>> [插圖]BLOB和TEXT類型的列不能有默認值。[插圖]在排序時只使用列的前max_sort_length個字節(jié)。
>> max_sort_length默認值為1024,該參數(shù)是動態(tài)參數(shù),任何客戶端都可以在MySQL數(shù)據(jù)庫運行時更改該參數(shù)的值
>> 在數(shù)據(jù)庫中,最小的存儲單元是頁(也可以稱為塊)。為了有效存儲列類型為BLOB或TEXT的大數(shù)據(jù)類型,一般將列的值存放在行溢出頁,而數(shù)據(jù)頁存儲的行數(shù)據(jù)只包含BLOB或TEXT類型數(shù)據(jù)列前一部分數(shù)據(jù)。
>> 在有些存儲引擎內(nèi)部,比如InnoDB存儲引擎,會將大VARCHAR類型字符串(如VARCHAR(65530))自動轉(zhuǎn)化為TEXT或BLOB類型
>> ENUM和SET類型都是集合類型,不同的是ENUM類型最多可枚舉65536個元素,而SET類型最多枚舉64個元素。
第3章 查詢處理
>> 在大多數(shù)編程語言中,代碼按編碼順序被處理。但在SQL語言中,第一個被處理的子句總是FROM子句
>> 每個操作都會產(chǎn)生一張?zhí)摂M表,該虛擬表作為一個處理的輸入。
>> 這些虛擬表對用戶是透明的,只有最后一步生成的虛擬表才會返回給用戶。如果沒有在查詢中指定某一子句,則將跳過相應的步驟。
>> 對虛擬表VT1應用ON篩選,只有那些符合的行才被插入虛擬表VT2中。
>> JOIN:如果指定了OUTER JOIN(如LEFT OUTER JOIN、RIGHT OUTER JOIN),那么保留表中未匹配的行作為外部行添加到虛擬表VT2中,產(chǎn)生虛擬表VT3。
>> 如果FROM子句包含兩個以上表,則對上一個連接生成的結果表VT3和下一個表重復執(zhí)行步驟1)~步驟3),直到處理完所有的表為止。
>> 第一步需要做的是對FROM子句前后的兩張表進行笛卡兒積操作,也稱做交叉連接(Cross Join),生成虛擬表VT1。如果FROM子句前的表中包含a行數(shù)據(jù),F(xiàn)ROM子句后的表中包含b行數(shù)據(jù),那么虛擬表VT1中將包含a*b行數(shù)據(jù)
>> SELECT查詢一共有3個過濾過程,分別是ON、WHERE、HAVING。ON是最先執(zhí)行的過濾過程。
>> 對于大多數(shù)的編程語言而言,邏輯表達式的值只有兩種:TRUE和FALSE。但是在關系數(shù)據(jù)庫中起邏輯表達式作用的并非只有兩種,還有一種稱為三值邏輯的表達式。這是因為在數(shù)據(jù)庫中對NULL值的比較與大多數(shù)編程語言不同
>> 對于在ON過濾條件下的NULL值比較,此時的比較結果為UNKNOWN,卻被視為FALSE來進行處理,即兩個NULL并不相同。但是在下面兩種情況下認為兩個NULL值的比較是相等的:
GROUP BY子句把所有NULL值分到同一組。
ORDER BY子句中把所有NULL值排列在一起
>> 因此在產(chǎn)生虛擬表VT2時,會增加一個額外的列來表示ON過濾條件的返回值,返回值有TRUE、FALSE、UNKNOWN,如表3-5所示。
>> 取出比較值為TRUE的記錄,產(chǎn)生虛擬表VT2,結果如表3-6所示。
>> 添加外部行的工作就是在VT2表的基礎上添加保留表中被過濾條件過濾掉的數(shù)據(jù),非保留表中的數(shù)據(jù)被賦予NULL值,最后生成虛擬表VT3
>> 在當前應用WHERE過濾器時,有兩種過濾是不被允許的:
由于數(shù)據(jù)還沒有分組,因此現(xiàn)在還不能在WHERE過濾器中使用where_condition=MIN(col)這類對統(tǒng)計的過濾。
由于沒有進行列的選取操作,因此在SELECT中使用列的別名也是不被允許的,如SELECT city as c FROM t WHERE c='ShangHai'是不允許出現(xiàn)的。
>> 如果在查詢中指定了DISTINCT子句,則會創(chuàng)建一張內(nèi)存臨時表(如果內(nèi)存中存放不下就放到磁盤上)。這張內(nèi)存臨時表的表結構和上一步產(chǎn)生的虛擬表一樣,不同的是對進行DISTINCT操作的列增加了一個唯一索引,以此來去除重復數(shù)據(jù)。
>> 對于使用了GROUP BY的查詢,再使用DISTINCT是多余的,因為已經(jīng)進行分組,不會移除任何行。
>> 關系數(shù)據(jù)庫是在數(shù)學的基礎上發(fā)展起來的,關系對應于數(shù)學中集合的概念。數(shù)據(jù)庫中常見的查詢操作其實對應的是集合的某些運算:選擇、投影、連接、并、交、差、除。最終的結果雖然是以一張二維表的方式呈現(xiàn)在用戶面前,但是從數(shù)據(jù)庫內(nèi)部來看是一系列的集合操作。因此,對于表中的記錄,用戶需要以集合的思想來理解。對于customers和orders表,更準確的描述應如圖3-2所示。
>> 由此可見,即使采用的是InnoDB存儲引擎表,對于沒有使用ORDER BY子句的選擇查詢,其結果永遠不會是按照主鍵順序進行排列的。因為
>> 沒有ORDER BY子句的查詢只代表從集合中查詢數(shù)據(jù),而集合是沒有順序概念的。
>> 因此要牢記,不要為表中的行假定任何特定的順序。就是說,在實際使用環(huán)境中,如果確實需要有序輸出行記錄,則必須使用ORDER BY子句
>> 在ORDER BY子句中,NULL值被認為是相同的值,會將其排序在一起。在MySQL數(shù)據(jù)庫中,NULL值在升序過程中總是首先被選出,即NULL值在ORDER BY子句中被視為最小值。
>> 如果只是選取前5條記錄,則非常輕松和容易;但是對100萬條記錄,選取從第80萬行記錄開始的5條記錄,則還需要掃描記錄到這個位置
>> 上一節(jié)介紹了邏輯查詢處理,并且描述了執(zhí)行查詢應該得到什么樣的結果。但是數(shù)據(jù)庫也許并不會完全按照邏輯查詢處理的方式來進行查詢。圖1-1顯示了在MySQL數(shù)據(jù)庫層有Parser和Optimizer兩個組件。Parser的工作就是分析SQL語句,而Optimizer的工作就是對這個SQL語句進行優(yōu)化,選擇一條最優(yōu)的路徑來選取數(shù)據(jù),但是必須保證物理查詢處理的最終結果和邏輯查詢處理是相等的。
第4章 子查詢
>> 子查詢可以按兩種方式進行分類。若按照期望值的數(shù)量,可以將子查詢分為標量子查詢和多值子查詢;若按查詢對外部查詢的依賴可分為獨立子查詢(self-contained subquery)和相關子查詢(correlated subquery)
>> MySQL優(yōu)化器對于IN語句的優(yōu)化是“LAZY”的。對于IN子句,如果不是顯式的列表定義,如IN ('a','b','c'),那么IN子句都會被轉(zhuǎn)換為EXISTS的相關子查詢
>> 如果子查詢和外部查詢分別返回M和N行,那么該子查詢被掃描為O(N+M*N)而不是O(M+N)。
>> 用戶通過EXPLAIN EXTENDED命令可以更為明確地得到優(yōu)化器的執(zhí)行方式
>> 有意思的是,翻閱官方的MySQL手冊會發(fā)現(xiàn),在子查詢章節(jié)中有相關子查詢的介紹,卻沒有獨立子查詢的介紹。這是因為在大多數(shù)情況下,MySQL數(shù)據(jù)庫都將獨立子查詢轉(zhuǎn)換為相關子查詢。
>> 注意到慢的原因是獨立子查詢被轉(zhuǎn)換成相關子查詢,而這個相關子查詢需要進行多次的分組操作。可以采取另一個方法,再嵌套一層子查詢,避免多次的分組操作,
>> 相關子查詢(Dependent Subquery或Correlated Subquery)是指引用了外部查詢列的子查詢,即子查詢會對外部查詢的每行進行一次計算
>> 。但是在優(yōu)化器內(nèi)部,這是一個動態(tài)的過程,隨情況的變化會有所不同,通過不止一種優(yōu)化方式來處理相關子查詢。
>> 這里再次提醒開發(fā)人員,對子查詢的編寫需要非常小心,盡可能地使用EXPLAIN來確認子查詢的執(zhí)行計劃,并確認是否可以對其進行進一步優(yōu)化。在測試機上執(zhí)行一句SQL需要1秒的時間看似很短,但這通常是數(shù)據(jù)量較小的緣故;如果在大數(shù)據(jù)量的生產(chǎn)環(huán)境中,這可能會帶來災難性的后果。
>> EXIST
>> S是一個非常強大的謂詞,它允許數(shù)據(jù)庫高效地檢查指定查詢是否產(chǎn)生某些行。通常EXISTS的輸入是一個子查詢,并關聯(lián)到外部查詢,但這不是必須的
>> 。根據(jù)子查詢是否返回行,該謂詞返回TRUE或FALSE。與其他謂詞和邏輯表達式不同的是,無論輸入子查詢是否返回行,EXISTS都不會返回UNKNOWN。如果子查詢的過濾器為某行返回UNKNOWN,則表示該行不返回,因此,這個UNKNOWN被認為是FALSE。
>> 盡管通常不建議在SQL語句中使用*,因為可能會引起一些問題的產(chǎn)生,但是在EXIST子查詢中*可以放心地使用。EXISTS只關心行是否存在,而不會去取各列的值。
>> EXISTS與IN的一個小區(qū)別體現(xiàn)在對三值邏輯的判斷上。EXISTS總是返回TRUE或FALSE,而對于IN,除了TRUE、FALSE值外,還有可能對NULL值返回UNKNOWN。但是在過濾器中,UNKNOWN的處理方式與FALSE相同,因此使用IN與使用EXISTS一樣, SQL優(yōu)化器會選擇相同的執(zhí)行計劃。
>> 但是輸入列表中包含NULL值時,NOT EXISTS和NOT IN之間的差異就表現(xiàn)得非常明顯了。
>> 對于包含NULL值的NOT IN來說,其總是返回FALSE和UNKNOWN
>> ,而對于NOT EXISTS,其總是返回TRUE和FALSE。這就是NOT EXISTS和NOT IN的最大區(qū)別。
>> 派生表又被稱為表子查詢,與其他表一樣出現(xiàn)在FROM的子句中,但是是從子查詢派生出的虛擬表中產(chǎn)生的
>> 目前派生表在使用上有以下使用規(guī)則:
列的名稱必須是唯一的。
在某些情況下不支持LIMIT。
>> 派生表是完全的虛擬表,并沒有也不可能被物理地具體化,因此優(yōu)化器不清楚派生表的信息,這對于涉及查看派生表的EXPLAIN執(zhí)行計劃來說,速度可能非常慢,
>> 由于目前Oracle和MySQL都將SEMI JOIN轉(zhuǎn)換為了EXISTS語句,因此在執(zhí)行效率上顯得非常低。從理論上來說,SEMI JOIN應該只需要關心外部表中與子查詢匹配的部分即可
>> 。這就是MariaDB要對SEMI JOIN進行的優(yōu)化,在MariaDB中子查詢變得實際可用得多,效率也得到了極大的提升
>> Table Pullout的作用就是根據(jù)唯一索引將子查詢重寫為JOIN語句
>> 預熱是指所要讀取的表中的數(shù)據(jù)都已經(jīng)在InnoDB存儲引擎的緩沖池中,這時不涉及磁盤的讀取。而無預熱指的是數(shù)據(jù)庫剛啟動,緩沖池中沒有數(shù)據(jù),需要讀取磁盤上的數(shù)據(jù)到緩沖池。
>> Duplicate Weedout優(yōu)化是指外部查詢條件的列是唯一的, MariaDB優(yōu)化器會先將子查詢查出的結果進行去重,這個步驟被稱為Duplicate Weedout或者Duplicate Elimination。
>> 如果子查詢是獨立子查詢,則優(yōu)化器可以選擇將獨立子查詢產(chǎn)生的結果填充到單獨一張物化臨時表(materialized temporary table)中
第5章 聯(lián)接與集合操作
>> 聯(lián)接查詢是一種常見的數(shù)據(jù)庫操作,即在兩張表(或更多表)中進行行匹配的操作。一般稱之為水平操作,這是因為對幾張表進行聯(lián)接操作所產(chǎn)生的結果集可以包含這幾張表中所有的列。對應于聯(lián)接的水平操作,一般將集合操作視為垂直操作。
MySQL數(shù)據(jù)庫支持如下的聯(lián)接查詢:
CROSS JOIN(交叉聯(lián)接)
INNER JOIN(內(nèi)聯(lián)接)
OUTER JOIN(外聯(lián)接)
其他
>> 在進行聯(lián)接操作時,請牢記第3章描述的邏輯查詢處理階段,尤其是關于聯(lián)接所涉及的階段。
>> 每個聯(lián)接都只發(fā)生在兩個表之間,即使FROM子句中包含多個表也是如此
>> 。每次聯(lián)接操作也只進行邏輯操作的前三個步驟,每次產(chǎn)生一個虛擬表,這個虛擬表再依次與FROM子句的下一個表進行聯(lián)接
>> 需要注意的是,不同聯(lián)接類型執(zhí)行的步驟不同。對于CROSS JOIN,只應用第一個階段的笛卡兒積。INNER JOIN應用第一和第二個步驟,OUTER JOIN應用所有的前三個步驟。
>> 對于表5-1左側(cè)的SQL聯(lián)接查詢語句,其由ANSI SQL 89標準引入,與新語法的區(qū)別是FROM子句中的表名之間用逗號分隔,沒有JOIN關鍵字,也沒有ON子句,其語法格式如下:
>> ANSI SQL 89只支持CROSS JOIN和INNTER JOIN,不支持OUTER JOIN。新語法是由ANSI SQL 92引入的,與舊語法的區(qū)別是引入了JOIN關鍵字和ON過濾子句,并去掉了表之間的逗號,其語法格式如下:
>> CROSS JOIN對兩個表執(zhí)行笛卡兒積,返回兩個表中所有列的組合。若左表有m行數(shù)據(jù),右表有n行數(shù)據(jù),則CROSS JOIN將返回m*n行的表。
>> 對于交叉聯(lián)接,筆者更喜歡使用ANSI SQL 89語法。這樣代碼會更短,語法更加易讀。不必擔心兩者的性能,因為正如前面所說的,優(yōu)化器將為兩者生成相同的執(zhí)行計劃。
>> CROSS JOIN的一個用處是快速生成重復測試數(shù)
>> 據(jù),因為通過它可以很快地構造m*n*o行的數(shù)據(jù)。
>> 雖然對兩個N行表進行笛卡兒積會產(chǎn)生N2行的數(shù)據(jù)。但是如果是對一行表與N行表進行CROSS JOIN,笛卡爾兒積返回的還是N行數(shù)據(jù)
>> 如果使用的是ANSI 92語法,則選擇在哪個子句中指定過濾條件,用戶具有更多的靈活性。因為前面說了,從邏輯上講,在哪里指定過濾條件都是一樣的,通常不會有性能上的差異。唯一的準則就是可讀性強。通過一種讓DBA、開發(fā)人員感覺更自然的方式進行代碼編寫。例如,在表之間匹配記錄的過濾器放在ON子句中,而只從一個表中過濾數(shù)據(jù)的條件放在WHERE子句中
>> 對于CROSS JOIN,筆者喜歡使用ANSI 89語法,而對于INNER JOIN正好相反,更傾向于使用ANSI 92語法。如果忘記指定聯(lián)接條件,則使用ANSI 89語法可能有些危險,因為可能會得到很大的笛卡兒積返回集
>> 特別需要注意的是,在MySQL數(shù)據(jù)庫中,如果INNER JOIN后不跟ON子句,也是可以通過語法解析器的,這時INNER JOIN等于CROSS JOIN,即產(chǎn)生笛卡兒積
>> 如果ON子句中的列具有相同的名稱,可以使用USING子句來進行簡化,得到的結果和上述兩語法的語句結果是一樣的:
>> 目前MySQL數(shù)據(jù)庫不支持FULL OUTER JOIN。
>> OUTER JOIN只在ANSI SQL 92中得到支持,在其他一些數(shù)據(jù)庫中可以使用(+)=、*=來表示LEFT JOIN,用=(+)、=*來擴展ANSI SQL 89語法使其支持OUTER JOIN。
>> 但是對MySQL數(shù)據(jù)庫來說,只有一種OUTER JOIN的聯(lián)接語法。
>> 需要注意的是,INNER JOIN中的過濾條件都可以寫在ON子句中,而OUTER JOIN的過濾條件不可以這樣處理,因為可能會得到不正確的結果
>> 與INNER JOIN不同的是,對于OUTER JOIN,必須制定ON子句,否則MySQL數(shù)據(jù)庫會拋出異常,
>> 前面介紹的都是EQUAL JOIN(等值聯(lián)接),即聯(lián)接條件是基于“等于”運算符的聯(lián)接操作。NONEQUI JOIN的聯(lián)接條件包含“等于”運算符之外的運算符。
>> 對于INNER JOIN的多表聯(lián)接查詢,可以隨意安排表的順序,而不會影響查詢的結果。這是因為優(yōu)化器會自動根據(jù)成本評估出訪問表的順序。在該查詢的執(zhí)行計劃中,可能會發(fā)現(xiàn)優(yōu)化器訪問表的順序不同于在查詢中指定的順序。
>> 如果認為不按優(yōu)化器所選擇的順序聯(lián)接表會更加高效,可以通過前面介紹的STRAIGHT_JOIN來強制聯(lián)接處理的順序
>> 聯(lián)接算法是MySQL數(shù)據(jù)庫用于處理聯(lián)接的物理策略。目前MySQL數(shù)據(jù)庫僅支持Nested-Loops Join算法。而MySQL的分支版本MariaDB除了支持Nested-Loops Join算法外,還支持Classic Hash Join算法。
>> Simple Nested-Loops Join從第一張表中每次讀取一條記錄,然后將記錄與嵌套表中的記錄進行比較
>> 對于聯(lián)接的列含有索引的情況,外部表的每條記錄不再需要掃描整張內(nèi)部表,只需要掃描內(nèi)部表上的索引即可得到聯(lián)接的判斷結果。
>> 根據(jù)前面描述的Simple Nested-Loops Join算法,優(yōu)化器在一般情況下總是選擇將聯(lián)接列含有索引的表作為內(nèi)部表。如果兩張表R和S在聯(lián)接的列上都有索引,并且索引的高度相同,那么優(yōu)化器會選擇將記錄數(shù)最少的表作為外部表,這是因為內(nèi)部表的掃描次數(shù)總是索引的高度,與記錄的數(shù)量無關。
>> Block Nested-Loops Join算法就是針對沒有索引的聯(lián)接情況設計的,其使用Join Buffer(聯(lián)接緩沖)來減少內(nèi)部循環(huán)讀取表的次數(shù)。
>> Block Nested-Loops Join算法先把對Outer Loop表(外部表)每次讀取的10行記錄(準確地說是10行需要進行聯(lián)接的列)放入Join Buffer中,然后在Inner Loop表(內(nèi)部表)中直接匹配這10行數(shù)據(jù)。因此,對Inner Loop表的掃描減少了1/10
>> 每次聯(lián)接使用一個Join Buffer,因此多表的聯(lián)接可以使用多個Join Buffer。
Join Buffer在聯(lián)接發(fā)生之前進行分配,在SQL語句執(zhí)行完后進行釋放。
Join Buffer只存儲需要進行查詢操作的相關列數(shù)據(jù),而不是整行的記錄。
>> Block Nested-Loops Join算法不支持OUTER JOIN
>> 可以看到這次執(zhí)行計劃的Extra列中并沒有Using join buffer的提示,這也就意味著此時優(yōu)化器沒有使用Block Nested-Loops Join算法。從MySQL 5.6及MariaDB 5.3開始,Join Buffer的使用得到了進一步擴展,在OUTER JOIN中使用Join Buffer受到支持
>> ?5.6(MariaDB 5.3)開始支持Batched Key Access Join算法(簡稱BKA),該算法的思想為結合索引和group這兩種方法(Simple Nested-
>> Loops Join和Block Nested-Loops Join只能使用一種)來提高search-for-match的操作,以此加快聯(lián)接的執(zhí)行效率
>> 因為Batched Key Access Join算法的本質(zhì)是通過Multi-Range Read接口將非主鍵索引對于記錄的訪問,轉(zhuǎn)化為根據(jù)ROWID排序的較為有序的記錄獲取,所以要想通過Batched Key Access Join算法來提高性能,不但需要確保聯(lián)接的列參與match的操作,還要有對非主鍵列的search操作。
>> Batched Key Access Join算法從本質(zhì)上來說還是Simple Nested-Loops Join算法,其發(fā)生的條件為內(nèi)部表上有索引,并且該索引為非主鍵的,并且聯(lián)接需要訪問內(nèi)部表主鍵上的索引。
>> Classic Hash Join算法同樣使用Join Buffer,先將外部表中數(shù)據(jù)放入Join Buffer中,然后根據(jù)鍵值產(chǎn)生一張散列表,這是第一個階段,稱為build階段。隨后讀取內(nèi)部表中的一條記錄,對其應用散列函數(shù),將其和散列表中的數(shù)據(jù)進行比較,這是第二個階段,稱為probe階段。
>> Hash Join只能應用于等值的聯(lián)接操作中,因為已通過散列函數(shù)生成新的聯(lián)接值,不能將Hash Join用于非等值的聯(lián)接操作中。
>> 倘若Join Buffer能夠完全存放下外部表的數(shù)據(jù),那么Classic Hash Join算法只需要掃描一次內(nèi)部表。反之,Classic Hash Join需要掃描多次內(nèi)部表
>> 正如前面所說,當Join Buffer不能存放下所有外部表中的數(shù)據(jù)時,Classic Hash Join需要掃描內(nèi)部表多次。對于這種情況,其他數(shù)據(jù)庫中使用的是Grace Hash Join算法,對內(nèi)部表和外部表都只需掃描一次,有興趣的讀者可以查找相關資料。MariaDB有計劃支持Grace Hash Join算法,相信不久的將來也能在MySQL數(shù)據(jù)庫中看到Grace Hash Join算法。
>> MySQL數(shù)據(jù)庫支持兩種集合操作:UNION ALL和UNION DISTINCT
>> 集合操作的兩個輸入必須擁有相同的列數(shù),若數(shù)據(jù)類型不同,MySQL數(shù)據(jù)庫會自動將進行隱式轉(zhuǎn)化。同時,結果列的名稱由第一個輸入決定。
>> UNION DISTINCT組合兩個輸入,并應用DISTINCT過濾重復項。一般省略DISTINCT關鍵字,直接用UNION
>> 由于向臨時表添加了唯一索引,插入的速度顯然會因此而受到影響。如果確認進行UNION操作的兩個集合中沒有重復的選項,最有效的辦法應該是使用UNION ALL。
UNION ALL組合兩個輸入中所有項的結果集,并包含重復的選項
>> MySQL數(shù)據(jù)庫并不原生支持EXCEPT的語法,不過我們?nèi)匀豢梢酝ㄟ^一些手段來得到EXCEPT的結果。EXCEPT集合操作允許用戶找出位于第一個輸入中但不位于第二個輸入中的行數(shù)據(jù)。同UNION一樣,EXCEPT可分為EXCEPT DISTINCT和EXCEPT ALL。
第6章 聚合和旋轉(zhuǎn)操作
>> MySQL數(shù)據(jù)庫支持聚合(aggregate)操作,按照分組對同一組內(nèi)的數(shù)據(jù)聚合進行統(tǒng)計操作。
>> GROUP_CONCAT將分組后的非NULL數(shù)據(jù)
>> 通過連接符進行拼接,對NULL數(shù)據(jù)返回NULL值。
>> MySQL數(shù)據(jù)庫僅支持流聚合,而其他的數(shù)據(jù)庫可能會支持散列聚合。流聚合依賴于獲得的存儲在GROUP BY列中的數(shù)據(jù)。如果一個SQL查詢中包含的GROUP BY語句多于一行,流聚合會先根據(jù)GROUP BY對行進行排序。
>> ing是一項可以把行旋轉(zhuǎn)為列的技術。在執(zhí)行Pivoting的過程中可能會使用到聚合。Pivoting技術應用得非常廣泛
>> 因此,在頻繁更改架構的情況下,可以在一個表中存儲所有的數(shù)據(jù),每行存儲一個屬性的值,多用VARCHAR來存儲,因為其可容納各種類型的數(shù)據(jù)
>> 在對通過開放架構設計的表進行添加、修改或刪除表和列時,只需要通過INSERT、UPDATE、DELETE操作來完成邏輯架構的更改即可。當然使用這種方法可能導致關系數(shù)據(jù)庫的其他特性無法使用,如完整性約束、SQL優(yōu)化等,同時查詢數(shù)據(jù)變得不如使用之前的SQL語句來得直接和直觀。所以,對于利用開放架構設計的表,一般使用Pivoting技術來查詢數(shù)據(jù)。
>> 這種旋轉(zhuǎn)方式是非常高效的,因為它只對表進行一次掃描。另外,這是一種靜態(tài)的Pivoting,用戶必須事先知道一共有多少個屬性,然而對于一般的開放架構表,用戶都會定義一個最大的屬性個數(shù),這樣可以比較容易地進行Pivoting。
第8章 事務編程
>> 對于Oracle數(shù)據(jù)庫來說,其默認的事務隔離級別為READ COMMITTED,不滿足I的要求,即隔離性的要求
>> 需要注意的是,持久性只能從事務本身的角度來保證結果的永久性,如事務提交后,所有的變化都是永久的,即使當數(shù)據(jù)庫由于崩潰而需要恢復時,也能保證恢復后提交的數(shù)據(jù)都不會丟失。但如果不是數(shù)據(jù)庫本身發(fā)生故障,而是一些外部的原因,如RAID卡損壞、自然災害等導致數(shù)據(jù)庫發(fā)生問題,那么所有提交的數(shù)據(jù)可能會丟失。因此持久性保證的是事務系統(tǒng)的高可靠性(high reliability),而不是高可用性(high availability)。對于高可用性的實現(xiàn),事務本身并不能保證,需要一些系統(tǒng)共同配合來完成。
>> 帶有保存點的扁平事務,除了支持扁平事務支持的操作外,允許在事務執(zhí)行過程中回滾到同一事務中較早的一個狀態(tài),這是因為可能某些事務在執(zhí)行過程中出現(xiàn)的錯誤并不會對所有的操作都無效,放棄整個事務不合乎要求,開銷也太大。保存點(savepoint)用來通知系統(tǒng)應該記住事務當前的狀態(tài),以便以后發(fā)生錯誤時,事務能回到該狀態(tài)。
>> 鏈事務可視為保存點模式的一個變種。帶有保存點的扁平事務,當發(fā)生系統(tǒng)崩潰時,所有的保存點都將消失,因為其保存點是易失的(volatile),而非持久的(persistent)。這意味著當恢復保存點時,事務需要從開始處重新執(zhí)行,而不能從最近的一個保存點繼續(xù)執(zhí)行。
>> 鏈事務的思想是:在提交一個事務時,釋放不需要的數(shù)據(jù)對象,將必要的處理上下文隱式地傳給下一個要開始的事務。注意,提交事務操作和開始下一個事務操作將合并為一個原子操作。這意味著下一個事務將看到上一個事務的結果,就好像在一個事務中進行的。
>> 鏈事務與帶有保存點的扁平事務不同的是,帶有保存點的扁平事務能回滾到任意正確的保存點。而鏈事務中的回滾僅限于當前事務,即只能恢復到最近一個保存點。對于鎖的處理,兩者也不相同。鏈事務在COMMIT后即釋放了當前事務所持有的鎖,而帶有保存點的扁平事務不影響迄今為止所持有的鎖。
>> MySQL命令行的默認設置下,事務都是自動提交(auto commit)的,即執(zhí)行SQL語句后就會馬上執(zhí)行COMMIT操作。因此要顯式地開啟一個事務須使用命令BEGIN和START TRANSACTION,或者執(zhí)行命令SET AUTOCOMMIT=0,以禁用當前會話的自動提交。
>> 注意 Microsoft SQL Server的數(shù)據(jù)庫管理員或開發(fā)人員往往忽視對于DDL語句的隱式提交操作,因為在Microsoft SQL Server數(shù)據(jù)庫中,即使是DDL也是可以回滾的,這和InnoDB存儲引擎、Oracle這些數(shù)據(jù)庫完全不同。
>> 令人驚訝的是,大部分數(shù)據(jù)庫系統(tǒng)都沒有提供真正的隔離性,最初或許是因為系統(tǒng)實現(xiàn)者并沒有真正理解這些問題,如今這些問題已經(jīng)弄清楚了,但是數(shù)據(jù)庫實現(xiàn)者在正確性和性能之間做了妥協(xié)。
>> 雖然ISO和ANSI SQL標準制定了四種事務隔離級別的標準,但是很少有數(shù)據(jù)庫廠商遵循這些標準。比如Oracle數(shù)據(jù)庫就不支持READ UNCOMMITTED和REPEATABLE READ的事務隔離級別。
>> InnoDB存儲引擎默認的支持隔離級別是REPEATABLE READ,但是與標準SQL不同的是,InnoDB存儲引擎在REPEATABLE READ事務隔離級別下,使用Next-Key Lock的鎖算法,因此避免了幻讀的產(chǎn)生。這與其他數(shù)據(jù)庫系統(tǒng)(如Microsoft SQL Server數(shù)據(jù)庫)是不同的。所以說,InnoDB存儲引擎在默認的REPEATABLE READ事務隔離級別下已經(jīng)能完全保證事務的隔離性要求,即達到SQL標準的SERIALIZABLE隔離級別。
>> 在SERIALIZABLE的事務隔離級別,InnoDB存儲引擎會對每個SELECT語句后自動加上LOCK IN SHARE MODE,即給每個讀取操作加一個共享鎖。在這個事務隔離級別下,讀占用鎖了,因此對于一致性的非鎖定讀不再予以支持。由于InnoDB存儲引擎在REPEATABLE READ隔離級別下就可以達到3 °的隔離,因此一般不在本地事務中使用SERIALIZABLE隔離級別,SERIALIZABLE事務隔
>> 離級別主要用于InnoDB存儲引擎的分布式事務。
>> XA事務允許不同數(shù)據(jù)庫之間的分布式事務,如一臺服務器是MySQL數(shù)據(jù)庫的,另一臺是Oracle數(shù)據(jù)庫的,可能還有一臺服務器是SQL Server數(shù)據(jù)庫的,只要參與到全局事務中的每個節(jié)點都支持XA事務即可
>> 分布式事務使用兩段式提交(two-phase commit)的方式。
>> 在單個節(jié)點上運行分布式事務沒有太大的實際意義,但是要在MySQL數(shù)據(jù)庫的命令下演示多個節(jié)點參與的分布式事務也是行不通的。通常來說,都是通過編程語言來完成分布式事務的操作的。當前Java的JTA(Java Transaction API)可以很好地支持MySQL的分布式事務,需要使用分布式事務應該認真參考其API。
>> 顯然,第三種方法要快得多!這是因為每一次提交都要寫一次重做日志,所以存儲過程load1和load2實際寫了10000次重做日志文件,而對于存儲過程load3來說,實際只寫了1次重做日志。
>> 大多數(shù)程序員會使用第一種或者第二種方法,有人可能不知道InnoDB存儲引擎自動提交的情況,另外有些人可能持有以下兩種觀點:首先,在曾經(jīng)使用過的數(shù)據(jù)庫中,對于事務的要求總是盡快地進行釋放,不能有長時間的事務;其次,可能擔心存在Oracle數(shù)據(jù)庫中由于沒有足夠UNDO空間產(chǎn)生的Snapshot Too Old的經(jīng)典問題。MySQL InnoDB存儲引擎都沒有上述兩個問題,因此程序員不論從何種角度出發(fā),都不應該在一個循環(huán)中反復地進行提交操作,不論是顯式的提交還是隱式的提交。
>> 喜歡使用自動回滾的人大多是以前使用Microsoft SQL Server數(shù)據(jù)庫的開發(fā)人員。在Microsoft SQL Server數(shù)據(jù)庫中,可以使用SET XABORT ON來自動回滾一個事務,因為Microsoft SQL Server數(shù)據(jù)庫不僅會自動回滾當前的事務,還會拋出異常,開發(fā)人員可以捕獲到這個異常。
>> 對于長事務的問題,有時可以通過將其轉(zhuǎn)化為小批量(mini batch)的事務來進行處理。當事務發(fā)生錯誤時,只需要回滾一部分數(shù)據(jù),然后接著上次的已完成的事務繼續(xù)進行。
第9章 索引
>> 順序讀取(sequntial read)是指順序地讀取磁盤上的頁。隨機讀取(random read)是指訪問的頁不是連續(xù)的,需要磁盤的磁頭不斷移動。這里需要注意的是,這里的“順序”指的是邏輯上的順序,在物理上不可能保證所有的數(shù)據(jù)都是順序的。
>> 在B+樹索引中,B+樹索引只能找到某條記錄所在的頁,需再根據(jù)二分查找法來進一步找到記錄所在頁的具體位置。
>> 在介紹B+樹前,先要了解一下二叉查找樹。B+樹是通過二叉查找樹,再由平衡二叉樹、B樹演化而來。
>> 若想最大性能地構造一個二叉查找樹,需要這棵二叉查找樹是平衡的,因此引入了新的定義—平衡二叉樹,又稱為AVL樹。
>> 平衡二叉樹的定義如下:首先符合二叉查找樹的定義,其次必須滿足任何節(jié)點的兩棵子樹的高度最大差為1
>> 平衡二叉樹在查找方面的性能是比較高的,但不是最高的,只是接近最高性能。要達到最好的性能需要建立一棵最優(yōu)二叉樹,但是最優(yōu)二叉樹的建立和維護需要大量的操作,因此一般只需建立一棵平衡二叉樹即可。
平衡二叉樹的查詢速度的確很快,但是維護一棵平衡二叉樹的代價非常大,通常需要1次或多次左旋或右旋來得到經(jīng)過插入或更新操作后二叉樹的平衡性
>> 相信在任何一本數(shù)據(jù)結構書中都能找到B+樹的定義,其定義十分復雜,這里列出B+樹的定義只會讓讀者感到更加困惑
>> B+樹是為磁盤或其他直接存取輔助設備設計的一種平衡查找樹,在B+樹中,所有記錄節(jié)點都是按鍵值的大小順序存放在同一層的葉子節(jié)點,各葉子節(jié)點通過指針進行鏈接
>> B+樹的插入要求必須保證插入后葉子節(jié)點中的記錄依然順序排列,同時需要考慮插入到B+樹的3種情況,每種情況都可能導致不同的插入算法,如表9-1所示。
>> 不管怎么變化,B+樹總會保持平衡,但是為了保持平衡需要在插入新的鍵值后做大量的拆分頁(split)操作,而B+樹主要用于磁盤,因此頁的拆分意味著磁盤的操作,應該在可能的情況下減少頁的拆分。為此,B+樹提供了旋轉(zhuǎn)(rotation)的功能。
>> B+樹索引的本質(zhì)就是B+樹在數(shù)據(jù)庫中的實現(xiàn),而B+樹索引在數(shù)據(jù)庫中的一個特點就是高扇出性。例如在InnoDB存儲引擎中,每個頁的大小為16KB。
>> 因此在數(shù)據(jù)庫中,B+樹的高度一般都在2~4層,這意味著查找某一鍵值最多只需要2到4次IO操作,這還不錯。因為現(xiàn)在一般的磁盤每秒至少可以做100次IO操作,2~4次的IO操作意味著查詢時間只需0.02~0.04秒。
>> 在MySQL數(shù)據(jù)庫中,索引是在存儲引擎層實現(xiàn)的,這意味著每個引擎的B+樹索引的實現(xiàn)方式可能是不同的,取決于存儲引擎實現(xiàn)的本身。
B+樹索引可以分為聚集索引與輔助索引(非聚集索引),但是這兩者本身都與之前討論的B+樹的數(shù)據(jù)結構一樣,區(qū)別僅在于所存放數(shù)據(jù)的內(nèi)容。
>> 聚集索引是根據(jù)主鍵創(chuàng)建的一棵B+樹,聚集索引的葉子節(jié)點存放了表中的所有記錄。輔助索引是根據(jù)索引鍵創(chuàng)建的一棵B+樹,與聚集索引不同的是,其葉子節(jié)點僅存放索引鍵值,以及該索引鍵值指向的主鍵。
>> 按性別進行查詢時,可取值的范圍一般只有“M”和“F”,因此上述SQL語句得到的結果可能是該表50%的數(shù)據(jù)(我們假設男女比例1:1),這時添加B+樹索引是完全沒有必要的。相反,如果某個字段的取值范圍很廣,幾乎沒有重復,即是高選擇性的,那么此時使用B+樹索引是最適合的
>> 怎樣查看索引是否是高選擇性的呢?可以通過SHOW INDEX語句中的Cardinality列來觀察。Cardinality值非常關鍵,表示索引中唯一只記錄數(shù)量的預估值。這里需要注意的是, Cardinality是一個預估值,而不是一個準確值,用戶也不可能得到一個準確的值。在實際應用中,Cardinality/n_rows_in_table應盡可能接近1,如果非常小,那么需要考慮是否還要建這個索引。
>> 在OLAP中添加索引依據(jù)的是宏觀的信息,而不是微觀信息,這是因為最終要得到的結果是提供給決策者的。例如不需要在OLAP中對姓名字段進行索引,因為很少會對單個用戶進行查詢。但是對于OLAP中的復雜查詢,需要涉及多張表之間的聯(lián)接操作,這時索引的添加是有意義的
>> 之前的MySQL版本不支持ICP,當進行索引查詢時,首先根據(jù)索引來查找記錄,然后再根據(jù)WHERE條件來過濾記錄。在支持ICP后,MySQL數(shù)據(jù)庫會在取出索引的同時,判斷是否可以進行WHERE條件的過濾,即將WHERE的部分過濾操作放在了存儲引擎層。在某些查詢中,ICP會大大減少上層SQL層對于記錄的索取(fetch),從而提高數(shù)據(jù)庫的整體性能。
>> ICP優(yōu)化支持range、ref、eq_ref和ref_or_null類型的查詢,當前支持MyISAM和InnoDB存儲引擎。當優(yōu)化器選擇ICP優(yōu)化時,可在執(zhí)行計劃的Extra列看到Using index condition提示。
>> 內(nèi)存數(shù)據(jù)庫中,一般使用T樹(T-Tree)作為其索引的數(shù)據(jù)結構
>> T樹的好處是節(jié)點不存放數(shù)據(jù),只存放指針,這樣能減少對內(nèi)存的使用,這對內(nèi)存數(shù)據(jù)庫來說顯得尤為重要。同時T樹也是一棵平衡二叉樹,以此保證查找的性能。T樹中的T指的是T樹中節(jié)點的形狀。
>> 當前MySQL數(shù)據(jù)庫中,Memory存儲引擎支持哈希索引。InnoDB存儲引擎支持自適應哈希索引,用戶僅能開啟該特性,不能對其進行人工干預
第10章 分區(qū)
>> 分區(qū)功能并不是在存儲引擎層完成的,因此不只有InnoDB存儲引擎支持分區(qū),常見的存儲引擎MyISAM、NDB等都支持分區(qū)
>> 分區(qū)的過程是將一個表或索引分解為多個更小、更可管理的部分。就訪問數(shù)據(jù)庫的應用而言,從邏輯上講,只有一個表或一個索引,但是在物理上這個表或索引可能由數(shù)十個物理分區(qū)組成。每個分區(qū)都是獨立的對象,可以獨自處理,也可以作為一個更大對象的一部分進行處理。
>> MySQL數(shù)據(jù)庫支持的分區(qū)類型為水平分區(qū),并不支持垂直分區(qū)。此外,MySQL數(shù)據(jù)庫的分區(qū)是局部分區(qū)索引,一個分區(qū)中既存放數(shù)據(jù)又存放索引。全局分區(qū)是指,數(shù)據(jù)存放各個分區(qū)中,但是所有數(shù)據(jù)的索引放在一個對象中。
>> 目前,MySQL數(shù)據(jù)庫暫時不支持全局分區(qū)。
>> 不論創(chuàng)建何種類型的分區(qū),如果表中存在主鍵
>> 或唯一索引時,分區(qū)列必須是唯一索引的一個組成部分,
>> 唯一索引可以是NULL值,并且只要求分區(qū)列是唯一索引的一個組成部分,不需要整個唯一索引列都是分區(qū)列
>> 查看表在磁盤上的物理文件,啟用分區(qū)之后,表不再由一個ibd文件組成,而是由建立分區(qū)時的各個分區(qū)ibd文件組成,比如下面的t#P#p0.ibd、t#P#p1.ibd。
>> 通過EXPLAIN PARTITION命令我們可以發(fā)現(xiàn),在上述語句中,SQL優(yōu)化器只需要搜索p2008這個分區(qū),而不會搜索所有的分區(qū)—稱為Partition Pruning(分區(qū)修剪),故查詢的速度得到了大幅度提升。
>> 產(chǎn)生這個問題的主要原因是,對RANGE分區(qū)的查詢,優(yōu)化器只能對YEAR()、TO_DAYS()、TO_SECONDS()和UNIX_TIMESTAMP()這類函數(shù)進行優(yōu)化選擇
>> 在執(zhí)行INSERT操作插入多個行數(shù)據(jù)的過程中如果遇到分區(qū)未定義的值, MyISAM和InnoDB存儲引擎的處理會完全不同。MyISAM引擎會將之前的行數(shù)據(jù)都插入,但之后的數(shù)據(jù)不會被插入。而InnoDB存儲引擎將其視為一個事務,沒有任何數(shù)據(jù)被插入
>> MySQL數(shù)據(jù)庫允許對NULL值進行分區(qū),但是處理方法可能與其他數(shù)據(jù)庫完全不同。MySQL數(shù)據(jù)庫的分區(qū)總是把NULL值視為小于任何的一個非NULL值,這和MySQL數(shù)據(jù)庫中處理NULL值的ORDER BY操作是一樣的。
>> 因此對于不同的分區(qū)類型,MySQL數(shù)據(jù)庫對NULL值的處理也各不相同。
>> 如果一百萬行和一千萬行的數(shù)據(jù)本身構成的B+樹的層次是一樣的,可能都是兩層,那么上述主鍵分區(qū)的索引并不會帶來性能的提高。假設一千萬行數(shù)據(jù)的B+樹的高度是3,一百萬行數(shù)據(jù)的B+樹的高度是2,這樣上述主鍵分區(qū)的索引可以避免一次IO,從而提高查詢的效率。
>> MySQL 5.6開始支持ALTER TABLE ... EXCHANGE PARTITION語法。該語句允許分區(qū)或子分區(qū)中的數(shù)據(jù)與另一個非分區(qū)的表中數(shù)據(jù)進行交換。如果非分區(qū)表的數(shù)據(jù)為空,那么相當于將分區(qū)中的數(shù)據(jù)移動到非分區(qū)表中。若分區(qū)表的數(shù)據(jù)為空,則相當于將外部表中的數(shù)據(jù)導入分區(qū)中。
>> 交換的表須與分區(qū)表有相同的表結構,但是表不能含有分區(qū)。