面試之數據庫篇

作者:我沒有三顆心臟
鏈接:http://www.lxweimin.com/p/71927a377dc6
來源:簡書

1. left join 與 inner join的區別

sql的left join 、right join 、inner join之間的區別

  1. left join(左聯接) 返回包括左表中的所有記錄和右表中聯結字段相等的記錄
  2. right join(右聯接) 返回包括右表中的所有記錄和左表中聯結字段相等的記錄
  3. inner join(等值連接) 只返回兩個表中聯結字段相等的行

left join sql語句如下:
select * from Aleft join B on A.aid = B.bid
left join是以A表的記錄為基礎的,A可以看成左表,B可以看成右表,left join是以左表為準的.換句話說,左表(A)的記錄將會全部表示出來,而右表(B)只會顯示符合搜索條件的記錄(例子中為: A.aid = B.bid)。B表不符合查詢要求的地方均為NULL。
right join sql語句如下:
select * from A right join B on A.aid = B.bid
仔細觀察一下,就會發現,和left join的結果剛好相反,這次是以右表(B)為基礎的,A表不符合連接要求的地方用NULL填充.
inner join sql語句如下:
select * from A inner join B on A.aid = B.bid
很明顯,這里只顯示出了 A.aID = B.bID的記錄.這說明inner join并不以誰為基礎,它只顯示符合條件的記錄。不符合要求的則舍去。

2.事務的簡單理解

事務簡單來說:一個 Session 中所進行所有的操作,要么同時成功,要么同時失敗;作為單個邏輯工作單元執行的一系列操作,滿足四大特性:

  1. 原子性(Atomicity):事務作為一個整體被執行 ,要么全部執行,要么全部不執行
  2. 一致性(Consistency):保證數據庫狀態從一個一致狀態轉變為另一個一致狀態
  3. 隔離性(Isolation):多個事務并發執行時,一個事務的執行不應影響其他事務的執行
  4. 持久性(Durability):一個事務一旦提交,對數據庫的修改應該永久保存

并發性問題:

  1. 丟失更新:一個事務的更新覆蓋了另一個事務的更新;
  2. 臟讀:一個事務讀取了另一個事務未提交的數據;
  3. 不可重復讀:不可重復讀的重點是修改,同樣條件下兩次讀取結果不同,也就是說,被讀取的數據可以被其它事務修改;
  4. 幻讀:幻讀的重點在于新增或者刪除,同樣條件下兩次讀出來的記錄數不一樣。

3.事務隔離級別

隔離級別決定了一個session中的事務可能對另一個session中的事務的影響。ANSI標準定義了4個隔離級別,MySQL的InnoDB都支持,分別是:

  1. 讀未提交(READ UNCOMMITTED):最低級別的隔離,通常又稱為dirty read,它允許一個事務讀取另一個事務還沒 commit 的數據,這樣可能會提高性能,但是會導致臟讀問題;
  2. 讀已提交(READ COMMITTED):在一個事務中只允許對其它事務已經 commit 的記錄可見,該隔離級別不能避免不可重復讀問題;
  3. 可重復讀(REPEATABLE READ):在一個事務開始后,其他事務對數據庫的修改在本事務中不可見,直到本事務 commit 或 rollback。但是,其他事務的 insert/delete 操作對該事務是可見的,也就是說,該隔離級別并不能避免幻讀問題。在一個事務中重復 select 的結果一樣,除非本事務中 update 數據庫。
  4. 序列化(SERIALIZABLE):最高級別的隔離,只允許事務串行執行。
    MySQL默認的隔離級別是可重復讀(REPEATABLE READ)

MySQL的事務支持不是綁定在MySQL服務器本身,而是與存儲引擎相關:

  • MyISAM:不支持事務,用于只讀程序提高性能;
  • InnoDB:支持ACID事務、行級鎖、并發;
  • Berkeley DB:支持事務。

