sql 2008 查询性能优化笔记
索引:
set statistics io on select p.productID,p.name,p.Weight,p.StandardCost from production.product p where p.productID = 738 create index testPro_In on production.product(StandardCost) /*大量的行返回使得非聚集索引,要用覆盖索引 不需要增加索引中列的数量或索引键的大小,因为所包含的列只保存在索引的叶子级别上。 include最好在以下情况使用: 1.不希望增加索引键的大小,但是仍然希望有一个覆盖索引 2.你打算索引一种不能被索引的数据类型(除了文本、ntext和图像) 3.你已经超过了一个索引的关键字列的最大数量 */ SELECT TOP 1000 [CardNumber] ,[ExpMonth] ,[ExpYear] FROM [AdventureWorks].[Sales].[CreditCard] cc where cc.[ExpMonth] between 6 and 9 and cc.[ExpYear] = 2008 order by cc.[ExpMonth] drop index [Sales].[CreditCard].ix_test --如果无include([CardNumber]),则逻辑读取 189次,有则逻辑读取 21 次 --注意创建索引的列的顺序先[ExpMonth],后[ExpYear] create nonclustered index ix_test on [AdventureWorks].[Sales].[CreditCard]( [ExpMonth],[ExpYear] )include([CardNumber]) SELECT [CardNumber] ,[ExpMonth] ,[ExpYear] FROM [AdventureWorks].[Sales].[CreditCard] cc where cc.[CardNumber]='11111555599668'
/*覆盖索引*/ select PostalCode from Person.Address where StateProvinceID=42 --drop index Person.Address. create nonclustered index IX_Address_StateProvinceID on Person.Address(StateProvinceID asc) include(PostalCode) with(drop_existing=on) /* 索引交叉 可以再一个表上开发多个索引,然后使用一个连接算法来在两个子集中得到索引交叉 */ --没有使用列SalesPersonID上的非聚集索引 select * from Sales.SalesOrderHeader where SalesPersonID=276 and OrderDate between '4/1/2002' and '7/1/2002' --OrderDate上的非聚集索引 create nonclustered index ix_testsa on Sales.SalesOrderHeader(OrderDate) --在创建OrderDate上的非聚集索引后,sql使用2个非聚集索引进行索引查找(非扫描) /* 过滤索引 */ --表 'SalesOrderHeader'。扫描计数 1,逻辑读取 686 次,物理读取 0 次 select OrderDate,ShipDate from Sales.SalesOrderHeader where PurchaseOrderNumber like 'PO5%' and SalesPersonID is not null --表 'SalesOrderHeader'。扫描计数 1,逻辑读取 5 次,物理读取 0 次, --create nonclustered index ix_test on Sales.SalesOrderHeader(PurchaseOrderNumber,SalesPersonID) --include(OrderDate,ShipDate) drop index Sales.SalesOrderHeader.ix_test create nonclustered index ix_test on Sales.SalesOrderHeader(PurchaseOrderNumber,SalesPersonID) include(OrderDate,ShipDate) where PurchaseOrderNumber is not null and SalesPersonID is not null dbcc show_statistics('[Sales].[SalesOrderDetail]',IX_SalesOrderDetail_ProductID2)
--drop index [Sales].[SalesOrderDetail].[IX_SalesOrderDetail_ProductID2] CREATE NONCLUSTERED INDEX [IX_SalesOrderDetail_ProductID2] ON [Sales].[SalesOrderDetail] ([ProductID]) include([UnitPrice],[ModifiedDate]) set statistics io on select p.PurchaseOrderID,p.VendorID,p.OrderDate from Purchasing.PurchaseOrderHeader p where p.VendorID = 85 and p.OrderDate < '12/5/2003' create nonclustered index ix_test on Purchasing.PurchaseOrderHeader (OrderDate) --开启更新统计 alter database AdventureWorks set auto_update_statistics on /* 密度=1/列中不同值的数量 密度越低,越适合非聚集索引 select 1.0/count(distinct c1) from t1 */ select 1.0/count(distinct VendorID) from Purchasing.PurchaseOrderHeader select * from sys.dm_db_index_physical_stats(db_id('CI-DNP02'),object_id('t1'),null,null,'sampled') /* avg_fragmentation_in_percent--表示索引和堆的逻辑平均碎片百分比,如是堆表且模式为samplyed, 则该值为NULL。如大于40%,可能要重建索引。 fragment_count--表示碎片的数量,或者组成索引和堆的单独页面组数。 page_count--组成统计的索引或数据页面数量的计数。 */ select * from sys.dm_exec_cached_plans
/* EXEC sp_executesql的运用: 此示例存储过程将动态生成并执行 INSERT 语句,以便向正确的表中插入新订单。 此示例使用订货日期生成应包含数据的表的名称,然后将此名称并入 INSERT 语句中 在该过程中使用 sp_executesql 比使用 EXECUTE 执行字符串更有效。 使用 sp_executesql 时,只生成 12 个版本的 INSERT 字符串,每个月的表对应 1 个字符串。 使用 EXECUTE 时,因为参数值不同,每个 INSERT 字符串均是唯一的。 尽管两种方法生成的批处理数相同,但由于 sp_executesql 生成的 INSERT 字符串类似, 因此,查询优化器更有可能重复使用执行计划。 */ CREATE PROCEDURE InsertSales @PrmOrderID INT, @PrmCustomerID INT, @PrmOrderDate DATETIME, @PrmDeliveryDate DATETIME AS DECLARE @InsertString NVARCHAR(500) DECLARE @OrderMonth INT -- Build the INSERT statement. SET @InsertString = 'INSERT INTO ' + /* Build the name of the table. */ SUBSTRING( DATENAME(mm, @PrmOrderDate), 1, 3) + CAST(DATEPART(yy, @PrmOrderDate) AS CHAR(4) ) + 'Sales' + /* Build a VALUES clause. */ ' VALUES (@InsOrderID, @InsCustID, @InsOrdDate,' + ' @InsOrdMonth, @InsDelDate)' /* Set the value to use for the order month because functions are not allowed in the sp_executesql parameter list. */ SET @OrderMonth = DATEPART(mm, @PrmOrderDate) EXEC sp_executesql @InsertString, N'@InsOrderID INT, @InsCustID INT, @InsOrdDate DATETIME, @InsOrdMonth INT, @InsDelDate DATETIME', @PrmOrderID, @PrmCustomerID, @PrmOrderDate, @OrderMonth, @PrmDeliveryDate GO DECLARE @SQLString nvarchar(500); DECLARE @ParmDefinition nvarchar(500); DECLARE @SalesOrderNumber nvarchar(25); DECLARE @IntVariable int; SET @SQLString = N'SELECT @SalesOrderOUT = MAX(SalesOrderNumber) FROM Sales.SalesOrderHeader WHERE CustomerID = @CustomerID'; SET @ParmDefinition = N'@CustomerID int, @SalesOrderOUT nvarchar(25) OUTPUT'; SET @IntVariable = 22276; EXECUTE sp_executesql @SQLString ,@ParmDefinition ,@CustomerID = @IntVariable ,@SalesOrderOUT = @SalesOrderNumber OUTPUT; -- This SELECT statement returns the value of the OUTPUT parameter. SELECT @SalesOrderNumber; -- This SELECT statement uses the value of the OUTPUT parameter in -- the WHERE clause. SELECT OrderDate, TotalDue FROM Sales.SalesOrderHeader WHERE SalesOrderNumber = @SalesOrderNumber;
/* 引起pro重新编译的情况: 1.由于常规表引起的重新编译 2.由于局部临时表引起的重新编译 */ IF(SELECT OBJECT_ID('dbo.p1')) IS NOT NULL DROP PROC dbo.p1; GO CREATE PROC dbo.p1 AS CREATE TABLE dbo.p1_t1(c1 INT); --Ensure table doesn't exist SELECT * FROM dbo.p1_t1; --Causes recompilation,因为该pro中的表以前不存在,则 --计划不会包含引用该表的select语句的处理策略,因此为了执行select语句,该pro必须重新编译 DROP TABLE dbo.p1_t1; GO EXEC dbo.p1; --First execution EXEC dbo.p1; --Second execution --2 IF(SELECT OBJECT_ID('dbo.p1')) IS NOT NULL DROP PROC dbo.p1; GO CREATE PROC dbo.p1 AS CREATE TABLE #p1_t1(c1 INT); --指定本地临时表 SELECT * FROM #p1_t1; --第一次执行时导致重新编译 DROP TABLE #p1_t1;--可选的,因为局部临时表在pro执行结束时自动卸载,这里是为良好的习惯卸载临时表 GO EXEC dbo.p1; --First execution EXEC dbo.p1; --Second execution,注意,第二次执行时,没有被重新编译 /* 避免引起pro重新编译的情况: 1.不要交替使用ddl和dml语句 2.统计变化引起的 3.使用表变量,表变量特点:1.没有事物日志开销,2.没有锁开销,3.没有回滚开销,4.因为没统计,所以数据量不能大。 4.避免在pro中修改set选项 */ IF (SELECT OBJECT_ID('dbo.spTempTable') ) IS NOT NULL DROP PROC dbo.spTempTable GO CREATE PROC dbo.spTempTable AS CREATE TABLE #MyTempTable (ID INT, Dsc NVARCHAR(50)) INSERT INTO #MyTempTable ( ID, Dsc ) SELECT ProductModelId, [Name] FROM Production.ProductModel AS pm; --Needs 1st recompilation SELECT * FROM #MyTempTable AS mtt; --Needs 2nd recompile CREATE CLUSTERED INDEX iTest ON #MyTempTable (ID); SELECT * FROM #MyTempTable AS mtt; --Needs 3rd recompilation CREATE TABLE #t2 (c1 INT); SELECT * FROM #t2; --Needs 4th recompilation GO EXEC spTempTable --First execution,一次执行该pro,被编译4次
/* 1.如果可以,就用between替换in/or,可进一步使用>=,<=的组合代替between 2.使用like替换substring */ SELECT d.Name FROM HumanResources.Department AS d WHERE SUBSTRING(d.[Name], 1, 1) = 'F' SELECT d.Name FROM HumanResources.Department AS d WHERE d.[Name] LIKE 'F%' --查找月份的方案: SELECT soh.SalesOrderID ,soh.OrderDate FROM Sales.SalesOrderHeader AS soh JOIN Sales.SalesOrderDetail AS sod ON soh.SalesOrderID = sod.SalesOrderID --WHERE soh.OrderDate >= '2002-04-01' AND soh.OrderDate < '2002-05-01' --good action WHERE DATEPART(yy, soh.OrderDate) = 2002 AND DATEPART(mm, soh.OrderDate) = 4--bad action SELECT p.FirstName FROM Person.Person AS p WHERE p.FirstName < 'B' OR p.Firstname >= 'C'; SELECT p.MiddleName FROM Person.Person AS p WHERE p.MiddleName < 'B' OR p.MiddleName >= 'C' OR p.MiddleName IS NULL; CREATE INDEX IX_Test1 ON Person.Person (MiddleName); CREATE INDEX IX_test2 ON Person.Person (FirstName); DROP INDEX person.Person.ix_test2 DROP INDEX Person.Person.IX_Test1 /* 使用exists代替count(*)验证数据存在 */ DECLARE @n INT SELECT @n = COUNT(*) FROM Sales.SalesOrderDetail AS sod WHERE sod.OrderQty = 1 IF @n > 0 PRINT 'Record Exists' IF EXISTS ( SELECT sod.* FROM Sales.SalesOrderDetail AS sod WHERE sod.OrderQty = 1 ) PRINT 'Record Exists' /* union 从最终的结果集删除重复并且在每个查询上有效的运行distinct,如允许有重复,则用union all 代替 */ /* 执行批或存储过程时,在批或存储过程中的每个查询执行之后,服务器报告所影响的行数。 可如下解决: set nocount on <sql queries> set nocount off */
锁:
begin tran delete from dbo.DatabaseLog where DatabaseLogID = 20 select * from sys.dm_tran_locks rollback select object_name(N'72057594038321152') /* 下面的select语句获取的(s)锁不会保存到事务结束。在默认隔离级别read_committed 之下, 数据被select语句读出后(s)锁立即被释放。 */ begin tran select * from Production.Product where productID = 1 --其他查询 commit /* 更新(u)模式,update:先(u)锁,后(x)锁。update执行中,只可(select)读,(u)锁阻止其他的update insert,delete 执行开始就获取(x)锁。insert,delete执行中,所有的action都被阻止。 */ BEGIN TRAN --1. Read data to be modified using (S)lock instead of (U)lock. -- Retain the (S)lock using REPEATABLEREAD locking hint,使用REPEATABLEREAD锁提示保持(s)锁 -- since the original (U)lock is retained until the conversion -- to (X)lock.此后,原来的(u)锁被保持到转换为(x)锁 SELECT * FROM t1 WITH (REPEATABLEREAD) WHERE c1 = 1; --Allow another equivalent update action to start concurrently --允许另一个等价的更新操作并发开始 WAITFOR DELAY '00:00:10'; --2. Modify the data by acquiring(获取) (X)lock UPDATE t1 WITH (XLOCK) SET c2 = GETDATE() WHERE c1 = 1; COMMIT /* 当一个select语句需要在获取数据后锁住该行数据,防被修改,可使用如下: */ begin tran select * from Production.Product where productID = 1 with(updlock)--(u)锁,也可with(xlock) --其他查询 commit
用对方法才有效率,做对事情才有效果
“麻烦”是自己“处理”不当的结果
“困难”是自己“学习”不够的反射
“挫折”是自己“努力”不足的代价
“麻烦”是自己“处理”不当的结果
“困难”是自己“学习”不够的反射
“挫折”是自己“努力”不足的代价