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

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

常見面試題目:

0. 基本SQL語法題目,在 正文「基礎SQL語法」中有13道題,這裏就略過了。面試

1. 索引的做用?她的優勢缺點是什麼?sql

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中的事務與鎖

相關文章
相關標籤/搜索