.NET面試題解析(11)-SQL語言基礎及數據庫基本原理


本文內容涉及到基本SQL語法,數據的基本存儲原理,數據庫一些概念、數據優化等。抱磚引玉,權當一個綜合復習!

常見面試題目:

0. 基本SQL語法題目,在 正文“基礎SQL語法”中有13道題,這里就略過了。

1. 索引的作用?她的優點缺點是什么?

2. 介紹存儲過程基本概念和 她的優缺點?

3. 使用索引有哪些需要注意的地方?

4. 索引碎片是如何產生的?有什么危害?又該如何處理?

5. 鎖的目的是什么?

6. 鎖的粒度有哪些?

7. 什么是事務?什么是鎖?

8. 視圖的作用,視圖可以更改么?

9. 什么是觸發器(trigger)? 觸發器有什么作用?

10. SQL里面IN比較快還是EXISTS比較快?

11. 維護數據庫的完整性和一致性,你喜歡用觸發器還是自寫業務邏輯?為什么?

  基礎SQL語法

以下SQL所使用的實例數據庫為Sqlite(因為相當輕量),數據庫文件(下載鏈接,test.db,6KB),SQLite數據庫管理工具推薦SQLite Expert Personal。

0. 創建表

定義如下表結構,后面的題目都以此表結構為依據。

Student(ID,Name,Age,Sex) 學生表   
Course(ID,Name,TeacherID) 課程表   
Score(StudentID,CourseID,Score) 成績表   
Teacher(ID,Name) 教師表

創建表的語法很簡單,SQL語句:

CREATE TABLE [Student] (
  [ID] INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, 
  [Name] NVARCHAR(20), 
  [Age] INT, 
  [Sex] INT);

CREATE TABLE [Course] (
  [ID] INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, 
  [Name] NVARCHAR(20), 
  [TeacherID] INT) 

CREATE TABLE [Score] (
  [Score] double,   
  [StudentID] INT,
  [CourseID] INT) 

CREATE TABLE [Teacher] (
  [ID] INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,  
  [Name] NVARCHAR(20))

1. 查詢語文“1”比數學“2”課程成績高的所有學生的姓名

這是一個嵌套查詢的題目,考察對子查詢的使用,子查詢結果作為一個集合可以當做一個獨立的表來看待,子查詢必須用括號括起來:

select st.[Name],c1.Score,c2.Score from
(select sc.[Score], sc.StudentID from Score sc  where sc.[CourseID]=1)c1,
(select sc.[Score],sc.StudentID from Score sc where sc.[CourseID]=2)c2
join Student st on st.[ID]= c1.[StudentID]
where c1.[Score]>c2.[Score] and c1.[StudentID]==c2.[StudentID]

2. 查詢平均成績大於60分的同學的學號和平均成績

GROUP BY 語句用於結合合計函數,根據一個或多個列對結果集進行分組。GROUP BY子句在SELECT語句的WHERE子句之后並ORDER BY子句之前。WHERE 關鍵字無法與合計函數一起使用,GROUP BY后面不能接WHERE條件,使用HAVING代替

select sc.[StudentID],avg(sc.Score) from Score sc
group by sc.[CourseID] having avg(sc.Score)>60

3. 查詢所有同學的學號、姓名、選課數、總成績;

select st.[ID],st.Name,count(sc.CourseID),sum(sc.Score) from Student st
left outer join Score sc on sc.[StudentID]=st.[ID]
group by st.[ID]

外連接的三種形式如下表,其中outer可以省略。與外連接對應的就是內連接inner join ,要兩個表同時滿足指定條件。

image

4. 查詢姓“張”的老師的個數;

select count(t.ID) from Teacher t where t.Name like '張%'

SQL LIKE子句使用通配符運算符比較相似的值。符合LIKE操作符配合使用2個通配符:

  • 百分號 (%):百分號代表零個,一個或多個字符

  • 下划線 (_):下划線表示單個數字或字符

5. 找出教師表中姓名重復的數據,然后刪除多余重復的記錄,只留ID小的那個。

select t.Name,count(t.Name) from Teacher t group by t.[Name] having count(t.Name)>1

