面試題-MySQL和Redis(更新版)


前言

MySQL和Redis部分的題目,是我根據Java Guide的面試突擊版本V3.0再整理出來的,其中,我選擇了一些比較重要的問題,並重新做出相應回答,並添加了一些比較重要的問題,希望對大家起到一定的幫助。

系列文章:

面試題-Java基礎

面試題-Java集合

面試題-Java多線程基礎、實現工具和可見性保證

面試題-線程池和原子變量

面試題-Java虛擬機

面試題-計算機網絡1

面試題-計算機網絡-HTTP部分


MySQL

  1. 解釋⼀下什么是池化設計思想。什么是數據庫連接池?為什么需要數據庫連接
    池?

    池化思想其實在 面試題-線程池和原子變量 中 為什么要使用線程池中解釋過,具體優點如下:

    • 提高了連接的可管理性
    • 降低頻繁創建和銷毀資源的開銷
    • 提前創建好資源,提高了任務的響應速度
  2. 范式你了解嗎?簡單說說

    • 第一范式:每個屬性都不可再分,如果僅僅支持第一范式,那么會出現一些問題

      • 數據冗余

      • 插入/刪除/修改 異常

        能具體說說這幾個異常是什么意思嗎?

        如果有一張學生成績表,包含了學生的所有成績,但是其中有所在系的字段,那么針對同一個學生的多門課程成績,系的字段就會冗余,這個就是數據冗余異常;因為系相關的信息沒有自己的表,所以如果這個系沒有學生,就無法插入系的信息,這個是插入異常;刪除異常同理,如果刪除學生信息,那么系的信息也會隨之刪除;修改異常也同理,當修改學生的系信息時,就會出現修改多次的現象。

    • 第二范式:消除了 非主屬性 對於 碼的 部分函數依賴,簡單說就是非主鍵的所有列,必須全部依賴於完整的主鍵,而不能只依賴於主鍵的一部分或者不依賴。

      舉個例子:如果有一個訂單明細表,訂單明細表中包含,orderId,productId,折扣,數量,商品單價和商品名稱,其中主鍵是OrderID+ProductID,折扣和數量是完全依賴主鍵的,但是商品單價和商品名稱是只依賴於商品id,這個設計就不符合第二范式的需求。需要拆分為一個商品表保存商品單價和商品名稱的字段。

    • 第三范式:消除了非主屬性 對於 碼的 傳遞函數依賴,簡單說就是非主鍵的所有列,必須直接依賴於主鍵,不能依賴於非主鍵列,然后傳遞依賴主鍵列。

      舉例來說:有一個課程表,課程id,教師名,教師地址,主鍵是課程id,一個課程id都可以完全確定一個教師名和教師地址,符合第二范式,但是教師地址依賴於非主鍵列教師名,存在傳遞依賴,所以不符合第三范式。

  3. redo log的底層實現你知道嗎?簡單說說

    redolog實現是一個環,大小是固定的。redolog的文件個數和大小也是可以配置的。redolog中有兩個指針,如果write指針追上了checkpoint指針,就需要等待刷新臟頁,然后才能寫入。

    • write:代表了寫入redolog的位置
    • checkpoint:代表了刷新到磁盤的位置
  4. redo log和bin log的區別?

    • 是否共有:redo log是innodb特有的;binlog是server層的,屬於共有的
    • 是否追加寫:redo log是一個環,是循環寫;binlog是追加的覆蓋寫
    • 作用不同:redo log用於數據庫異常宕機的恢復工作;binlog用於備份
  5. 簡單說說事務的ACID特性

    • A:原子性,關注單個事務中的所有操作要么都成功要么都失敗
    • C:一致性,關注事務開始和結束之后數據庫的完整性約束沒有改變
    • I:隔離性,關注多個事務的之間是否互相影響
    • D: 持久性 ,關注事務結束后,對數據庫的修改是否可以永久的保存到數據庫中
  6. 如果沒有隔離性,會發生什么問題?

    如果沒有隔離性,會出現下面三個問題

    • 臟讀:事務讀取到其他事務未提交的數據
    • 不可重復讀:事務中對同一數據的查詢結果不同
    • 幻讀:事務中同一個sql查詢到的數據行數不同
  7. 事務的隔離級別有哪些?分別可以解決什么問題?

    • 讀不可提交:有臟讀/不可重復讀和幻讀的問題
    • 讀可提交:解決了臟讀的問題
    • 可重復讀:解決了臟讀和不可重復讀的問題
    • 串行:解決了三個問題,但性能最差
  8. 隔離性是如何實現的?你知道MVCC嗎?

    隔離性的實現是數據庫創建了一個視圖,訪問的時候以這個視圖的邏輯結果為准

    • 讀不可提交,不創建視圖
    • 讀可提交:每一條SQL都創建一個視圖
    • 可重復讀:在事務開始的時候創建一個視圖,事務中的所有sql都以這個視圖為准
    • 串行:用鎖實現的

    MVCC是多版本並發訪問,通過回滾日志實現的,每一條更新操作都對應一條回滾日志,當前的值,都可以通過回滾日志找到歷史更新過的值,多事務之間可以訪問到不同的數據就是使用MVCC實現的。

  9. 你知道全局鎖嗎?

    全局鎖會鎖住整個數據庫實例,只能讀,任何關於更新的操作都會阻塞。需要了解全局鎖對業務的影響:

    • 如果鎖主庫,那么業務基本停擺
    • 如果鎖從庫,那么鎖定期間,從庫無法同步主庫的更新,可能會導致主從不一致
  10. 簡單說說表鎖?