4.事務提交模式

  • 自動提交模式
    默認方式,每執行完一條SQL語句就自動提交事務,每條SQL語句的執行都被單獨提交,因此一個事務只有一條SQL語句組成。
  • 手動提交模式
    通常一個新的數據庫連接產生,便意味著一個新的事務生成。我們要將一個整體操作設置為一個事務,而不是一條SQL語句當成一個事務。
    Conncetion.setAutoCommit(false):設置為手動提交模式
    Connection.commit():提交事務
    Connection.rollback():回滾事務
    一個事務中可以包含若干條SQL語句
    // 開啟事務,對數據的操作就不會立即生效。
    connection.setAutoCommit(false);

    // A賬戶減去500塊
    String sql = "UPDATE a SET money=money-500 ";
    preparedStatement = connection.prepareStatement(sql);
    preparedStatement.executeUpdate();

    // 在轉賬過程中出現問題
    int a = 3 / 0;

    // B賬戶多500塊
    String sql2 = "UPDATE b SET money=money+500";
    preparedStatement = connection.prepareStatement(sql2);
    preparedStatement.executeUpdate();

    // 如果程序能執行到這里,沒有拋出異常,我們就提交數據
    connection.commit();

    // 關閉事務【自動提交】
    connection.setAutoCommit(true);

} catch(SQLException e) {
    try {
        // 如果出現了異常,就會進到這里來,我們就把事務回滾【將數據變成原來那樣】
        connection.rollback();

        // 關閉事務【自動提交】
        connection.setAutoCommit(true);
    } catch (SQLException e1) {
        e1.printStackTrace();
    }
}

5.分布式事務其中一個事務出錯

XA模式

6.三大范式

  1. 第一范式: 列不可分
    1NF(第一范式)是對屬性具有原子性的要求,不可再分
  2. 第二范式: 消除非主屬性對碼的部分函數依賴
    2NF(第二范式)是對記錄有唯一性的要求,即實體的唯一性,不存在部分依賴,每一列與主鍵都相關
  3. 第三范式: 消除非主屬性對碼的傳遞函數依賴
    3NF(第三范式)對字段有冗余性的要求,任何字段不能由其他字段派生出來,它要求字段沒有冗余,即不存在依賴傳遞

7.索引

  1. 什么是索引
    索引會影響到where后面的查找和order by后面的排序
  2. 聚集索引和非聚集索引
  • 非聚集索引:字典的檢索或者書的目錄(數據與索引分開)
  • 聚集索引:頁碼下面的數字(頁碼在需要查找的數據的里面)
  1. 底層數據結構是什么,為什么使用這種數據結構?
    MyIsam使用的是B+tree(多路查找樹)索引,既有索引列的值也有指向數據行的指針,使用二分法查找,時間復雜度為O(logn),構建出的索引樹如圖(非聚集索引)
    MyIsam在物理上有三個文件組成:
  • MYI:存儲索引
  • MYD:存儲數據
  • FRM:表結構
    因此MyISAM中索引檢索的算法為首先按照B+Tree搜索算法搜索索引,如果指定的Key存在,則取出其data域的值,然后以data域的值為地址,讀取相應數據記錄。


    image.png

    image.png

InnoDB使用的是B+tree做的索引。InnoDB的數據文件本身就是索引文件。在InnoDB中,表數據文件本身就是按B+Tree組織的一個索引結構,這棵樹的葉節點data域保存了完整的數據記錄。這個索引的key是數據表的主鍵,因此InnoDB表數據文件本身就是主索引。

image.png

InnoDB的數據文件本身要按主鍵聚集(只有一個聚集索引),所以InnoDB要求表必須有主鍵。默認會拿主鍵(id)作為聚集索引。如果沒有主鍵會取非空唯一的作為聚集索引。如果都沒有InnoDB會自己維護一個唯一id來作為聚集索引。
InnoDB的輔助索引data域存儲相應記錄主鍵的值而不是地址。換句話說,InnoDB的所有輔助索引都引用主鍵作為data域。

image.png

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

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

  1. 索引的優缺點,建索引的原則
    優點: 提高檢索速度,降低磁盤的IO次數
    缺點: 索引需要存儲,需要空間,索引實際上是一張表,字段更新會有性能損耗

適合:

  • 頻繁作為where條件的字段(where id = ?)
  • 關聯字段可以建索引,例如外鍵字段
  • Order by 的 column,Group by 的 column(排序)