刪除多余的記錄,寫這種稍微復雜一點的sql的時候,要學會拆解,此題可以拆解為三個部分(刪除+重復數據+重復數據中ID最小的數據),先分別把3個部分的sql寫了,然后再一步步合並,這樣就輕松多了。

delete from Teacher where Name in
       (select t2.Name from Teacher t2 group by t2.[Name] having count(t2.Name)>1)
and ID not in
       (select min(t3.ID) from Teacher t3 group by t3.Name having count(t3.Name)>1)

6. 按照成績分段標示(<60不及格,60-80良,>80優),輸出所有學生姓名、課程名、成績、成績分段標示。

select st.Name,c.Name,sc.Score,(case
       when sc.Score > 80 then ''       
       when sc.Score < 60 then '不及格'
       else '' end) as 'Remark'
from Score sc
inner join Student st on st.ID=sc.[StudentID]
inner join Course c on c.ID=sc.[CourseID]

image

7. 查詢所有課程成績小於60分的同學的學號、姓名信息

這個比較簡單,下面給出了兩種方法,使用join鏈接和子查詢:

select distinct st.* from Student st
left join Score sc on sc.[StudentID]=st.ID
where sc.[Score]<60
-- --
select * FROM Student st WHERE st.ID in 
       (SELECT s1.ID FROM Student s1,Score s2 WHERE s1.ID=s2.[StudentID] AND s2.Score<60)

8. 查詢各科成績最高和最低的分:以如下形式顯示:課程名稱,最高分,最低分

select c.Name as 課程,max(sc.Score) as 最高分,min(sc.Score) as 最低分 from Score sc 
left join Course c on c.ID=sc.[CourseID]
group by sc.[CourseID]

9. 查詢不同老師所教不同課程平均分從高到低顯示

select t.Name,c.Name, avg(sc.Score) from Score sc,Teacher t,Course c
where sc.[CourseID]=c.ID and c.TeacherID=t.ID
group by sc.[CourseID] order by avg(sc.Score) desc

image

10. 查詢和“1”號的同學學習的課程完全相同的其他同學學號和姓名

select s1.StudentID from Score s1 where s1.CourseID in(select s2.CourseID from Score s2 where s2.StudentID=1)
group by s1.StudentID having count(*)=(select count(*) from Score s3 where s3.StudentID=3)

11. 查詢選修“張老師”老師所授課程的學生中,成績最高的學生姓名及其成績

select t.Name,c.Name,s1.Score from Score s1,Teacher t,Course c 
       where t.ID=c.[TeacherID] and s1.CourseID=c.ID and t.Name='張老師'
       and s1.Score=(select max(Score) from Score where CourseID=c.ID)

image

注意:多表連接查詢有多種寫法,比如本題目中多表連接可以有以下兩種方式:

select t.Name,c.Name,s1.Score from Score s1,Teacher t,Course c where t.ID=c.[TeacherID] and s1.CourseID=c.ID 
-- 下面的寫法和上面的效果是一樣的! --
select t.Name,c.Name,s1.Score from Score s1
join Teacher t on t.ID=c.[TeacherID] 
join Course c on s1.CourseID=c.ID

FROM TABLE1,TABLE2…效果等效於FROM TABLE1 join TABLE2…,都是內連接inner操作。詳細的SQL表連接操作可以參考:深入理解SQL的四種連接-左外連接、右外連接、內連接、全連接

12. 查詢所有成績第二名到第四名的成績

select  s.[StudentID],s.Score from Score s order by s.Score desc limit 2 offset 2
-- SQL 2005/2008中的分頁函數是ROW_NUMBER() Over (Order by 列...)--
select  t.[StudentID],t.Score from(
        select s2.[StudentID],s2.Score,ROW_NUMBER() OVER (ORDER BY s2.[Score]) AS rn from Score s2) t
where t.rn>=2 and t.rn<=4

這是一個分頁的題目,上面這是Sqlite提供的內置方法limite進行分頁,不同數據庫的分頁方式又有些差別,但都大同小異。基本的過程都是先根據條件查詢所需數據(加上行號),然后再此基礎上返回指定行區間段的數據。其實SQlServer也很簡單,不同的版本也有些不同,可以參考:SQL Server 常用分頁SQL

