這些年背過的面試題——MySQL篇

資料庫工作筆記發表於2024-02-06

這些年背過的面試題——MySQL篇


來源:阿里雲開發者

阿里妹導讀

本文是技術人面試系列MySQL篇,面試中關於MySQL都需要了解哪些基礎?一文帶你詳細瞭解,歡迎收藏!

WhyMysql?

NoSQL資料庫四大家族 


  • 列儲存 Hbase
  • K-V儲存 Redis
  • 影像儲存 Neo4j
  • 文件儲存 MongoDB
  • 雲端儲存OSS



海量Aerospike

Aerospike(簡稱AS)是一個分散式,可擴充套件的鍵值儲存的NoSQL資料庫。T級別大資料高併發的結構化資料儲存,採用混合架構,索引儲存在記憶體中,而資料可儲存在機械硬碟(HDD)或固態硬碟(SSD) 上,讀寫操作達微妙級,99%的響應可在1毫秒內實現。


這些年背過的面試題——MySQL篇


Aerospike作為一個大容量的NoSql解決方案,適合對容量要求比較大,QPS相對低一些的場景,主要用在廣告行業,個性化推薦廠告是建立在了和掌握消費者獨特的偏好和習性的基礎之上,對消費者的購買需求做出準確的預測或引導,在合適的位置、合適的時間,以合適的形式向消費者呈現與其需求高度吻合的廣告,以此來促進使用者的消費行為。


這些年背過的面試題——MySQL篇


(ETL資料倉儲技術)抽取(extract)、轉換(transform)、載入(load)


  • 使用者行為日誌收集系統收集日誌之後推送到ETL做資料的清洗和轉換

  • 把ETL過後的資料傳送到推薦引擎計算每個消費者的推薦結果,其中推薦邏輯包括規則和演算法兩部分

  • 收集使用者最近瀏覽、最長停留等特徵,分析商品相似性、使用者相似性、相似性等演算法。

  • 把推薦引擎的結果存入Aerospike叢集中,並提供給廣告投放引擎實時獲取

    分別透過HDFS和HBASE對日誌進行離線和實時的分析,然後把使用者畫像的標籤(tag : 程式猿、宅男...)結果存入高效能的Nosql資料庫Aerospike中,同時把資料備份到異地資料中心。前端廣告投放請求透過決策引擎(投放引擎)向使用者畫像資料庫中讀取相應的使用者畫像資料,然後根據競價演算法出價進行競價。競價成功之後就可以展現廣告了。而在競價成功之後,具體給使用者展現什麼樣的廣告,就是有上面說的個性化推薦廣告來完成的。

這些年背過的面試題——MySQL篇



圖譜Neo4j


Neo4j是一個開源基於java開發的圖形noSql資料庫,它將結構化資料儲存在圖中而不是表中。它是一個嵌入式的、基於磁碟的、具備完全的事務特性的Java持久化引擎。程式資料是在一個物件導向的、靈活的網路結構下,而不是嚴格的表中,但具備完全的事務特性、企業級的資料庫的所有好處。


一種基於圖的資料結構,由節點(Node)和邊(Edge)組成。其中節點即實體,由一個全域性唯一的ID標示,邊就是關係用於連線兩個節點。通俗地講,知識圖譜就是把所有不同種類的資訊,連線在一起而得到的一個關係網路。知識圖譜提供了從“關係”的角度去分析問題的能力。
網際網路、大資料的背景下,谷歌、百度、搜狗等搜尋引擎紛紛基於該背景,建立自己的知識圖Knowledge Graph、知心和知立方,主要用於改進搜尋質量。
自己專案主要用作好友推薦,圖資料庫(Graph database)指的是以圖資料結構的形式來儲存和查詢資料的資料庫。關係圖譜中,關係的組織形式採用的就是圖結構,所以非常適合用相簿進行儲存。


這些年背過的面試題——MySQL篇

  • 優勢總結:

  • 效能上,使用cql查詢,對長程關係的查詢速度快

  • 擅於發現隱藏的關係,例如透過判斷圖上兩點之間有沒有走的通的路徑,就可以發現事物間的關聯

這些年背過的面試題——MySQL篇



// 查詢三層級關係節點如下:with可以將前面查詢結果作為後面查詢條件match (na:Person)-[re]-(nb:Person) where na.name="林婉兒" WITH na,re,nb match (nb:Person)- [re2:Friends]->(nc:Person) return na,re,nb,re2,nc// 直接拼接關係節點查詢match data=(na:Person{name:"範閒"})-[re]->(nb:Person)-[re2]->(nc:Person) return data// 使用深度運算子顯然使用以上方式比較繁瑣,可變數量的關係->節點可以使用-[:TYPE*minHops..maxHops]-。match data=(na:Person{name:"範閒"})-[*1..2]-(nb:Person) return data



文件MongoDB


MongoDB 是一個基於分散式檔案儲存的資料庫,是非關聯式資料庫中功能最豐富、最像關聯式資料庫的。在高負載的情況下,透過新增更多的節點,可以保證伺服器效能。由 C++ 編寫,可以為 WEB 應用提供可擴充套件、高效能、易部署的資料儲存解決方案。

