主頁 > 資料庫 > MySQL索引背后的資料結構及演算法原理

MySQL索引背后的資料結構及演算法原理

2020-09-17 02:09:58 資料庫

        本文(面對的是程式員而非專業資料庫管理員DBA)以MySQL資料庫為研究物件,討論與資料庫索引相關的一些話題,特別需要說明的是,MySQL支持諸多存盤引擎,而各種存盤引擎對索引的支持也各不相同,因此MySQL資料庫支持多種索引型別,如BTree索引,哈希索引,全文索引等等,為了避免混亂,本文將只關注于BTree索引,因為這是平常使用MySQL時主要打交道的索引,至于哈希索引和全文索引本文暫不討論,

文章主要內容分為三個部分,

第一部分主要從資料結構及演算法理論層面討論MySQL資料庫索引的數理基礎,

第二部分結合MySQL資料庫中MyISAM和InnoDB資料存盤引擎中索引的架構實作討論聚集索引、非聚集索引及覆寫索引等話題,

第三部分根據上面的理論基礎,討論MySQL中高性能使用索引的策略,

資料結構及演算法基礎

索引的本質

MySQL官方對索引的定義為:索引(Index)是幫助MySQL高效獲取資料的資料結構,提取句子主干,就可以得到索引的本質:索引是資料結構,

我們知道,資料庫查詢是資料庫的最主要功能之一,我們都希望查詢資料的速度能盡可能的快,因此資料庫系統的設計者會從查詢演算法的角度進行優化,最基本的查詢演算法當然是順序查找(linear search),這種復雜度為O(n)的演算法在資料量很大時顯然是糟糕的,好在計算機科學的發展提供了很多更優秀的查找演算法,例如二分查找(binary search)、二叉樹查找(binary tree search)等,如果稍微分析一下會發現,每種查找演算法都只能應用于特定的資料結構之上,例如二分查找要求被檢索資料有序,而二叉樹查找只能應用于二叉查找樹上,但是資料本身的組織結構不可能完全滿足各種資料結構(例如,理論上不可能同時將兩列都按順序進行組織),所以,在資料之外,資料庫系統還維護著滿足特定查找演算法的資料結構,這些資料結構以某種方式參考(指向)資料,這樣就可以在這些資料結構上實作高級查找演算法,這種資料結構,就是索引,

看一個例子:

圖1

圖1展示了一種可能的索引方式,左邊是資料表,一共有兩列七條記錄,最左邊的是資料記錄的物理地址(注意邏輯上相鄰的記錄在磁盤上也并不是一定物理相鄰的),為了加快Col2的查找,可以維護一個右邊所示的二叉查找樹,每個節點分別包含索引鍵值和一個指向對應資料記錄物理地址的指標,這樣就可以運用二叉查找在O(log2n)O(log2n)的復雜度內獲取到相應資料,

雖然這是一個貨真價實的索引,但是實際的資料庫系統幾乎沒有使用二叉查找樹或其進化品種紅黑樹(red-black tree)實作的,原因會在下文介紹,

B-Tree和B+Tree(重點)

目前大部分資料庫系統及檔案系統都采用B-Tree或其變種B+Tree作為索引結構,在本文的下一節會結合存盤器原理及計算機存取原理討論為什么B-Tree和B+Tree在被如此廣泛用于索引,這一節先單純從資料結構角度描述它們,

B-Tree

為了描述B-Tree,首先定義一條資料記錄為一個二元組[key, data],key為記錄的鍵值,對于不同資料記錄,key是互不相同的;data為資料記錄除key外的資料,那么B-Tree是滿足下列條件的資料結構:

d為大于1的一個正整數,稱為B-Tree的度,

h為一個正整數,稱為B-Tree的高度,

每個非葉子節點由n-1個key和n個指標組成,其中d<=n<=2d,

每個葉子節點最少包含一個key和兩個指標,最多包含2d-1個key和2d個指標,葉節點的指標均為null ,

所有葉節點具有相同的深度,等于樹高h,

key和指標互相間隔,節點兩端是指標,

一個節點中的key從左到右非遞減排列,

所有節點組成樹結構,

每個指標要么為null,要么指向另外一個節點,

如果某個指標在節點node最左邊且不為null,則其指向節點的所有key小于v(key1)v(key1),其中v(key1)v(key1)為node的第一個key的值,

如果某個指標在節點node最右邊且不為null,則其指向節點的所有key大于v(keym)v(keym),其中v(keym)v(keym)為node的最后一個key的值,

如果某個指標在節點node的左右相鄰key分別是keyikeyi和keyi+1keyi+1且不為null,則其指向節點的所有key小于v(keyi+1)v(keyi+1)且大于v(keyi)v(keyi),

圖2是一個d=2的B-Tree示意圖,

圖2

由于B-Tree的特性,在B-Tree中按key檢索資料的演算法非常直觀:首先從根節點進行二分查找,如果找到則回傳對應節點的data,否則對相應區間的指標指向的節點遞回進行查找,直到找到節點或找到null指標,前者查找成功,后者查找失敗,B-Tree上查找演算法的偽代碼如下:

  1. BTree_Search(node, key) {
  2. if(node == null) return null;
  3. foreach(node.key)
  4. {
  5. if(node.key[i] == key) return node.data[i];
  6. if(node.key[i] > key) return BTree_Search(point[i]->node);
  7. }
  8. return BTree_Search(point[i+1]->node);
  9. }
  10. data = BTree_Search(root, my_key);

關于B-Tree有一系列有趣的性質,例如一個度為d的B-Tree,設其索引N個key,則其樹高h的上限為logd((N+1)/2)logd((N+1)/2),檢索一個key,其查找節點個數的漸進復雜度為O(logdN)O(logdN),從這點可以看出,B-Tree是一個非常有效率的索引資料結構,

 

另外,由于插入洗掉新的資料記錄會破壞B-Tree的性質,因此在插入洗掉時,需要對樹進行一個分裂、合并、轉移等操作以保持B-Tree性質,本文不打算完整討論B-Tree這些內容,因為已經有許多資料詳細說明了B-Tree的數學性質及插入洗掉演算法,有興趣的朋友可以在本文末的參考文獻一欄找到相應的資料進行閱讀,

B+Tree(狠狠狠重要)

B-Tree有許多變種,其中最常見的是B+Tree,例如MySQL就普遍使用B+Tree實作其索引結構,

