Index Seek 和Index Scan的性能差别

时间:2021-01-15 02:46:57

最近碰到几个年轻的程序员,惊讶的发现,很多人对数据库的认识只停留在对表的select,update,insert和delete,连最最基本的聚集索引和非聚集索引都搞不清!

所以今天通过一个案例先来讲讲非聚集索引的seek和scan的性能差别。

1.问题描述

A服务器在工作时间段整体CPU较基线偏高,且有越来越高的趋势,虽然暂时在可接受范围内,但本着防患于未然的原则,应该引起重视。

Index Seek 和Index Scan的性能差别

2.发现问题

首先我们要找出开销较大的sql语句,看看是否有空间优化。

方法很多种,最简单的是通过DMV的sys.dm_exec_query_stats。笔者这里提供了一段脚本,供大家参考。

SELECT TOP 20
[DatabaseName] = DB_NAME(qt.dbid)
,[Individual Query] = SUBSTRING (qt.text,(qs.statement_start_offset/2) + 1,
((CASE WHEN qs.statement_end_offset = -1
    THEN LEN(CONVERT(NVARCHAR(MAX), qt.text)) * 2
    ELSE qs.statement_end_offset
    END - qs.statement_start_offset)/2) + 1)
,[Parent Query] = qt.text 
, qp.query_plan
, qs.plan_generation_num
, qs.creation_time
, qs.last_execution_time
,[Total Elapsed Duration (s)] = CAST(qs.total_elapsed_time / 1000000.0 AS DECIMAL(28, 2))
--,qs.total_elapsed_time
,[Execution count] = qs.execution_count
,[Average Duration (s)] = CAST(qs.total_elapsed_time / 1000000.0 / qs.execution_count AS DECIMAL(28, 2))
,[Total CPU time (s)] = CAST((qs.total_worker_time) / 1000000.0 AS DECIMAL(28,2))
,[CPU time average (s)] =  CAST((qs.total_worker_time) / 1000000.0 / qs.execution_count AS DECIMAL(28, 2))
,[% CPU] = CAST(qs.total_worker_time * 100.0 / qs.total_elapsed_time AS DECIMAL(28, 2))
,[% Waiting] = CAST((qs.total_elapsed_time - qs.total_worker_time)* 100.0 / qs.total_elapsed_time AS DECIMAL(28, 2))
,[Total time blocked (s)] = CAST((qs.total_elapsed_time - qs.total_worker_time) / 1000000.0 AS DECIMAL(28,2))
,[Blocking average (s)] = CAST((qs.total_elapsed_time - qs.total_worker_time) / 1000000.0 / qs.execution_count AS DECIMAL(28, 2))
,[Total IO] = (qs.total_logical_reads + qs.total_logical_writes)
,[Average IO] = (qs.total_logical_reads + qs.total_logical_writes) / qs.execution_count
FROM sys.dm_exec_query_stats qs
CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) qt
CROSS APPLY sys.dm_exec_query_plan(qs.plan_handle) qp
WHERE qs.total_elapsed_time<> 0
ORDER BY [Total CPU time (s)] DESC

以下是找出的cpu开销很大的语句。

select sn,CallID,SensitiveString,RPT_Type from dbo.User_PWd where callid=N'00001073sdfadfas0218905'

该语句有明显的索引缺失问题,单次运行成本不是最优

 

3.分析问题

该语句比较简单,表User_PWd没有合适的索引,导致tablescan

逻辑读消耗很高:

    Table 'User_PWd'. Scan count 1, logical reads 2613, physical reads 22, read-ahead reads 2613, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

Index Seek 和Index Scan的性能差别

4.解决问题

对User_PWd增加索引:

CREATE INDEX idx_CallID ON [dbo].[User_PWd] ([CallID]) INCLUDE ([SN],[SensitiveString],[RPT_Type]) with (online = on)

 

Index Seek 和Index Scan的性能差别

逻辑读只有3

Table 'User_PWd'. Scan count 1, logical reads 3, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

cpu成本由0.34减小到0.00328。

之后观察CTI服务器的CPU,已经基本在到基线水平以下。

Index Seek 和Index Scan的性能差别