這些年背過的面試題——MySQL篇


什麼是BSON


{key:value,key2:value2}和Json類似,是一種二進位制形式的儲存格式,支援內嵌的文件物件和陣列物件,但是BSON有JSON沒有的一些資料型別,比如 value包括字串,double,Array,DateBSON可以做為網路資料交換的一種儲存形式,它的優點是靈活性高,但它的缺點是空間利用率不是很理想。


BSON有三個特點:輕量性、可遍歷性、高效性



/* 查詢 find() 方法可以傳入多個鍵(key),每個鍵(key)以逗號隔開*/db.collection.find({key1:value1, key2:value2}).pretty()/* 更新 $set :設定欄位值 $unset :刪除指定欄位 $inc:對修改的值進行自增*/db.collection.update({where},{$set:{欄位名:值}},{multi:true})/* 刪除 justOne :如果設為true,只刪除一個文件,預設false,刪除所有匹配條件的文件*/db.collection.remove({where}, {justOne: , writeConcern:  } )


優點:


  • 文件結構的儲存方式,能夠更便捷的獲取資料。

    對於一個層級式的資料結構來說,使用扁平式的,表狀的結構來查詢儲存資料非常的困難。

  • 內建GridFS,支援大容量的儲存。
    GridFS是一個出色的分散式檔案系統,支援海量的資料儲存,滿足對大資料集的快速範圍查詢。
  • 效能優越

    千萬級別的文件物件,近10G的資料,對有索引的ID的查詢 不會比mysql慢,而對非索引欄位的查詢,則是全面勝出。 mysql實際無法勝任大資料量下任意欄位的查詢,而mongodb的查詢效能實在牛逼。寫入效能同樣很令人滿意,同樣寫入百萬級別的資料,mongodb基本10分鐘以下可以解決。


缺點:


  • 不支援事務
  • 磁碟佔用空間大

MySQL 8.0 版本


1. 效能:MySQL 8.0 的速度要比 MySQL 5.7 快 2 倍。
2. NoSQL:MySQL 從 5.7 版本開始提供 NoSQL 儲存功能,在 8.0 版本中nosql得到了更大的改進。
3. 視窗函式:實現若干新的查詢方式。視窗函式與 SUM()、COUNT() 這種集合函式類似,但它不會將多行查詢結果合併為一行,而是將結果放回多行當中,即視窗函式不需要 GROUP BY。
4. 隱藏索引:在 MySQL 8.0 中,索引可以被“隱藏”和“顯示”。當對索引進行隱藏時,它不會被查詢最佳化器所使用。我們可以使用這個特性用於效能除錯,例如我們先隱藏一個索引,然後觀察其對資料庫的影響。如果資料庫效能有所下降,說明這個索引是有用的,然後將其“恢復顯示”即可;如果資料庫效能看不出變化,說明這個索引是多餘的,可以考慮刪掉。


雲端儲存


這些年背過的面試題——MySQL篇


使用步驟


1、開通服務

2、建立儲存空間

3、上傳檔案、下載檔案、刪除檔案

4、域名繫結、日誌記錄


5、根據開放介面進行鑑權訪問
功能


圖片編輯(裁剪、模糊、水印)

影片截圖


音訊轉碼、影片修復
CDN加速
物件儲存OSS與阿里雲CDN服務結合,可最佳化靜態熱點檔案下載加速的場景(即同一地區大量使用者同時下載同一個靜態檔案的場景)。可以將OSS的儲存空間(Bucket)作為源站,利用阿里雲CDN將源內容釋出到邊緣節點。當大量終端使用者重複訪問同一檔案時,可以直接從邊緣節點獲取已快取的資料,提高訪問的響應速度。


FastDFS


開源的輕量級分散式檔案系統。它對檔案進行管理,功能包括:檔案儲存、檔案同步、檔案訪問(檔案上傳、檔案下載)等,解決了大容量儲存和負載均衡的問題。使用FastDFS很容易搭建一套高效能的檔案伺服器叢集提供檔案上傳、下載等服務。如相簿網站、影片網站等。


擴充套件能力: 支援水平擴充套件,可以動態擴容;
高可用性: 一是整個檔案系統的可用性,二是資料的完整和一致性;
彈性儲存: 可以根據業務需要靈活地增刪儲存池中的資源,而不需要中斷系統執行。


這些年背過的面試題——MySQL篇


特性


  • 和流行的web server無縫銜接,FastDFS已提供apache和nginx擴充套件模組

  • 檔案ID由FastDFS生成,作為檔案訪問憑證,FastDFS不需要傳統的name server

  • 分組儲存,靈活簡潔、對等結構,不存在單點

  • 檔案不分塊儲存,上傳的檔案和OS檔案系統中的檔案一一對應

  • 中、小檔案均可以很好支援,支援海量小檔案儲存

  • 支援相同內容的檔案只儲存一份,節約磁碟空間

  • 支援多塊磁碟,支援單盤資料恢復

  • 支援線上擴容 支援主從檔案

  • 下載檔案支援多執行緒方式,支援斷點續傳


