(已上岸)記一次Java後臺實習面試問題——附答案

2020年6月5日,某互聯網公司的 Java開發實習生 ,技術面二面。

面試官:自我介紹、項目提了一嘴、JDK 1.8、集合源碼、TCP細節、HTTP和HTTPS、redis數據結構和常用方法、消息隊列基礎、Spring、SpringCloud、Maven、智力題、手撕算法(排列、DP)。。。

TCP 在之前的學習過程中有一個全面的知識整理:《面試?一篇就搞定!!史上最全TCP面試題 + 解答!》《面試必考問題一文搞定:HTTP和HTTPS?TLS的工作流程?》

redis 也簡單整理過一點:《面試必問:Redis 5 種基礎數據結構》、《緩存擊穿、緩存穿透、緩存雪崩》、《redis 超時刪除策略》

前面的一切都比較順利,問的都不難沒出什麼大錯。

突然,面試官話鋒一轉:你的簡歷上怎麼沒提到關係型數據庫的東西呢?工作中一定要用到的啊!學校學過吧?你肯定也用過吧!

:emmmmm(因爲數據庫這方便確實沒怎麼複習過,心虛,就沒往簡歷上寫)

面試官:這樣吧,時間也差不多了,簡單問一下吧!從你的這個項目上問吧?

問:項目下單秒殺怎麼做的併發優化?答:巴拉巴拉。。。

問:xxxxx?答:阿巴阿巴。。。

問:xxxxx?答:emmmm。。。

雖然最後介於前面回答的還不錯,也還是勉強過關了。事後,痛定思痛決定還是要好好學習一下數據庫的東西,就從查資料整理這次面試問題開始吧!!只是整理一個大概的學習方向,細節上還是要再去深入學習的。

正文

這個秒殺的業務是怎麼保證不會超額售賣呢?

  1. 隊列,將所有的請求有序入隊,完全串行化處理。到達庫存的閾值的時候停止消費隊列中的消息,結束秒殺。雖然解決了超賣問題,但是非常容易出現消費速度遠遠小於請求入隊的速度,最終導致內存被大量消耗。

  2. 悲觀鎖,最容易想到的,更新庫存的操作進行加鎖。代碼裏搞 Synchronized ,或者在 SQL 上用 for update,這麼做的缺點也很明顯,高併發場景下大量請求等待,導致系統連接數飆升等等。

    關於 for update:僅適用於 InnoDB 引擎,且必須在事務(BEGIN/COMMIT)內才能生效,預設是行級鎖,但是隻有當明確索引的時候纔是行鎖,否則會升級到表鎖。相較於 Myisam 引擎,則只支持表級鎖。

  3. 樂觀鎖,使用版本號的更新,或者先查庫存,要進行減庫存的時候,再查庫存,前後庫存一致才能修改,但是併發的時候只有一個線程可以修改成功。

  4. 緩存鎖,修改某個數據時,把數據的 id 入緩存,其他線程再來修改本數據時,發現緩存中有這個 id,就阻止。好像可以用一些緩存組件的輕量級鎖機制 CAS 實現減庫存。(僅做記錄,目前還沒具體使用過,就不瞎說了)

  5. 分佈式鎖,(沒用過。。)

看你簡歷上說用了存儲過程,這樣真的性能好麼?

我認爲這個是詢問存儲過程優缺點的:

優點:

  1. 生產環境下可以直接修改存儲過程的方式修改業務邏輯或缺陷,並且不用重啓服務。
  2. 存儲過程經過編譯之後存儲,執行的時候迴避一條一條 SQL 執行要快。(其實,大多數情況下並沒有明顯的速度優勢)
  3. 減少了網絡 IO 的影響,尤其是在高併發的情況下。存儲過程全部在數據庫服務器上執行,避免了業務代碼中多條 SQL 分別到別的服務器上去執行所造成的時間消耗。
  4. 方便 DBA 優化,SQL 全部集中在一起。

缺點:

  1. 存儲過程總體還是一個過程化的方法,如果業務邏輯很複雜,就比較難處理了。
  2. 不能像代碼一樣有非常方便的調試器。
  3. 不便於加緩存。
  4. 不便於數據庫的分割,數據庫分割之後,存儲過程難以分清數據存儲在哪個庫中。
  5. 不支持集羣,而且難以橫向擴展。

一條慢sql、慢查詢怎麼排查原因?怎麼優化?

總結自——雲棲社區——《一條SQL語句執行得很慢的原因有哪些?》

