怀疑一切,但不否定一切

posts(21) comments(78) trackbacks(4)
  • 博客园
  • 联系
  • 订阅 订阅
  • 管理

与我联系

  • 发短消息

搜索

 

常用链接

  • 我的参与
  • 我的新闻
  • 最新评论
  • 我的标签

留言簿

  • 给我留言
  • 查看公开留言
  • 查看私人留言

随笔分类

  •  iBatis(1)
  •  Notification Service(1)
  •  SQL Server 性能优化(12)
  •  SQLServer2005小结(2)

随笔档案

  • 2008年8月 (2)
  • 2008年7月 (1)
  • 2008年6月 (5)
  • 2008年5月 (3)
  • 2008年4月 (4)
  • 2008年3月 (3)
  • 2007年11月 (1)
  • 2007年4月 (1)

最新评论

  • 1. re: 使用ad hoc的利弊
  • @Cheney Shue 多谢,那我不太明白sys.dm_exec_cached_plans里关于ad hoc的说明到是什么意思了?如果在.NET程序中去调用那些SQL语句,所得到的结果和在SSMS中...
  • --凉面
  • 2. re: 使用ad hoc的利弊
  • ad-hoc确实不是这样的。下面是adhoc的定义,从wiki上复制过来的:Ad hoc querying is a term in information science.Many applicat...
  • --Cheney Shue
  • 3. re: 使用ad hoc的利弊
  • 即席查询是能够进行参数嗅探的,而一般的卸载程序内的sql语句也不会变动大小写和空格啥的,都能被数据库嗅探到,而成为一个编译计划的。 最近发现一个写sql语句不太爽的地方就是不方便管理,在数据库内可以...
  • --PerfectDesign
  • 4. re: 使用ad hoc的利弊
  • 从SSMS命令窗口发送的SQL语句就是ad hoc?你用过ad hoc工具吗?
    文不对题,明明说的是使用参数绑定避免硬解析,跟ad hoc没关系。
  • --唐开礼
  • 5. re: 磁盘I/O的性能评估方法
  • 请教下I/O问题
    MSN: jxtu@msn.com
  • --_chill

阅读排行榜

  • 1. 数据库分页操作(2206)
  • 2. 为什么尽量避免使用触发器?(2168)
  • 3. 生成器工作内幕分析(1811)
  • 4. 为SQLSERVER打好地基-硬盘碎片和索引碎片(1720)
  • 5. 结果集大小如何影响并发性(1634)

评论排行榜

  • 1. 为什么尽量避免使用触发器?(17)
  • 2. 为SQLSERVER打好地基-硬盘碎片和索引碎片(16)
  • 3. 结果集大小如何影响并发性(13)
  • 4. 数据库分页操作(10)
  • 5. 如何编写高效的存储过程(8)

View Post

如何有效利用索引编写高效过程

USE AdventureWorks;
GO
--主键只用来保证数据,而聚集索引影响数据的逻辑排序
--
下面的语句执行计划中并没有看到排序操作,证明数据是按SalesOrderID排过序的,而且是一种双向链表
SELECT TOP(10) * FROM Sales.SalesOrderHeader ORDER BY SalesOrderID
SELECT TOP(10) * FROM Sales.SalesOrderHeader ORDER BY SalesOrderID DESC

SELECT TOP(10) SalesOrderID FROM Sales.SalesOrderHeader ORDER BY SalesOrderID
SELECT TOP(10) SalesOrderID FROM Sales.SalesOrderHeader

--======================================================================
--
查看表内数据是如何存储的
--
======================================================================
SELECT object_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
IF OBJECT_ID(N'bigrows',N'U') IS NOT NULL
    
DROP TABLE dbo.bigrows
GO
CREATE TABLE dbo.bigrows
(
    a 
char(3000),
    b 
char(3000),
    c 
char(2000),
    d 
char(60)
)
GO
INSERT INTO dbo.bigrows
    
SELECT REPLICATE('a',3000),REPLICATE('b',3000),
            
REPLICATE('c',2000),REPLICATE('d',6)
--2005使用行溢出数据突破了変长列的限制,但这样会增加I/O操作。
IF OBJECT_ID(N'bigrows',N'U') IS NOT NULL
    