組成


  • 客戶端(client)

    透過專有介面,使用TCP/IP協議與跟蹤器伺服器或儲存節點進行資料互動。

  • 跟蹤器(tracker)

    Trackerserver作用是負載均衡和排程,透過Tracker server在檔案上傳時可以根據策略找到檔案上傳的地址。Tracker在訪問上起負載均衡的作用。

  • 儲存節點(storage)

    Storageserver作用是檔案儲存,客戶端上傳的檔案最終儲存在Storage伺服器上,Storage server沒有實現自己的檔案系統而是利用作業系統的檔案系統來管理檔案。儲存節點中的伺服器均可以隨時增加或下線而不會影響線上服務。


上傳


這些年背過的面試題——MySQL篇


下載


這些年背過的面試題——MySQL篇


斷點續傳
續傳涉及到的檔案大小MD5不會改變。續傳流程與檔案上傳類似,先定位到源storage,完成完整或部分上傳,再透過binlog進行同group內server檔案同步。
配置最佳化
配置檔案:tracker.conf 和 storage.conf 



// FastDFS採用記憶體池的做法。 // v5.04對預分配採用增量方式,tracker一次預分配 1024個,storage一次預分配256個。 max_connections = 10240// 根據實際需要將 max_connections 設定為一個較大的數值,比如 10240 甚至更大。// 同時需要將一個程式允許開啟的最大檔案數調大vi /etc/security/limits.conf 重啟系統生效 * soft nofile 65535 * hard nofile 65535

work_threads = 4 // 說明:為了避免CPU上下文切換的開銷,以及不必要的資源消耗,不建議將本引數設定得過大。// 公式為: work_threads + (reader_threads + writer_threads) = CPU數

// 對於單盤掛載方式,磁碟讀寫執行緒分 別設定為 1即可 // 如果磁碟做了RAID,那麼需要酌情加大讀寫執行緒數,這樣才能最大程度地發揮磁碟效能disk_rw_separated:磁碟讀寫是否分離 disk_reader_threads:單個磁碟讀執行緒數 disk_writer_threads:單個磁碟寫執行緒數


避免重複
如何避免檔案重複上傳 解決方案 上傳成功後計算檔案對應的MD5然後存入MySQL,新增檔案時把檔案MD5和之前存入MYSQL中的儲存的資訊對比 。DigestUtils.md5DigestAsHex(bytes)。

事務


1、事務4大特性

事務4大特性:原子性、一致性、隔離性、永續性
原⼦性: 事務是最⼩的執⾏單位,不允許分割。事務的原⼦性確保動作要麼全部完成,要麼全不執行
一致性: 執⾏事務前後,資料保持⼀致,多個事務對同⼀個資料讀取的結果是相同的;
隔離性: 併發訪問資料庫時,⼀個⽤戶的事務不被其他事務所⼲擾,各併發事務之間資料庫是獨⽴的;
永續性: ⼀個事務被提交之後。它對資料庫中資料的改變是持久的,即使資料庫發⽣故障也不應該對其有任何影響。
實現保證:
MySQL的儲存引擎InnoDB使用重做日誌保證一致性與永續性,回滾日誌保證原子性,使用各種鎖來保證隔離性。


2、事務隔離級別

讀未提交:最低的隔離級別,允許讀取尚未提交的資料變更,可能會導致髒讀、幻讀或不可重複讀。
讀已提交:允許讀取併發事務已經提交的資料,可以阻⽌髒讀,但是幻讀或不可重複讀仍有可能發⽣。
可重複讀:同⼀欄位的多次讀取結果都是⼀致的,除⾮資料是被本身事務⾃⼰所修改,可以阻⽌髒讀和不可重複讀,會有幻讀。
序列化:最⾼的隔離級別,完全服從ACID的隔離級別。所有的事務依次逐個執⾏,這樣事務之間就完全不可能產⽣⼲擾。


這些年背過的面試題——MySQL篇



3、預設隔離級別-RR

預設隔離級別:可重複讀;
同⼀欄位的多次讀取結果都是⼀致的,除⾮資料是被本身事務⾃⼰所修改;
可重複讀是有可能出現幻讀的,如果要保證絕對的安全只能把隔離級別設定成SERIALIZABLE;這樣所有事務都只能順序執行,自然不會因為併發有什麼影響了,但是效能會下降許多。
第二種方式,使用MVCC解決快照讀幻讀問題(如簡單select),讀取的不是最新的資料。維護一個欄位作為version,這樣可以控制到每次只能有一個人更新一個版本。



select id from table_xx where id = ? and version = Vupdate id from table_xx where id = ? and version = V+1


第三種方式,如果需要讀最新的資料,可以透過GapLock+Next-KeyLock可以解決當前讀幻讀問題,



select id from table_xx where id > 100 for update;select id from table_xx where id > 100 lock in share mode;



4、RR和RC使用場景

事務隔離級別RC(read commit)和RR(repeatable read)兩種事務隔離級別基於多版本併發控制MVCC(multi-version concurrency control)來實現。


這些年背過的面試題——MySQL篇



5、行鎖,表鎖,意向鎖