與B-Tree相比,B+Tree有以下不同點:

每個節點的指標上限為2d而不是2d+1,

內節點不存盤data,只存盤key;葉子節點不存盤指標,

圖3是一個簡單的B+Tree示意,

圖3

由于并不是所有節點都具有相同的域,因此B+Tree中葉節點和內節點一般大小不同,這點與B-Tree不同,雖然B-Tree中不同節點存放的key和指標可能數量不一致,但是每個節點的域和上限是一致的,所以在實作中B-Tree往往對每個節點申請同等大小的空間,

一般來說,B+Tree比B-Tree更適合實作外存盤索引結構,具體原因與外存盤器原理及計算機存取原理有關,將在下面討論,

帶有順序訪問指標的B+Tree

一般在資料庫系統或檔案系統中使用的B+Tree結構都在經典B+Tree的基礎上進行了優化,增加了順序訪問指標,

圖4

如圖4所示,在B+Tree的每個葉子節點增加一個指向相鄰葉子節點的指標,就形成了帶有順序訪問指標的B+Tree,做這個優化的目的是為了提高區間訪問的性能,例如圖4中如果要查詢key為從18到49的所有資料記錄,當找到18后,只需順著節點和指標順序遍歷就可以一次性訪問到所有資料節點,極大提到了區間查詢效率,

這一節對B-Tree和B+Tree進行了一個簡單的介紹,下一節結合存盤器存取原理介紹為什么目前B+Tree是資料庫系統實作索引的首選資料結構,

為什么使用B-Tree(B+Tree)

上文說過,紅黑樹等資料結構也可以用來實作索引,但是檔案系統及資料庫系統普遍采用B-/+Tree作為索引結構,這一節將結合計算機組成原理相關知識討論B-/+Tree作為索引的理論基礎,

一般來說,索引本身也很大,不可能全部存盤在記憶體中,因此索引往往以索引檔案的形式存盤的磁盤上,這樣的話,索引查找程序中就要產生磁盤I/O消耗,相對于記憶體存取,I/O存取的消耗要高幾個數量級,所以評價一個資料結構作為索引的優劣最重要的指標就是在查找程序中磁盤I/O操作次數的漸進復雜度,換句話說,索引的結構組織要盡量減少查找程序中磁盤I/O的存取次數,下面先介紹記憶體和磁盤存取原理,然后再結合這些原理分析B-/+Tree作為索引的效率,

主存存取原理

目前計算機使用的主存基本都是隨機讀寫存盤器(RAM),現代RAM的結構和存取原理比較復雜,這里本文拋卻具體差別,抽象出一個十分簡單的存取模型來說明RAM的作業原理,

圖5

從抽象角度看,主存是一系列的存盤單元組成的矩陣,每個存盤單元存盤固定大小的資料,每個存盤單元有唯一的地址,現代主存的編址規則比較復雜,這里將其簡化成一個二維地址:通過一個行地址和一個列地址可以唯一定位到一個存盤單元,圖5展示了一個4 x 4的主存模型,

主存的存取程序如下:

當系統需要讀取主存時,則將地址信號放到地址總線上傳給主存,主存讀到地址信號后,決議信號并定位到指定存盤單元,然后將此存盤單元資料放到資料總線上,供其它部件讀取,

寫主存的程序類似,系統將要寫入單元地址和資料分別放在地址總線和資料總線上,主存讀取兩個總線的內容,做相應的寫操作,

這里可以看出,主存存取的時間僅與存取次數呈線性關系,因為不存在機械操作,兩次存取的資料的“距離”不會對時間有任何影響,例如,先取A0再取A1和先取A0再取D3的時間消耗是一樣的,

磁盤存取原理

上文說過,索引一般以檔案形式存盤在磁盤上,索引檢索需要磁盤I/O操作,與主存不同,磁盤I/O存在機械運動耗費,因此磁盤I/O的時間消耗是巨大的,

圖6是磁盤的整體結構示意圖,

圖6

一個磁盤由大小相同且同軸的圓形盤片組成,磁盤可以轉動(各個磁盤必須同步轉動),在磁盤的一側有磁頭支架,磁頭支架固定了一組磁頭,每個磁頭負責存取一個磁盤的內容,磁頭不能轉動,但是可以沿磁盤半徑方向運動(實際是斜切向運動),每個磁頭同一時刻也必須是同軸的,即從正上方向下看,所有磁頭任何時候都是重疊的(不過目前已經有多磁頭獨立技術,可不受此限制),

圖7是磁盤結構的示意圖,

圖7

盤片被劃分成一系列同心環,圓心是盤片中心,每個同心環叫做一個磁道,所有半徑相同的磁道組成一個柱面,磁道被沿半徑線劃分成一個個小的段,每個段叫做一個扇區,每個扇區是磁盤的最小存盤單元,為了簡單起見,我們下面假設磁盤只有一個盤片和一個磁頭,

當需要從磁盤讀取資料時,系統會將資料邏輯地址傳給磁盤,磁盤的控制電路按照尋址邏輯將邏輯地址翻譯成物理地址,即確定要讀的資料在哪個磁道,哪個扇區,為了讀取這個扇區的資料,需要將磁頭放到這個扇區上方,為了實作這一點,磁頭需要移動對準相應磁道,這個程序叫做尋道,所耗費時間叫做尋道時間,然后磁盤旋轉將目標扇區旋轉到磁頭下,這個程序耗費的時間叫做旋轉時間,

區域性原理與磁盤預讀

由于存盤介質的特性,磁盤本身存取就比主存慢很多,再加上機械運動耗費,磁盤的存取速度往往是主存的幾百分分之一,因此為了提高效率,要盡量減少磁盤I/O,為了達到這個目的,磁盤往往不是嚴格按需讀取,而是每次都會預讀,即使只需要一個位元組,磁盤也會從這個位置開始,順序向后讀取一定長度的資料放入記憶體,這樣做的理論依據是計算機科學中著名的區域性原理:

當一個資料被用到時,其附近的資料也通常會馬上被使用,

程式運行期間所需要的資料通常比較集中,

由于磁盤順序讀取的效率很高(不需要尋道時間,只需很少的旋轉時間),因此對于具有區域性的程式來說,預讀可以提高I/O效率,

