一、索引
- 介紹
索引分為聚集索引和非聚集索引,數據庫中的索引類似於一本書的目錄,在一本書中通過目錄可以快速找到你想要的信息,而不需要讀完全書。
索引主要目的是提高了SQL Server系統的性能,加快數據的查詢速度與減少系統的響應時間 。但是索引對於提高查詢性能也不是萬能的,也不是建立越多的索引就越好。
索引建少了,用 WHERE 子句找數據效率低,不利於查找數據。
索引建多了,不利於新增、修改和刪除等操作,因為做這些操作時,SQL SERVER 除了要更新數據表本身,還要連帶立即更新所有的相關索引,而且過多的索引也會浪費硬盤空間。
- 索引的分類
索引就類似於中文字典前面的目錄,按照拼音或部首都可以很快的定位到所要查找的字。
1、唯一索引(UNIQUE):每一行的索引值都是唯一的(創建了唯一約束,系統將自動創建唯一索引)
2、主鍵索引:當創建表時指定的主鍵列,會自動創建主鍵索引,並且擁有唯一的特性。
3、聚集索引(CLUSTERED):聚集索引就相當於使用字典的拼音查找,因為聚集索引存儲記錄是物理上連續存在的,即拼音 a 過了后面肯定是 b 一樣。
4、非聚集索引(NONCLUSTERED):非聚集索引就相當於使用字典的部首查找,非聚集索引是邏輯上的連續,物理存儲並不連續。
PS:聚集索引一個表只能有一個(因為數據真實的物理存儲順序就是按照聚集索引存儲的。),而非聚集索引一個表可以存在多個。
查看表中的索引: select * from sys.indexes where object_id = OBJECT_ID('表名');
問題:主鍵就是加了唯一性約束的聚集索引?
不對, 因為一般創建表時,主鍵的索引默認為聚集索引,但是我們可以手動改為非聚集索引【NONCLUSTERED 】的。
CREATE TABLE [dbo].[OrderExpertAmount]( [ID] [int] IDENTITY(1,1) NOT NULL, [OrderID] [int] NOT NULL, [Remark] [nvarchar](max) NULL, CONSTRAINT [PK_OrderExpertAmount] PRIMARY KEY CLUSTERED ( [ID] ASC )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] ) ON [PRIMARY]
我們可以對表執行以下語句,看報不報錯。
創建聚集索引:CREATE CLUSTERED INDEX index_orderid ON OrderExpertAmount(OrderID);
參考:主鍵就是聚集索引嗎?
實際上,讓主鍵ID作為聚集索引是一種資源浪費,因為:ID號是自動生成的,我們並不知道每條記錄的ID號,所以我們很難在實踐中用ID號來進行查詢。
更多參考:聚集索引和非聚集索引的區別有哪些
- 實例
-- 創建唯一聚集索引
create unique clustered --表示創建唯一聚集索引 index UQ_Clu_StuNo --索引名稱 on Student(S_StuNo) --數據表名稱(建立索引的列名)
PS:當 create index 時,如果未指定 clustered 和 nonclustered,那么默認為 nonclustered。
- 索引定義原則:
避免對經常更新的表進行過多的索引,並且索引中的列盡可能少。而對經常用於查詢的字段應該創建索引,但要避免添加不必要的字段。
在條件表達式中經常用到的、不同值較多的列上建立索引,在不同值少的列上不要建立索引。
在頻繁進行排序或分組(即進行 GROUP BY 或 ORDER BY 操作)的列上建立索引,如果待排序的列有多個,可以在這些列上建立組合索引。
在選擇索引鍵時,盡可能采用小數據類型的列作為鍵以使每個索引頁能容納盡可能多的索引鍵和指針,通過這種方式,可使一個查詢必需遍歷的索引頁面降低到最小,此外,盡可能的使用整數做為鍵值,因為整數的訪問速度最快。
- 索引可以優化SQL查詢性能,在sql管理器中
1、查看耗時:工具里面第一個工具(sql server profiler),連接上遠程的服務器,看每個查詢耗費的時間(監測SQL語句執行的性能參數)。
2、在查詢分析器中,運行一個查詢,執行
3、切換到sql server profiler中,可以看到第2步sql語句具體的用時。
4、索引自建:切換到查詢分析器,選中耗時的sql語句右鍵-》數據庫引擎優化顧問中的分析查詢-》登錄-》開始分析-》
操作-》應用建議
索引導出:數據庫右鍵-》任務-》生成腳本-》
二、視圖
視圖是一種虛擬表,來自一個或者多個表的行或者列,視圖並不是數據庫中存儲的數據值,可以簡單的理解視圖就是封裝了一段查詢語句,調用該視圖就得到查詢語句查詢出來的臨時表。
創建視圖:View_UWBandVIDEO