表鎖是鎖住整張表,以一個sql為例,lock tables t1 read,t2 write;這句sql執行完畢后:

  • t1表只能讀
  • t2表執行者可以讀寫,其他人不可讀寫
  1. 你知道元數據鎖嗎?

    元數據鎖主要控制並發的DDL和DML操作,DML操作獲取的是元數據讀鎖;DDL操作獲取的是寫鎖,讀寫互斥,讀讀不互斥。

  2. 你知道共享鎖排他鎖和意向鎖嗎?

    InnoDB存儲引擎中支持行鎖,行鎖分為三種鎖

    • 共享鎖:可以理解為讀鎖
    • 排他鎖:可以理解為寫鎖
    • 意向鎖:意向鎖的出現主要是為了提高加表鎖的判斷效率,如果沒有意向鎖,線程需要加表鎖時需要對每一行進行判斷,是否有行級鎖的存在,加了意向鎖相當於多一個變量保存狀態,這樣可以O(1)的效率判斷是否可以加表鎖。
  3. 行鎖的三種鎖定范圍(一致性鎖定讀時使用)

    • 只鎖定單行:查詢條件中有索引,並且是唯一索引時,可以只鎖定單行
    • 鎖范圍,不包括記錄本身
    • next-key lock:單行和范圍鎖的合體:查詢條件中有索引,並且不是唯一索引時使用
  4. 什么是索引?索引有什么作用?

    索引是一種數據結構,可以方便我們快速的查找數據,提高查找效率。

  5. Hash索引和B+樹索引的區別?

    • Hash索引適合等值查詢,不適合范圍查詢
    • Hash索引沒辦法利用索引完成排序
    • 如果有大量重復鍵值對,Hash索引的效率會變低,因為需要解決hash碰撞的問題,解決hash碰撞一般采用拉鏈法
    • B+樹索引是一種查詢樹,天然有序,所以可以利用它做范圍查詢和排序,並且因為B+樹的層數少,IO次數也少
  6. 聚集索引和非聚集索引的區別?

    • 聚集索引葉子節點存儲的是整行數據;非聚集索引存儲的是主鍵的值
    • 如果使用非聚集索引時,待查詢的列索引無法覆蓋,那么就需要回表查詢
  7. 聯合索引和最左匹配原則是什么意思?

    • Mysql中可以針對多列創建聯合索引
    • 最左匹配原則指的是,mysql使用索引時,會從最左邊的一列開始匹配,建立一個(k1,k2,k3)的聯合索引,相當於建立了(k1),(k1,k2)和(k1,k2,k3)三個索引
  8. 分庫分表之后,id 主鍵如何處理?

    分庫分表之后,主鍵id就需要一個全局的id,一般來說生成主鍵id有以下幾種方案:

    • UUID:太長,無序不可讀,不適合作為主鍵
    • 利用redis生成id:性能較好,比較靈活,但是需要引入新的組件,增加了系統的復雜度
    • 美團的Leaf:分布式唯一id生成器,也需要依賴關系型數據庫和zookeeper等。
  9. ⼀條SQL語句在MySQL中如何執⾏的?

    查詢語句的執行流程如下:

    1. 連接器中校驗是否有權限,如果沒有權限,直接返回錯誤;mysql8.0之前的版本,會去查詢緩存中檢查是否有緩存,如果有直接返回
    2. 分析器進行詞法分析和語法分析,詞法分析主要關注關鍵字和相關的字段;語法分析主要檢查sql語句是否有語法錯誤
    3. 優化器根據自己的算法確定執行計划,包括索引的選擇等等。
    4. 執行器調用存儲引擎接口,獲取數據返回

    更新語句的執行流程如下:

    1. 相關的校驗等操作和查詢操作是類似的,后面的更新操作不太一樣
    2. 執行器調用存儲引擎的更新接口后,存儲引擎會更新數據,然后記錄redo log,redo log此時為preopare狀態
    3. 執行器寫入binlog日志
    4. 執行器調用存儲引擎的提交接口,存儲引擎把redo log的狀態更新為commit,此為兩階段提交,可以保證 異常宕機的重啟恢復和二進制日志的備份的數據是一致的。
  10. ⼀條SQL語句執⾏得很慢的原因有哪些?

    首先刨析這個問題,有兩種情況:

    • SQL語句平時執行的很快,只是偶爾變慢
    • 在數據量一定的情況下,SQL語句的每次查詢都很慢

    針對第一種情況,SQL本身寫的應該沒什么問題,偶爾變慢的情況應該出在MySQL本身,有以下幾種情況可能會導致查詢變慢:

    • 數據庫在刷新臟頁:redolog滿了;內存不足需要淘汰一部分臟頁
    • 獲取不到鎖,阻塞了:如果某張表或者行已經被別的線程加鎖,暫時獲取不到鎖

    針對第二種情況,可能有以下的原因:

    • 字段沒正確建立索引;字段中包含表達式或者函數操作,導致有索引但是沒有用上
    • 數據庫因為算法綜合判斷,可能不會選擇索引,而選擇全表掃描,算法中的影響因素主要有下面幾個:
      • 掃描行數
      • 是否排序
      • 是否回表
      • 是否需要臨時表
  11. MyISAM引擎和innoDB引擎的區別?

    • 是否支持事務
    • 是否支持外鍵
    • 表鎖和行鎖
    • 是否支持MVCC
  12. MySQL中如果有兩個線程想要操作同一行數據,如何避免死鎖?(重要)

    核心有兩點,知道mysql的鎖定機制和防止死鎖的理論知識,然后結合理論知識判斷如何預防死鎖即可:

    防止死鎖只要破壞死鎖的四個條件之一就可以:

    • 互斥:互斥本身無法破壞
    • 請求並保持:一次性申請全部的鎖即可,表現在sql語句中就是where條件中直接包括多個資源,直接鎖定多個資源的數據。
    • 不可剝奪:如果是應用層面,可以通過設置事務超時時間來實現事務超時釋放鎖並回滾事務,使用Transactional注解中的timeout即可實現。
    • 循環等待條件:每個線程都按照同樣的順序申請資源,比如業務中會更新多條數據,更新操作都按照主鍵序號從小到大更新即可。
  13. 幻讀在InnoDB中是如何被解決的?

    innodb中的間隙鎖就是為了解決幻讀問題出現的。因為鎖定了間隙,所以無法進行插入操作。這樣自然就解決了可重復讀隔離級別下的幻讀問題。

  14. InnoDB的MVCC如何解決臟讀和不可重復讀的問題?

    • 一致性讀只能讀取事務開始時的快照版本(不加鎖的select 解決了不可重復讀的問題)
    • 其他事務的讀只能讀到事務開始時,最新的已提交的版本快照(解決了臟讀的問題)
    • 當前讀(for update和share mode)讀的是當前行的最新版本並且會加行鎖
    • 一個事務內是可以讀取到其所做的未提交的版本快照