分兩種情況:

  1. 大多數情況正常,偶爾很慢:

    • 數據庫更新頻繁,redo log 很快被寫滿後數據庫暫停其他操作,全身心去將數據同步到磁盤中,這種情況下可能會導致某條"不幸運"的 SQL 執行緩慢。
    • 搶不到鎖,要執行的 SQL 需要涉及到的表、行,被加鎖了,一直在等鎖。(可以用 show processlist 命令來查看當前的狀態)
  2. 數據量不變的情況下,一直很慢:

    • 條件字段沒有索引,或者有索引但是沒有用索引,從而進行了全表掃描。(可以用 EXPLAIN 關鍵字對查詢語句進行估算分析,下文詳細介紹)

      一部分會導致索引失效的情況:

      1. 如果條件中有 or ,即使條件字段有索引也不會命中。(這也是爲什麼儘量少用 or 的原因)只有當條件中只有 or 關鍵字,並且 or 前後的兩個條件的列都有索引時,才能命中索引。
      2. like 語句是以 % 開頭不能命中索引,只有當 % 不在開頭的時候,索引才起效。
      3. 如果列類型是字符串,條件中的數據要使用引號引起來,否則不走索引。
      4. 聯合索引要符合最左原則,否則不走索引。
      5. b+tree 索引 is null 不走索引,但是 is not null 走索引。
      6. 存儲引擎不能使用索引中範圍條件右邊的列。
      7. 在索引列上做計算、函數、類型轉換操作,會導致索引失效從而進行全表掃描。
    • 數據庫選錯索引(系統採樣統計導致的“失誤”),從而沒有走索引,而是全表掃描。**PS:**這種情況可以使用 show index from table_name 來查詢表中索引的相關信息,其中 Cardinality 字段表示索引的基數。如果系統統計的基數和實際差距很大的話,可以使用 analyze table table_name 重新進行統計分析。

    無論是不走索引,還是選錯索引,在分析之後都可以使用 FORCE INDEX(index_name)、USE INDEX(index_name)、IGNORE INDEX(index_name) 關鍵字進行強制使用索引或者忽略索引。

具體內容還是要去看原作者的文章,這裏只是籠統地總結一下。

主鍵索引和非主鍵索引有什麼區別?

主鍵索引存放的值是整行數據,而非主鍵索引上存放的是主鍵的值,所以不難理解上學的時候教材上說:MySQL 的非主鍵索引是一種二級索引,主鍵索引是一種聚簇索引。

聯合索引只用其中一部分字段能否命中?

聯合索引是由多個字段組成的索引,查詢時只使用聯合索引的一個字段,如果這個字段在聯合索引的所有字段的第一個(最左),那就會用到索引,否則就無法使用到索引。

建立原則:例如,a、b兩個字段,如果經常用到 a 條件或者 a+b 條件去查詢,並且很少單獨使用 b 條件查詢,那麼就可以建立 a,b 聯合索引。反之,如果 a、b 分別經常獨立被用作查詢條件,那就需要分別建立單列索引。儘量選擇查詢中過濾性最好的字段,而且字段順序越靠前越好。

MySQL 索引種類

  1. 按數據結構分

    • b+tree 索引 O(logn) 重要!
    • hash 索引 重要!
      • 僅僅滿足"="、“in”、"<=>" 查詢,不能使用範圍查詢。
      • hash 索引查找效率非常高,不需要像 b+tree 一樣從根到目標節點。
      • 只有 memory 存儲引擎顯示支持 hash 索引。
    • FULLTEST 索引
    • r-tree 索引

    這篇文章非常的嗯——> 《MySQL索引背後的數據結構及算法原理 》

  2. 按物理存儲分

    • 聚簇索引
    • 非聚簇索引
  3. 從邏輯形式分

    • 主鍵索引:特殊的唯一索引,不能爲空
    • 單列索引
    • 聯合索引
    • 唯一索引
    • 空間索引

建立索引的字段怎麼選擇的?

  1. 頻繁作爲查詢條件的字段(where 後面經常出現的)。。。廢話。。
  2. 唯一性太差的字段不適合單獨建立索引,即使頻繁作爲查詢條件。
  3. 更新非常頻繁的字段不適合創建索引。
  4. 聯合索引見上文。

實際工程中,還是要具體問題具體分析的。。。感覺這個問題很水

怎麼知道索引是否命中?

部分文字引用自: 《技術指南網》

可以使用 EXPLAIN 語句 ;來估算索引的使用情況。

txbwVg.png

student 表,name 字段建立索引 name_idx

EXPLAIN SELECT
	*
FROM
	student
WHERE
	age > (
		SELECT
			age
		FROM
			student
		WHERE
			NAME = '李四'
	)