InnoDB⽀持⾏級鎖(row-level locking)和表級鎖,預設為⾏級鎖
InnoDB按照不同的分類的鎖:
共享/排它鎖(Shared and Exclusive Locks):行級別鎖,
意向鎖(Intention Locks),表級別鎖
間隙鎖(Gap Locks),鎖定一個區間
記錄鎖(Record Locks),鎖定一個行記錄
表級鎖:(序列化)
Mysql中鎖定 粒度最大的一種鎖,對當前操作的整張表加鎖,實現簡單 ,資源消耗也比較少,加鎖快,不會出現死鎖 。其鎖定粒度最大,觸發鎖衝突的機率最高,併發度最低,MyISAM和 InnoDB引擎都支援表級鎖。
行級鎖:(RR、RC)
Mysql中鎖定 粒度最小 的一種鎖,只針對當前操作的行進行加鎖。 行級鎖能大大減少資料庫操作的衝突。其加鎖粒度最小,併發度高,但加鎖的開銷也最大,加鎖慢,會出現死鎖。 InnoDB支援的行級鎖,包括如下幾種:
記錄鎖(Record Lock): 對索引項加鎖,鎖定符合條件的行。其他事務不能修改和刪除加鎖項;
間隙鎖(Gap Lock): 對索引項之間的“間隙”加鎖,鎖定記錄的範圍,不包含索引項本身,其他事務不能在鎖範圍內插入資料。
Next-key Lock: 鎖定索引項本身和索引範圍。即Record Lock和Gap Lock的結合。可解決幻讀問題。
InnoDB 支援多粒度鎖(multiple granularity locking),它允許行級鎖與表級鎖共存,而意向鎖就是其中的一種表鎖。
共享鎖( shared lock, S )鎖允許持有鎖讀取行的事務。加鎖時將自己和子節點全加S鎖,父節點直到表頭全加IS鎖
排他鎖( exclusive lock, X )鎖允許持有鎖修改行的事務。 加鎖時將自己和子節點全加X鎖,父節點直到表頭全加IX鎖 
意向共享鎖(intention shared lock, IS):事務有意向對錶中的某些行加共享鎖(S鎖)
意向排他鎖(intention exclusive lock, IX):事務有意向對錶中的某些行加排他鎖(X鎖)


這些年背過的面試題——MySQL篇



6、MVCC多版本併發控制

MVCC是一種多版本併發控制機制,透過事務的可見性看到自己預期的資料,能降低其系統開銷。(RC和RR級別工作)
InnoDB的MVCC,是透過在每行記錄後面儲存系統版本號(可以理解為事務的ID),每開始一個新的事務,系統版本號就會自動遞增,事務開始時刻的系統版本號會作為事務的ID。這樣可以確保事務讀取的行,要麼是在事務開始前已經存在的,要麼是事務自身插入或者修改過的,防止幻讀的產生。
1.MVCC手段只適用於Msyql隔離級別中的讀已提交(Read committed)和可重複讀(Repeatable Read).
2.Read uncimmitted由於存在髒讀,即能讀到未提交事務的資料行,所以不適用MVCC.
3.簡單的select快照度不會加鎖,刪改及select for update等需要當前讀的場景會加鎖
原因是MVCC的建立版本和刪除版本只要在事務提交後才會產生。客觀上,mysql使用的是樂觀鎖的一整實現方式,就是每行都有版本號,儲存時根據版本號決定是否成功。Innodb的MVCC使用到的快照儲存在Undo日誌中,該日誌透過回滾指標把一個資料行所有快照連線起來。
版本鏈
在InnoDB引擎表中,它的聚簇索引記錄中有兩個必要的隱藏列:
trx_id
這個id用來儲存的每次對某條聚簇索引記錄進行修改的時候的事務id。
roll_pointer
每次對哪條聚簇索引記錄有修改的時候,都會把老版本寫入undo日誌中。這個roll_pointer就是存了一個指標,它指向這條聚簇索引記錄的上一個版本的位置,透過它來獲得上一個版本的記錄資訊。(注意插入操作的undo日誌沒有這個屬性,因為它沒有老版本)
每次修改都會在版本鏈中記錄。SELECT可以去版本鏈中拿記錄,這就實現了讀-寫,寫-讀的併發執行,提升了系統的效能。

索引


1、Innodb和Myisam引擎

Myisam:支援表鎖,適合讀密集的場景,不支援外來鍵,不支援事務,索引與資料在不同的檔案
Innodb:支援行、表鎖,預設為行鎖,適合併發場景,支援外來鍵,支援事務,索引與資料同一檔案


2、雜湊索引

雜湊索引用索引列的值計算該值的hashCode,然後在hashCode相應的位置存執該值所在行資料的物理位置,因為使用雜湊演算法,因此訪問速度非常快,但是一個值只能對應一個hashCode,而且是雜湊的分佈方式,因此雜湊索引不支援範圍查詢和排序的功能。


3、B+樹索引