Create view View_UWBandVIDEO as SELECT dbo.INFO_PARKING_LOT.ID, dbo.VIDEO_HISTORY_CO.TIME, dbo.VIDEO_HISTORY_CO.FLAG, dbo.VIDEO_HISTORY_CO.PATH, dbo.VIDEO_HISTORY_CO.DEVICEID, dbo.INFO_PARKING_LOT.PARKINGNAME, dbo.INFO_PARKING_LOT.PARKINGID, dbo.INFO_PARKING_LOT.STATUS, dbo.INFO_PARKING_LOT.TIMEIN, dbo.INFO_PARKING_LOT.TIMEOUT, dbo.INFO_PARKING_LOT.PLATE, dbo.INFO_PARKING_LOT.OBUID FROM dbo.INFO_PARKING_LOT INNER JOIN dbo.VIDEO_HISTORY_CO ON dbo.INFO_PARKING_LOT.ID = dbo.VIDEO_HISTORY_CO.LOTID
程序中執行視圖查詢,就像查詢數據表一樣操作:
select * FROM View_UWBandVIDEO where lotid=’211001’
但是視圖沒法傳參數。
三、儲存過程
是在大型數據庫系統中,一組為了完成特定功能的SQL 語句集,它存儲在數據庫中,一次編譯后永久有效,用戶通過指定存儲過程的名字並給出參數(如果該存儲過程帶有參數)來執行它。
- 存儲過程的好處:
1.由於數據庫執行動作時,是先編譯后執行的。然而存儲過程是一個編譯過的代碼塊,所以執行效率要比T-SQL語句高。
2.一個存儲過程在程序在網絡中交互時可以替代大堆的T-SQL語句,所以也能降低網絡的通信量,提高通信速率。
3.通過存儲過程能夠使沒有權限的用戶在控制之下間接地存取數據庫,從而確保數據的安全。
但是存儲過程:移植性差、代碼可復用差。
- 用法:
-------------創建名為GetUserAccount的存儲過程---------------- create Procedure GetUserAccount as select * from UserAccount go -------------執行上面的存儲過程---------------- exec GetUserAccount
結果:相當於運行 select * from UserAccount 這行代碼,結果為整個表的數據。
eg 之前做項目過程中用到的部分存儲過程:

