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
用对方法才有效率,做对事情才有效果
“麻烦”是自己“处理”不当的结果
“困难”是自己“学习”不够的反射

“挫折”是自己“努力”不足的代价

原文地址:https://www.cnblogs.com/ly7454/p/3437122.html