12. 查詢各科成績前2名的記錄:(不考慮成績並列情況)

select * from Score s1 where s1.Score 
       in(select s2.Score from Score s2 where s1.[CourseID]=s2.[CourseID] order by s2.Score desc limit 2 offset 0)
order by s1.[CourseID],s1.[Score] desc
-- 上面是sqlite中的語法,sqlite中沒有top,使用limit代替,效果是一樣的 --
select * from Score s1 where s1.Score 
       in(select Top 2 s2.Score from Score s2 where s1.[CourseID]=s2.[CourseID] order by s2.Score desc)
order by s1.[CourseID],s1.[Score] desc

image

  數據庫基本存儲原理

微笑 基本存儲單元——頁

數據庫文件存儲是以頁為存儲單元的,一個頁是8K(8192Byte),一個頁就可以存放N行數據。我們表里的數據都是存放在頁上的,這種叫數據頁。還有一種頁存放索引數據的,叫索引頁。

同時,頁也是IO讀取的最小單元(物理IO上不是按行讀取),也是所有權的最小單位。如果一頁中包含了表A的一行數據,這頁就只能存儲表A的行數據了。或是一頁中包含了索引B的條目,那這頁也僅僅只能存儲索引B的條目了。每頁中除去存儲數據之外,還存儲一些頁頭信息以及行偏移以便SQL Server知道具體每一行在頁中的存儲位置。

image

數據庫的基本物理存儲單元是頁,一個表由很多個頁組成,那這些頁又是如何組織的呢?我們一般都會對表創建索引,這些索引又是如何存儲的呢?不要走開,請看下文。

大笑 表/索引的存儲結構

如下圖,是一個B樹(二叉搜索樹)的示例,都是小的元素放左邊,大的元素放右邊,依次構造的,比如要查找元素9,從根節點開始,只要比較三次就找到他了,查詢效率是非常高的。

image

B+樹和B-樹都是B樹的變種,或者說是更加高級的復雜版本(關於B樹的資料,有興趣可以自己去學習,這里只是拋磚引玉)。B+樹和B-樹是數據庫中廣泛應用的索引存儲結構,它可以極大的提高數據查找的效率。前面說了數據庫存儲的基本單元是頁,因此,索引樹上的節點就是頁了。

因此不難看出,索引的主要優點和目的就是為了提高查詢效率。

 

為了保證數據的查詢效率,當新增、修改、刪除數據的時候,都需要維護這顆索引樹,就可能會出現分裂、合並節點(頁)的情況(這是樹的結構所決定的,想要更好理解這一點,可以嘗試自己代碼實現一下B-樹B+樹)。好!重點來了!

索引的缺點:

  • 當新增、修改、刪除數據的時候,需要維護索引樹,有一定的性能影響;
  • 同上面,在頻繁的樹維護過程中,B樹的頁拆分、合並會造成大量的索引碎片,又會極大的印象查詢效率 ,因此索引還需要維護;
  • 非聚集索引需要額外的存儲空間,不過這個一般問題都不是很大,但是需要注意的一個問題;

大笑 聚集索引

聚集索引決定了表數據的物理存儲順序,也就是說表的物理存儲是根據聚集索引結構進行順序存儲的,因此一個表只能有一個聚集索引。如下圖,就是一個聚集索引的樹結構:

  • 所有數據都在葉子節點的頁上,在葉子節點(數據頁)之間有一個鏈指針,這是B+樹的特點;
  • 非葉子節點都是索引頁,存儲的就是聚集索引字段的值;
  • 表的物理存儲就是依據聚集索引的結構的,一個表只能有一個聚集索引;

image

聚集索引的所有的數據都存儲在葉子節點上,數據查詢的復雜度都是一樣的(樹的深度),按照聚集索引列查找數據效率是非常高的。上面說了,聚集索引決定了表的物理存儲結構,那如果沒有創建聚集索引,會如何呢?——表內的所有頁都無序存放,是一個無序的堆結構。堆數據的查詢就會造成表掃描,性能是非常低的。

