USE AdventureWorks; GO --主键只用来保证数据,而聚集索引影响数据的逻辑排序 --下面的语句执行计划中并没有看到排序操作,证明数据是按SalesOrderID排过序的,而且是一种双向链表 SELECTTOP(10) *FROM Sales.SalesOrderHeader ORDERBY SalesOrderID SELECTTOP(10) *FROM Sales.SalesOrderHeader ORDERBY SalesOrderID DESC SELECTTOP(10) SalesOrderID FROM Sales.SalesOrderHeader ORDERBY SalesOrderID SELECTTOP(10) SalesOrderID FROM Sales.SalesOrderHeader --====================================================================== --查看表内数据是如何存储的 --====================================================================== SELECTobject_name(p.object_id) as tablename,i.name as indexname, rows,a.type_desc as page_type_desc, total_pages as pages,first_page FROM sys.partitions p JOIN sys.system_internals_allocation_units a ON p.partition_id = a.container_id JOIN sys.indexes i ON p.index_id = i.index_id AND p.object_id= i.object_id WHERE p.object_id=object_id(N'Sales.SalesOrderHeader') --查看所含的pages字段最多的则为聚集索引叶级,其余的为别的索引页 --0xA01400000100得知第一页为5280,此为聚集索引叶的第一页 DBCC TRACEON(3604) GO DBCC PAGE(AdventureWorks,1,5280,3)--查看第4~7字节的值是按照SalesOrderID从小到大排列的 GO --查看IX_SalesOrderHeader_CustomerID索引的第一页的第一个值是否为1 --0x105200000100得到第一页为21008 DBCC PAGE(AdventureWorks,1,21008,1) GO --====================================================================== --行长度的限制8060 --====================================================================== USE tempdb; GO IFOBJECT_ID(N'bigrows',N'U') ISNOTNULL DROPTABLE dbo.bigrows GO CREATETABLE dbo.bigrows ( a char(3000), b char(3000), c char(2000), d char(60) ) GO INSERTINTO dbo.bigrows SELECTREPLICATE('a',3000),REPLICATE('b',3000), REPLICATE('c',2000),REPLICATE('d',6) --2005使用行溢出数据突破了変长列的限制,但这样会增加I/O操作。 IFOBJECT_ID(N'bigrows',N'U') ISNOTNULL DROPTABLE dbo.bigrows GO CREATETABLE dbo.bigrows ( a varchar(3000), b varchar(3000), c varchar(3000), d varchar(3000) ) GO INSERTINTO dbo.bigrows SELECTREPLICATE('a',3000),REPLICATE('b',3000), REPLICATE('c',3000),REPLICATE('d',3000) SELECTobject_name(object_id) as name, partition_id,partition_number as pnum,rows, allocation_unit_id as au_id,type_desc as page_type,total_pages as pages FROM sys.partitions p JOIN sys.allocation_units a ON p.partition_id = a.container_id WHEREobject_id=object_id(N'dbo.bigrows'); --====================================================================== --非聚集索引通过聚集索引进行查找 --====================================================================== --SQL2005新增用于查看表中各索引的统计的动态函数 SELECT*FROM sys.dm_db_index_physical_stats(DB_ID(N'AdventureWorks'), OBJECT_ID(N'Sales.SalesOrderHeader'), NULL, NULL , 'DETAILED'); --索引的深度 SELECTINDEXPROPERTY(object_ID('Sales.SalesOrderHeader'),N'PK_SalesOrderHeader_SalesOrderID','IndexDepth') SELECTINDEXPROPERTY(object_ID('Sales.SalesOrderHeader'),N'AK_SalesOrderHeader_SalesOrderNumber','IndexDepth') --聚集索引查找,因为索引的深度为3,所以此时逻辑读为3 SELECT*FROM Sales.SalesOrderHeader WHERE SalesOrderID=43659 --非聚集索引通过聚集索引进行查找(逻辑读有5次,因为聚集为3,非聚集为2) SELECT*FROM Sales.SalesOrderHeader WHERE SalesOrderNumber=N'SO45283' --====================================================================== --复合索引测试 --====================================================================== BEGINTRANSACTION GO DROPINDEX IX_SalesOrderHeader_CustomerID ON Sales.SalesOrderHeader GO CREATENONCLUSTEREDINDEX IX_SalesOrderHeader_CustomerID ON Sales.SalesOrderHeader ( CustomerID, CreditCardID ) WITH( STATISTICS_NORECOMPUTE =OFF, IGNORE_DUP_KEY =OFF, ALLOW_ROW_LOCKS =ON, ALLOW_PAGE_LOCKS =ON) ON[PRIMARY] GO COMMIT UPDATESTATISTICS Sales.SalesOrderHeader IX_SalesOrderHeader_CustomerID --查看此索引的统计信息 DBCC SHOW_STATISTICS('Sales.SalesOrderHeader',IX_SalesOrderHeader_CustomerID) SELECTCOUNT(*) --EQ_ROWS等于上限值的个数 FROM Sales.SalesOrderHeader WHERE CustomerID=1 SELECTCOUNT(*) --RANGE_ROWS此范围内不包括边界的记录数 FROM Sales.SalesOrderHeader WHERE CustomerID>1AND CustomerID<19 SELECTCOUNT(DISTINCT CustomerID)--DISTINCT_RANGE_ROWS 此范围内不包括边界的不同记录数 FROM Sales.SalesOrderHeader WHERE CustomerID>1AND CustomerID<19 SELECT1.0*COUNT(*)/COUNT(DISTINCT CustomerID)--AVG_RANGE_ROWS 数据分布程度 FROM Sales.SalesOrderHeader WHERE CustomerID>1AND CustomerID<19 --查看Sales.SalesOrderHeader表中索引信息 SELECT*FROM sys.dm_db_index_physical_stats(DB_ID(N'AdventureWorks'), OBJECT_ID(N'Sales.SalesOrderHeader'), NULL, NULL , 'DETAILED'); --IX_SalesOrderHeader_CustomerID的索引编号为5深度为2,叶级有71个页面,根叶1页 SELECTQUOTENAME(name) FROM sys.indexes WHEREobject_id=722101613AND index_id =5; DBCC DROPCLEANBUFFERS; GO DBCC FREEPROCCACHE; GO --非聚集索引查找+键值查找(逻辑读为8,非聚集索引查找2页加每个值一次聚集索引查找) SELECT*FROM Sales.SalesOrderHeader WHERE CustomerID=19965 --非聚集索引扫描+键值查找(因此使用复合索引除第一个字段外,不会进行索引查找) SELECT*FROM Sales.SalesOrderHeader WHERE CreditCardID=15466 --覆盖索引查找(逻辑读为2,说明只在非聚集索引中查找到了所需的数据) SELECT SalesOrderID,CreditCardID FROM Sales.SalesOrderHeader WHERE CustomerID=19965 --作为查询条件的字段会自动创建统计 SELECT*FROM Production.Location WHERE ModifiedDate>'20070101' --所有自动创建的统计 SELECT m.name+'.'+object_name(s.object_id) as[table],s.name as[statistics],c.name as[column] FROM sys.stats s join sys.stats_columns sc on s.stats_id = sc.stats_id and s.object_id= sc.object_id join sys.columns c on sc.column_id = c.column_id and c.object_id= sc.object_id join sys.objects o on s.object_id= o.object_id join sys.schemas m on m.schema_id = o.schema_id where s.auto_created=1and o.type='U' SELECTobject_name(id),name,statblob--统计信息二进制大型对象(BLOB),仅内部使用,返回NULL. FROM sys.sysindexes --SELECT * FROM sys.stats_columns --==================================================================================== --选择度过低而无法有效利用索引查找而选择表扫描,查询2004-7-01之后的订单 --==================================================================================== USE AdventureWorks; GO BEGINTRANSACTION GO DROPINDEX IX_OrderDate ON Sales.SalesOrderHeader GO CREATENONCLUSTEREDINDEX IX_OrderDate ON Sales.SalesOrderHeader ( OrderDate ) WITH( STATISTICS_NORECOMPUTE =OFF, IGNORE_DUP_KEY =OFF, ALLOW_ROW_LOCKS =ON, ALLOW_PAGE_LOCKS =ON) ON[PRIMARY] GO COMMIT DBCC DROPCLEANBUFFERS; GO DBCC FREEPROCCACHE; GO --分别使用2004-7-01低选择度与2004-7-26高选择度比较两种查询的性能差异 SELECT SalesOrderID,OrderDate,Status,CustomerID,TaxAmt,TotalDue FROM Sales.SalesOrderHeader WHERE OrderDate>'2004-7-01' --首先在非聚焦索引叶级中扫描获得最大及最小的订单号,由于此时表扫描只是在叶级所以I/O操作会明显减少 DECLARE@minint, @maxint SELECT@min=MIN(SalesOrderID),@max=MAX(SalesOrderID) FROM Sales.SalesOrderHeader WHERE OrderDate>'2004-7-01' --使用聚焦索引 SELECT SalesOrderID,OrderDate,Status,CustomerID,TaxAmt,TotalDue FROM Sales.SalesOrderHeader WHERE SalesOrderID BETWEEN@minAND@max GO --================================================ --什么是查询参数规范SARGS --================================================ --1、不要对字段做运算 SELECT*FROM Sales.SalesOrderHeader WHERE CustomerID+1=2 SELECT*FROM Sales.SalesOrderHeader WHERE CustomerID=1 --2、不要对字段使用函数(数学函数、日期函数、字符串函数等) SELECT*FROM Sales.SalesOrderHeader WHEREABS(SalesOrderID-44659)<1 SELECT*FROM Sales.SalesOrderHeader WHERE SalesOrderID<44660AND SalesOrderID>44658 --3、不要使用负向查询(NOT、!=、<>、!>、!<、NOT EXISTS、NOT IN) SELECT*FROM Sales.SalesOrderDetail WHERE OrderQty!=1 SELECT*FROM Sales.SalesOrderDetail WHERE OrderQty>1OR OrderQty<1 --4、小心使用OR(虽然有聚集索引但是因Status无索引可用只能进行表扫描) SELECT*FROM Sales.SalesOrderHeader WHERE SalesOrderID=43659OR[Status]=1 --================================================================================ --三种联结(嵌套循环、合并联结、哈稀联结),所占用的资源依次增加,SQL优先选用嵌套循环 --================================================================================ --1、嵌套循环(外表、内表),记录少的表作为外表,在内表中根据每个联结字段的值进行 --循环,只有在外表数据量较少时使用 SELECT* FROM Sales.SalesOrderDetail D JOIN Sales.SalesOrderHeader O ON D.SalesOrderID = O.SalesOrderID WHERE O.SalesOrderID BETWEEN43659AND43665 --2、合并联结的算法前提是字段已排序 SELECT* FROM Sales.SalesOrderDetail D JOIN Sales.SalesOrderHeader O ON D.SalesOrderID = O.SalesOrderID WHERE O.SalesOrderID BETWEEN43659AND54000 --3、哈稀联结(在要联结的字段上无可用索引时使用此连接算法) IFOBJECT_ID(N'SumPrice',N'U') ISNOTNULL DROPTABLE SumPrice GO SELECT O.SalesOrderID,YEAR(O.OrderDate)+MONTH(O.OrderDate) AS Date,CAST(SUM(D.OrderQty*D.UnitPrice*(1-D.UnitPriceDiscount)) ASdecimal(18,2)) AS Price INTO SumPrice FROM Sales.SalesOrderDetail D JOIN Sales.SalesOrderHeader O ON D.SalesOrderID=O.SalesOrderID GROUPBY O.SalesOrderID,O.OrderDate GO SELECT O.SalesOrderID,O. FROM SumPrice D JOIN Sales.SalesOrderHeader O ON D.SalesOrderID = O.SalesOrderID WHERE O.SalesOrderID BETWEEN43659AND54000 --========================================================================================== --代替LEFT JOIN的方法 --为了防止在查询中出现LEFT操作,在设计数据库时应尽量避免关联表中不一致的情况出现 --比如产品的类别和具体产品的对应关系中,可在类别加入一个无类别的记录以对应不知是何类别的产品 --========================================================================================== BEGINTRANSACTION GO DROPINDEX IX_SalesOrderHeader_CustomerID ON Sales.SalesOrderHeader GO CREATENONCLUSTEREDINDEX IX_SalesOrderHeader_CustomerID ON Sales.SalesOrderHeader ( CustomerID ) WITH( STATISTICS_NORECOMPUTE =OFF, IGNORE_DUP_KEY =OFF, ALLOW_ROW_LOCKS =ON, ALLOW_PAGE_LOCKS =ON) ON[PRIMARY] GO COMMIT DBCC FLUSHPROCINDB(8); GO DBCC DROPCLEANBUFFERS; GO --1、查询所有客户的总订单数量 SELECT C.CustomerID,COALESCE(SUM(OrderQty),0) AS Quantity FROM Sales.Customer C LEFTJOIN ( Sales.SalesOrderHeader O JOIN Sales.SalesOrderDetail D ON O.SalesOrderID=D.SalesOrderID ) ON C.CustomerID=O.CustomerID GROUPBY C.CustomerID GO --2、替代的查询方式 DECLARE@CustomerInfotable ( CustomerID intnotnull, Quantity smallintnotnull ) INSERTINTO@CustomerInfo SELECT CustomerID,0 FROM Sales.Customer; WITH SumQuantity AS ( SELECT O.CustomerID,COALESCE(SUM(OrderQty),0) AS Quantity FROM Sales.SalesOrderHeader O JOIN Sales.SalesOrderDetail D ON O.SalesOrderID=D.SalesOrderID GROUPBY O.CustomerID ) UPDATE@CustomerInfo SET C.Quantity=S.Quantity FROM@CustomerInfo C JOIN SumQuantity S ON S.CustomerID=C.CustomerID; SELECT*FROM@CustomerInfo; GO --========================================================================================== --控制表之间的联结 --========================================================================================== USE Northwind; GO SELECTDISTINCT C.CompanyName AS customer, S.CompanyName AS supplier FROM dbo.Customers AS C LEFTOUTERJOIN dbo.Orders AS O ON O.CustomerID = C.CustomerID LEFTOUTERJOIN dbo.[Order Details]AS OD ON OD.OrderID = O.OrderID LEFTOUTERJOIN dbo.Products AS P ON P.ProductID = OD.ProductID LEFTOUTERJOIN dbo.Suppliers AS S ON S.SupplierID = P.SupplierID --OPTION(FORCE ORDER); --上面的查询过多的使用外联结,可使用下面的方式替换 SELECTDISTINCT C.CompanyName AS customer, S.CompanyName AS supplier FROM dbo.Orders AS O JOIN dbo.[Order Details]AS OD ON OD.OrderID = O.OrderID JOIN dbo.Products AS P ON P.ProductID = OD.ProductID JOIN dbo.Suppliers AS S ON S.SupplierID = P.SupplierID RIGHTOUTERJOIN dbo.Customers AS C ON O.CustomerID = C.CustomerID; SELECTDISTINCT C.CompanyName AS customer, S.CompanyName AS supplier FROM dbo.Customers AS C LEFTOUTERJOIN (dbo.Orders AS O JOIN dbo.[Order Details]AS OD ON OD.OrderID = O.OrderID JOIN dbo.Products AS P ON P.ProductID = OD.ProductID JOIN dbo.Suppliers AS S ON S.SupplierID = P.SupplierID) ON O.CustomerID = C.CustomerID; --============================================================ --存储过程相关 --============================================================ USE Northwind; GO --*********************************************************** --1、计划缓存重用造成的无效查询计划 --造成上述问题的原因是因为SQLServer为了节省编译的开销会优先考虑使用缓存的计划 --2000下只能使用过程级重新编译,2005下提供了语句级重新编译的特性。从而避免了编译 --整个过程的开销 IFOBJECT_ID(N'dbo.usp_GetOrders',N'P') ISNOTNULL DROPPROC dbo.usp_GetOrders; GO CREATEPROC dbo.usp_GetOrders @odateASDATETIME AS SELECT c.CustomerID, c.CompanyName, o.EmployeeID, o.OrderID, o.OrderDate FROM dbo.Orders o join dbo.Customers c on o.CustomerID = c.CustomerID WHERE OrderDate >=@odate; --OPTION(RECOMPILE); GO --选择度高的测试 EXEC dbo.usp_GetOrders '19980506'; --选择度低的测试 EXEC dbo.usp_GetOrders '19960101'; --查看缓存计划 SELECT* FROM sys.syscacheobjects WHERE sql NOTLIKE'%cache%' AND sql LIKE'%usp_GetOrders%'; --清除缓存计划 DBCC FREEPROCCACHE; GO --*********************************************************** --2、参数嗅探测 --以当前日期插入一条记录,高选择度查询 INSERTINTO dbo.Orders(OrderDate, CustomerID, EmployeeID) VALUES(GETDATE(), N'ALFKI', 1); GO ALTERPROC dbo.usp_GetOrders @dASINT=0 AS DECLARE@odateASDATETIME; SET@odate=DATEADD(day, -@d, CONVERT(VARCHAR(8), GETDATE(), 112)); SELECT OrderID, CustomerID, EmployeeID, OrderDate FROM dbo.Orders WHERE OrderDate >=@odate; GO --查看表dbo.Orders空间情况 SELECT*FROM sys.dm_db_index_physical_stats(DB_ID(N'Northwind'), OBJECT_ID(N'dbo.Orders'), NULL, NULL , 'DETAILED'); --此时虽然选择度高,但是因为在过程编译时优化器无法根据参数进行最优化 EXEC dbo.usp_GetOrders; --使用聚集索引代替非聚集索引进行查询的参数嗅探测试 USE AdventureWorks; GO IFOBJECT_ID(N'dbo.GetCutomerOrderByDate',N'P') ISNOTNULL DROPPROC dbo.GetCutomerOrderByDate; GO CREATEPROCEDURE dbo.GetCutomerOrderByDate @startdatetime AS BEGIN SET NOCOUNT ON; DECLARE@minint, @maxint SELECT@min=MIN(SalesOrderID),@max=MAX(SalesOrderID) FROM Sales.SalesOrderHeader WHERE OrderDate>@start SELECT SalesOrderID,OrderDate,Status,CustomerID,TaxAmt,TotalDue FROM Sales.SalesOrderHeader WHERE SalesOrderID BETWEEN@minAND@max END GO DBCC FREEPROCCACHE; GO --低选择度测试 EXEC dbo.GetCutomerOrderByDate '2004-5-01' --高选择度测试 EXEC dbo.GetCutomerOrderByDate '2004-7-26' --缓存计划 SELECT* FROM sys.syscacheobjects WHERE sql NOTLIKE'%cache%' AND sql LIKE'%GetCutomerOrderByDate%'; --*********************************************************** --3、Execute/sp_executesql USE Northwind; GO DBCC FREEPROCCACHE; GO -- DECLARE@iASINT; DECLARE@sqlASVARCHAR(52); SET@i=10248; WHILE@i<10252 BEGIN SET@sql='SELECT * FROM dbo.Orders WHERE OrderID = ' +CAST(@iASVARCHAR(10)) + N';'; EXEC(@sql); SET@i=@i+1 END --查看缓存计划,可以看到除了参数化计划外还为每个不同的值保存一个缓存计划 SELECT cacheobjtype, objtype, usecounts, sql FROM sys.syscacheobjects WHERE sql NOTLIKE'%cache%' AND sql NOTLIKE'%sys.%'; --存储过程中调用同样存在上面的问题,为每个参数分配一个缓存计划 IFOBJECT_ID(N'dbo.GetOrderByID',N'P') ISNOTNULL DROPPROC dbo.GetOrderByID; GO CREATEPROCEDURE dbo.GetOrderByID @iASINT AS BEGIN SET NOCOUNT ON; DECLARE@sqlASVARCHAR(52); SET@sql='SELECT * FROM dbo.Orders WHERE OrderID = ' +CAST(@iASVARCHAR(10)) + N';'; EXEC(@sql); END GO DECLARE@iASINT; SET@i=10248; WHILE@i<10252 BEGIN EXEC dbo.GetOrderByID @i SET@i=@i+1 END GO ALTERPROCEDURE dbo.GetOrderByID @iASINT AS BEGIN SET NOCOUNT ON; DECLARE@sqlASNVARCHAR(52); SET@sql= N'SELECT * FROM dbo.Orders WHERE OrderID = @ID' EXEC sp_executesql @stmt=@sql, @params=N'@ID as int', @ID=@i END GO DBCC FREEPROCCACHE; GO DECLARE@iASINT; SET@i=10248; WHILE@i<10252 BEGIN EXEC dbo.GetOrderByID @i SET@i=@i+1 END --动态语句的选择度测试 IFOBJECT_ID(N'dbo.usp_GetOrders',N'P') ISNOTNULL DROPPROC dbo.usp_GetOrders; GO CREATEPROC dbo.usp_GetOrders @odateASDATETIME AS SET NOCOUNT ON; DECLARE@sqlASNVARCHAR(100); SET@sql=N'SELECT OrderID, CustomerID, EmployeeID, OrderDate FROM dbo.Orders WHERE OrderDate >= @odate OPTION(RECOMPILE);' EXEC sp_executesql @stmt=@sql, @params=N'@odate as datetime', @odate=@odate GO SELECT OrderID, CustomerID, EmployeeID, OrderDate FROM dbo.Orders WHERE OrderDate >='19980506'option(recompile) --选择度高的测试 EXEC dbo.usp_GetOrders '19980506'; --选择度低的测试 EXEC dbo.usp_GetOrders '19960101'; --查看缓存计划 SELECT* FROM sys.syscacheobjects WHERE sql NOTLIKE'%cache%' AND sql LIKE'%usp_GetOrders%'; --清除缓存计划 DBCC FREEPROCCACHE; GO --动态语句EXEC IFOBJECT_ID(N'dbo.usp_GetOrders',N'P') ISNOTNULL DROPPROC dbo.usp_GetOrders; GO CREATEPROC dbo.usp_GetOrders @odateASvarchar(10) AS SET NOCOUNT ON; DECLARE@sqlASVARCHAR(120); SET@sql='SELECT OrderID, CustomerID, EmployeeID, OrderDate FROM dbo.Orders WHERE OrderDate >='''+@odate+''';' print@sql EXEC(@sql) --OPTION(RECOMPILE); GO SELECT OrderID, CustomerID, EmployeeID, OrderDate FROM dbo.Orders WHERE OrderDate >='19980506' selectconvert(datetime,'19980506') --选择度高的测试 EXEC dbo.usp_GetOrders '19980506'; --选择度低的测试 EXEC dbo.usp_GetOrders '19960101'; --查看缓存计划 SELECT* FROM sys.syscacheobjects WHERE sql NOTLIKE'%cache%' AND sql LIKE'%usp_GetOrders%'; --清除缓存计划 DBCC FREEPROCCACHE; GO --============================================================ --日志相关 --============================================================ DBCC LOGINFO; GO DBCC SQLPERF(LOGSPACE); GO --2005 WHILE1=1 BEGIN UPDATETOP(5000) dbo.A SET CustomerID = N'ABCDE' WHERE CustomerID = N'OLDWO'; IF@@rowcount<5000BREAK; END GO --2000 SETROWCOUNT5000; WHILE1=1 BEGIN DELETEFROM dbo.LargeOrders WHERE OrderDate <'19970101'; IF@@rowcount<5000BREAK; END SETROWCOUNT0;