不適合:

  • Where條件中用不到的字段
  • 頻繁更新的字段
  • 數據值分布均勻(男,女,真假值)
  • 表數量可以確定行數,且數據量不是很大

原則:
一個最重要的原則是最左前綴原理,在提這個之前要先說下聯合索引,MySQL中的索引可以以一定順序引用多個列,這種索引叫做聯合索引,一般的,一個聯合索引是一個有序元組<a1, a2, …, an>,其中各個元素均為數據表的一列。另外,單列索引可以看成聯合索引元素數為1的特例。

- 那么查詢的時候,如果查詢【A】【A,B】 【A,B,C】,那么可以通過索引查詢
- 如果查詢的時候,采用【A,C】,那么C這個雖然是索引,但是由于中間缺失了B,因此C這個索引是用不到的,只能用到A索引
- 如果查詢的時候,采用【B】 【B,C】 【C】,由于沒有用到第一列索引,不是最左前綴,那么后面的索引也是用不到了
- 如果查詢的時候,采用范圍查詢,并且是最左前綴,也就是第一列索引,那么可以用到索引,但是范圍后面的列無法用到索引

索引失效:
Select * from user order by age 索引生效
Index(name,age) 復合索引(建索引最好用復合索引)
Select * from user oder by age 索引失效
因為符合索引由name,age組成前面部分是name,不能跨越name直接到age
Select * from user oder by name,age 索引生效
Select * from user where age = 18 and name = 'zsj'索引失效
Index(age,name) 復合索引
Select * from user where age>18 and name = 'zsj' age用上索引后面失效

  1. 行鎖和表鎖
    在執行sql語句,如
update table set c1 = ? where c2 = ?  會檢索出一行或者幾行數據

在InnoDB中檢索出的這幾行會被鎖住,此刻其他線程只能等待,提交后其他線程才能訪問(行鎖)

8.什么是視圖?使用視圖的場景

視圖是一種虛擬的表,具有和物理表相同的功能。可以對視圖進行增,改,查,操作,視圖通常是有一個表或者多個表的行或列的子集。對視圖的修改不影響基本表。它使得我們獲取數據更容易,相比多表查詢。

  1. 不希望訪問者獲取整個表的信息,只暴露部分字段給訪問者,所以就建一個虛表,就是視圖。
  2. 查詢的數據來源于不同的表,而查詢者希望以統一的方式查詢,這樣也可以建立一個視圖,把多個表查詢結果聯合起來,查詢者只需要直接從視圖中獲取數據,不必考慮數據來源于不同表所帶來的差異。

注意:這個視圖是在數據庫中創建的 而不是用代碼創建的。

9.DDL與DML

DML數據操縱語言:就是我們最經常用到的 SELECT、UPDATE、INSERT、DELETE。 主要用來對數據庫的數據進行一些操作。
DDL數據庫定義語言:其實就是我們在創建表的時候用到的一些sql,比如說:CREATE、ALTER、DROP等。DDL主要是用在定義或改變表的結構,數據類型,表之間的鏈接和約束等初始化工作上。
truncate table:
屬于DDL(Data Definition Language,數據庫定義語言)
不可回滾
不可帶 where
表內容刪除
刪除速度快
保留表而刪除所有數據的時候用truncate

10.數據庫悲觀鎖和樂觀鎖

數據庫管理系統(DBMS)中的并發控制的任務是確保在多個事務同時存取數據庫中同一數據時不破壞事務的隔離性和統一性以及數據庫的統一性。

樂觀并發控制(樂觀鎖)和悲觀并發控制(悲觀鎖)是并發控制主要采用的技術手段。

  1. MySQL InnoDB中使用悲觀鎖:
    要使用悲觀鎖,我們必須關閉mysql數據庫的自動提交屬性,因為MySQL默認使用autocommit模式,也就是說,當你執行一個更新操作后,MySQL會立刻將結果進行提交。 set autocommit=0;