預讀的長度一般為頁(page)的整倍數,頁是計算機管理存盤器的邏輯塊,硬體及作業系統往往將主存和磁盤存盤區分割為連續的大小相等的塊,每個存盤塊稱為一頁(在許多作業系統中,頁得大小通常為4k),主存和磁盤以頁為單位交換資料,當程式要讀取的資料不在主存中時,會觸發一個缺頁例外,此時系統會向磁盤發出讀盤信號,磁盤會找到資料的起始位置并向后連續讀取一頁或幾頁載入記憶體中,然后例外回傳,程式繼續運行,

B-/+Tree索引的性能分析

到這里終于可以分析B-/+Tree索引的性能了,

上文說過一般使用磁盤I/O次數評價索引結構的優劣,先從B-Tree分析,根據B-Tree的定義,可知檢索一次最多需要訪問h個節點,資料庫系統的設計者巧妙利用了磁盤預讀原理,將一個節點的大小設為等于一個頁,這樣每個節點只需要一次I/O就可以完全載入,為了達到這個目的,在實際實作B-Tree還需要使用如下技巧:

每次新建節點時,直接申請一個頁的空間,這樣就保證一個節點物理上也存盤在一個頁里,加之計算機存盤分配都是按頁對齊的,就實作了一個node只需一次I/O,

B-Tree中一次檢索最多需要h-1次I/O(根節點常駐記憶體),漸進復雜度為O(h)=O(logdN)O(h)=O(logdN),一般實際應用中,出度d是非常大的數字,通常超過100,因此h非常小(通常不超過3),

綜上所述,用B-Tree作為索引結構效率是非常高的,

而紅黑樹這種結構,h明顯要深的多,由于邏輯上很近的節點(父子)物理上可能很遠,無法利用區域性,所以紅黑樹的I/O漸進復雜度也為O(h),效率明顯比B-Tree差很多,

上文還說過,B+Tree更適合外存索引,原因和內節點出度d有關,從上面分析可以看到,d越大索引的性能越好,而出度的上限取決于節點內key和data的大小:

dmax=floor(pagesize/(keysize+datasize+pointsize))dmax=floor(pagesize/(keysize+datasize+pointsize))

floor表示向下取整,由于B+Tree內節點去掉了data域,因此可以擁有更大的出度,擁有更好的性能,

這一章從理論角度討論了與索引相關的資料結構與演算法問題,下一章將討論B+Tree是如何具體實作為MySQL中索引,同時將結合MyISAM和InnDB存盤引擎介紹非聚集索引和聚集索引兩種不同的索引實作形式,

MySQL索引實作

在MySQL中,索引屬于存盤引擎級別的概念,不同存盤引擎對索引的實作方式是不同的,本文主要討論MyISAM和InnoDB兩個存盤引擎的索引實作方式,

MyISAM索引實作

MyISAM引擎使用B+Tree作為索引結構,葉節點的data域存放的是資料記錄的地址,下圖是MyISAM索引的原理圖:

圖8

這里設表一共有三列,假設我們以Col1為主鍵,則圖8是一個MyISAM表的主索引(Primary key)示意,可以看出MyISAM的索引檔案僅僅保存資料記錄的地址,在MyISAM中,主索引和輔助索引(Secondary key)在結構上沒有任何區別,只是主索引要求key是唯一的,而輔助索引的key可以重復,如果我們在Col2上建立一個輔助索引,則此索引的結構如下圖所示:

圖9

同樣也是一顆B+Tree,data域保存資料記錄的地址,因此,MyISAM中索引檢索的演算法為首先按照B+Tree搜索演算法搜索索引,如果指定的Key存在,則取出其data域的值,然后以data域的值為地址,讀取相應資料記錄,

MyISAM的索引方式也叫做“非聚集”的,之所以這么稱呼是為了與InnoDB的聚集索引區分,

InnoDB索引實作(狠狠狠狠重點)

雖然InnoDB也使用B+Tree作為索引結構,但具體實作方式卻與MyISAM截然不同,

第一個重大區別是InnoDB的資料檔案本身就是索引檔案,從上文知道,MyISAM索引檔案和資料檔案是分離的,索引檔案僅保存資料記錄的地址,而在InnoDB中,表資料檔案本身就是按B+Tree組織的一個索引結構,這棵樹的葉節點data域保存了完整的資料記錄,這個索引的key是資料表的主鍵,因此InnoDB表資料檔案本身就是主索引,

圖10

圖10是InnoDB主索引(同時也是資料檔案)的示意圖,可以看到葉節點包含了完整的資料記錄,這種索引叫做聚集索引,因為InnoDB的資料檔案本身要按主鍵聚集,所以InnoDB要求表必須有主鍵(MyISAM可以沒有),如果沒有顯式指定,則MySQL系統會自動選擇一個可以唯一標識資料記錄的列作為主鍵,如果不存在這種列,則MySQL自動為InnoDB表生成一個隱含欄位作為主鍵,這個欄位長度為6個位元組,型別為長整形,

第二個與MyISAM索引的不同是InnoDB的輔助索引data域存盤相應記錄主鍵的值而不是地址,換句話說,InnoDB的所有輔助索引都參考主鍵作為data域,例如,圖11為定義在Col3上的一個輔助索引:

圖11

這里以英文字符的ASCII碼作為比較準則,聚集索引這種實作方式使得按主鍵的搜索十分高效,但是輔助索引搜索需要檢索兩遍索引:首先檢索輔助索引獲得主鍵,然后用主鍵到主索引中檢索獲得記錄,

了解不同存盤引擎的索引實作方式對于正確使用和優化索引都非常有幫助,例如知道了InnoDB的索引實作后,就很容易明白為什么不建議使用過長的欄位作為主鍵,因為所有輔助索引都參考主索引,過長的主索引會令輔助索引變得過大,再例如,用非單調的欄位作為主鍵在InnoDB中不是個好主意,因為InnoDB資料檔案本身是一顆B+Tree,非單調的主鍵會造成在插入新記錄時資料檔案為了維持B+Tree的特性而頻繁的分裂調整,十分低效,而使用自增欄位作為主鍵則是一個很好的選擇,

下一章將具體討論這些與索引有關的優化策略,

索引使用策略及優化

MySQL的優化主要分為結構優化(Scheme optimization)和查詢優化(Query optimization),本章討論的高性能索引策略主要屬于結構優化范疇,本章的內容完全基于上文的理論基礎,實際上一旦理解了索引背后的機制,那么選擇高性能的策略就變成了純粹的推理,并且可以理解這些策略背后的邏輯,