因此聚集索引的的重要性不言而喻,一般來說,大多會對主鍵建立聚集索引,大多數普通情況這么做也可以。但實際應用應該尊從一個原則就是“頻繁使用的、排序的字段上創建聚集索引”

大笑 非聚集索引

除了聚集索引以外的其他索引,都稱之為非聚集索引,非聚集索引一般都是為了優化特定的查詢效率而創建的。非聚集索引也是B樹(B+樹和B-樹)的結構,與非聚集索引的存儲結構唯一不一樣的,就是非聚集索引中不存儲真正的數據行,因為在聚集索引中已經存放了所有數據,非聚集索引只包含一個指向數據行的指針即可。

13102058-b7c8255002de4ee4beb89c2dc0c0526c

  • 非聚集索引的創建會單獨創建索引文件來存儲索引結構,會占用一定存儲空間,就是用空間換時間;
  • 非聚集索引的目的很單純:提高特定條件的查詢效率,一個表有可能根據多種查詢需求創建多個非聚集索引;

數據查詢SQL簡單來看,分為兩個部分:SELECT****Where ****,因此索引的創建也是根據這兩部分來決定的。根據這兩點,有兩種主要的索引形式:復合索引和覆蓋索引,在實際使用中,根據具體情況可能都會用到,只要能提高查詢效率就是好索引。

覆蓋索引:就是在索引中包含的數據列(非索引列,SELECT需要的列),這樣在使用該索引查詢數據時就不會再進行鍵查找(也叫書簽查找)了。

復合索引:主要針對Where中有多個條件的情況,索引包含多個數據列。在使用復合索引時,應注意多個索引鍵的順序問題,這個是會影響查詢效率的,一般的原則是唯一性高的放前面,還有就是SQl語句中Where條件的順序應該和索引順序一致。

image

 

微笑 索引碎片

前面說過了,索引在使用一段時間后(主要是新增、修改、刪除數據,如果該頁已經存儲滿了,就要進行頁的拆分,頻繁的拆分,會產生較多的索引碎片)會產生索引碎片,這就造成了索引頁在磁盤上存儲的不連續。會造成磁盤的訪問使用的是隨機的i/o,而不是順序的i/o讀取,這樣訪問索引頁會變得更慢。如果碎片過多,數據庫是可會能不使用該索引的(她嫌棄你太慢了,數據庫會選擇一個更優的執行計划)。

解決這個問題主要是兩種方法:

第一種是預防:設置頁的填充因子

意思就是在頁上設置一段空白區域,在新增數據的時候,可以使用這段空白區域,可以一定的避免頁的拆分,從而減少索引碎片的產生。

填充因子就是用來描述這種頁中填充數據的一個比例,一般默認是100%填充的。如果我們修改填充因子為80%,那么頁在存儲數據時,就會剩余20%的剩余空間,這樣在下次插入的時候就不會拆分頁了。 那么是不是我們可以把填充因子設置低一點,留更多的剩余空間,不是很好嘛?當然也不好,填充因子設置的低,會需要分配更多的存儲空間,葉子節點的深度會增加,這樣是會影響查詢效率的,因此,這是要根據實際情況而定的。

那么一般我們是怎么設置填充因子的呢,主要根據表的讀寫比例而定的。如果讀的多,填充因子可以設置高一點,如100%,讀寫各一半,可以80~90%;修改多可以設置50~70%。

第二種是索引修復:定期對索引進行檢查、維護,寫一段SQL檢查索引的碎片比例,如果碎片過多,進行碎片修復或重建,定期執行即可。具體可以參考本文末尾的相關參考資料。

大笑 索引使用總結

  • 創建索引的的字段盡量小,最好是數值,比如整形int等;
  • 對於頻繁修改的字段,盡量不要創建索引,維護索引的成本很高,而且更容易產生索引碎片;
  • 定期的索引維護,如索引碎片的修復等;
  • 不要建立或維護不必要的重復索引,會增加修改數據(新增、修改、刪除數據)的成本;
  • 使用唯一性高的字段創建索引,切不可在性別這樣的低唯一性的字段上創建索引;
  • 在SQL語句中,盡量不要在Where條件中使用函數、運算符或表達式計算,會造成索引無法正常使用;
  • 應盡量避免在 where 子句中對字段進行 null 值判斷,否則將導致引擎放棄使用索引而進行全表掃描;
  • 應盡量避免在 where 子句中使用!=或<>操作符,否則將導致引擎放棄使用索引而進行全表掃描;

  事務與鎖