查找所有年齡大於’李四’的學生信息,結果:
txO2sP.png
字段解釋:

  1. id:id 越大執行優先級越高,相同則執行順序從上到下。
  2. select_type:表示每個子句的類型。
    • SIMPLE:簡單語句。
    • PRIMARY:如果存在子查詢,則最外層語句標記爲主語句。
    • UNION:UNION關鍵字中第二個或後面的語句。
    • DEPENDENT UNION:UNION中的第二個或後面的SELECT語句,取決於外面的查詢 。
    • UNION RESULT:UNION的結果)。
    • SUBQUERY:子查詢中的第一個語句。
    • DEPENDENT SUBQUERY:子查詢中的第一個語句,取決於外層的查詢。
    • DERIVED:派生表的語句,FROM 子句的子查詢。
    • UNCACHEABLE SUBQUERY:一個子查詢的結果不能被緩存,必須重新評估外鏈接的第一行。
  3. table:表示語句是關於那張表的,有時會是派生表 derivedX ,這裏的 X 表示產生派生表的語句的 id 。
  4. type:表示語句在表中查找的方式,又稱“訪問類型”:ALL、index、range、ref、eq_ref、const、system、NULL(從左到右性能越來越好)
    • ALL:Full Table Scan,全表掃描。
    • index: Full Index Scan,遍歷索引樹。
    • range:檢索給定範圍的行,使用一個索引來選擇行 。
    • ref: 表示上述表的連接匹配條件,即哪些列或常量被用於查找索引列上的值。
    • eq_ref : 類似 ref,區別就在使用的索引是唯一索引,對於每個索引鍵值,表中只有一條記錄匹配,例如:多表連接中使用 primary key 或者 unique key 作爲關聯條件。
    • const、system : 當 MySQL 對查詢某部分進行優化,並轉換爲一個常量時,使用這些類型訪問。例如:將主鍵置於where列表中,MySQL 就能將該查詢轉換爲一個常量,system 是 const 類型的特例,當查詢的表只有一行的情況下,使用 system 。
    • NULL : MySQL在優化過程中分解語句,執行時甚至不用訪問表或索引,例如:從一個索引列裏選取最小值可以通過單獨索引查找完成。
  5. possible_keys:指出 MySQL 能使用哪個索引在表中找到記錄,查詢涉及到的字段上若存在索引,則該索引將被列出,但只是 ‘possible’ 所以該索引不一定被查詢使用。
  6. key:表示 MySQL 實際決定使用的鍵(索引),如果沒有選擇索引,鍵是 NULL。如果想要強制 MySQL 使用或忽視 possible_keys 列中的索引,在查詢中使用 FORCE INDEX(index_name)、USE INDEX(index_name)、IGNORE INDEX(index_name) 即可實現。
  7. key_len:表示索引中使用的字節數,可通過該列計算查詢中使用的索引的長度(key_len顯示的值爲索引字段的最大可能長度,並非實際使用長度,即key_len是根據表定義計算而得,不是通過表內檢索出的)。不損失精確性的情況下,長度越短越好。
  8. ref:表示上述表的連接匹配條件,即 使用哪個列或常數與key一起從表中選擇行。
  9. rows:表示查詢所需記錄需要讀取的行數(根據表統計信息和索引選用情況估算出的值)。
  10. Extra: 該列包含MySQL解決查詢的詳細信息。
    • Using where:列數據是僅僅使用了索引中的信息,而沒有讀取實際的表就返回的。這發生在對錶的全部的請求列都是同一個索引的時候,表示 MySQL 服務器將在存儲引擎檢索行後再進行過濾。
    • Using temporary:表示MySQL需要使用臨時表來存儲結果集,常見於排序和分組查詢。
    • Using filesort:MySQL 中無法利用索引完成的排序操作稱爲“文件排序”。
    • Using join buffer:強調了在獲取連接條件時沒有使用索引,並且需要連接緩衝區來存儲中間結果。如果出現了這個值,那應該注意,根據查詢的具體情況可能需要添加索引來改進能。
    • Impossible where:這個值強調了 where 語句會導致沒有符合條件的行。
    • Select tables optimized away:這個值意味着僅通過使用索引、優化器,可能僅從聚合函數結果中返回一行。

PS:EXPLAIN 不包含觸發器、存儲過程、自定義函數對查詢的影響情況,且不考慮 cache 和 MySQL 執行查詢時所做的優化工作。而且結果的部分信息是估算值。只能解釋 SELECT 操作,其他操作需要重寫爲 SELECT 後查看執行計劃。


我對數據庫的瞭解還是太少了,不能保證上面的回答是正確且合理的。還請賜教!只是整理一個大概的學習方向,細節上還是要再去深入學習的。

菜鳥本菜,不吝賜教,感激不盡!

更多題解源碼和學習筆記:githubCSDNM1ng

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章