示例資料庫

為了討論索引策略,需要一個資料量不算小的資料庫作為示例,本文選用MySQL官方檔案中提供的示例資料庫之一:employees,這個資料庫關系復雜度適中,且資料量較大,下圖是這個資料庫的E-R關系圖(參考自MySQL官方手冊):

圖12

MySQL官方檔案中關于此資料庫的頁面為http://dev.mysql.com/doc/employee/en/employee.html,里面詳細介紹了此資料庫,并提供了下載地址和匯入方法,如果有興趣匯入此資料庫到自己的MySQL可以參考文中內容,

最左前綴原理與相關優化

高效使用索引的首要條件是知道什么樣的查詢會使用到索引,這個問題和B+Tree中的“最左前綴原理”有關,下面通過例子說明最左前綴原理,

這里先說一下聯合索引的概念,在上文中,我們都是假設索引只參考了單個的列,實際上,MySQL中的索引可以以一定順序參考多個列,這種索引叫做聯合索引,一般的,一個聯合索引是一個有序元組<a1, a2, …, an>,其中各個元素均為資料表的一列,實際上要嚴格定義索引需要用到關系代數,但是這里我不想討論太多關系代數的話題,因為那樣會顯得很枯燥,所以這里就不再做嚴格定義,另外,單列索引可以看成聯合索引元素數為1的特例,

以employees.titles表為例,下面先查看其上都有哪些索引:

  1. SHOW INDEX FROM employees.titles;
  2. +--------+------------+----------+--------------+-------------+-----------+-------------+------+------------+
  3. | Table | Non_unique | Key_name | Seq_in_index | Column_name | Collation | Cardinality | Null | Index_type |
  4. +--------+------------+----------+--------------+-------------+-----------+-------------+------+------------+
  5. | titles | 0 | PRIMARY | 1 | emp_no | A | NULL | | BTREE |
  6. | titles | 0 | PRIMARY | 2 | title | A | NULL | | BTREE |
  7. | titles | 0 | PRIMARY | 3 | from_date | A | 443308 | | BTREE |
  8. | titles | 1 | emp_no | 1 | emp_no | A | 443308 | | BTREE |
  9. +--------+------------+----------+--------------+-------------+-----------+-------------+------+------------+

從結果中可以到titles表的主索引為<emp_no, title, from_date>,還有一個輔助索引<emp_no>,為了避免多個索引使事情變復雜(MySQL的SQL優化器在多索引時行為比較復雜),這里我們將輔助索引drop掉:

  1. ALTER TABLE employees.titles DROP INDEX emp_no;

這樣就可以專心分析索引PRIMARY的行為了,

情況一:全列匹配,

  1. EXPLAIN SELECT * FROM employees.titles WHERE emp_no='10001' AND title='Senior Engineer' AND from_date='1986-06-26';
  2. +----+-------------+--------+-------+---------------+---------+---------+-------------------+------+-------+
  3. | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
  4. +----+-------------+--------+-------+---------------+---------+---------+-------------------+------+-------+
  5. | 1 | SIMPLE | titles | const | PRIMARY | PRIMARY | 59 | const,const,const | 1 | |
  6. +----+-------------+--------+-------+---------------+---------+---------+-------------------+------+-------+

很明顯,當按照索引中所有列進行精確匹配(這里精確匹配指“=”或“IN”匹配)時,索引可以被用到,這里有一點需要注意,理論上索引對順序是敏感的,但是由于MySQL的查詢優化器會自動調整where子句的條件順序以使用適合的索引,例如我們將where中的條件順序顛倒:

  1. EXPLAIN SELECT * FROM employees.titles WHERE from_date='1986-06-26' AND emp_no='10001' AND title='Senior Engineer';
  2. +----+-------------+--------+-------+---------------+---------+---------+-------------------+------+-------+
  3. | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
  4. +----+-------------+--------+-------+---------------+---------+---------+-------------------+------+-------+
  5. | 1 | SIMPLE | titles | const | PRIMARY | PRIMARY | 59 | const,const,const | 1 | |
  6. +----+-------------+--------+-------+---------------+---------+---------+-------------------+------+-------+

效果是一樣的,

情況二:最左前綴匹配,

  1. EXPLAIN SELECT * FROM employees.titles WHERE emp_no='10001';
  2. +----+-------------+--------+------+---------------+---------+---------+-------+------+-------+
  3. | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
  4. +----+-------------+--------+------+---------------+---------+---------+-------+------+-------+
  5. | 1 | SIMPLE | titles | ref | PRIMARY | PRIMARY | 4 | const | 1 | |
  6. +----+-------------+--------+------+---------------+---------+---------+-------+------+-------+

當查詢條件精確匹配索引的左邊連續一個或幾個列時,如<emp_no>或<emp_no, title>,所以可以被用到,但是只能用到一部分,即條件所組成的最左前綴,上面的查詢從分析結果看用到了PRIMARY索引,但是key_len為4,說明只用到了索引的第一列前綴,

情況三:查詢條件用到了索引中列的精確匹配,但是中間某個條件未提供,

  1. EXPLAIN SELECT * FROM employees.titles WHERE emp_no='10001' AND from_date='1986-06-26';
  2. +----+-------------+--------+------+---------------+---------+---------+-------+------+-------------+
  3. | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
  4. +----+-------------+--------+------+---------------+---------+---------+-------+------+-------------+
  5. | 1 | SIMPLE | titles | ref | PRIMARY | PRIMARY | 4 | const | 1 | Using where |
  6. +----+-------------+--------+------+---------------+---------+---------+-------+------+-------------+

此時索引使用情況和情況二相同,因為title未提供,所以查詢只用到了索引的第一列,而后面的from_date雖然也在索引中,但是由于title不存在而無法和左前綴連接,因此需要對結果進行掃描過濾from_date(這里由于emp_no唯一,所以不存在掃描),如果想讓from_date也使用索引而不是where過濾,可以增加一個輔助索引<emp_no, from_date>,此時上面的查詢會使用這個索引,除此之外,還可以使用一種稱之為“隔離列”的優化方法,將emp_no與from_date之間的“坑”填上,