//0.開始事務
begin;/begin work;/start transaction; (三者選一就可以)
//1.查詢出商品信息
select status from t_goods where id=1 for update;
//2.根據商品信息生成訂單
insert into t_orders (id,goods_id) values (null,1);
//3.修改商品status為2
update t_goods set status=2;
//4.提交事務
commit;/commit work;

上面的查詢語句中,我們使用了 select…for update 的方式,這樣就通過開啟排他鎖的方式實現了悲觀鎖。此時在t_goods表中,id為1的 那條數據就被我們鎖定了,其它的事務必須等本次事務提交之后才能執行。這樣我們可以保證當前的數據不會被其它事務修改。
上面我們提到,使用 select…for update 會把數據給鎖住,不過我們需要注意一些鎖的級別,MySQL InnoDB默認行級鎖。行級鎖都是基于索引的,如果一條SQL語句用不到索引是不會使用行級鎖的,會使用表級鎖把整張表鎖住,這點需要注意。

優點與不足:
悲觀并發控制實際上是“先取鎖再訪問”的保守策略,為數據處理的安全提供了保證。但是在效率方面,處理加鎖的機制會讓數據庫產生額外的開銷,還有增加產生死鎖的機會;另外,在只讀型事務處理中由于不會產生沖突,也沒必要使用鎖,這樣做只能增加系統負載;還有會降低了并行性,一個事務如果鎖定了某行數據,其他事務就必須等待該事務處理完才可以處理那行數

2.樂觀鎖:假設不會發生并發沖突,只在提交操作時檢查是否違反數據完整性。
樂觀鎖是一種不會阻塞其他線程并發的控制,它不會使用數據庫的鎖進行實現,它的設計里面由于不阻塞其他線程,所以并不會引起線程頻繁掛起和恢復,這樣便能夠提高并發能力,所以也有人把它稱為非阻塞鎖。一般的實現樂觀鎖的方式就是記錄數據版本。
數據版本,為數據增加的一個版本標識。當讀取數據時,將版本標識的值一同讀出,數據每更新一次,同時對版本標識進行更新。當我們提交更新的時候,判斷數據庫表對應記錄的當前版本信息與第一次取出來的版本標識進行比對,如果數據庫表當前版本號與第一次取出來的版本標識值相等,則予以更新,否則認為是過期數據。
實現數據版本有兩種方式,第一種是使用版本號,第二種是使用時間戳。

優點與不足:
樂觀并發控制相信事務之間的數據競爭(data race)的概率是比較小的,因此盡可能直接做下去,直到提交的時候才去鎖定,所以不會產生任何鎖和死鎖。但如果直接簡單這么做,還是有可能會遇到不可預期的結果,例如兩個事務都讀取了數據庫的某一行,經過修改以后寫回數據庫,這時就遇到了問題

11.SQL約束有幾種

  • NOT NULL: 用于控制字段的內容一定不能為空(NULL)。
  • UNIQUE: 控件字段內容不能重復,一個表允許有多個 Unique 約束。
  • PRIMARY KEY: 也是用于控件字段內容不能重復,但它在一個表只允許出現一個。
  • FOREIGN KEY: 用于預防破壞表之間連接的動作,也能防止非法數據插入外鍵列,因為它必須是它指向的那個表中的值之一。
  • CHECK: 用于控制字段的值范圍。

12.MySQL存儲引擎中的MyISAM和InnoDB區別詳解

在MySQL 5.5之前,MyISAM是mysql的默認數據庫引擎,其由早期的ISAM(Indexed Sequential Access Method:有索引的順序訪問方法)所改良。雖然MyISAM性能極佳,但卻有一個顯著的缺點:不支持事務處理。不過,MySQL也導入了另一種數據庫引擎InnoDB,以強化參考完整性與并發違規處理機制,后來就逐漸取代MyISAM。
InnoDB是MySQL的數據庫引擎之一,其由Innobase oy公司所開發,2006年五月由甲骨文公司并購。與傳統的ISAM、MyISAM相比,InnoDB的最大特色就是支持ACID兼容的事務功能,類似于PostgreSQL。目前InnoDB采用雙軌制授權,一是GPL授權,另一是專有軟件授權。具體地,MyISAM與InnoDB作為MySQL的兩大存儲引擎的差異主要包括:

13.varchar和char的區別

  1. char是一種固定長度的類型,varchar是一種可變長度的類型
    例如:
    定義一個char[10]和varchar[10],如果存進去的是 'test',那么char所占的長度依然為10,除了字符 'test' 外,后面跟六個空格,varchar就立馬把長度變為4了,取數據的時候,char類型的要用trim()去掉多余的空格,而varchar是不需要的
  2. char的存取速度還是要比varchar要快得多,因為其長度固定,方便程序的存儲于查找
    char也為此付出的是空間的代價,因為其長度固定,所以難免會有多余的空格占位符占據空間,可謂是以空間換取時間效率。
  3. varchar是以空間效率為首位。
    char的存儲方式是:對英文字符(ASCII)占用1個字節,對一個漢字占用兩個字節。
    varchar的存儲方式是:對每個英文字符占用2個字節,漢字也占用2個字節。
    兩者的存儲數據都非unicode的字符數據。

14. IN 和 EXISTS 的區別及應用

select * from Awhere id in(select id from B)
以上查詢使用了in語句,in()只執行一次,它查出B表中的所有id字段并緩存起來.之后,檢查A表的id是否與B表中的id相等,如果相等則將A表的記錄加入結果集中,直到遍歷完A表的所有記錄.它的查詢過程類似于以下過程

List resultSet=[];
Array A=(select * from A);
Array B=(select id from B);
for(int i=0;i<A.length;i++) {
   for(int j=0;j<B.length;j++) {
      if(A[i].id==B[j].id) {
         resultSet.add(A[i]);
         break;
      }
   }
}return resultSet;

可以看出,當B表數據較大時不適合使用in(),因為它會B表數據全部遍歷一次.
如:A表有10000條記錄,B表有1000000條記錄,那么最多有可能遍歷100001000000次,效率很差.
再如:A表有10000條記錄,B表有100條記錄,那么最多有可能遍歷10000
100次,遍歷次數大大減少,效率大大提升.結論:in()適合B表比A表數據小的情況

select a.* from A a where exists(select 1 from B b where a.id=b.id)
以上查詢使用了exists語句,exists()會執行A.length次,它并不緩存exists()結果集,因為exists()結果集的內容并不重要,重要的是結果集中是否有記錄,如果有則返回true,沒有則返回false.
它的查詢過程類似于以下過程

List resultSet=[];
Array A=(select * from A)
for(int i=0;i<A.length;i++) {
   if(exists(A[i].id) {
    執行select 1 from B b where b.id=a.id是否有記錄返回
       resultSet.add(A[i]);
   }
}return resultSet;

當B表比A表數據大時適合使用exists(),因為它沒有那么遍歷操作,只需要再執行一次查詢就行。
如:A表有10000條記錄,B表有1000000條記錄,那么exists()會執行10000次去判斷A表中的id是否與B表中的id相等。
如:A表有10000條記錄,B表有100000000條記錄,那么exists()還是執行10000次,因為它只執行A.length次,可見B表數據越多,越適合exists()發揮效果。
再如:A表有10000條記錄,B表有100條記錄,那么exists()還是執行10000次,還不如使用in()遍歷10000*100次,因為in()是在內存里遍歷比較,而exists()需要查詢數據庫,我們都知道查詢數據庫所消耗的性能更高,而內存比較很快.
結論:exists()適合B表比A表數據大的情況當A表數據與B表數據一樣大時,in與exists效率差不多,可任選一個使用.

15.優化MySQL

  1. 通過explain查詢和分析SQL的執行計劃:
    使用 EXPLAIN 關鍵字可以知道MySQL是如何處理你的SQL語句的,以便分析查詢語句或是表結構的性能瓶頸。通過explain命令可以得到表的讀取順序、數據讀取操作的操作類型、哪些索引可以使用、哪些索引被實際使用、表之間的引用以及每張表有多少行被優化器查詢等問題。當擴展列extra出現Using filesort和Using temporay,則往往表示SQL需要優化了。

多數來源:
簡書:我沒有三顆心臟
掘金:吳德寶AllenWu MySQL索引和SQL調優

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容