優點:
B+樹的磁碟讀寫代價低,更少的查詢次數,查詢效率更加穩定,有利於對資料庫的掃描
B+樹是B樹的升級版,B+樹只有葉節點存放資料,其餘節點用來索引。索引節點可以全部加入記憶體,增加查詢效率,葉子節點可以做雙向連結串列,從而提高範圍查詢的效率,增加的索引的範圍。
在大規模資料儲存的時候,紅黑樹往往出現由於樹的深度過大而造成磁碟IO讀寫過於頻繁,進而導致效率低下的情況。所以,只要我們透過某種較好的樹結構減少樹的結構儘量減少樹的高度,B樹與B+樹可以有多個子女,從幾十到上千,可以降低樹的高度。
磁碟預讀原理:將一個節點的大小設為等於一個頁,這樣每個節點只需要一次I/O就可以完全載入。為了達到這個目的,在實際實現B-Tree還需要使用如下技巧:每次新建節點時,直接申請一個頁的空間,這樣就保證一個節點物理上也儲存在一個頁裡,加之計算機儲存分配都是按頁對齊的,就實現了一個node只需一次I/O。


4、建立索引


CREATE  [UNIQUE | FULLTEXT]  INDEX  索引名 ON  表名(欄位名) [USING 索引方法];說明:UNIQUE:可選。表示索引為唯一性索引。FULLTEXT:可選。表示索引為全文索引。INDEX和KEY:用於指定欄位為索引,兩者選擇其中之一就可以了,作用是一樣的。索引名:可選。給建立的索引取一個新名稱。欄位名1:指定索引對應的欄位的名稱,該欄位必須是前面定義好的欄位。注:索引方法預設使用B+TREE。


5、聚簇索引和非聚簇索引

聚簇索引:將資料儲存與索引放到了一塊,索引結構的葉子節點儲存了行資料(主鍵索引)
非聚簇索引:將資料與索引分開儲存,索引結構的葉子節點指向了資料對應的位置(輔助索引)
聚簇索引的葉子節點就是資料節點,而非聚簇索引的葉子節點仍然是索引節點,只不過有指向對應資料塊的指標。


6、最左字首問題

最左字首原則主要使用在聯合索引中,聯合索引的B+Tree是按照第一個關鍵字進行索引排列的。
聯合索引的底層是一顆B+樹,只不過聯合索引的B+樹節點中儲存的是鍵值。由於構建一棵B+樹只能根據一個值來確定索引關係,所以資料庫依賴聯合索引最左的欄位來構建。
採用>、<等進行匹配都會導致後面的列無法走索引,因為透過以上方式匹配到的資料是不可知的。

SQL查詢


1、SQL語句的執行過程

查詢語句:



select * from student  A where A.age='18' and A.name='張三';

這些年背過的面試題——MySQL篇



結合上面的說明,我們分析下這個語句的執行流程:
①透過客戶端/伺服器通訊協議與 MySQL 建立連線。並查詢是否有許可權
②Mysql8.0之前開看是否開啟快取,開啟了 Query Cache 且命中完全相同的 SQL 語句,則將查詢結果直接返回給客戶端;
③由解析器進行語法語義解析,並生成解析樹。如查詢是select、表名tb_student、條件是id='1'
④查詢最佳化器生成執行計劃。根據索引看看是否可以最佳化
⑤查詢執行引擎執行 SQL 語句,根據儲存引擎型別,得到查詢結果。若開啟了 Query Cache,則快取,否則直接返回。


2、回表查詢和覆蓋索引

普通索引(唯一索引+聯合索引+全文索引)需要掃描兩遍索引樹
(1)先透過普通索引定位到主鍵值id=5;
(2)在透過聚集索引定位到行記錄;
這就是所謂的回表查詢,先定位主鍵值,再定位行記錄,它的效能較掃一遍索引樹更低。
覆蓋索引:主鍵索引==聚簇索引==覆蓋索引
如果where條件的列和返回的資料在一個索引中,那麼不需要回查表,那麼就叫覆蓋索引。
實現覆蓋索引:常見的方法是,將被查詢的欄位,建立到聯合索引裡去。


3、Explain及最佳化

參考:



mysql> explain select * from staff;+----+-------------+-------+------+---------------+------+---------+------+------+-------+| id | select_type | table | type | possible_keys | key  | key_len | ref  | rows | Extra |+----+-------------+-------+------+---------------+------+---------+------+------+-------+|  1 | SIMPLE      | staff | ALL  | NULL          | 索引  | NULL    | NULL |    2 | NULL  |+----+-------------+-------+------+---------------+------+---------+------+------+-------+1 row in set


索引最佳化:
①最左字首索引:like只用於'string%',語句中的=和in會動態調整順序
②唯一索引:唯一鍵區分度在0.1以上
③無法使用索引:!= 、is null 、 or、>< 、(5.7以後根據數量自動判定)in 、not in
④聯合索引:避免select * ,查詢列使用覆蓋索引

SELECT uid From user Where gid = 2 order by ctime asc limit 10ALTER TABLE user add index idx_gid_ctime_uid(gid,ctime,uid) #建立聯合覆蓋索引,避免回表查詢
語句最佳化:
①char固定長度查詢效率高,varchar第一個位元組記錄資料長度
②應該針對Explain中Rows增加索引
③group/order by欄位均會涉及索引
④Limit中分頁查詢會隨著start值增大而變緩慢,透過子查詢+表連線解決



select * from mytbl order by id limit 100000,10  改進後的SQL語句如下:select * from mytbl where id >= ( select id from mytbl order by id limit 100000,1 ) limit 10select * from mytbl inner ori join (select id from mytbl order by id limit 100000,10) as tmp on tmp.id=ori.id;