首先我們看下title一共有幾種不同的值:

  1. SELECT DISTINCT(title) FROM employees.titles;
  2. +--------------------+
  3. | title |
  4. +--------------------+
  5. | Senior Engineer |
  6. | Staff |
  7. | Engineer |
  8. | Senior Staff |
  9. | Assistant Engineer |
  10. | Technique Leader |
  11. | Manager |
  12. +--------------------+

只有7種,在這種成為“坑”的列值比較少的情況下,可以考慮用“IN”來填補這個“坑”從而形成最左前綴:

  1. EXPLAIN SELECT * FROM employees.titles
  2. WHERE emp_no='10001'
  3. AND title IN ('Senior Engineer', 'Staff', 'Engineer', 'Senior Staff', 'Assistant Engineer', 'Technique Leader', 'Manager')
  4. AND from_date='1986-06-26';
  5. +----+-------------+--------+-------+---------------+---------+---------+------+------+-------------+
  6. | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
  7. +----+-------------+--------+-------+---------------+---------+---------+------+------+-------------+
  8. | 1 | SIMPLE | titles | range | PRIMARY | PRIMARY | 59 | NULL | 7 | Using where |
  9. +----+-------------+--------+-------+---------------+---------+---------+------+------+-------------+

這次key_len為59,說明索引被用全了,但是從type和rows看出IN實際上執行了一個range查詢,這里檢查了7個key,看下兩種查詢的性能比較:

  1. SHOW PROFILES;
  2. +----------+------------+-------------------------------------------------------------------------------+
  3. | Query_ID | Duration | Query |
  4. +----------+------------+-------------------------------------------------------------------------------+
  5. | 10 | 0.00058000 | SELECT * FROM employees.titles WHERE emp_no='10001' AND from_date='1986-06-26'|
  6. | 11 | 0.00052500 | SELECT * FROM employees.titles WHERE emp_no='10001' AND title IN ... |
  7. +----------+------------+-------------------------------------------------------------------------------+

“填坑”后性能提升了一點,如果經過emp_no篩選后余下很多資料,則后者性能優勢會更加明顯,當然,如果title的值很多,用填坑就不合適了,必須建立輔助索引,

情況四:查詢條件沒有指定索引第一列,

  1. EXPLAIN SELECT * FROM employees.titles WHERE from_date='1986-06-26';
  2. +----+-------------+--------+------+---------------+------+---------+------+--------+-------------+
  3. | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
  4. +----+-------------+--------+------+---------------+------+---------+------+--------+-------------+
  5. | 1 | SIMPLE | titles | ALL | NULL | NULL | NULL | NULL | 443308 | Using where |
  6. +----+-------------+--------+------+---------------+------+---------+------+--------+-------------+

由于不是最左前綴,索引這樣的查詢顯然用不到索引,

情況五:匹配某列的前綴字串,

  1. EXPLAIN SELECT * FROM employees.titles WHERE emp_no='10001' AND title LIKE 'Senior%';
  2. +----+-------------+--------+-------+---------------+---------+---------+------+------+-------------+
  3. | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
  4. +----+-------------+--------+-------+---------------+---------+---------+------+------+-------------+
  5. | 1 | SIMPLE | titles | range | PRIMARY | PRIMARY | 56 | NULL | 1 | Using where |
  6. +----+-------------+--------+-------+---------------+---------+---------+------+------+-------------+

此時可以用到索引,但是如果通配符不是只出現在末尾,則無法使用索引,(原文表述有誤,如果通配符%不出現在開頭,則可以用到索引,但根據具體情況不同可能只會用其中一個前綴)

情況六:范圍查詢,

  1. EXPLAIN SELECT * FROM employees.titles WHERE emp_no < '10010' and title='Senior Engineer';
  2. +----+-------------+--------+-------+---------------+---------+---------+------+------+-------------+
  3. | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
  4. +----+-------------+--------+-------+---------------+---------+---------+------+------+-------------+
  5. | 1 | SIMPLE | titles | range | PRIMARY | PRIMARY | 4 | NULL | 16 | Using where |
  6. +----+-------------+--------+-------+---------------+---------+---------+------+------+-------------+

范圍列可以用到索引(必須是最左前綴),但是范圍列后面的列無法用到索引,同時,索引最多用于一個范圍列,因此如果查詢條件中有兩個范圍列則無法全用到索引,

  1. EXPLAIN SELECT * FROM employees.titles
  2. WHERE emp_no < '10010'
  3. AND title='Senior Engineer'
  4. AND from_date BETWEEN '1986-01-01' AND '1986-12-31';
  5. +----+-------------+--------+-------+---------------+---------+---------+------+------+-------------+
  6. | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
  7. +----+-------------+--------+-------+---------------+---------+---------+------+------+-------------+
  8. | 1 | SIMPLE | titles | range | PRIMARY | PRIMARY | 4 | NULL | 16 | Using where |
  9. +----+-------------+--------+-------+---------------+---------+---------+------+------+-------------+

可以看到索引對第二個范圍索引無能為力,這里特別要說明MySQL一個有意思的地方,那就是僅用explain可能無法區分范圍索引和多值匹配,因為在type中這兩者都顯示為range,同時,用了“between”并不意味著就是范圍查詢,例如下面的查詢:

  1. EXPLAIN SELECT * FROM employees.titles
  2. WHERE emp_no BETWEEN '10001' AND '10010'
  3. AND title='Senior Engineer'
  4. AND from_date BETWEEN '1986-01-01' AND '1986-12-31';
  5. +----+-------------+--------+-------+---------------+---------+---------+------+------+-------------+
  6. | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
  7. +----+-------------+--------+-------+---------------+---------+---------+------+------+-------------+
  8. | 1 | SIMPLE | titles | range | PRIMARY | PRIMARY | 59 | NULL | 16 | Using where |
  9. +----+-------------+--------+-------+---------------+---------+---------+------+------+-------------+

看起來是用了兩個范圍查詢,但作用于emp_no上的“BETWEEN”實際上相當于“IN”,也就是說emp_no實際是多值精確匹配,可以看到這個查詢用到了索引全部三個列,因此在MySQL中要謹慎地區分多值匹配和范圍匹配,否則會對MySQL的行為產生困惑,

情況七:查詢條件中含有函式或運算式,