事務就是作為一個邏輯工作單元的SQL語句,如果任何一個語句操作失敗那么整個操作就被失敗,以后操作就會回滾到操作前狀態,或者是上個節點。為了確保要么執行,要么不執行,就可以使用事務。而鎖是實現事務的關鍵,鎖可以保證事務的完整性和並發性。

這部分的理論性太強了,不如看看下文章末尾的參考資料更好(博客園的高質量文章還是相當多的),簡單總結一下就好了!

image

和在.NET中的鎖用途類似,數據庫中的鎖也是為了解決在並發訪問時出現各種沖突的一種機制。

image

 

  題目答案解析:

1. 索引的作用?和它的優點缺點是什么?

索引就一種特殊的查詢表,數據庫的搜索引擎可以利用它加速對數據的檢索。索引很類似與現實生活中書的目錄,不需要查詢整本書內容就可以找到想要的數據。缺點是它減慢了數據錄入的速度,同時也增加了數據庫的尺寸大小。

2. 介紹存儲過程基本概念和 她的優缺點

存儲過程是一個預編譯的SQL語句,他的優點是允許模塊化的設計,也就是說只需創建一次,在該程序中就可以調用多次。例如某次操作需要執行多次SQL,就可以把這個SQL做一個存儲過程,因為存儲過程是預編譯的,所以使用存儲過程比單純SQL語句執行要快。缺點是可移植性差,交互性差。

3. 使用索引有哪些需要注意的地方?

  • 創建索引的的字段盡量小,最好是數值,比如整形int等;
  • 對於頻繁修改的字段,盡量不要創建索引,維護索引的成本很高,而且更容易產生索引碎片;
  • 定期的索引維護,如索引碎片的修復等;
  • 不要建立或維護不必要的重復索引,會增加修改數據(新增、修改、刪除數據)的成本;
  • 使用唯一性高的字段創建索引,切不可在性別這樣的低唯一性的字段上創建索引;
  • 在SQL語句中,盡量不要在Where條件中使用函數、運算符或表達式計算,會造成索引無法正常使用;
  • 應盡量避免在 where 子句中對字段進行 null 值判斷,否則將導致引擎放棄使用索引而進行全表掃描;
  • 應盡量避免在 where 子句中使用!=或<>操作符,否則將導致引擎放棄使用索引而進行全表掃描;

4. 索引碎片是如何產生的?有什么危害?又該如何處理?

索引在使用一段時間后(主要是新增、修改、刪除數據,如果該頁已經存儲滿了,就要進行頁的拆分,頻繁的拆分,會產生較多的索引碎片)會產生索引碎片。

索引碎片會嚴重印象數據的查詢效率,如果碎片太多,索引可能不會被使用。

碎片的處理方式主要有兩種:

第一種是預防:設置頁的填充因子

意思就是在頁上設置一段空白區域,在新增數據的時候,可以使用這段空白區域,可以一定的避免頁的拆分,從而減少索引碎片的產生。

填充因子就是用來描述這種頁中填充數據的一個比例,一般默認是100%填充的。如果我們修改填充因子為80%,那么頁在存儲數據時,就會剩余20%的剩余空間,這樣在下次插入的時候就不會拆分頁了。 那么是不是我們可以把填充因子設置低一點,留更多的剩余空間,不是很好嘛?當然也不好,填充因子設置的低,會需要分配更多的存儲空間,葉子節點的深度會增加,這樣是會影響查詢效率的,因此,這是要根據實際情況而定的。

那么一般我們是怎么設置填充因子的呢,主要根據表的讀寫比例而定的。如果讀的多,填充因子可以設置高一點,如100%,讀寫各一半,可以80~90%;修改多可以設置50~70%。

第二種是索引修復:定期對索引進行檢查、維護,寫一段SQL檢查索引的碎片比例,如果碎片過多,進行碎片修復或重建,定期執行即可。具體可以參考本文末尾的相關參考資料。

5. 鎖的目的是什么?