⑤count會進行全表掃描,如果估算可以使用explain
⑥delete刪除表時會增加大量undo和redo日誌, 確定刪除可使用trancate
表結構最佳化:
①單庫不超過200張表
②單表不超過500w資料
③單表不超過40列
④單表索引不超過5個
資料庫正規化 :
①第一正規化(1NF)列不可分割
②第二正規化(2NF)屬性完全依賴於主鍵 [ 消除部分子函式依賴 ]
③第三正規化(3NF)屬性不依賴於其它非主屬性 [ 消除傳遞依賴 ]
配置最佳化:
配置連線數、禁用Swap、增加記憶體、升級SSD硬碟


4、JOIN查詢


這些年背過的面試題——MySQL篇


left join(左聯接) 返回包括左表中的所有記錄和右表中關聯欄位相等的記錄 
right join(右聯接) 返回包括右表中的所有記錄和左表中關聯欄位相等的記錄
inner join(等值連線) 只返回兩個表中關聯欄位相等的行

叢集


1、主從複製過程

MySQl主從複製:


  • 原理:將主伺服器的binlog日誌複製到從伺服器上執行一遍,達到主從資料的一致狀態。
  • 過程:從庫開啟一個I/O執行緒,向主庫請求Binlog日誌。主節點開啟一個binlog dump執行緒,檢查自己的二進位制日誌,併傳送給從節點;從庫將接收到的資料儲存到中繼日誌(Relay log)中,另外開啟一個SQL執行緒,把Relay中的操作在自身機器上執行一遍
  • 優點:
    • 作為備用資料庫,並且不影響業務
    • 可做讀寫分離,一個寫庫,一個或多個讀庫,在不同的伺服器上,充分發揮伺服器和資料庫的效能,但要保證資料的一致性


binlog記錄格式:statement、row、mixed
基於語句statement的複製、基於行row的複製、基於語句和行(mix)的複製。其中基於row的複製方式更能保證主從庫資料的一致性,但日誌量較大,在設定時考慮磁碟的空間問題。


2、資料一致性問題

"主從複製有延時",這個延時期間讀取從庫,可能讀到不一致的資料。
快取記錄寫key法:
在cache裡記錄哪些記錄發生過的寫請求,來路由讀主庫還是讀從庫
非同步複製:
在非同步複製中,主庫執行完操作後,寫入binlog日誌後,就返回客戶端,這一動作就結束了,並不會驗證從庫有沒有收到,完不完整,所以這樣可能會造成資料的不一致。
半同步複製:
當主庫每提交一個事務後,不會立即返回,而是等待其中一個從庫接收到Binlog併成功寫入Relay-log中才返回客戶端,透過一份在主庫的Binlog,另一份在其中一個從庫的Relay-log,可以保證了資料的安全性和一致性。
全同步複製:
指當主庫執行完一個事務,所有的從庫都執行了該事務才返回給客戶端。因為需要等待所有從庫執行完該事務才能返回,所以全同步複製的效能必然會收到嚴重的影響


3、叢集架構

Keepalived + VIP + MySQL 主從/雙主
當寫節點 Master db1 出現故障時,由 MMM Monitor 或 Keepalived 觸發切換指令碼,將 VIP 漂移到可用的 Master db2 上。當出現網路抖動或網路分割槽時,MMM Monitor 會誤判,嚴重時來回切換寫 VIP 導致叢集雙寫,當資料複製延遲時,應用程式會出現資料錯亂或資料衝突的故障。有效避免單點失效的架構就是採用共享儲存,單點故障切換可以透過分散式哨兵系統監控。


這些年背過的面試題——MySQL篇


架構選型:MMM 叢集 -> MHA叢集 -> MHA+Arksentinel。


這些年背過的面試題——MySQL篇



4、故障轉移和恢復

轉移方式及恢復方法

1. 虛擬IP或DNS服務 (Keepalived +VIP/DNS  和 MMM 架構)


問題:在虛擬 IP 運維過程中,重新整理ARP過程中有時會出現一個 VIP 繫結在多臺伺服器同時提供連線的問題。這也是為什麼要避免使用 Keepalived+VIP 和 MMM 架構的原因之一,因為它處理不了這類問題而導致叢集多點寫入。



2. 提升備庫為主庫(MHA、QMHA)
嘗試將原 Master 設定 read_only 為 on,避免叢集多點寫入。藉助 binlog server 保留 Master 的 Binlog;當出現資料延遲時,再提升 Slave 為新 Master 之前需要進行資料補齊,否則會丟失資料。

面試題


分庫分表


如何進行分庫分表
分表使用者id進行分表,每個表控制在300萬資料。
分庫根據業務場景和地域分庫,每個庫併發不超過2000


Sharding-jdbc 這種 client 層方案的優點在於不用部署,運維成本低,不需要代理層的二次轉發請求,效能很高,但是各個系統都需要耦合 Sharding-jdbc 的依賴,升級比較麻煩
Mycat 這種 proxy 層方案的缺點在於需要部署,自己運維一套中介軟體,運維成本高,但是好處在於對於各個專案是透明的,如果遇到升級之類的都是自己中介軟體那裡搞就行了
水平拆分:一個表放到多個庫,分擔高併發,加快查詢速度


  • id保證業務在關聯多張表時可以在同一庫上操作
  • range方便擴容和資料統計
  • hash可以使得資料更加平均