很不幸,如果查詢條件中含有函式或運算式,則MySQL不會為這列使用索引(雖然某些在數學意義上可以使用),例如:

  1. EXPLAIN SELECT * FROM employees.titles WHERE emp_no='10001' AND left(title, 6)='Senior';
  2. +----+-------------+--------+------+---------------+---------+---------+-------+------+-------------+
  3. | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
  4. +----+-------------+--------+------+---------------+---------+---------+-------+------+-------------+
  5. | 1 | SIMPLE | titles | ref | PRIMARY | PRIMARY | 4 | const | 1 | Using where |
  6. +----+-------------+--------+------+---------------+---------+---------+-------+------+-------------+

雖然這個查詢和情況五中功能相同,但是由于使用了函式left,則無法為title列應用索引,而情況五中用LIKE則可以,再如:

 

  1. EXPLAIN SELECT * FROM employees.titles WHERE emp_no - 1='10000';
  2. +----+-------------+--------+------+---------------+------+---------+------+--------+-------------+
  3. | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
  4. +----+-------------+--------+------+---------------+------+---------+------+--------+-------------+
  5. | 1 | SIMPLE | titles | ALL | NULL | NULL | NULL | NULL | 443308 | Using where |
  6. +----+-------------+--------+------+---------------+------+---------+------+--------+-------------+

顯然這個查詢等價于查詢emp_no為10001的函式,但是由于查詢條件是一個運算式,MySQL無法為其使用索引,看來MySQL還沒有智能到自動優化常量運算式的程度,因此在寫查詢陳述句時盡量避免運算式出現在查詢中,而是先手工私下代數運算,轉換為無運算式的查詢陳述句,

索引選擇性與前綴索引

既然索引可以加快查詢速度,那么是不是只要是查詢陳述句需要,就建上索引?答案是否定的,因為索引雖然加快了查詢速度,但索引也是有代價的:索引檔案本身要消耗存盤空間,同時索引會加重插入、洗掉和修改記錄時的負擔,另外,MySQL在運行時也要消耗資源維護索引,因此索引并不是越多越好,一般兩種情況下不建議建索引,

第一種情況是表記錄比較少,例如一兩千條甚至只有幾百條記錄的表,沒必要建索引,讓查詢做全表掃描就好了,至于多少條記錄才算多,這個個人有個人的看法,我個人的經驗是以2000作為分界線,記錄數不超過 2000可以考慮不建索引,超過2000條可以酌情考慮索引,