主要解決多個用戶同時對數據庫的並發操作時會帶來以下數據不一致的問題:

  • 丟失更新,同時修改一條數據
  • 讀臟,A修改了數據后,B讀取后A又取消了修改,B讀臟
  • 不可重復讀,A用戶讀取數據,隨后B用戶讀取該數據並修改,此時A用戶再讀取數據時發現前后兩次的值不一致
  • 還有一種是幻讀,這個情況好像不多。

並發控制的主要方法是封鎖,鎖就是在一段時間內禁止用戶做某些操作以避免產生數據不一致

6. 鎖的粒度有哪些?

  • 數據庫鎖:鎖定整個數據庫,這通常發生在整個數據庫模式改變的時候。
  • 表鎖:鎖定整個表,這包含了與該表相關聯的所有數據相關的對象,包括實際的數據行(每一行)以及與該表相關聯的所有索引中的鍵。
  • 區段鎖:鎖定整個區段,因為一個區段由8頁組成,所以區段鎖定是指鎖定控制了區段、控制了該區段內8個數據或索引頁以及這8頁中的所有數據行。
  • 頁鎖:鎖定該頁中的所有數據或索引鍵。
  • 行或行標識符:雖然從技術上將,鎖是放在行標識符上的,但是本質上,它鎖定了整個數據行。

7. 什么是事務?什么是鎖?

事務就是被綁定在一起作為一個邏輯工作單元的SQL語句分組,如果任何一個語句操作失敗那么整個操作就被失敗,以后操作就會回滾到操作前狀態,或者是上個節點。為了確保要么執行,要么不執行,就可以使用事務。要將所有組語句作為事務考慮,就需要通過ACID測試,即原子性,一致性,隔離性和持久性。
鎖是實現事務的關鍵,鎖可以保證事務的完整性和並發性。

8. 視圖的作用,視圖可以更改么?

視圖是虛擬的表,與包含數據的表不一樣,視圖只包含使用時動態檢索數據的查詢;不包含任何列或數據。使用視圖可以簡化復雜的sql操作,隱藏具體的細節,保護數據;視圖創建后,可以使用與表相同的方式利用它們。

視圖的目的在於簡化檢索,保護數據,並不用於更新。

9. 什么是觸發器(trigger)? 觸發器有什么作用?

觸發器是數據庫中由一定時間觸發的特殊的存儲過程,他不是由程序掉用也不是手工啟動的。觸發器的執行可以由對一個表的insert,delete, update等操作來觸發,觸發器經常用於加強數據的完整性約束和業務規則等等。

10. SQL里面IN比較快還是EXISTS比較快?

這個題不能一概而論,要根據具體情況來看。IN適合於外表大而內表小的情況;EXISTS適合於外表小而內表大的情況。

如果查詢語句使用了not in,那么對內外表都進行全表掃描,沒有用到索引;而not exists的子查詢依然能用到表上的索引。所以無論哪個表大,用not exists都比not in 要快。參考資料:http://www.cnblogs.com/seasons1987/archive/2013/07/03/3169356.html

11. 維護數據庫的完整性和一致性,你喜歡用觸發器還是自寫業務邏輯?為什么?

盡可能使用約束,如check、主鍵、外鍵、非空字段等來約束。這樣做效率最高,也最方便。其次是使用觸發器,這種方法可以保證,無論什么業務系統訪問數據庫都可以保證數據的完整新和一致性。最后考慮的是自寫業務邏輯,但這樣做麻煩,編程復雜,效率低下。

 

版權所有,文章來源:http://www.cnblogs.com/anding

個人能力有限,本文內容僅供學習、探討,歡迎指正、交流。

.NET面試題解析(00)-開篇來談談面試 & 系列文章索引

  參考資料:

書籍:CLR via C#

書籍:你必須知道的.NET

SQL常考筆試題[轉]

深入理解SQL的四種連接-左外連接、右外連接、內連接、全連接

博主以前寫的:數據庫索引及基本優化入門

SQL Server索引的維護 - 索引碎片、填充因子 <第三篇>

SQL Server 鎖

SQL Server 事務語法

SQL Server中的事務與鎖


免責聲明!

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



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