視頻歷史表中查實時數據,三次查詢, [sp_berthCheckResult] 第一次查詢需要查出一些列,將他們設置為參數,,最后將這些參數insert到一個臨時表(#tmp 需加#號),這個臨時表需要創建,字段的長度需要定義好, ① 拼接語句中,單引號需要轉義:’’’ ② 執行語句中注意轉化,EXEC sp_executesql Int類型的flagco CAST( @flagco as nvarchar(50)) Datetime類型的timeTemp2,,04 27 2017 1:20PM需要轉為'2017-04-27 13:20:22'。CAST((convert(varchar(100),@timeTemp2,20)) as varchar) sql日期時間格式轉換: convert(varchar(100),@timeTemp2,20 存儲過程的修改, 編寫存儲過程腳本為->創建到,修改。。然后刪除,在執行這個創建sql USE [CIP-MIDDLEWARE] GO /****** Object: StoredProcedure [dbo].[sp_berthCheckResult] Script Date: 05/12/2017 10:57:49 ******/ SET ANSI_NULLS ON GO SET QUOTED_IDENTIFIER ON GO -- ============================================= -- Author: <hy> -- Create date: <2017-5-12> -- Description: <Description,視頻歷史表中查實時數據,三次查詢,> -- ============================================= ALTER PROCEDURE [dbo].[sp_berthCheckResult] -- Add the parameters for the stored procedure here @LOTID varchar(50)='' AS BEGIN -- SET NOCOUNT ON added to prevent extra result sets from -- interfering with SELECT statements. SET NOCOUNT ON; -- Insert statements for procedure here create table #tmp( AUTOINDEX bigint null, LOTID varchar(10), TIME datetime null, FLAG int null, PATH varchar(1024) null, DEVICEID varchar(50) null, RELIABILITY varchar(50) null, FLAGREMARK nvarchar(1024) null, ISILLEGPARKING int null, ILLEGREMARK nvarchar(1024) null ) declare @strwhere nvarchar(500); declare @sql1 nvarchar(2000); declare @sql2 nvarchar(2000); declare @sql3 nvarchar(2000); --set @sql1=' LOTID='''+@LOTID+''''; if(@LOTID<>'') begin set @strwhere=' LOTID='''+@LOTID+''' '; end ---第一次查詢,需要flagco,PATH,DEVICEID,RELIABLITY,FLAGCOREMARK,ISILLEGPARKING,ILLEGREMARK, declare @flagco int; declare @timeTemp1 datetime ; --可以不要 declare @autoindex bigint; declare @PATH varchar(1024); declare @DEVICEID varchar(50); declare @RELIABLITY varchar(50); declare @FLAGCOREMARK nvarchar(1024); declare @ISILLEGPARKING int; declare @ILLEGREMARK nvarchar(1024); set @sql1='select top 1 @flagco=FLAGCO,@timeTemp1=TIME,@autoindex=AUTOINDEX,@PATH=PATH,@DEVICEID=DEVICEID, @RELIABLITY=RELIABLITY,@FLAGCOREMARK=FLAGCOREMARK,@ISILLEGPARKING=ISILLEGPARKING,@ILLEGREMARK=ILLEGREMARK from VIDEO_HISTORY_CO where '+@strwhere+' order by TIME desc'; EXEC sp_executesql @sql1,N'@flagco int OUTPUT,@timeTemp1 datetime OUTPUT,@autoindex bigint OUTPUT,@PATH varchar(1024) OUTPUT,@DEVICEID varchar(50) OUTPUT, @RELIABLITY varchar(50) OUTPUT,@FLAGCOREMARK nvarchar(1024) OUTPUT,@ISILLEGPARKING int OUTPUT,@ILLEGREMARK nvarchar(1024) OUTPUT', @flagco OUTPUT,@timeTemp1 OUTPUT ,@autoindex OUTPUT,@PATH OUTPUT,@DEVICEID OUTPUT,@RELIABLITY OUTPUT,@FLAGCOREMARK OUTPUT,@ISILLEGPARKING OUTPUT,@ILLEGREMARK OUTPUT; print @sql1; Print @timeTemp1; --第二次查詢,需要flagco,TIME declare @flagcoTemp int; declare @timeTemp2 datetime; set @sql2='select top 1 @flagcoTemp=FLAGCO,@timeTemp=TIME from VIDEO_HISTORY_CO where '+@strwhere+' AND FLAGCO!=99 AND FLAGCO!='+ CAST(@flagco as nvarchar(50)) +' order by TIME desc'; EXEC sp_executesql @sql2,N'@flagcoTemp int OUTPUT,@timeTemp datetime OUTPUT',@flagcoTemp OUTPUT,@timeTemp2 OUTPUT; print @sql2; print @flagcoTemp; print @timeTemp2; --第三次查詢,需要flagco,TIME declare @time datetime; set @sql3='select top 1 @time=TIME from VIDEO_HISTORY_CO where '+@strwhere+' AND FLAGCO=' + CAST( @flagco as nvarchar(50)) + ' AND TIME>'''+ CAST((convert(varchar(100),@timeTemp2,20)) as varchar) +''' order by TIME asc'; EXEC sp_executesql @sql3,N'@time datetime OUTPUT',@time OUTPUT; print @sql3; print @time; insert into #tmp (AUTOINDEX, LOTID, FLAG,TIME,PATH,DEVICEID,RELIABILITY,FLAGREMARK,ISILLEGPARKING,ILLEGREMARK)values(@autoindex, @LOTID, @flagco,@time,@PATH,@DEVICEID,@RELIABLITY,@FLAGCOREMARK,@ISILLEGPARKING,@ILLEGREMARK); select * from #tmp; drop table #tmp; END 為了查詢數據量少,限定個時間。第一次查詢時,只查TIME>(當前時間-N天)的。 ' AND TIME>'''+ CAST((convert(varchar(100),DATEADD(day,-30,GETDATE()),20)) as varchar) +''' order by TIME desc';
問題一、sql 在將 nvarchar 值轉換成數據類型 int 時失敗
存儲過程需要的參數是int類型,按道理直接傳值(+@Id)是沒有問題的,
if @Id <> 0
set @sql += ' and Id = ' +@Id
但是在執行存儲過程的時候,卻彈出:“sql 在將 nvarchar 值轉換成數據類型 int 時失敗。”
這時候需要轉換一下:
set @sql += ' and Id = ' +Cast(@Id as nvarchar(50))
四、Declare的使用
聲明變量的意思,eg 1:
IF 1=1 BEGIN DECLARE @test VARCHAR SET @test='1' PRINT 'in if:'+@test END
運行看結果,輸出 in if:1 這是可以預想的結果。那我們在if外面使用變量@test試試。
eg 2:
IF 1=1 BEGIN DECLARE @test VARCHAR SET @test='1' PRINT 'in if:'+@test END PRINT 'out if:'+@test
這樣會是什么結果呢,不知道大家怎么想的,以我的大腦順勢就想到這應該報錯啊,出了變量的作用域了。
實際結果不僅沒報錯而且@test的值還在。
in if:1
out if:1
eg 3:
IF 1=1 BEGIN DECLARE @test VARCHAR SET @test='1' PRINT 'in if:'+@test END GO PRINT 'out if:'+@test
這下對了,檢查語法后SQL報錯 “必須聲明標量變量"@test"”
注:GO就是用於一個sql語句的結束
比如說一個批處理語句是這樣的 select *from ,b select *from a 在后一個select后面加上一個GO,這樣可以一次執行兩條sql 語句
同時,分號(;) 也是語句的結尾,用了分號也不行。
五、exist與not exist
- exists (sql 返回結果集,為真)
- not exists (sql不返回結果集,為真)
如下:
表A
ID NAME
1 A1
2 A2
3 A3
表B
ID AID NAME
1 1 B1
2 2 B2
3 2 B3
SELECT ID,NAME FROM A WHERE EXIST (SELECT * FROM B WHERE A.ID=B.AID)
執行結果為
1 A1
2 A2
原因可以按照如下分析

SELECT ID,NAME FROM A WHERE EXISTS (SELECT * FROM B WHERE B.AID=1) ---> SELECT * FROM B WHERE B.AID=1有值,返回真,所以有數據 SELECT ID,NAME FROM A WHERE EXISTS (SELECT * FROM B WHERE B.AID=2) ---> SELECT * FROM B WHERE B.AID=2有值,返回真,所以有數據 SELECT ID,NAME FROM A WHERE EXISTS (SELECT * FROM B WHERE B.AID=3) ---> SELECT * FROM B WHERE B.AID=3無值,返回假,所以沒有數據
NOT EXISTS 就是反過來
SELECT ID,NAME FROM A WHERE NOT EXIST (SELECT * FROM B WHERE A.ID=B.AID)
執行結果為
3 A3
===========================================================EXISTS = IN,意思相同
不過語法上有點點區別,好像使用IN效率要差點,應該是不會執行索引的原因
SELECT ID,NAME FROM A WHERE ID IN (SELECT AID FROM B)
六、查詢優化小技巧
技巧1 比較運算符能用 “=”就不用“<>”。“=”增加了索引的使用幾率。
技巧2 明知只有一條查詢結果,那請使用 “LIMIT 1” 。“LIMIT 1”可以避免全表掃描,找到對應結果就不會再繼續掃描了。
技巧3 為列選擇合適的數據類型。能用TINYINT就不用SMALLINT,能用SMALLINT就不用INT,道理你懂的,磁盤和內存消耗越小越好嘛。
技巧4 將大的DELETE,UPDATE or INSERT 查詢變成多個小查詢
技巧5 使用UNION ALL 代替 UNION,如果結果集允許重復的話。因為 UNION ALL 不去重,效率高於 UNION。
技巧6 盡量避免使用 “SELECT *”。如果不查詢表中所有的列,盡量避免使用 SELECT *,因為它會進行全表掃描,不能有效利用索引,增大了數據庫服務器的負擔,以及它與應用程序客戶端之間的網絡IO開銷。
技巧7 ORDER BY 的列盡量被索引。ORDER BY的列如果被索引,性能也會更好。
技巧8 使用 LIMIT 實現分頁邏輯。不僅提高了性能,同時減少了不必要的數據庫和應用間的網絡傳輸。
技巧9 使用 EXPLAIN 關鍵字去查看執行計划。EXPLAIN 可以檢查索引使用情況以及掃描的行。