SQL Server 致程序員(容易忽略的錯誤)

標籤:SQL SERVER/MSSQL/DBA/T-SQL好習慣/數據庫/需要注意的地方/程序員/容易犯的錯誤/遇到的問題

概述

因爲每天需要審覈程序員發佈的SQL語句,所以收集了一些程序員的一些常見問題,還有一些平時收集的其它一些問題,這也是很多人容易忽視的問題,在以後收集到的問題會補充在文章末尾,歡迎關注,由於收集的問題很多是針對於生產數據,測試且數據量比較大,這裏就不把數據共享出來了,大家理解意思就行。

 

步驟

大小寫

大寫T-SQL 語言的所有關鍵字都使用大寫,規範要求。

使用“;”

使用“;”作爲 Transact-SQL 語句終止符。雖然分號不是必需的,但使用它是一種好的習慣,對於合併操作MERGE語句的末尾就必須要加上“;”

(cte表表達式除外)

數據類型

避免使用ntext、text 和 image 數據類型,用 nvarchar(max)、varchar(max) 和 varbinary(max)替代

後續版本會取消ntext、text 和 image 該三種類型

 

查詢條件不要使用計算列

複製代碼
例如year(createdate)=2014,使用createdate>=20140101and createdate<=20141231’來取代。
複製代碼
IF OBJECT_ID('News','U') IS NOT NULL DROP TABLE News
GO
CREATE TABLE News
(ID INT NOT NULL PRIMARY KEY IDENTITY(1,1),
NAME NVARCHAR(100) NOT NULL,
Createdate DATETIME NOT NULL
)
GO
CREATE NONCLUSTERED INDEX [IX1_News] ON [dbo].[News] 
(
    [Createdate] ASC
)
INCLUDE ( [NAME]) WITH (STATISTICS_NORECOMPUTE  = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
GO

GO
INSERT INTO News(NAME,Createdate) 
VALUES( '新聞','2014-08-20 00:00:00'),( '新聞','2014-08-20 00:00:00'),( '新聞','2014-08-20 00:00:00'),( '新聞','2014-08-20 00:00:00')
複製代碼


---使用計算列查詢(走的是索引掃描)

SELECT ID,NAME,Createdate FROM News
WHERE YEAR(Createdate)=2014

---不使用計算列(走的是索引查找)

SELECT ID,NAME,Createdate FROM News
WHERE CreateDate>='2014-01-01 00:00:00' and CreateDate<'2015-01-01 00:00:00'

對比兩個查詢顯然絕大部分情況下走索引查找的查詢性能要高於走索引掃描,特別是查詢的數據庫不是非常大的情況下,索引查找的消耗時間要遠遠少於索引掃描的時間,如果想詳細瞭解索引的體系結構可以查看了我前面寫的幾篇關於聚集、非聚集、堆的索引體系機構的文章。

複製代碼

 

請參看:http://www.cnblogs.com/chenmh/p/3780221.html

 請參看:http://www.cnblogs.com/chenmh/p/3782397.html

 

建表時字段不允許爲null


      發現很多人在建表的時候不會注意這一點,在接下來的工作中當你需要查詢數據的時候你往往需要在WHERE條件中多加一個判斷條件IS NOT NULL,這樣的一個條件不僅僅增加了額外的開銷,而且對查詢的性能產生很大的影響,有可能就因爲多了這個查詢條件導致你的查詢變的非常的慢;還有一個比較重要的問題就是允許爲空的數據可能會導致你的查詢結果出現不準確的問題,接下來我們就舉個例子討論一下。

複製代碼
T-SQL是三值邏輯(true,flase,unknown)
IF OBJECT_ID('DBO.Customer','U') IS NOT NULL DROP TABLE DBO.Customer
GO
CREATE TABLE DBO.Customer
(Customerid int not null );
GO
IF OBJECT_ID('DBO.OrderS','U') IS NOT NULL DROP TABLE DBO.OrderS
GO
CREATE TABLE DBO.OrderS
(Orderid int not null,
custid int);
GO
INSERT INTO Customer VALUES(1),(2),(3);
INSERT INTO OrderS VALUES(1,1),(2,2),(3,NULL);

----查詢沒有訂單的顧客
SELECT Customerid FROM DBO.Customer WHERE Customerid NOT IN(SELECT custid FROM OrderS);

---分析爲什麼查詢結果沒有數據
/*
因爲true,flase,unknown都是真值
因爲not in 是需要結果中返回flase值,not true=flase,not flase=flase,not unknown=unknown
因爲null值是unknown所以not unknownn無法判斷結果是什麼值所以不能返回數據
*/

--可以將查詢語句修改爲
SELECT Customerid FROM DBO.Customer WHERE Customerid NOT IN(SELECT custid FROM OrderS WHERE custid is not null);
--或者使用EXISTS,因爲EXISTS是二值邏輯只有(true,flase)所以不存在未知。
SELECT Customerid FROM DBO.Customer A WHERE  NOT EXISTS(SELECT custid FROM OrderS WHERE OrderS.custid=A.Customerid );

---in查詢可以返回值,因爲in是true,子查詢true,flase,unknown都是真值所以可以返回子查詢的true
SELECT Customerid FROM DBO.Customer WHERE Customerid  IN(SELECT custid FROM OrderS);


----如果整形字段可以賦0,字符型可以賦值空(這裏只是給建議)這裏的空和NULL是不一樣的意思
--增加整形字段可以這樣寫
ALTER TABLE TABLE_NAME ADD  COLUMN_NAME  INT NOT NULL DEFAULT(0)

--增加字符型字段可以這樣寫
ALTER TABLE TABLE_NAME ADD  COLUMN_NAME  NVARCHAR(50) NOT NULL DEFAULT('')

 

複製代碼

 

分組統計時避免使用count(*)

複製代碼
IF OBJECT_ID('DBO.Customer','U') IS NOT NULL DROP TABLE DBO.Customer
GO
CREATE TABLE DBO.Customer
(Customerid int not null );
GO
IF OBJECT_ID('DBO.OrderS','U') IS NOT NULL DROP TABLE DBO.OrderS
GO
CREATE TABLE DBO.OrderS
(Orderid int not null,
custid int);
GO
INSERT INTO Customer VALUES(1),(2),(3);
INSERT INTO OrderS VALUES(1,1),(2,2),(3,NULL);
例如:需要統計每一個顧客的訂單數量
---如果使用count(*)
SELECT Customerid,COUNT(*) FROM Customer TA LEFT JOIN OrderS TB ON TA.Customerid=TB.custid 
GROUP BY Customerid ;

實際情況customerid=3是沒有訂單的,數量應該是0,但是結果是1,count()裏面的字段是左連接右邊的表字段,如果你用的是主表字段結果頁是錯誤的。

----正確的方法是使用count(custid)
SELECT Customerid,COUNT(custid) FROM Customer TA LEFT JOIN OrderS TB ON TA.Customerid=TB.custid 
GROUP BY Customerid;

 

複製代碼

 

子查詢的表加上表別名

複製代碼
IF OBJECT_ID('DBO.Customer','U') IS NOT NULL DROP TABLE DBO.Customer
GO
CREATE TABLE DBO.Customer
(Customerid int not null );
GO
IF OBJECT_ID('DBO.OrderS','U') IS NOT NULL DROP TABLE DBO.OrderS
GO
CREATE TABLE DBO.OrderS
(Orderid int not null,
custid int);
GO
INSERT INTO Customer VALUES(1),(2),(3);
INSERT INTO OrderS VALUES(1,1),(2,2),(3,NULL);
複製代碼

大家發現下面語句有沒有什麼問題,查詢結果是怎樣呢?

SELECT Customerid FROM Customer WHERE Customerid IN(SELECT Customerid FROM OrderS WHERE Orderid=2 );


正確查詢結果下查詢出的結果是沒有customerid爲3的值

爲什麼結果會這樣呢?

大家仔細看應該會發現子查詢的orders表中沒有Customerid字段,所以SQL取的是Customer表的Customerid值作爲相關子查詢的匹配字段。

所以我們應該給子查詢加上表別名,如果加上表別名,如果字段錯誤的話會有錯誤標示

 正確的寫法:

SELECT Customerid  FROM Customer WHERE Customerid IN(SELECT tb.custid   FROM OrderS tb WHERE Orderid=2 );

建立自增列時單獨再給自增列添加唯一約束

複製代碼
複製代碼
USE tempdb 
CREATE TABLE TEST
(ID INT NOT NULL IDENTITY(1,1),
orderdate date NOT NULL DEFAULT(CURRENT_TIMESTAMP),
NAME NVARCHAR(30) NOT NULL,
CONSTRAINT CK_TEST_NAME CHECK(NAME LIKE '[A-Za-z]%' ) 
);

GO
INSERT INTO tempdb.DBO.TEST(NAME)
VALUES('A中'),('a名'),('Aa'),('ab'),('AA'),('az');

----4.插入報錯後,自增值依舊增加
INSERT INTO tempdb.DBO.TEST(NAME)
VALUES('');
GO
SELECT IDENT_CURRENT('tempdb.DBO.TEST');
SELECT * FROM tempdb.DBO.TEST;

---插入正常的數據
INSERT INTO tempdb.DBO.TEST(NAME)
VALUES('cc');

SELECT IDENT_CURRENT('tempdb.DBO.TEST')
SELECT * FROM tempdb.DBO.TEST;


----5.顯示插入自增值
SET IDENTITY_INSERT tempdb.DBO.TEST ON

INSERT INTO tempdb.DBO.TEST(ID,NAME)
VALUES(8,'A中');

SET IDENTITY_INSERT tempdb.DBO.TEST OFF

----會發現ID並不是根據自增值排列的,而且根據插入的順序排列的
SELECT IDENT_CURRENT('tempdb.DBO.TEST');
SELECT * FROM tempdb.DBO.TEST;

----6.插入重複的自增值
SET IDENTITY_INSERT tempdb.DBO.TEST ON

INSERT INTO tempdb.DBO.TEST(ID,NAME)
VALUES(8,'A中');

SET IDENTITY_INSERT tempdb.DBO.TEST OFF

SELECT IDENT_CURRENT('tempdb.DBO.TEST')
SELECT * FROM tempdb.DBO.TEST;
---所以如果要保證ID是唯一的,單單隻設置自增值不行,需要給字段設置主鍵或者唯一約束
DROP TABLE tempdb.DBO.TEST;
複製代碼

 

複製代碼

 

查詢時一定要制定字段查詢

l  查詢時一定不能使用”*”來代替字段來進行查詢,無論你查詢的字段有多少個,就算字段太多無法走索引也避免瞭解析”*”帶來的額外消耗。

l  查詢字段值列出想要的字段,避免出現多餘的字段,字段越多查詢開銷越大而且可能會因爲多列出了某個字段而引起查詢不走索引。

創建測試數據庫

複製代碼
CREATE TABLE [Sales].[Customer](
    [CustomerID] [int] IDENTITY(1,1) NOT FOR REPLICATION NOT NULL,
    [PersonID] [int] NULL,
    [StoreID] [int] NULL,
    [TerritoryID] [int] NULL,
    [AccountNumber]  AS (isnull('AW'+[dbo].[ufnLeadingZeros]([CustomerID]),'')),
    [rowguid] [uniqueidentifier] ROWGUIDCOL  NOT NULL,
    [ModifiedDate] [datetime] NOT NULL,
 CONSTRAINT [PK_Customer_CustomerID] PRIMARY KEY CLUSTERED 
(
    [CustomerID] 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 NONCLUSTERED INDEX [IX1_Customer] ON [Sales].[Customer] 
(
    [PersonID] ASC
)
INCLUDE ( [StoreID],
[TerritoryID],
[AccountNumber],
[rowguid]) WITH (PAD_INDEX  = OFF, STATISTICS_NORECOMPUTE  = OFF, SORT_IN_TEMPDB = OFF, IGNORE_DUP_KEY = OFF, DROP_EXISTING = OFF, ONLINE = OFF, ALLOW_ROW_LOCKS  = ON, ALLOW_PAGE_LOCKS  = ON) ON [PRIMARY]
GO
複製代碼

查詢測試

複製代碼
---使用SELECT  * 查詢
SET STATISTICS IO ON
SET STATISTICS TIME ON
SELECT  *  FROM [Sales].[Customer]
WHERE PersonID=1;
 SET STATISTICS TIME OFF
 SET STATISTICS IO OFF
複製代碼

由於建的索引‘IX1_Customer’沒有包含ModifiedDate字段,所以需要通過鍵查找去聚集索引中獲取該字段的值

複製代碼
 ---列出需要的字段查詢,因爲字段不包含不需要的列,所以走索引
SET STATISTICS IO ON
SET STATISTICS TIME ON  
  SELECT CustomerID, 
       [PersonID]
      ,[StoreID]
      ,[TerritoryID]
      ,[AccountNumber]
      ,[rowguid]
  FROM [Sales].[Customer]
  WHERE PersonID=1;
 SET STATISTICS TIME OFF
 SET STATISTICS IO OFF
複製代碼

由於查詢語句中沒有對ModifiedDate字段進行查詢,所以只走索引查找就可以查詢到需要的數據,所以建議在查詢語句中列出你需要的字段而不是爲了方便用*來查詢所有的字段,如果真的

需要查詢所有的字段也同樣建議把所有的字段列出來取代‘*’。

 

使用存儲過程的好處

  1. 減少網絡通信量。調用一個行數不多的存儲過程與直接調用SQL語句的網絡通信量可能不會有很大的差別,可是如果存儲過程包含上百行SQL語句,那麼其性能絕對比一條一條的調用SQL語句要高得多。
  2. 執行速度更快。有兩個原因:首先,在存儲過程創建的時候,數據庫已經對其進行了一次解析和優化。其次,存儲過程一旦執行,在內存中就會保留一份這個存儲過程緩存計劃,這樣下次再執行同樣的存儲過程時,可以從內存中直接調用。
  3. 更強的適應性:由於存儲過程對數據庫的訪問是通過存儲過程來進行的,因此數據庫開發人員可以在不改動存儲過程接口的情況下對數據庫進行任何改動,而這些改動不會對應用程序造成影響。
  4. 布式工作:應用程序和數據庫的編碼工作可以分別獨立進行,而不會相互壓制。
  5. 更好的封裝移植性。
  6. 安全性,它們可以防止某些類型的 SQL 插入攻擊。
複製代碼
PROCEDURE [dbo].[SPSalesPerson]
(@option varchar(50))
AS
BEGIN
SET NOCOUNT ON
IF @option='select'
    BEGIN
    SELECT [DatabaseLogID]
          ,[PostTime]
          ,[DatabaseUser]
          ,[Event]
          ,[Schema]
          ,[Object]
          ,[TSQL]
          ,[XmlEvent]
      FROM [dbo].[DatabaseLog]
      END
IF @option='SalesPerson'
   BEGIN
   SELECT [BusinessEntityID]
      ,[TerritoryID]
      ,[SalesQuota]
      ,[Bonus]
      ,[CommissionPct]
      ,[SalesYTD]
      ,[SalesLastYear]
      ,[rowguid]
      ,[ModifiedDate]
   FROM [Sales].[SalesPerson]
   WHERE BusinessEntityID<300
   END
SET NOCOUNT OFF  
END
複製代碼
複製代碼
EXEC SPSalesPerson @option='select'
EXEC SPSalesPerson @option='SalesPerson'

DBCC FREEPROCCACHE----清空緩存

---測試兩個查詢是否都走了緩存計劃
SELECT usecounts,size_in_bytes,cacheobjtype,objtype,TEXT FROM  sys.dm_exec_cached_plans  cp  
CROSS APPLY sys.dm_exec_sql_text(cp.plan_handle) st;

--執行計劃在第一次執行SQL語句時產生,緩存在內存中,這個緩存的計劃一直可用,直到 SQL Server 重新啓動,或直到它由於使用率較低而溢出內存。

默認情況下,存儲過程將返回過程中每個語句影響的行數。如果不需要在應用程序中使用該信息(大多數應用程序並不需要),請在存儲過程中使用 SET NOCOUNT ON 語句以終止該行爲。根據存儲過程中包含的影響行的語句的數量,這將刪除客戶端和服務器之間的一個或多個往返過程。儘管這不是大問題,但它可以爲高流量應用程序的性能產生負面影響。
複製代碼

判斷一條查詢是否有值

複製代碼
--以下四個查詢都是判斷連接查詢無記錄時所做的操作
---性能最差消耗0.8秒
SET STATISTICS IO ON 
SET STATISTICS TIME ON
DECLARE @UserType INT ,@Status INT
SELECT  @UserType=COUNT(c.Id) FROM Customerfo t INNER JOIN Customer c ON c.Id=t.CustomerId    
        WHERE c.customerTel='13400000000'
            IF(@UserType=0)
            BEGIN
                SET @Status = 2      
                    PRINT  @Status
            END
SET STATISTICS TIME OFF    
SET STATISTICS IO OFF            
    go    

----性能較好消耗0.08秒    
SET STATISTICS IO ON 
SET STATISTICS TIME ON
        
IF NOT EXISTS(SELECT c.Id FROM Customerfo t INNER JOIN Customer c ON c.Id=t.CustomerId WHERE c.customerTel='13400000000')
    BEGIN
    DECLARE @Status int
     SET @Status = 2 
    PRINT @Status
    END
        
SET STATISTICS TIME OFF    
SET STATISTICS IO OFF
    go    

----性能較好消耗0.08秒            
SET STATISTICS IO ON 
SET STATISTICS TIME ON
        
IF NOT EXISTS(SELECT top 1 c.id FROM Customerfo t INNER JOIN Customer c ON c.Id=t.CustomerId WHERE c.customerTel='13400000000'
        ORDER BY NEWID() )
    BEGIN
    DECLARE @Status int
     SET @Status = 2 
    PRINT @Status
    END
        
SET STATISTICS TIME OFF    
SET STATISTICS IO OFF    
            
GO

---性能和上面的一樣0.08秒
SET STATISTICS IO ON 
SET STATISTICS TIME ON
        
    IF NOT EXISTS(SELECT 1  FROM Customerfo t INNER JOIN Customer c ON c.Id=t.CustomerId WHERE c.customerTel='13410700660' )
    BEGIN
    DECLARE @Status int
     SET @Status = 2 
    PRINT @Status
    END
        
SET STATISTICS TIME OFF    
SET STATISTICS IO OFF    

這裏說一下SELECT 1,之前因爲有程序員誤認爲查詢SELECT 1無論查詢的數據有多少隻返回一個1,其實不是這樣的,和查詢字段是一樣的意思只是有多少記錄就返回多少個1,1也不是查詢的第一個字段。
複製代碼

 理解TRUNCATE和DELETE的區別

複製代碼
---創建表Table1
IF OBJECT_ID('Table1','U') IS NOT NULL
DROP TABLE Table1
GO
CREATE TABLE Table1
(ID INT NOT NULL,
FOID INT NOT NULL)
GO

--插入測試數據
INSERT INTO Table1
VALUES(1,101),(2,102),(3,103),(4,104)
GO

---創建表Table2
IF OBJECT_ID('Table2','U') IS NOT NULL
DROP TABLE Table2
GO
CREATE TABLE Table2
(
FOID INT NOT NULL)
GO
--插入測試數據
INSERT INTO Table2 VALUES(101),(102),(103),(104)
GO 
SELECT * FROM Table1
GO 
SELECT * FROM Table2
GO

在Table1表中創建觸發器,當表中的數據被刪除時同時刪除Table2表中對應的FOID
複製代碼
CREATE TRIGGER TG_Table1 ON Table1
AFTER DELETE
AS
BEGIN
  DELETE FROM TA FROM Table2 TA INNER JOIN deleted TB ON TA.FOID=TB.FOID 
END
GO
複製代碼


複製代碼
---測試DELETE刪除操作
DELETE FROM Table1 WHERE ID=1

GO
---執行觸發器成功,Table2表中的FOID=101的數據也被刪除
SELECT * FROM Table1
GO
SELECT * FROM Table2
複製代碼
複製代碼
---測試TRUNCATE刪除操作
TRUNCATE TABLE Table1

GO
---Table2中的數據沒有被刪除
SELECT * FROM Table1
GO
SELECT * FROM Table2
複製代碼
複製代碼
複製代碼
---查看TRUNCATE和DELETE的日誌記錄情況
CHECKPOINT
GO
SELECT * FROM fn_dblog(NULL,NULL)
GO
DELETE FROM Table2
WHERE FOID=102
GO
SELECT * FROM fn_dblog(NULL,NULL)
複製代碼

  在第四行記錄有一個lop_delete_rows,lcx_heap的刪除操作日誌記錄
複製代碼
----TRUNCATE日誌記錄
CHECKPOINT
GO
SELECT * FROM fn_dblog(NULL,NULL)
GO
TRUNCATE TABLE Table2
GO
SELECT * FROM fn_dblog(NULL,NULL)
GO
複製代碼

 TRUNCATE操作沒有記錄刪除日誌操作

主要的原因是因爲TRUNCATE操作不會激活觸發器,因爲TRUNCATE操作不會記錄各行的日誌刪除操作,所以當你需要刪除一張表的數據時你需要考慮是否應該如有記錄日誌刪除操作,而不是根據個人的習慣來操作。

 

事務的理解

複製代碼
---創建表Table1
IF OBJECT_ID('Table1','U') IS NOT NULL
DROP TABLE Table1
GO
CREATE TABLE Table1
(ID INT NOT NULL PRIMARY KEY,
Age INT NOT NULL CHECK(Age>10 AND Age<50));
GO

---創建表Table2
IF OBJECT_ID('Table2','U') IS NOT NULL
DROP TABLE Table2
GO
CREATE TABLE Table2
(
ID INT NOT NULL)
GO
複製代碼

1.簡單的事務提交

複製代碼
BEGIN TRANSACTION
INSERT INTO Table1(ID,Age)
VALUES(1,20)
INSERT INTO Table1(ID,Age)
VALUES(2,5)
INSERT INTO Table1(ID,Age)
VALUES(2,20)
INSERT INTO Table1(ID,Age)
VALUES(3,20)
COMMIT TRANSACTION
GO
---第二條記錄沒有執行成功,其他的都執行成功
SELECT * FROM Table1
所以並不是事務中的任意一條語句報錯整個事務都會回滾,其它的可執行成功的語句依然會執行成功並提交。
複製代碼

2.TRY...CATCH

複製代碼
DELETE FROM Table1

BEGIN TRY
BEGIN TRANSACTION
INSERT INTO Table1(ID,Age)
VALUES(1,20)
INSERT INTO Table1(ID,Age)
VALUES(2,20)
INSERT INTO Table1(ID,Age)
VALUES(3,20)
INSERT INTO Table3
VALUES(1) 
COMMIT TRANSACTION
END TRY
BEGIN CATCH
ROLLBACK TRANSACTION
END CATCH

----重新打開一個回話執行查詢,發現由於存在對象出錯BEGIN CATCH並沒有收到執行報錯,且事務一直處於打開狀態,沒有被提交,也沒有執行回滾。
SELECT * FROM Table1

---如果事務已經提交查詢XACT_STATE()的狀態值是0,或者執行DBCC OPENTRAN
SELECT XACT_STATE()

DBCC OPENTRAN

---手動執行提交或者回滾操作
ROLLBACK TRANSACTION

複製代碼
TRY...CATCH不會返回對象錯誤或者字段錯誤等類型的錯誤

想詳細瞭解TRY...CATCH請參考http://www.cnblogs.com/chenmh/articles/4012506.html

 

3.打開XACT_ABORT

複製代碼
SET XACT_ABORT ON
BEGIN TRANSACTION
INSERT INTO Table1(ID,Age)
VALUES(1,20)
INSERT INTO Table1(ID,Age)
VALUES(2,20)
INSERT INTO Table1(ID,Age)
VALUES(3,20)
INSERT INTO Table3
VALUES(1) 
COMMIT TRANSACTION
SET XACT_ABORT OFF

---事務全部執行回滾操作(對象table3是不存在報錯,但是也回滾所有的提交,跟上面的TRY...CATCH的區別)
SELECT * FROM Table1
複製代碼

複製代碼
---查詢是否有打開事務
SELECT XACT_STATE()

DBCC OPENTRAN
未查詢到有打開事務

當 SET XACT_ABORT 爲 ON 時,如果執行 Transact-SQL 語句產生運行時錯誤,則整個事務將終止並回滾。

當 SET XACT_ABORT 爲 OFF 時,有時只回滾產生錯誤的 Transact-SQL 語句,而事務將繼續進行處理。如果錯誤很嚴重,那麼即使 SET XACT_ABORT 爲 OFF,也可能回滾整個事務。OFF 是默認設置。

編譯錯誤(如語法錯誤)不受 SET XACT_ABORT 的影響。

複製代碼

      所以我們應該根據自己的需求選擇正確的事務。

    

修改字段NOT NULL的過程

複製代碼
在Address表中的有一個Address字段,該字段允許爲NULL,現在需要將其修改爲NOT NULL.
BEGIN TRANSACTION
SET QUOTED_IDENTIFIER ON
SET ARITHABORT ON
SET NUMERIC_ROUNDABORT OFF
SET CONCAT_NULL_YIELDS_NULL ON
SET ANSI_NULLS ON
SET ANSI_PADDING ON
SET ANSI_WARNINGS ON
COMMIT
BEGIN TRANSACTION
GO
CREATE TABLE dbo.Tmp_Address
    (
    ID int NOT NULL,
    Address nvarchar(MAX) NOT NULL
    )  ON [PRIMARY]
     TEXTIMAGE_ON [PRIMARY]
GO
ALTER TABLE dbo.Tmp_Address SET (LOCK_ESCALATION = TABLE)
GO
IF EXISTS(SELECT * FROM dbo.Address)
     EXEC('INSERT INTO dbo.Tmp_Address (ID, Address)
        SELECT ID, Address FROM dbo.Address WITH (HOLDLOCK TABLOCKX)')
GO
DROP TABLE dbo.Address
GO
EXECUTE sp_rename N'dbo.Tmp_Address', N'Address', 'OBJECT' 
GO
COMMIT

---從上面就是一個重置字段爲非空的過程,從上面的語句我們可以看到首先要創建一張臨時表在臨時表中Address字段建成了NOT NULL,然後將原表中的數據插入到臨時表當中,最後修改表名,大家可以想一下如果我要修改的表有幾千萬數據,那這個過程該多麼長而且內存一下子就會增加很多,所以大家建表的時候就要養成設字段爲NOT NULL

--當你要向現有的表中增加一個字段的時候你也要不允許爲NULL,可以用默認值替代空
Alter Table Address Add Type smallint Not Null Default (1)
複製代碼

 條件字段的先後順序

你平時在寫T_SQL語句的時候WHERE條件後面的字段的先後順序你有注意嗎?

複製代碼
---創建測試表 
IF OBJECT_ID('TAINFO','U')IS NOT NULL DROP TABLE TAINFO
GO

CREATE TABLE [dbo].[TAINFO](
ID INT NOT NULL PRIMARY KEY IDENTITY(1,1),
OID INT NOT NULL,
Stats SMALLINT CHECK (Stats IN(1,2)),
MAC uniqueidentifier NOT NULL

) ON [PRIMARY]

GO
---插入測試數據
INSERT INTO TAINFO(OID,Stats,MAC)
 VALUES(101,1,'46B550F9-6E24-436D-9BC7-F0650F562E54'),(101,2,'46B550F9-6E24-436D-9BC7-F0650F562E54'),(102,1,'46B550F9-6E24-436D-9BC7-F0650F562E54'),
 (102,2,'46B550F9-6E24-436D-9BC7-F0650F562E54'),(103,2,'46B550F9-6E24-436D-9BC7-F0650F562E54'),(103,2,'46B550F9-6E24-436D-9BC7-F0650F562E54'),
 (103,1,'46B550F9-6E24-436D-9BC7-F0650F562E54'),(103,1,'46B550F9-6E24-436D-9BC7-F0650F562E54')
 GO
複製代碼

 

如果這是你的寫的查詢語句

 SELECT ID,OID,Stats MAC FROM TAINFO WHERE MAC='46B550F9-6E24-436D-9BC7-F0650F562E54' AND STATS=1  AND OID=102  

我現在根據你的查詢語句創建一條索引

CREATE INDEX IX2_TAINFO ON TAINFO(MAC,STATS,OID)

分別執行三條查詢語句

 ---1.WHERE條件是索引字段且查詢字段也是索引字段
 SELECT ID,OID,Stats MAC FROM TAINFO WHERE MAC='46B550F9-6E24-436D-9BC7-F0650F562E54' AND STATS=1  AND OID=102  
 --2.WHERE 條件是索引的部分字段(這條語句或許是平時查詢該表用到的最多的一條語句)
 SELECT ID,OID,Stats MAC FROM TAINFO WHERE OID=102  AND STATS=1
 --3.WHERE 條件是索引的部分字段
  SELECT ID,OID,Stats MAC FROM TAINFO WHERE STATS=1

執行計劃分別爲

 從上面三天查詢語句可以看出,只有第一條語句走的是索引查找,另外兩條語句走的是索引掃描,而我們從字段的名稱應該可以看的出OID字段應該是該表的一個外鍵字段也是經常會被用作查詢的字段。

接下來我們重新換一下索引順序

 --創建索引
DROP INDEX IX2_TAINFO ON TAINFO
GO
CREATE INDEX IX1_TAINFO ON TAINFO(OID)
INCLUDE(STATS,MAC)
GO

依然執行前面的三條查詢語句分析執行計劃

 分析執行計劃前面兩條查詢語句都走的是索引查找,第三條查詢的是索引掃描,而根據一般單獨用第三條查詢的業務應該不會常見,所以現在一條索引解決了兩個常用查詢的索引需求,避免了建兩條索引的必要(所以當你建索引的時候索引的順序很重要,一般把查詢最頻繁的字段設第一個字段,可以避免建多餘的索引)。

爲什麼要把這個問題提出來呢,因爲平時有遇到程序員在寫查詢語句的時候對於同一個查詢條件每次的寫法都不一樣,往往是根據自己想到哪個字段就寫哪個字段先,這樣的習慣往往是不好的,就好比上面的例子如果別人看到你的查詢條件建一個索引也是這樣寫的話往往一個表會出現很多多餘的索引(或許有人會說DBA建好索引的順序就好了,這裏把這個因素排除吧),像後面的那個索引就解決了兩個查詢的需求。

所以這裏我一般是這樣規定where條件的,對於經常用作查詢的字段放在第一個位置(比如上面例子的OID),其它的字段根據表的實際字段順序排列,這樣往往你的查詢語句走索引的概率會更大。

 

 理解外連接

複製代碼
---創建測試表
IF OBJECT_ID('DBO.OrderS','U') IS NOT NULL DROP TABLE DBO.OrderS
GO
CREATE TABLE DBO.OrderS
(Orderid INT NOT NULL,
custid INT NOT NULL,
stats INT NOT NULL);
GO
IF OBJECT_ID('DBO.Customer','U') IS NOT NULL DROP TABLE DBO.Customer
GO
CREATE TABLE DBO.Customer
(Customerid INT NOT NULL );
GO

---插入測試數據
INSERT INTO OrderS VALUES(1,101,0),(2,102,0),(3,103,1),(4,104,0);
GO
INSERT INTO Customer VALUES(101),(102),(103);


----查詢OrderS 表中stats不等於1且不在Customer 表中的數據
SELECT TA.Orderid,TA.custid,TA.stats,TB.Customerid  FROM OrderS TA LEFT JOIN Customer TB ON TA.stats<>'1' AND TA.custid=TB.Customerid 
WHERE TB.Customerid IS NULL
複製代碼

看到這結果是不是有點疑惑,我在連接條件裏面寫了TA.stats<>'1',爲什麼結果還會查詢出。

接下來我們換一種寫法吧!

----查詢OrderS 表中stats不等於1且不在Customer 表中的數據
SELECT TA.Orderid,TA.custid,TA.stats,TB.Customerid  FROM OrderS TA LEFT JOIN Customer TB ON  TA.custid=TB.Customerid 
WHERE TA.stats<>'1' AND TB.Customerid IS NULL

 接下來我就解釋一下原因:對於外連接,連接條件不會改變主表的數據,即不會刪減主表的數據

對於上面的查詢主表是orders,所以無論你在連接條件on裏面怎樣設置主表的條件都不影響主表數據的輸出,影響主表數據的輸出只在where條件裏,where條件影響最後數據的輸出。而對於附表Customer 的條件就應該寫在連接條件(on)裏而不是where條件裏,這裏說的是外連接(包括左連接和右連接)。

對於inner join就不存在這種情況,無論你的條件是寫在where後面還是on後面都是一樣的,但是還是建議寫在where後面。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章