垂直拆分:一個表拆成多個表,可以將一些冷資料拆分到冗餘庫中

不是寫瓶頸優先進行分表
  • 分庫資料間的資料無法再透過資料庫直接查詢了。會產生深分頁的問題

  • 分庫越多,出現問題的可能性越大,維護成本也變得更高。

  • 分庫後無法保障跨庫間事務,只能藉助其他中介軟體實現最終一致性。


分庫首先需考慮滿足業務最核心的場景:
1、訂單資料按使用者分庫,可以提升使用者的全流程體驗
2、超級客戶導致資料傾斜可以使用最細粒度唯一標識進行hash拆分
3、按照最細粒度如訂單號拆分以後,資料庫就無法進行單庫排重了
三個問題:


  • 富查詢:採用分庫分表之後,如何滿足跨越分庫的查詢?使用ES的寬表

    藉助分庫閘道器+分庫業務雖然能夠實現多維度查詢的能力,但整體上效能不佳且對正常的寫入請求有一定的影響。業界應對多維度實時查詢的最常見方式便是藉助 ElasticSearch;

  • 資料傾斜:資料分庫基礎上再進行分表;

  • 分散式事務:跨多庫的修改及多個微服務間的寫操作導致的分散式事務問題?

  • 深分頁問題:按遊標查詢,或者叫每次查詢都帶上上一次查詢經過排序後的最大 ID;



如何將老資料進行遷移

雙寫不中斷遷移


  • 線上系統裡所有寫庫的地方,增刪改操作,除了對老庫增刪改,都加上對新庫的增刪改;

  • 系統部署以後,還需要跑程式讀老庫資料寫新庫,寫的時候需要判斷updateTime;

  • 迴圈執行,直至兩個庫的資料完全一致,最後重新部署分庫分表的程式碼就行了;



系統效能的評估及擴容

和家親目前有1億使用者:場景 10萬寫併發,100萬讀併發,60億資料量
設計時考慮極限情況,32庫*32表~64個表,一共1000 ~ 2000張表


  • 支援3萬的寫併發,配合MQ實現每秒10萬的寫入速度

  • 讀寫分離6萬讀併發,配合分散式快取每秒100讀併發

  • 2000張表每張300萬,可以最多寫入60億的資料

  • 32張使用者表,支撐億級使用者,後續最多也就擴容一次


動態擴容的步驟


  1. 推薦是 32 庫 * 32 表,對於我們公司來說,可能幾年都夠了。

  2. 配置路由的規則,uid % 32 = 庫,uid / 32 % 32 = 表

  3. 擴容的時候,申請增加更多的資料庫伺服器,呈倍數擴容

  4. 由 DBA 負責將原先資料庫伺服器的庫,遷移到新的資料庫伺服器上去

  5. 修改一下配置,重新發布系統,上線,原先的路由規則變都不用變

  6. 直接可以基於 n 倍的資料庫伺服器的資源,繼續進行線上系統的提供服務。



如何生成自增的id主鍵


  • 使用redis可以

  • 併發不高可以單獨起一個服務,生成自增id

  • 設定資料庫step自增步長可以支撐水平伸縮

  • UUID適合檔名、編號,但是不適合做主鍵

  • snowflake雪花演算法,綜合了41時間(ms)10機器、12序列號(ms內自增)


其中機器預留的10bit可以根據自己的業務場景配置。

線上故障及最佳化


更新失敗 | 主從同步延時

以前線上確實處理過因為主從同步延時問題而導致的線上的 bug,屬於小型的生產事故。
是這個麼場景。有個同學是這樣寫程式碼邏輯的。先插入一條資料,再把它查出來,然後更新這條資料。在生產環境高峰期,寫併發達到了 2000/s,這個時候,主從複製延時大概是在小几十毫秒。線上會發現,每天總有那麼一些資料,我們期望更新一些重要的資料狀態,但在高峰期時候卻沒更新。使用者跟客服反饋,而客服就會反饋給我們。
我們透過 MySQL 命令:

show slave status
檢視 Seconds_Behind_Master ,可以看到從庫複製主庫的資料落後了幾 ms。
一般來說,如果主從延遲較為嚴重,有以下解決方案:


  • 分庫,拆分為多個主庫,每個主庫的寫併發就減少了幾倍,主從延遲可以忽略不計。

  • 重寫程式碼,寫程式碼的同學,要慎重,插入資料時立馬查詢可能查不到。

  • 如果確實是存在必須先插入,立馬要求就查詢到,然後立馬就要反過來執行一些操作,對這個查詢設定直連主庫或者延遲查詢。主從複製延遲一般不會超過50ms



應用崩潰 | 分庫分表最佳化