DROP TABLE dbo.bigrows
GO
CREATE TABLE dbo.bigrows
(
    a 
varchar(3000),
    b 
varchar(3000),
    c 
varchar(3000),
    d 
varchar(3000)
)
GO
INSERT INTO dbo.bigrows
    
SELECT REPLICATE('a',3000),REPLICATE('b',3000),
            
REPLICATE('c',3000),REPLICATE('d',3000)
SELECT object_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
WHERE object_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');
--索引的深度
SELECT INDEXPROPERTY(object_ID('Sales.SalesOrderHeader'),N'PK_SalesOrderHeader_SalesOrderID','IndexDepth')
SELECT INDEXPROPERTY(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'

--======================================================================
--
复合索引测试
--
======================================================================
BEGIN TRANSACTION
GO
DROP INDEX IX_SalesOrderHeader_CustomerID ON Sales.SalesOrderHeader
GO
CREATE NONCLUSTERED INDEX 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

UPDATE STATISTICS Sales.SalesOrderHeader IX_SalesOrderHeader_CustomerID
--查看此索引的统计信息
DBCC SHOW_STATISTICS('Sales.SalesOrderHeader',IX_SalesOrderHeader_CustomerID)

SELECT COUNT(*) --EQ_ROWS等于上限值的个数
FROM Sales.SalesOrderHeader
WHERE CustomerID=1

SELECT COUNT(*) --RANGE_ROWS此范围内不包括边界的记录数
FROM Sales.SalesOrderHeader
WHERE CustomerID>1 AND CustomerID<19

SELECT  COUNT(DISTINCT CustomerID)--DISTINCT_RANGE_ROWS 此范围内不包括边界的不同记录数
FROM Sales.SalesOrderHeader
WHERE CustomerID>1 AND CustomerID<19

SELECT  1.0*COUNT(*)/COUNT(DISTINCT CustomerID)--AVG_RANGE_ROWS 数据分布程度
FROM Sales.SalesOrderHeader
WHERE CustomerID>1 AND 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页
SELECT QUOTENAME(name)
FROM sys.indexes
WHERE  object_id = 722101613 AND 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=1 and o.type='U'

SELECT object_name(id),name,statblob--统计信息二进制大型对象(BLOB),仅内部使用,返回NULL.
FROM sys.sysindexes

--SELECT * FROM sys.stats_columns
--
====================================================================================
--
选择度过低而无法有效利用索引查找而选择表扫描,查询2004-7-01之后的订单
--
====================================================================================
USE AdventureWorks;
GO
BEGIN TRANSACTION
GO
DROP INDEX IX_OrderDate ON Sales.SalesOrderHeader
GO
CREATE NONCLUSTERED INDEX 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 @min int,
        
@max int
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 @min AND @max
GO
--================================================
--
什么是查询参数规范SARGS
--
================================================
--
1、不要对字段做运算
SELECT * FROM Sales.SalesOrderHeader WHERE CustomerID+1=2
SELECT * FROM Sales.SalesOrderHeader WHERE CustomerID=1
--2、不要对字段使用函数(数学函数、日期函数、字符串函数等)
SELECT * FROM Sales.SalesOrderHeader WHERE ABS(SalesOrderID-44659)<1
SELECT * FROM Sales.SalesOrderHeader WHERE SalesOrderID<44660 AND SalesOrderID>44658
--3、不要使用负向查询(NOT、!=、<>、!>、!<、NOT EXISTS、NOT IN)
SELECT * FROM Sales.SalesOrderDetail WHERE OrderQty!=1
SELECT * FROM Sales.SalesOrderDetail WHERE OrderQty>1 OR OrderQty<1
--4、小心使用OR(虽然有聚集索引但是因Status无索引可用只能进行表扫描)
SELECT * FROM Sales.SalesOrderHeader WHERE SalesOrderID=43659 OR [Status]=1

--================================================================================
--
三种联结(嵌套循环、合并联结、哈稀联结),所占用的资源依次增加,SQL优先选用嵌套循环
--
================================================================================
--
1、嵌套循环(外表、内表),记录少的表作为外表,在内表中根据每个联结字段的值进行
--
循环,只有在外表数据量较少时使用
SELECT *
FROM Sales.SalesOrderDetail D JOIN Sales.SalesOrderHeader O
    
ON D.SalesOrderID = O.SalesOrderID
WHERE O.SalesOrderID BETWEEN 43659 AND 43665
--2、合并联结的算法前提是字段已排序
SELECT *
FROM Sales.SalesOrderDetail D JOIN Sales.SalesOrderHeader O
    
ON D.SalesOrderID = O.SalesOrderID
WHERE O.SalesOrderID BETWEEN 43659 AND 54000
--3、哈稀联结(在要联结的字段上无可用索引时使用此连接算法)
IF OBJECT_ID(N'SumPrice',N'U') IS NOT NULL
    
DROP TABLE SumPrice
GO
SELECT O.SalesOrderID,YEAR(O.OrderDate)+MONTH(O.OrderDate) AS Date,CAST(SUM(D.OrderQty*D.UnitPrice*(1-D.UnitPriceDiscount)) AS decimal(18,2)) AS Price
INTO SumPrice
FROM 
    Sales.SalesOrderDetail D 
        
JOIN 
    Sales.SalesOrderHeader O
        
ON D.SalesOrderID=O.SalesOrderID
GROUP BY O.SalesOrderID,O.OrderDate
GO
SELECT O.SalesOrderID,O.
FROM SumPrice D JOIN Sales.SalesOrderHeader O
    
ON D.SalesOrderID = O.SalesOrderID
WHERE O.SalesOrderID BETWEEN 43659 AND 54000

--==========================================================================================
--
代替LEFT JOIN的方法
--
为了防止在查询中出现LEFT操作,在设计数据库时应尽量避免关联表中不一致的情况出现
--
比如产品的类别和具体产品的对应关系中,可在类别加入一个无类别的记录以对应不知是何类别的产品
--
==========================================================================================
BEGIN TRANSACTION
GO
DROP INDEX IX_SalesOrderHeader_CustomerID ON Sales.SalesOrderHeader
GO
CREATE NONCLUSTERED INDEX 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 LEFT JOIN            
    (    Sales.SalesOrderHeader O 
        
JOIN Sales.SalesOrderDetail D ON O.SalesOrderID=D.SalesOrderID
    ) 
ON C.CustomerID=O.CustomerID
GROUP BY C.CustomerID
GO
--2、替代的查询方式
DECLARE @CustomerInfo table
(
    CustomerID    
int            not null,
    Quantity    
smallint    not null
)
INSERT INTO @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
    
GROUP BY 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
SELECT DISTINCT C.CompanyName AS customer, S.CompanyName AS supplier
FROM dbo.Customers AS C
  
LEFT OUTER JOIN dbo.Orders AS O
    
ON O.CustomerID = C.CustomerID
  
LEFT OUTER JOIN dbo.[Order Details] AS OD
    
ON OD.OrderID = O.OrderID
  
LEFT OUTER JOIN dbo.Products AS P
    
ON P.ProductID = OD.ProductID
  
LEFT OUTER JOIN dbo.Suppliers AS S
    
ON S.SupplierID = P.SupplierID
--OPTION(FORCE ORDER);
--
上面的查询过多的使用外联结,可使用下面的方式替换
SELECT DISTINCT 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
  
RIGHT OUTER JOIN dbo.Customers AS C
    
ON O.CustomerID = C.CustomerID;

SELECT DISTINCT C.CompanyName AS customer, S.CompanyName AS supplier
FROM dbo.Customers AS C
  
LEFT OUTER JOIN
    (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下提供了语句级重新编译的特性。从而避免了编译
--
整个过程的开销
IF OBJECT_ID(N'dbo.usp_GetOrders',N'P') IS NOT NULL
  
DROP PROC dbo.usp_GetOrders;
GO
CREATE PROC dbo.usp_GetOrders
  
@odate AS DATETIME
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 NOT LIKE '%cache%'
  
AND sql LIKE '%usp_GetOrders%';
--清除缓存计划
DBCC FREEPROCCACHE;
GO
--***********************************************************
--
2、参数嗅探测
--
以当前日期插入一条记录,高选择度查询
INSERT INTO dbo.Orders(OrderDate, CustomerID, EmployeeID) VALUES(GETDATE(), N'ALFKI', 1);
GO
ALTER PROC dbo.usp_GetOrders
  
@d AS INT = 0
AS
DECLARE