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

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

  • 相关阅读:
    跨表修改数据|查询修改数据|两表联合修改数据
    .net oracle 连接
    型材优化截取初步代码
    JQuery.getJSON 从aspx页面返回JSON数据 .
    asp.net form验证
    ASP.NET读取纯真IP数据库QQWry.Dat,得到地址名称
    防止PHP木马,应在PHP.INI里禁止的一些函数
    sqlserver2005中的数据库日志收缩
    jquery.pagination.js分页插件的运用
    ASP.NET前台代码绑定后台变量方法总结
  • 原文地址:https://www.cnblogs.com/ly7454/p/3437122.html
Copyright © 2020-2023  润新知