我們有一個線上通行記錄的表,由於資料量過大,進行了分庫分表,當時分庫分表初期經常產生一些問題。典型的就是通行記錄查詢中使用了深分頁,透過一些工具如MAT、Jstack追蹤到是由於sharding-jdbc內部引用造成的。
通行記錄資料被存放在兩個庫中。如果沒有提供切分鍵,查詢語句就會被分發到所有的資料庫中,比如查詢語句是 limit 10、offset 1000,最終結果只需要返回 10 條記錄,但是資料庫中介軟體要完成這種計算,則需要 (1000+10)*2=2020 條記錄來完成這個計算過程。如果 offset 的值過大,使用的記憶體就會暴漲。雖然 sharding-jdbc 使用歸併演算法進行了一些最佳化,但在實際場景中,深分頁仍然引起了記憶體和效能問題。
這種在中間節點進行歸併聚合的操作,在分散式框架中非常常見。比如在 ElasticSearch 中,就存在相似的資料獲取邏輯,不加限制的深分頁,同樣會造成 ES 的記憶體問題。
業界解決方案:
方法一:全域性視野法
(1)將order by time offset X limit Y,改寫成order by time offset 0 limit X+Y
(2)服務層對得到的N*(X+Y)條資料進行記憶體排序,記憶體排序後再取偏移量X後的Y條記錄
這種方法隨著翻頁的進行,效能越來越低。
方法二:業務折衷法-禁止跳頁查詢
(1)用正常的方法取得第一頁資料,並得到第一頁記錄的time_max
(2)每次翻頁,將order by time offset X limit Y,改寫成order by time where time>$time_max limit Y
以保證每次只返回一頁資料,效能為常量。
方法三:業務折衷法-允許模糊資料
(1)將order by time offset X limit Y,改寫成order by time offset X/N limit Y/N
方法四:二次查詢法
(2)將order by time offset X limit Y,改寫成order by time offset X/N limit Y
(3)找到最小值time_min
(4)between二次查詢,order by time between time_i_max
(5)設定虛擬time_min,找到time_min在各個分庫的offset,從而得到time_min在全域性的offset
(6)得到了time_min在全域性的offset,自然得到了全域性的offset X limit Y


查詢異常 | SQL 調優

分庫分表前,有一段用使用者名稱來查詢某個使用者的 SQL 語句:


select * from user where name = "xxx" and community="other";


為了達到動態拼接的效果,這句 SQL 語句被一位同事進行了如下修改。他的本意是,當 name 或者 community 傳入為空的時候,動態去掉這些查詢條件。這種寫法,在 MyBaits 的配置檔案中,也非常常見。大多數情況下,這種寫法是沒有問題的,因為結果集合是可以控制的。但隨著系統的執行,使用者表的記錄越來越多,當傳入的 name 和 community 全部為空時,悲劇的事情發生了:


select * from user where 1=1


資料庫中的所有記錄,都會被查詢出來,載入到 JVM 的記憶體中。由於資料庫記錄實在太多,直接把記憶體給撐爆了。由於這種原因引起的記憶體溢位,發生的頻率非常高,比如匯入Excel檔案時。
通常的解決方式是強行加入分頁功能,或者對一些必填的引數進行校驗


這些年背過的面試題——MySQL篇


Controller 層
現在很多專案都採用前後端分離架構,所以 Controller 層的方法,一般使用 @ResponseBody 註解,把查詢的結果,解析成 JSON 資料返回。這在資料集非常大的情況下,會佔用很多記憶體資源。假如結果集在解析成 JSON 之前,佔用的記憶體是 10MB,那麼在解析過程中,有可能會使用 20M 或者更多的記憶體
因此,保持結果集的精簡,是非常有必要的,這也是 DTO(Data Transfer Object)存在的必要。網際網路環境不怕小結果集的高併發請求,卻非常恐懼大結果集的耗時請求,這是其中一方面的原因。
Service 層
Service 層用於處理具體的業務,更加貼合業務的功能需求。一個 Service,可能會被多個 Controller 層所使用,也可能會使用多個 dao 結構的查詢結果進行計算、拼裝。


int getUserSize() {        List users = dao.getAllUser();        return null == users ? 0 : users.size();}


程式碼review中發現了定時炸彈,這種在資料量達到一定程度後,才會暴露問題。
ORM 層
比如使用Mybatis時,有一個批次匯入服務,在 MyBatis 執行批次插入的時候,竟然產生了記憶體溢位,按道理這種插入操作是不會引起額外記憶體佔用的,最後透過原始碼追蹤到了問題。
這是因為 MyBatis 迴圈處理 batch 的時候,操作物件是陣列,而我們在介面定義的時候,使用的是 List;當傳入一個非常大的 List 時,它需要呼叫 List 的 toArray 方法將列表轉換成陣列(淺複製);在最後的拼裝階段,又使用了 StringBuilder 來拼接最終的 SQL,所以實際使用的記憶體要比 List 多很多。
事實證明,不論是插入操作還是查詢動作,只要涉及的資料集非常大,就容易出現問題。由於專案中眾多框架的引入,想要分析這些具體的記憶體佔用,就變得非常困難。所以保持小批次操作和結果集的乾淨,是一個非常好的習慣。
‍‍



來自 “ ITPUB部落格 ” ,連結:https://blog.itpub.net/70027826/viewspace-3006448/,如需轉載,請註明出處,否則將追究法律責任。

相關文章