另一種不建議建索引的情況是索引的選擇性較低,所謂索引的選擇性(Selectivity),是指不重復的索引值(也叫基數,Cardinality)與表記錄數(#T)的比值:

Index Selectivity = Cardinality / #T

顯然選擇性的取值范圍為(0, 1],選擇性越高的索引價值越大,這是由B+Tree的性質決定的,例如,上文用到的employees.titles表,如果title欄位經常被單獨查詢,是否需要建索引,我們看一下它的選擇性:

  1. SELECT count(DISTINCT(title))/count(*) AS Selectivity FROM employees.titles;
  2. +-------------+
  3. | Selectivity |
  4. +-------------+
  5. | 0.0000 |
  6. +-------------+

title的選擇性不足0.0001(精確值為0.00001579),所以實在沒有什么必要為其單獨建索引,

有一種與索引選擇性有關的索引優化策略叫做前綴索引,就是用列的前綴代替整個列作為索引key,當前綴長度合適時,可以做到既使得前綴索引的選擇性接近全列索引,同時因為索引key變短而減少了索引檔案的大小和維護開銷,下面以employees.employees表為例介紹前綴索引的選擇和使用,

從圖12可以看到employees表只有一個索引<emp_no>,那么如果我們想按名字搜索一個人,就只能全表掃描了:

  1. EXPLAIN SELECT * FROM employees.employees WHERE first_name='Eric' AND last_name='Anido';
  2. +----+-------------+-----------+------+---------------+------+---------+------+--------+-------------+
  3. | id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
  4. +----+-------------+-----------+------+---------------+------+---------+------+--------+-------------+
  5. | 1 | SIMPLE | employees | ALL | NULL | NULL | NULL | NULL | 300024 | Using where |
  6. +----+-------------+-----------+------+---------------+------+---------+------+--------+-------------+

如果頻繁按名字搜索員工,這樣顯然效率很低,因此我們可以考慮建索引,有兩種選擇,建<first_name>或<first_name, last_name>,看下兩個索引的選擇性:

  1. SELECT count(DISTINCT(first_name))/count(*) AS Selectivity FROM employees.employees;
  2. +-------------+
  3. | Selectivity |
  4. +-------------+
  5. | 0.0042 |
  6. +-------------+
  7. SELECT count(DISTINCT(concat(first_name, last_name)))/count(*) AS Selectivity FROM employees.employees;
  8. +-------------+
  9. | Selectivity |
  10. +-------------+
  11. | 0.9313 |
  12. +-------------+

<first_name>顯然選擇性太低,<first_name, last_name>選擇性很好,但是first_name和last_name加起來長度為30,有沒有兼顧長度和選擇性的辦法?可以考慮用first_name和last_name的前幾個字符建立索引,例如<first_name, left(last_name, 3)>,看看其選擇性:

  1. SELECT count(DISTINCT(concat(first_name, left(last_name, 3))))/count(*) AS Selectivity FROM employees.employees;
  2. +-------------+
  3. | Selectivity |
  4. +-------------+
  5. | 0.7879 |
  6. +-------------+

選擇性還不錯,但離0.9313還是有點距離,那么把last_name前綴加到4:

  1. SELECT count(DISTINCT(concat(first_name, left(last_name, 4))))/count(*) AS Selectivity FROM employees.employees;
  2. +-------------+
  3. | Selectivity |
  4. +-------------+
  5. | 0.9007 |
  6. +-------------+

這時選擇性已經很理想了,而這個索引的長度只有18,比<first_name, last_name>短了接近一半,我們把這個前綴索引 建上:

  1. ALTER TABLE employees.employees
  2. ADD INDEX `first_name_last_name4` (first_name, last_name(4));

此時再執行一遍按名字查詢,比較分析一下與建索引前的結果:

  1. SHOW PROFILES;
  2. +----------+------------+---------------------------------------------------------------------------------+
  3. | Query_ID | Duration | Query |
  4. +----------+------------+---------------------------------------------------------------------------------+
  5. | 87 | 0.11941700 | SELECT * FROM employees.employees WHERE first_name='Eric' AND last_name='Anido' |
  6. | 90 | 0.00092400 | SELECT * FROM employees.employees WHERE first_name='Eric' AND last_name='Anido' |
  7. +----------+------------+---------------------------------------------------------------------------------+

性能的提升是顯著的,查詢速度提高了120多倍,

前綴索引兼顧索引大小和查詢速度,但是其缺點是不能用于ORDER BY和GROUP BY操作,也不能用于Covering index(即當索引本身包含查詢所需全部資料時,不再訪問資料檔案本身),

InnoDB的主鍵選擇與插入優化

在使用InnoDB存盤引擎時,如果沒有特別的需要,請永遠使用一個與業務無關的自增欄位作為主鍵,

經常看到有帖子或博客討論主鍵選擇問題,有人建議使用業務無關的自增主鍵,有人覺得沒有必要,完全可以使用如學號或身份證號這種唯一欄位作為主鍵,不論支持哪種論點,大多數論據都是業務層面的,如果從資料庫索引優化角度看,使用InnoDB引擎而不使用自增主鍵絕對是一個糟糕的主意,

上文討論過InnoDB的索引實作,InnoDB使用聚集索引,資料記錄本身被存于主索引(一顆B+Tree)的葉子節點上,這就要求同一個葉子節點內(大小為一個記憶體頁或磁盤頁)的各條資料記錄按主鍵順序存放,因此每當有一條新的記錄插入時,MySQL會根據其主鍵將其插入適當的節點和位置,如果頁面達到裝載因子(InnoDB默認為15/16),則開辟一個新的頁(節點),

如果表使用自增主鍵,那么每次插入新的記錄,記錄就會順序添加到當前索引節點的后續位置,當一頁寫滿,就會自動開辟一個新的頁,如下圖所示:

圖13

這樣就會形成一個緊湊的索引結構,近似順序填滿,由于每次插入時也不需要移動已有資料,因此效率很高,也不會增加很多開銷在維護索引上,

如果使用非自增主鍵(如果身份證號或學號等),由于每次插入主鍵的值近似于隨機,因此每次新紀錄都要被插到現有索引頁得中間某個位置:

圖14

此時MySQL不得不為了將新記錄插到合適位置而移動資料,甚至目標頁面可能已經被回寫到磁盤上而從快取中清掉,此時又要從磁盤上讀回來,這增加了很多開銷,同時頻繁的移動、分頁操作造成了大量的碎片,得到了不夠緊湊的索引結構,后續不得不通過OPTIMIZE TABLE來重建表并優化填充頁面,

因此,只要可以,請盡量在InnoDB上采用自增欄位做主鍵,

參考:

[0]  程式員的sql金典

[1] Baron Scbwartz等 著,王小東等 譯;高性能MySQL(High Performance MySQL);電子工業出版社,2010

[2] Michael Kofler 著,楊曉云等 譯;MySQL5權威指南(The Definitive Guide to MySQL5);人民郵電出版社,2006

[3] 姜承堯 著;MySQL技術內幕-InnoDB存盤引擎;機械工業出版社,2011

轉載請註明出處,本文鏈接:https://www.uj5u.com/shujuku/61048.html

標籤:MySQL

上一篇:MySQL到底能有多少個欄位

下一篇:為什么學習mysql

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • GPU虛擬機創建時間深度優化

    **?桔妹導讀:**GPU虛擬機實體創建速度慢是公有云面臨的普遍問題,由于通常情況下創建虛擬機屬于低頻操作而未引起業界的重視,實際生產中還是存在對GPU實體創建時間有苛刻要求的業務場景。本文將介紹滴滴云在解決該問題時的思路、方法、并展示最終的優化成果。 從公有云服務商那里購買過虛擬主機的資深用戶,一 ......

    uj5u.com 2020-09-10 06:09:13 more
  • 可編程網卡芯片在滴滴云網路的應用實踐

    **?桔妹導讀:**隨著云規模不斷擴大以及業務層面對延遲、帶寬的要求越來越高,采用DPDK 加速網路報文處理的方式在橫向縱向擴展都出現了局限性。可編程芯片成為業界熱點。本文主要講述了可編程網卡芯片在滴滴云網路中的應用實踐,遇到的問題、帶來的收益以及開源社區貢獻。 #1. 資料中心面臨的問題 隨著滴滴 ......

    uj5u.com 2020-09-10 06:10:21 more
  • 滴滴資料通道服務演進之路

    **?桔妹導讀:**滴滴資料通道引擎承載著全公司的資料同步,為下游實時和離線場景提供了必不可少的源資料。隨著任務量的不斷增加,資料通道的整體架構也隨之發生改變。本文介紹了滴滴資料通道的發展歷程,遇到的問題以及今后的規劃。 #1. 背景 資料,對于任何一家互聯網公司來說都是非常重要的資產,公司的大資料 ......

    uj5u.com 2020-09-10 06:11:05 more
  • 滴滴AI Labs斬獲國際機器翻譯大賽中譯英方向世界第三

    **桔妹導讀:**深耕人工智能領域,致力于探索AI讓出行更美好的滴滴AI Labs再次斬獲國際大獎,這次獲獎的專案是什么呢?一起來看看詳細報道吧! 近日,由國際計算語言學協會ACL(The Association for Computational Linguistics)舉辦的世界最具影響力的機器 ......

    uj5u.com 2020-09-10 06:11:29 more
  • MPP (Massively Parallel Processing)大規模并行處理

    1、什么是mpp? MPP (Massively Parallel Processing),即大規模并行處理,在資料庫非共享集群中,每個節點都有獨立的磁盤存盤系統和記憶體系統,業務資料根據資料庫模型和應用特點劃分到各個節點上,每臺資料節點通過專用網路或者商業通用網路互相連接,彼此協同計算,作為整體提供 ......

    uj5u.com 2020-09-10 06:11:41 more
  • 滴滴資料倉庫指標體系建設實踐

    **桔妹導讀:**指標體系是什么?如何使用OSM模型和AARRR模型搭建指標體系?如何統一流程、規范化、工具化管理指標體系?本文會對建設的方法論結合滴滴資料指標體系建設實踐進行解答分析。 #1. 什么是指標體系 ##1.1 指標體系定義 指標體系是將零散單點的具有相互聯系的指標,系統化的組織起來,通 ......

    uj5u.com 2020-09-10 06:12:52 more
  • 單表千萬行資料庫 LIKE 搜索優化手記

    我們經常在資料庫中使用 LIKE 運算子來完成對資料的模糊搜索,LIKE 運算子用于在 WHERE 子句中搜索列中的指定模式。 如果需要查找客戶表中所有姓氏是“張”的資料,可以使用下面的 SQL 陳述句: SELECT * FROM Customer WHERE Name LIKE '張%' 如果需要 ......

    uj5u.com 2020-09-10 06:13:25 more
  • 滴滴Ceph分布式存盤系統優化之鎖優化

    **桔妹導讀:**Ceph是國際知名的開源分布式存盤系統,在工業界和學術界都有著重要的影響。Ceph的架構和演算法設計發表在國際系統領域頂級會議OSDI、SOSP、SC等上。Ceph社區得到Red Hat、SUSE、Intel等大公司的大力支持。Ceph是國際云計算領域應用最廣泛的開源分布式存盤系統, ......

    uj5u.com 2020-09-10 06:14:51 more
  • es~通過ElasticsearchTemplate進行聚合~嵌套聚合

    之前寫過《es~通過ElasticsearchTemplate進行聚合操作》的文章,這一次主要寫一個嵌套的聚合,例如先對sex集合,再對desc聚合,最后再對age求和,共三層嵌套。 Aggregations的部分特性類似于SQL語言中的group by,avg,sum等函式,Aggregation ......

    uj5u.com 2020-09-10 06:14:59 more
  • 爬蟲日志監控 -- Elastc Stack(ELK)部署

    傻瓜式部署,只需替換IP與用戶 導讀: 現ELK四大組件分別為:Elasticsearch(核心)、logstash(處理)、filebeat(采集)、kibana(可視化) 下載均在https://www.elastic.co/cn/downloads/下tar包,各組件版本最好一致,配合fdm會 ......

    uj5u.com 2020-09-10 06:15:05 more
最新发布
  • day02-2-商鋪查詢快取

    功能02-商鋪查詢快取 3.商鋪詳情快取查詢 3.1什么是快取? 快取就是資料交換的緩沖區(稱作Cache),是存盤資料的臨時地方,一般讀寫性能較高。 快取的作用: 降低后端負載 提高讀寫效率,降低回應時間 快取的成本: 資料一致性成本 代碼維護成本 運維成本 3.2需求說明 如下,當我們點擊商店詳 ......

    uj5u.com 2023-04-20 08:33:24 more
  • MySQL中binlog備份腳本分享

    關于MySQL的二進制日志(binlog),我們都知道二進制日志(binlog)非常重要,尤其當你需要point to point災難恢復的時侯,所以我們要對其進行備份。關于二進制日志(binlog)的備份,可以基于flush logs方式先切換binlog,然后拷貝&壓縮到到遠程服務器或本地服務器 ......

    uj5u.com 2023-04-20 08:28:06 more
  • day02-短信登錄

    功能實作02 2.功能01-短信登錄 2.1基于Session實作登錄 2.1.1思路分析 2.1.2代碼實作 2.1.2.1發送短信驗證碼 發送短信驗證碼: 發送驗證碼的介面為:http://127.0.0.1:8080/api/user/code?phone=xxxxx<手機號> 請求方式:PO ......

    uj5u.com 2023-04-20 08:27:27 more
  • 快取與資料庫雙寫一致性幾種策略分析

    本文將對幾種快取與資料庫保證資料一致性的使用方式進行分析。為保證高并發性能,以下分析場景不考慮執行的原子性及加鎖等強一致性要求的場景,僅追求最終一致性。 ......

    uj5u.com 2023-04-20 08:26:48 more
  • sql陳述句優化

    問題查找及措施 問題查找 需要找到具體的代碼,對其進行一對一優化,而非一直把關注點放在服務器和sql平臺 降低簡化每個事務中處理的問題,盡量不要讓一個事務拖太長的時間 例如檔案上傳時,應將檔案上傳這一步放在事務外面 微軟建議 4.啟動sql定時執行計劃 怎么啟動sqlserver代理服務-百度經驗 ......

    uj5u.com 2023-04-20 08:26:35 more
  • 云時代,MySQL到ClickHouse資料同步產品對比推薦

    ClickHouse 在執行分析查詢時的速度優勢很好的彌補了MySQL的不足,但是對于很多開發者和DBA來說,如何將MySQL穩定、高效、簡單的同步到 ClickHouse 卻很困難。本文對比了 NineData、MaterializeMySQL(ClickHouse自帶)、Bifrost 三款產品... ......

    uj5u.com 2023-04-20 08:26:29 more
  • sql陳述句優化

    問題查找及措施 問題查找 需要找到具體的代碼,對其進行一對一優化,而非一直把關注點放在服務器和sql平臺 降低簡化每個事務中處理的問題,盡量不要讓一個事務拖太長的時間 例如檔案上傳時,應將檔案上傳這一步放在事務外面 微軟建議 4.啟動sql定時執行計劃 怎么啟動sqlserver代理服務-百度經驗 ......

    uj5u.com 2023-04-20 08:25:13 more
  • Redis 報”OutOfDirectMemoryError“(堆外記憶體溢位)

    Redis 報錯“OutOfDirectMemoryError(堆外記憶體溢位) ”問題如下: 一、報錯資訊: 使用 Redis 的業務介面 ,產生 OutOfDirectMemoryError(堆外記憶體溢位),如圖: 格式化后的報錯資訊: { "timestamp": "2023-04-17 22: ......

    uj5u.com 2023-04-20 08:24:54 more
  • day02-2-商鋪查詢快取

    功能02-商鋪查詢快取 3.商鋪詳情快取查詢 3.1什么是快取? 快取就是資料交換的緩沖區(稱作Cache),是存盤資料的臨時地方,一般讀寫性能較高。 快取的作用: 降低后端負載 提高讀寫效率,降低回應時間 快取的成本: 資料一致性成本 代碼維護成本 運維成本 3.2需求說明 如下,當我們點擊商店詳 ......

    uj5u.com 2023-04-20 08:24:03 more
  • day02-短信登錄

    功能實作02 2.功能01-短信登錄 2.1基于Session實作登錄 2.1.1思路分析 2.1.2代碼實作 2.1.2.1發送短信驗證碼 發送短信驗證碼: 發送驗證碼的介面為:http://127.0.0.1:8080/api/user/code?phone=xxxxx<手機號> 請求方式:PO ......

    uj5u.com 2023-04-20 08:23:11 more