Redis

  1. 為什么要用redis,不用map或者guava?

    緩存分為本地緩存和分布式緩存,map和guava屬於本地緩存,特點是:

    • 輕量快速
    • 生命周期隨着JVM銷毀而結束
    • 不同實例中都需要各自持有一份本地緩存,緩存不具有一致性

    分布式緩存,可以保證緩存的一致性,但是會增加系統架構的復雜度。

  2. 簡單說說Redis的RDB持久化?

    RDB持久化是指把當前進程數據生成快照保存到硬盤的過程。RDB的觸發可以分為手動觸發和自動觸發兩種。

    • 手動觸發:可以用save或者bgsave命令,區別在於bgsave不會阻塞當前redis實例,bgsave會fork一個子進程完成持久化工作,阻塞只發生在fork階段
    • 自動觸發:可以在配置文件中使用save配置,配置m秒內存在n次修改,自動觸發持久化過程。
  3. 說說RDB bgsave持久化的詳細過程

    • 父進程檢查當前是否有在運行的子進程,如果有正在運行子進程,直接返回
    • 父進程fork一個子進程出來,fork階段會阻塞,fork完畢后不再阻塞父進程,可以響應其他指令
    • 子進程創建RDB文件,根據父進程內存生成臨時快照文件,完成后對原有文件進行原子性替換
    • 子進程發送信號給父進程通知完畢,父進程更新相關的統計信息
  4. RDB持久化的優缺點?

    • 優點:
      • RDB方式產生的文件是一個壓縮的二進制文件,非常適合全量備份和復制場景
      • Redis加載RDB恢復數據遠遠快於AOF方式
    • 缺點:
      • RDB無法實時持久化/秒級持久化
      • 老版本無法兼容新的RDB文件的格式問題
  5. 簡單說說Redis的AOF持久化?

    AOF持久化,是把每次寫命令都記錄到獨立的日志中,重啟恢復后再執行AOF命令重新恢復數據。AOF持久化主要是解決了實時性的問題。

  6. 說說AOF持久化的詳細過程

    • 所有命令寫入追加到aof_buf緩沖區中
    • 文件同步,文件同步可以在配置文件中配置三種同步機制,考慮到性能和數據安全性,一般推薦everysec
      • always:寫入到aof_buf以后,直接調用fsync系統調用同步到AOF文件
      • everysec:寫入到aof_buf后,先調用write系統調用,寫入系統緩沖區后就會返回,然后由專門線程,每秒調用一次fsync操作
      • no:寫入到aof_buf后,調用write系統調用寫入緩沖區后返回,等待操作系統執行同步磁盤的工作。
  7. 講解下Redis線程模型?

    Redis線程模型中包含四個組成部分:

    • 套接字
    • IO多路復用程序
    • 事件分發器
    • 事件處理器

    IO多路復用程序用select或者其他系統調用管理多個套接字的網絡事件,然后放入到一個統一的隊列中,事件分發器從隊列中取事件,然后分發到不同的處理器中進行處理。

  8. 談談 redis 和 memcached 的區別?

    • 數據結構的豐富度:redis支持更豐富的數據結構。
    • 持久化功能:redis支持RDB或者AOF方式的持久化;memcached不支持
    • 集群功能:redis支持分布式;memcached不支持,需要客戶端上使用分布式一致性hash算法來決定目標節點
    • 線程模型不同:redis是單線程的,由一個線程負責處理所有的網絡事件和業務處理;memcached是一主多從的reactor線程模型。
  9. 說說Redis中的數據類型

    • String類型:一般用於復雜計數功能的緩存
    • Hash類型:hash是以一個String類型為鍵,多個field和value的映射表作為值的數據結構,可以用於存儲包括多列的行信息
    • list類型:list簡單理解就是鏈表,可以用來實現關注列表,消息列表等等
    • Set類型:去重的數據結構,可以方便的實現交集並集等操作
    • Sorted Set類型:增加了一個score權重,可以把Set中的數據按照score排序
  10. redis的過期策略以及內存淘汰機制

    redis中可以設置過期時間,當到了過期時間后,redis中會采取下面兩種策略刪除過期數據:

    • 定期刪除:每隔100ms隨機抽取一些過期的key進行刪除
    • 惰性刪除:當客戶端訪問到這個key了,才會實際刪除

    上面兩種機制都無法保證完全清除過期的key,redis中引入了內存淘汰機制來保證內存不會被過期數據占滿。默認有六種淘汰機制:

    • 從已過期的數據中刪除的:lru,random和ttl
    • 從所有key中刪除:lru,random
    • 不刪除:no-eviction
  11. 簡單談談redis的事務

    redis支持簡單的事務,使用multi和exec來開始和執行事務,類似關系型數據庫中的begin和commit。redis中不支持回滾事務,當出錯時,根據不同的錯誤類型,處理機制也不同。

    • 當有語法錯誤時,會導致整個事務中的命令都不會執行

    • 當有運行時錯誤時,不會影響事務中的其他命令的執行

      另外,redis中提供了watch命令,watch一個key之后,事務中如果有其他客戶端修改了該key,整個事務不執行。

  12. 你知道緩存雪崩嗎?簡單說說

    緩存雪崩指的是,當redis集群不可用或者同時有大量緩存失效時,會造成大量的請求都走數據庫,把數據庫服務擊垮。

    • 針對redis集群不可用,應該事前做好redis集群的高可用性檢查,選擇合適的內存淘汰策略。
    • 針對大量緩存同時失效,可以采取設置隨機過期時間的方式,來避免緩存同時失效
    • 提前設計好 本地緩存和限流降級方案,本地緩存+redis中如果都沒有,再去查數據庫,查數據庫的請求要通過限流降級組件來保護數據庫。
  13. 你知道緩存穿透嗎?

    緩存穿透指的是,大量請求的key本身不在緩存中,導致直接走了數據庫查詢。舉例來說,黑客攻擊時,會偽造很多不存在的key來導致大量請求落到數據庫中。解決方法有兩種:

    • 最基本的是做好參數校驗,不合規的參數直接返回
    • 使用布隆過濾器,其實本質就是一個hash函數加位數組,報存在,有一定概率的誤判(hash沖突),但是不存在一定是准確的,比如兩個字符串的hash值相同得到位數組的位置就相同。提前把有效的key存儲在布隆過濾器中,這樣當無效key傳遞過來時,直接返回。
  14. 如何用redis實現分布式鎖?你還知道其他更好的方式嗎?

    實際業務中沒有使用過,這塊等待有真實業務場景應用后再來補充。

  15. 如何保證緩存與數據庫雙寫時的數據⼀致性?

    • 簡單的解決方式:如果有更新需求,先刪除緩存,再更新數據庫,如果數據庫更新失敗,緩存也為空,不會出現不一致。
    • 簡單解決方式的不足:刪除緩存完畢后,更新數據庫之前,又有一個線程要訪問該數據,結果發現緩存中沒有數據,從數據庫中查詢出舊數據,然后更新到緩存中,這個時候更新數據庫,那么緩存和數據庫又發生了不一致。
    • 解決方案:使用一個隊列把讀請求和寫請求串在一起,寫請求執行完畢后才會執行讀請求和更新,但是要注意測試讀請求的阻塞時長 如何保證緩存與數據庫的雙寫一致性?


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM