mysql count 条件_我以为我对MySql很了解,直到我面试了字节跳动
小濕來到字節跳動,在靜待,此時一位長發飄飄的面試官走來。小濕心里想:哎啊,今天遇到個美女面試官,好好表現
面試官:小濕,我看你簡歷上寫了熟練掌握MySql和MySql的調優是吧?
小濕:是的,面試官。
面試官:說一下你在項目中如何使用MySql的?
小濕:增刪改查,沒了。
面試官:如何調優的你是?
小濕:加索引,沒了。
面試官:我們公司的門你知道在哪里吧,用我送你嗎?
哈哈,上面場景是開玩笑的當然,言歸正傳,大家如果真遇到這種問題如何回答?數據庫是如何調優的?或者說大家可能都清楚加索引是數據庫調優方法之一,但是關于索引又知道多少呢?下面我們一起進入主題
面試開始
面試官:說一下in關鍵字和exists關鍵字吧
小濕:好的,in關鍵字盡量用在內表小的地方,exists關鍵字用在外表小的地方,如果用not in ,則是內外表都全表掃描,無索引,效率低,可考慮使用not exists,也可以考慮用連接來優化。(內心OS,這能難得到我?)
接下來分析一波(面試回答問題一定要條理清晰!)
in關鍵字是把外表和內表做hash連接,先查詢內表,再把內表的結果和外表匹配,對外表使用索引(外表效率高,可以用大表),而內表都需要查詢,使用in關鍵字可以加快效率。exists關鍵字是對外表做loop循環,每次循環對內表進行查詢(對內表可以使用索引,查詢效率高,可以用大表),而外表有多大都需要遍歷,使用exists關鍵字可以加快效率。
舉個例子:select * from A where A.id in (select id from B);對A表使用索引效率高,建議A為大表。select * from A where exists (select * from B where A.id=B.id);對B使用索引效率高,因為外表A總是要全表,而且要循環,所以B表建議使用大表。
面試官:說一下count吧
小濕:count關鍵字是用來進行不為null的行數統計的,有三種用法分別是:count(列名)、count(常量)和count(*)。在《阿里巴巴Java開發手冊》中強制要求不讓使用 count(列名)或 count(常量)來替代 count(*)
區別:列名、 常量 和 *這三個條件中,常量是一個固定值,肯定不為null。*可以理解為查詢整行,所以肯定也不為null,那么就只有列名的查詢結果有可能是null了,所以count(常量) 和 count(*)表示的是直接查詢符合條件的數據庫表的行數。而count(列名)表示的是查詢符合條件的列的值不為null的行數。count(*)是SQL92定義的標準統計行數的語法,因為他是標準語法,所以mysql數據庫對他進行過很多優化
SQL92,是數據庫的一個ANSI/ISO標準。它定義了一種語言(SQL)以及數據庫的行為(事務、隔離級別等
面試官:你能說一下這些優化嗎?
小濕:面帶微笑,當然可以的。這里的介紹要區分不同的執行引擎,MySQL中比較常用的執行引擎就是InnoDB和MyISAM。我們知道MyISAM不支持事務,表級鎖,而InnoDB支持事務,支持行級鎖。
因為MyISAM是表級鎖,所以在一張表上的操作是串行執行的。所以,MyISAM做了一個簡單的優化,那就是它可以把表的總行數單獨記錄下來,如果從一張表中使用count(*)進行查詢的時候,可以直接返回這個記錄下來的數值就可以了,當然,前提是不能有where條件。為什么MyISAM可以這樣做呢?因為它是表級鎖,不會有并發的數據庫修改記錄的行為,查詢的行數是準確的
對于InnoDB則不適合這種緩存操作了,它是支持事務和行級鎖,表的行數可能會被并發修改,那么緩存記錄下來的行數就不準確了,那么InnoDB則不可避免的要進行掃表了。于是從mysql8.0.13開始,select count(*) 在掃表的過程中做了一些優化,前提是查詢語句中不包含where或group by等條件。我們的目的只是為了統計總行數,并不關心查到的具體值,所以可以選擇一個成本較低的索引進行,節省時間。而且InnoDB中索引分為聚簇索引(主鍵索引)和非聚簇索引(非主鍵索引),聚簇索引的葉子節點中保存的是整行記錄,而非聚簇索引的葉子節點中保存的是該行記錄的主鍵的值。所以,相比之下,非聚簇索引要比聚簇索引小很多,所以MySQL會優先選擇最小的非聚簇索引來掃表。所以,當我們建表的時候,除了主鍵索引以外,創建一個非主鍵索引還是有必要的。
面試官:mysql中字段為什么要求定義為not null?
小濕:null值會占用更多的字節,且會在程序中造成很多與預期不符的情況。
面試官:(此時面試官點了點頭,繼續微笑著說到)你剛剛提到過引擎,除了你說的兩點區別,還有別的嗎?
小濕:接下來我來分點介紹
- 上面剛剛介紹的,MyISAM不支持事務,表級鎖,而InnoDB支持事務,支持行級鎖
- InnoDB 支持外鍵,而 MyISAM 不支持。對一個包含外鍵的 InnoDB 表轉為 MYISAM 會失敗
- InnoDB 是聚集索引,MyISAM 是非聚集索引
面試官:那你能說說什么是索引嗎?
小濕:索引其實是一種數據結構,能夠幫助我們快速的檢索數據庫中的數據。在用法上索引大概分為以下幾類:
- 普通索引normal:僅僅加索查詢;
- 唯一索引unique:加索查詢,列值唯一,可以有NULL。
- 主鍵索引primary:加速查詢,列值唯一,不可以為NULL,表中只有一個。
- 組合索引:多列值組成一個索引,專門用于組合搜索,其效率大于索引合并
- 全文索引full text:對文本的內容進行分詞,進行搜索
面試官:那索引具體底層是什么樣子的?(言外之意就是數據結構)
小濕:索引的數據結構和具體存儲引擎的實現有關,MySQL主要有兩種結構:Hash索引和B+ Tree索引,我們使用的是InnoDB引擎,默認的是B+樹
面試官:為什么采用B+樹呢,和Hash索引比較如何?
小濕:好的,接下來我來詳細介紹下兩者:
面試官:關于B+樹的葉子節點,可以存放哪些東西?
小濕:在B+樹的索引中,葉子節點可能存儲了當前的key值,也可能存儲了當前的key值以及整行的數據,這就是聚簇索引和非聚簇索引。在InnoDB引擎中,只有主鍵是聚簇索引,如果沒有主鍵則挑選一個唯一鍵作為聚簇索引,如果沒有唯一鍵,則隱式的生成一個鍵來建立聚簇索引
面試官:聚簇索引和非聚簇索引特點?
小濕:聚簇索引和非聚簇索引,聚簇索引更快,因為主鍵索引樹的葉子節點直接就是我們要查詢的整行數據了。而非主鍵索引的葉子節點是主鍵的值,查到主鍵的值以后,還需要再通過主鍵的值再進行一次查詢。當查詢使用局促索引的時候,在對應的葉子結點上可以獲取到整行的數據,不再需要回表查詢,而非聚簇索引則需要回表查詢。
面試官:那非聚簇索引一定要回表搜索嗎?
小濕:不一定,這涉及到查詢語句所要求的字段是否全部命中了索引,如果全部命中了索引,那么就不必再進行回表查詢,即覆蓋索引。
覆蓋索引(covering index)指一個查詢語句的執行只用從索引中就能夠取得,不必從數據表中讀取。也可以稱之為實現了索引覆蓋。 當一條查詢語句符合覆蓋索引條件時,MySQL只需要通過索引就可以返回查詢所需要的數據,這樣避免了查到索引后再返回表操作,減少I/O提高效率。 如表covering_index_sample中有一個普通索引 idx_key1_key2(key1,key2)。當我們通過SQL語句:select key2 from covering_index_sample where key1 = ‘keytest’;的時候,就可以通過覆蓋索引查詢,無需回表
此時,面試官投來了一個肯定的眼神(表現不錯,繼續為難她一波),不過她好像沒打算就此放過我,果不其然,又來了第二波!
面試官:在建立索引的時候,你一般考慮哪些因素?
小濕:建立索引的時候一般要考慮字段的使用頻率,經常作為查詢條件的字段比較適合索引。當然也不能過度建立索引,因為索引也是占據內存的,而且修改表會導致索引更新,所以在建立索引的時候也要考慮表結構。
在一些場合使用聯合索引是比較好的效果,比如我們可以建立一個(學校-班級-ID)的聯合索引,這樣會比建立三個索引效果好,但是如果我們只使用其中一個索引ID不會走聯合索引,會導致全表掃描,所以要分業務情況。使用聯合索引時需要注意順序,盡量把區分度大的索引放在前面。
面試官:為什么建立聯合索引?(區分度大的索引放在前面)
小濕:在聯合索引使用中,如果想要命中索引需要按照建立索引時的字段順序挨個使用,否則無法命中索引。聯合索引中有個最左匹配原則,當我們建立聯合索引(A,B,C),實際上已經建立了(A)、(A,B)、(A,B,C)三個聯合索引。
比如我們上面說的(學校-班級-ID)聯合索引,b+樹是按照從左到右的順序來建立搜索樹的,b+樹優先比較學校來確定下一步的搜索方向,如果還未達到條件則繼續執行搜索。如果只有學校字段,班級字段缺失,只能找到這個學校的所有字段,然后再匹配相應ID的學生,此種情況無法用到聯合索引。
面試官:如何判斷創建的索引是否使用到,或者說如何分析Sql語句?
小濕:我一般都是通過explain命令來查看語句的執行計劃,使用explain關鍵字可以模擬優化器執行SQL查詢語句,從而知道MySQL是如何處理你的SQL語句的。分析你的查詢語句或是表結構的性能瓶頸。
面試官:簡單說一下Explain的字段?
小濕:(下面是相關字段,沒必要全說,說熟悉的即可)
- id:select查詢的序列號,包含一組數字,表示查詢中執行select子句或操作表的順序
- select_type:查詢的類型,主要是用于區別普通查詢、聯合查詢、子查詢等的復雜查詢
- table:指的就是當前執行的表
- type:type所顯示的是查詢使用了哪種類型。查詢性能從最好到最差依次是:system > const > eq_ref > ref > range > index > all,一般來說,得保證查詢至少達到range級別,最好能達到ref
- possible_keys 和 key:possible_keys 顯示可能應用在這張表中的索引,一個或多個。查詢涉及到的字段上若存在索引,則該索引將被列出,但不一定被查詢實際使用。key則表示實際使用的索引,如果為NULL,則沒有使用索引。(可能原因包括沒有建立索引或索引失效)
- key_len:表示索引中使用的字節數,可通過該列計算查詢中使用的索引的長度,在不損失精確性的情況下,長度越短越好
- ref:顯示索引的那一列被使用了,如果可能的話,最好是一個常數。哪些列或常量被用于查找索引列上的值
- rows:根據表統計信息及索引選用情況,大致估算出找到所需的記錄所需要讀取的行數,也就是說,用的越少越好
- Extra:包含不適合在其他列中顯式但十分重要的額外信息
面試官:什么情況下針對列創建了索引,查詢的時候卻沒有使用?
小濕:比如索引列參與了數學運算或者函數。字符串like時的左邊是通配符,類似于"%aa"這種,當mysql分析全表掃描比使用索引快的時候不使用索引。
面試官:那你知道在MySQL 5.6中,對索引做了哪些優化嗎?
小濕:好像知道有個索引下推,默認是開啟的。官方文檔中給的例子和解釋如下:
SELECT * FROM people WHERE zipcode=‘95054’ AND lastname LIKE ‘%etrunia%’ AND address LIKE ‘%Main Street%’people表中(zipcode,lastname,firstname)構成一個索引,如果沒有使用索引下推技術,則MySQL會通過zipcode='95054’從存儲引擎中查詢對應的數據,返回到MySQL服務端,然后MySQL服務端基于lastname LIKE '%etrunia%'和address LIKE '%Main Street%'來判斷數據是否符合條件。如果使用了索引下推技術,則MYSQL首先會返回符合zipcode='95054’的索引,然后根據lastname LIKE '%etrunia%'和address LIKE '%Main Street%'來判斷索引是否符合條件。如果符合條件,則根據該索引來定位對應的數據,如果不符合,則直接reject掉。有了索引下推優化,可以在有like條件查詢的情況下,減少回表次數。
面試官:MySql的架構流程了解嗎?
小濕:(聽見這種問題我就拿起筆來邊說邊畫),客戶端會先通過連接器連接,然后查詢緩存中是否有我們想要的數據,即是否緩存命中。命中則直接返回數據,否則進入分析器和優化器,分析Sql語句和優化Sql語句,然后執行器選擇相應的引擎執行
面試官:說說數據庫的事務~
小濕:事務是一系列的操作,他們要符合ACID特性
- 原子性(Atomicity):事務必須是原子工作單元,對于數據修改,要么全都執行,要么全部不執行
- 一致性(Consistency):系統(數據庫)總是從一個一致性的狀態轉移到另一個一致性的狀態,不會存在中間狀態
- 隔離性(Isolation):一個事務在完全提交之前,對其他事務是不可見的
- 持久性(Durability):一旦事務提交,那么就永遠是這樣子了,哪怕系統崩潰也不會影響到這個事務的結果
面試官:事務是否存在問題呢在并發情況下?
小濕:事務在并發下存在臟讀、不可重復讀、虛讀等問題
- 臟讀:事務A讀取到事務B未提交的數據,結果事務B回滾了,造成錯誤
- 不可重復讀:事務A執行中,讀取數據num=10,此時事務B執行完成并提交,修改了num=11。事務A再讀取num為11,這種情況叫做不可重復讀
- 虛讀:事務A讀取了一個范圍的數據(比如10<num<20),讀取到3條,結果事務B插入了一條數據成功提交,事務A讀取到這個范圍變成4條,即虛讀
- 不可重復讀的重點是修改,同樣的條件,你讀取過的數據,再次讀取出來發現值不一樣;(主要在于update和delete)幻讀的重點在于新增或者刪除,同樣的條件,第 1 次和第 2 次讀出來的記錄數不一樣。(主要在于insert)
面試官:如何解決這些問題呢?
小濕:為了解決以上事務并發時出現的一系列問題,就需要設置事務的隔離級別。隔離級別就是多個線程開啟各自事務操作數據庫中數據時,數據庫系統要負責隔離操作,以保證各個線程在獲取數據時的準確性
MySql中定義了4中隔離級別:
- 未提交讀(READ UNCOMMITTED):事務A可以讀取到事務B未提交的數據。最低級別,會造成臟讀的情況
- 已提交讀(READ COMMITTED):事務A只能讀取到事務B已經提交的數據,解決了臟讀的問題,但是存在不可重復讀和虛讀的問題
- 可重復讀(REPEATABLE READ):解決了事務A在執行中前后讀取數據不一致的問題,即不可重復讀的問題,不會出現剛剛讀取num=10,過會再讀取num變為11的情況。但是還是會存在虛讀的問題,即事務A讀取一個范圍的數據量可能會發生變化造成“幻覺”
- 可串行化(SERIALIZABLE):這是最高的隔離級別,可以解決上面提到的所有問題,因為他強制將所以的操作串行執行,這會導致并發性能極速下降,因此也不是很常用
在MySQL數據庫中,支持上面四種隔離級別,默認的為Repeatable read (可重復讀);而在Oracle數據庫中,只支持Serializable (串行化)級別和Read committed (讀已提交)這兩種級別,其中默認的為Read committed級別
面試官:能不能詳細介紹下MySql是如何控制隔離級別的?
小濕:(還要詳細,上面的難道還不夠?)是通過排它鎖和共享鎖。
- 排它鎖:被加鎖的對象只能被持有鎖的事務讀取和修改,其他事務無法在該對象上加其他鎖,也不能讀取和修改該對象
- 共享鎖:被加鎖的對象可以被持鎖事務讀取,但是不能被修改,其他事務也可以在上面再加共享鎖
在對不論什么數據進行讀操作之前要申請并獲得S鎖(共享鎖,其他事務能夠繼續加共享鎖,但不能加排它鎖),在進行寫操作之前要申請并獲得X鎖(排它鎖,其他事務不能再獲得不論什么鎖)。加鎖不成功,則事務進入等待狀態,直到加鎖成功才繼續運行。
面試官:你剛剛提到了數據庫的鎖,簡單說一下鎖吧
小濕:對數據的操作其實只有兩種,也就是讀和寫,而數據庫在實現鎖時,也會對這兩種操作使用不同的鎖;InnoDB 實現了標準的行級鎖,也就是共享鎖(Shared Lock)和排它鎖(Exclusive Lock)。
共享鎖(讀鎖),允許事務讀一行數據。排它鎖(寫鎖),允許事務刪除或更新一行數據。而它們的名字也暗示著各自的另外一個特性,共享鎖之間是兼容的,而互斥鎖與其他任意鎖都不兼容,如下圖
Lock鎖根據粒度主要分為表鎖、頁鎖和行鎖。不同的存儲引擎擁有的鎖粒度都不同
面試官:那悲觀鎖和樂觀鎖了解嗎
小濕:悲觀鎖和樂觀鎖是一種思想,一種處理方式,不可和上面的鎖機制(表鎖,行鎖,排他鎖,共享鎖)混為一談
- 悲觀鎖:即對于數據的處理持悲觀態度,總認為會發生并發沖突,獲取和修改數據時,別人會修改數據。所以在整個數據處理過程中,需要將數據鎖定。悲觀鎖的實現,通常依靠數據庫提供的鎖機制實現,比如mysql的排他鎖,select .... for update來實現悲觀鎖
- 樂觀鎖:顧名思義,就是對數據的處理持樂觀態度,樂觀的認為數據一般情況下不會發生沖突,只有提交數據更新時,才會對數據是否沖突進行檢測。如果發現沖突了,則返回錯誤信息給用戶,讓用戶自已決定如何操作。樂觀鎖的實現不依靠數據庫提供的鎖機制,需要我們自已實現,實現方式一般是記錄數據版本,一種是通過版本號,一種是通過時間戳。
給表加一個版本號或時間戳的字段,讀取數據時,將版本號一同讀出,數據更新時,將版本號加1。當我們提交數據更新時,判斷當前的版本號與第一次讀取出來的版本號是否相等。如果相等,則予以更新,否則認為數據過期,拒絕更新,讓用戶重新操作。
面試官:數據庫一個連接多久,每次都要釋放嗎?(言外之意就是數據庫的池化思想。)
小濕:數據庫連接是一種有限的昂貴的資源,對數據庫連接的管理能影響到整個應用程序的伸縮性和健壯性,數據庫連接池正式針對這個問題提出來的。數據庫連接池負責分配、管理和釋放數據庫連接,它允許應用程序重復使用一個現有的數據庫連接,而不是重新建立一個。(數據庫連接池思想和線程池思想一樣)常用的三種連接池:
- C3p0連接池:開源的JDBC連接池,實現了數據源和JNDI綁定,支持JDBC3規范和JDBC2的標準擴展。目前使用它的開源項目有Hibernate、Spring等。單線程,性能較差,適用于小型系統,代碼600KB左右
- Dbcp連接池:由Apache開發的一個Java數據庫連接池項目, Tomcat使用的連接池組件就是DBCP。預先將數據庫連接放在內存中,應用程序需要建立數據庫連接時直接到連接池中申請一個就行,用完再放回。單線程,并發量低,性能不好,適用于小型系統
- Druid連接池:Druid不僅是一個數據庫連接池,還包含一個ProxyDriver、一系列內置的JDBC組件庫、一個SQL Parser
面試官:MySql數據丟失了怎么辦?(持久化機制)
小濕:這個我記得在InnoDB中有個redo 日志是用來保證 MySQL 持久化功能的。MySql的操作是要寫入到日志中 ,并不會直接刷新到硬盤上進行持久化。如果我們每一次的操作都要寫入到硬盤中再更新,整個過程IO成本、查找成本都很高。
日志即起到一個中間轉折的作用,當有一條記錄需要更新的時候,InnoDB 引擎就會先把記錄寫到 redo log(粉板)里面,并更新內存,這個時候更新就算完成了。同時,InnoDB 引擎會在適當的時候,將這個操作記錄更新到磁盤里面,而這個更新往往是在系統比較空閑的時候做
面試官:今天的面試先到這兒吧,你的MySQL掌握的還不錯,明天有時間嗎?進行下一輪面試
都這么為難我了,還不直接給我發offer??
好了,回家洗洗睡吧,準備下期繼續面
了解更多技術干貨,關注公眾號【程序控】,我是小濕,一個有趣的靈魂!下期見
http://weixin.qq.com/r/8z8SCtzEdYEKrZhp92rh (二維碼自動識別)
總結
以上是生活随笔為你收集整理的mysql count 条件_我以为我对MySql很了解,直到我面试了字节跳动的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 无法读取内存属于错误吗_深入了解 Jav
- 下一篇: python requests的作用_P