SQL Server 2008 Query Performance Tuning Distilled
Grant Fritchey and Sajal Dam
SQL Server 2008 Query Performance Tuning Distilled Copyright © 2009 by Grant Fritchey and Sajal Dam All rights reserved. No part of this work may be reproduced or transmitted in any form or by any means, electronic or mechanical, including photocopying, recording, or by any information storage or retrieval system, without the prior written permission of the copyright owner and the publisher. ISBN-13 (pbk): 978-1-4302-1902-6 ISBN-13 (electronic): 978-1-4302-1903-3 Printed and bound in the United States of America 9 8 7 6 5 4 3 2 1 Trademarked names may appear in this book. Rather than use a trademark symbol with every occurrence of a trademarked name, we use the names only in an editorial fashion and to the benefit of the trademark owner, with no intention of infringement of the trademark. Lead Editor: Jonathan Gennick Development Editor: Douglas Pundick Technical Reviewer: Joseph Sack Editorial Board: Clay Andres, Steve Anglin, Mark Beckner, Ewan Buckingham, Tony Campbell, Gary Cornell, Jonathan Gennick, Michelle Lowman, Matthew Moodie, Jeffrey Pepper, Frank Pohlmann, Ben Renow-Clarke, Dominic Shakeshaft, Matt Wade, Tom Welsh Project Manager: Richard Dal Porto Copy Editor: Kim Wimpsett Associate Production Director: Kari Brooks-Copony Production Editor: Kelly Winquist Compositor: Patrick Cunningham Proofreader: April Eddy Indexer: John Collin Artist: April Milne Cover Designer: Kurt Krames Manufacturing Director: Tom Debolski Distributed to the book trade worldwide by Springer-Verlag New York, Inc., 233 Spring Street, 6th Floor, New York, NY 10013. Phone 1-800-SPRINGER, fax 201-348-4505, e-mail kn`ano)ju
Contents at a Glance About the Author . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xix About the Technical Reviewer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xxi Acknowledgments . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xxiii Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xxv
CHAPTER 1
SQL Query Performance Tuning . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
CHAPTER 2
System Performance Analysis . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17
CHAPTER 3
SQL Query Performance Analysis . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61
CHAPTER 4
Index Analysis . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 101
CHAPTER 5
Database Engine Tuning Advisor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 151
CHAPTER 6
Bookmark Lookup Analysis . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 163
CHAPTER 7
Statistics Analysis. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 175
CHAPTER 8
Fragmentation Analysis . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 209
CHAPTER 9
Execution Plan Cache Analysis . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 241
CHAPTER 10
Stored Procedure Recompilation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 283
CHAPTER 11
Query Design Analysis . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 313
CHAPTER 12
Blocking Analysis . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 351
CHAPTER 13
Deadlock Analysis. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 401
CHAPTER 14
Cursor Cost Analysis . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 415
CHAPTER 15
Database Workload Optimization . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 439
CHAPTER 16
SQL Server Optimization Checklist . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 475
INDEX . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 497
iii
Contents About the Author . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xix About the Technical Reviewer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xxi Acknowledgments . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xxiii Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xxv
CHAPTER 1
SQL Query Performance Tuning . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1 The Performance-Tuning Process . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2 The Core Process . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2 Iterating the Process . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4 Performance vs. Price . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7 Performance Targets . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7 “Good Enough” Tuning . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7 Performance Baseline . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8 Where to Focus Efforts . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9 SQL Server Performance Killers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10 Poor Indexing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11 Inaccurate Statistics . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11 Excessive Blocking and Deadlocks . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11 Non-Set-Based Operations. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12 Poor Query Design . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12 Poor Database Design . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12 Excessive Fragmentation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13 Nonreusable Execution Plans . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13 Poor Execution Plans . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13 Frequent Recompilation of Execution Plans . . . . . . . . . . . . . . . . . . . . 14 Improper Use of Cursors . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14 Improper Configuration of the Database Log . . . . . . . . . . . . . . . . . . . 14 Excessive Use or Improper Configuration of tempdb . . . . . . . . . . . . . 14 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15
v
vi
NCO NTENT S
CHAPTER 2
System Performance Analysis . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17 Performance Monitor Tool . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17 Dynamic Management Views . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19 Hardware Resource Bottlenecks . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20 Identifying Bottlenecks . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20 Bottleneck Resolution . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21 Memory Bottleneck Analysis . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 21 SQL Server Memory Management . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22 Available Bytes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25 Pages/sec and Page Faults/sec Counters . . . . . . . . . . . . . . . . . . . . . . 25 Buffer Cache Hit Ratio . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26 Page Life Expectancy . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 26 Checkpoint Pages/sec . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27 Lazy writes/sec. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27 Memory Grants Pending . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27 Target Server Memory (KB) and Total Server Memory (KB) . . . . . . . 27 Memory Bottleneck Resolutions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27 Optimizing Application Workload. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29 Allocating More Memory to SQL Server . . . . . . . . . . . . . . . . . . . . . . . . 29 Increasing System Memory . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29 Changing from a 32-bit to a 64-bit Processor . . . . . . . . . . . . . . . . . . 29 Enabling 3GB of Process Space. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 30 Using Memory Beyond 4GB Within SQL Server . . . . . . . . . . . . . . . . . 30 Disk Bottleneck Analysis . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32 Disk Counters . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32 % Disk Time . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33 Current Disk Queue Length . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33 Disk Transfers/sec . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34 Disk Bytes/sec . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 34 Avg. Disk Sec/Read and Avg. Disk Sec/Write . . . . . . . . . . . . . . . . . . . 34 Disk Bottleneck Resolutions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35 Optimizing Application Workload. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35 Using a Faster Disk Drive . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35 Using a RAID Array . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 35 Using a SAN System . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 37 Aligning Disks Properly . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38 Using a Battery-Backed Controller Cache . . . . . . . . . . . . . . . . . . . . . . 38 Adding System Memory . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 38
NC O N T E N T S
Creating Multiple Files and Filegroups . . . . . . . . . . . . . . . . . . . . . . . . . 39 Placing the Table and Index on Separate Disks . . . . . . . . . . . . . . . . . 42 Saving Log Files to a Separate Physical Disk . . . . . . . . . . . . . . . . . . . 42 Partitioning Tables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43 Processor Bottleneck Analysis. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 43 % Processor Time . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44 % Privileged Time . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44 Processor Queue Length. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44 Context Switches/sec . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44 Batch Requests/sec . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45 SQL Compilations/sec . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45 SQL Recompilations/sec . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45 Processor Bottleneck Resolutions. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45 Optimizing Application Workload. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45 Eliminating Excessive Compiles/Recompiles . . . . . . . . . . . . . . . . . . . 46 Using More or Faster Processors . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46 Using a Large L2/L3 Cache . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 46 Running More Efficient Controllers/Drivers . . . . . . . . . . . . . . . . . . . . . 47 Not Running Unnecessary Software . . . . . . . . . . . . . . . . . . . . . . . . . . . 47 Network Bottleneck Analysis . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47 Bytes Total/sec . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48 % Net Utilization . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48 Network Bottleneck Resolutions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48 Optimizing Application Workload. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48 Adding Network Adapters . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49 Moderating and Avoiding Interruptions . . . . . . . . . . . . . . . . . . . . . . . . 49 SQL Server Overall Performance. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 49 Missing Indexes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 50 Database Blocking . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 51 Nonreusable Execution Plans . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52 General Behavior . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 52 Creating a Baseline . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53 Creating a Reusable List of Performance Counters . . . . . . . . . . . . . . 53 Creating a Counter Log Using the List of Performance Counters . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56 Minimizing Performance Monitor Overhead . . . . . . . . . . . . . . . . . . . . 57 System Behavior Analysis Against Baseline . . . . . . . . . . . . . . . . . . . . . . . . . 59 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 60
vii
viii
NCO NTENT S
CHAPTER 3
SQL Query Performance Analysis . . . . . . . . . . . . . . . . . . . . . . . . . . 61 The SQL Profiler Tool . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61 Profiler Traces . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62 Events . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63 Data Columns . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 66 Filters . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67 Trace Templates. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69 Trace Data . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69 Trace Automation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70 Capturing a Trace Using the GUI . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 70 Capturing a Trace Using Stored Procedures . . . . . . . . . . . . . . . . . . . . 71 Combining Trace and Performance Monitor Output . . . . . . . . . . . . . . . . . . 72 SQL Profiler Recommendations. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 73 Limiting the Number of Events and Data Columns . . . . . . . . . . . . . . 73 Discarding Start Events for Performance Analysis . . . . . . . . . . . . . . . 74 Limiting the Trace Output Size . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75 Avoiding Online Data Column Sorting. . . . . . . . . . . . . . . . . . . . . . . . . . 75 Running Profiler Remotely . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75 Limiting the Use of Certain Events . . . . . . . . . . . . . . . . . . . . . . . . . . . . 75 Query Performance Metrics Without Profiler. . . . . . . . . . . . . . . . . . . . . . . . . 76 Costly Queries . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 76 Identifying Costly Queries . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 77 Identifying Slow-Running Queries . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 82 Execution Plans . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83 Analyzing a Query Execution Plan . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 84 Identifying the Costly Steps in an Execution Plan . . . . . . . . . . . . . . . . 87 Analyzing Index Effectiveness . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87 Analyzing Join Effectiveness . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 89 Actual vs. Estimated Execution Plans . . . . . . . . . . . . . . . . . . . . . . . . . . 93 Plan Cache . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 95 Query Cost . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 95 Client Statistics . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 96 Execution Time . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 97 STATISTICS IO. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 98 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 100
CHAPTER 4
Index Analysis . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 101 What Is an Index? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 101 The Benefit of Indexes. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 103 Index Overhead. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 105
NC O N T E N T S
Index Design Recommendations. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 107 Examine the WHERE Clause and Join Criteria Columns . . . . . . . . . 107 Use Narrow Indexes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 109 Examine Column Uniqueness. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 111 Examine the Column Data Type . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 114 Consider Column Order . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 114 Consider the Type of Index . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 117 Clustered Indexes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 117 Heap Tables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 118 Relationship with Nonclustered Indexes . . . . . . . . . . . . . . . . . . . . . . 118 Clustered Index Recommendations . . . . . . . . . . . . . . . . . . . . . . . . . . 120 Nonclustered Indexes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 126 Nonclustered Index Maintenance . . . . . . . . . . . . . . . . . . . . . . . . . . . . 127 Defining the Bookmark Lookup . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 127 Nonclustered Index Recommendations . . . . . . . . . . . . . . . . . . . . . . . 127 Clustered vs. Nonclustered Indexes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 128 Benefits of a Clustered Index over a Nonclustered Index . . . . . . . . 129 Benefits of a Nonclustered Index over a Clustered Index . . . . . . . . 131 Advanced Indexing Techniques . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 132 Covering Indexes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 132 Index Intersections . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 135 Index Joins . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 137 Filtered Indexes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 138 Indexed Views . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 141 Index Compression . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 145 Special Index Types. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 147 Full-Text. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 147 Spatial . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 148 XML . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 148 Additional Characteristics of Indexes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 148 Different Column Sort Order. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 148 Index on Computed Columns . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 148 Index on BIT Data Type Columns . . . . . . . . . . . . . . . . . . . . . . . . . . . . 149 CREATE INDEX Statement Processed As a Query. . . . . . . . . . . . . . . 149 Parallel Index Creation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 149 Online Index Creation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 150 Considering the Database Engine Tuning Advisor . . . . . . . . . . . . . . 150 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 150
ix
x
NCO NTENT S
CHAPTER 5
Database Engine Tuning Advisor
. . . . . . . . . . . . . . . . . . . . . . . . . 151
Database Engine Tuning Advisor Mechanisms. . . . . . . . . . . . . . . . . . . . . . 151 Database Engine Tuning Advisor Examples . . . . . . . . . . . . . . . . . . . . . . . . 155 Tuning a Query . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 155 Tuning a Trace Workload . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 159 Database Engine Tuning Advisor Limitations . . . . . . . . . . . . . . . . . . . . . . . 161 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 162
CHAPTER 6
Bookmark Lookup Analysis . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 163 Purpose of Bookmark Lookups . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 163 Drawbacks of Bookmark Lookups . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 165 Analyzing the Cause of a Bookmark Lookup. . . . . . . . . . . . . . . . . . . . . . . . 166 Resolving Bookmark Lookups . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 169 Using a Clustered Index . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 169 Using a Covering Index . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 169 Using an Index Join . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 173 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 174
CHAPTER 7
Statistics Analysis . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 175 The Role of Statistics in Query Optimization . . . . . . . . . . . . . . . . . . . . . . . . 175 Statistics on an Indexed Column . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 176 Benefits of Updated Statistics . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 177 Drawbacks of Outdated Statistics . . . . . . . . . . . . . . . . . . . . . . . . . . . . 179 Statistics on a Nonindexed Column . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 180 Benefits of Statistics on a Nonindexed Column . . . . . . . . . . . . . . . . 181 Drawback of Missing Statistics on a Nonindexed Column . . . . . . . 185 Analyzing Statistics . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 187 Density . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 190 Statistics on a Multicolumn Index . . . . . . . . . . . . . . . . . . . . . . . . . . . . 191 Statistics on a Filtered Index . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 192 Statistics Maintenance . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 193 Automatic Maintenance . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 193 Manual Maintenance . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 195 Statistics Maintenance Status . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 198 Analyzing the Effectiveness of Statistics for a Query . . . . . . . . . . . . . . . . 199 Resolving a Missing Statistics Issue . . . . . . . . . . . . . . . . . . . . . . . . . . 199 Resolving an Outdated Statistics Issue . . . . . . . . . . . . . . . . . . . . . . . 202
NC O N T E N T S
Recommendations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 204 Backward Compatibility of Statistics . . . . . . . . . . . . . . . . . . . . . . . . . 204 Auto Create Statistics . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 204 Auto Update Statistics . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 205 Automatic Update Statistics Asynchronously . . . . . . . . . . . . . . . . . . 207 Amount of Sampling to Collect Statistics . . . . . . . . . . . . . . . . . . . . . . 208 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 208
CHAPTER 8
Fragmentation Analysis
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 209
Causes of Fragmentation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 209 Page Split by an UPDATE Statement . . . . . . . . . . . . . . . . . . . . . . . . . 212 Page Split by an INSERT Statement . . . . . . . . . . . . . . . . . . . . . . . . . . 215 Fragmentation Overhead . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 217 Analyzing the Amount of Fragmentation . . . . . . . . . . . . . . . . . . . . . . . . . . . 220 Analyzing the Fragmentation of a Small Table . . . . . . . . . . . . . . . . . 222 Fragmentation Resolutions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 224 Dropping and Re-creating the Index . . . . . . . . . . . . . . . . . . . . . . . . . . 225 Re-creating the Index with the DROP_EXISTING Clause . . . . . . . . . 225 Executing the ALTER INDEX REBUILD Statement . . . . . . . . . . . . . . . 226 Executing the ALTER INDEX REORGANIZE Statement . . . . . . . . . . . 228 Significance of the Fill Factor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 230 Automatic Maintenance . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 233 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 239
CHAPTER 9
Execution Plan Cache Analysis
. . . . . . . . . . . . . . . . . . . . . . . . . . . 241
Execution Plan Generation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 241 Parser . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 243 Algebrizer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 243 Optimization . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 244 Execution Plan Caching . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 251 Components of the Execution Plan . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 251 Query Plan . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 251 Execution Context . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 251 Aging of the Execution Plan . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 252 Analyzing the Execution Plan Cache. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 252 Execution Plan Reuse . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 253 Ad Hoc Workload . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 254 Prepared Workload . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 255 Plan Reusability of an Ad Hoc Workload . . . . . . . . . . . . . . . . . . . . . . 256 Plan Reusability of a Prepared Workload . . . . . . . . . . . . . . . . . . . . . . 264
xi
xii
NCO NTENT S
Query Plan Hash and Query Hash . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 274 Execution Plan Cache Recommendations . . . . . . . . . . . . . . . . . . . . . . . . . . 278 Explicitly Parameterize Variable Parts of a Query . . . . . . . . . . . . . . . 278 Create Stored Procedures to Implement Business Functionality . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 278 Code with sp_executesql to Avoid Stored Procedure Maintenance . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 278 Implement the Prepare/Execute Model to Avoid Resending a Query String . . . . . . . . . . . . . . . . . . . . . . . . . . . 279 Avoid Ad Hoc Queries . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 279 Prefer sp_executesql over EXECUTE for Dynamic Queries . . . . . . . 279 Parameterize Variable Parts of Queries with Care . . . . . . . . . . . . . . 280 Do Not Allow Implicit Resolution of Objects in Queries . . . . . . . . . . 280 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 281
CHAPTER 10
Stored Procedure Recompilation. . . . . . . . . . . . . . . . . . . . . . . . . . 283 Benefits and Drawbacks of Recompilation . . . . . . . . . . . . . . . . . . . . . . . . . 283 Identifying the Statement Causing Recompilation . . . . . . . . . . . . . . . . . . . 286 Analyzing Causes of Recompilation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 288 Schema or Bindings Changes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 289 Statistics Changes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 289 Deferred Object Resolution. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 292 SET Options Changes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 294 Execution Plan Aging . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 295 Explicit Call to sp_recompile . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 295 Explicit Use of the RECOMPILE Clause . . . . . . . . . . . . . . . . . . . . . . . . 296 Avoiding Recompilations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 298 Do Not Interleave DDL and DML Statements . . . . . . . . . . . . . . . . . . 298 Avoiding Recompilations Caused by Statistics Change . . . . . . . . . . 300 Using Table Variables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 302 Avoiding Changing SET Options Within a Stored Procedure . . . . . . 304 Using OPTIMIZE FOR Query Hint . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 305 Using Plan Guides . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 307 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 311
NC O N T E N T S
CHAPTER 11
Query Design Analysis . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 313 Query Design Recommendations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 313 Operating on Small Result Sets . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 314 Limit the Number of Columns in select_list . . . . . . . . . . . . . . . . . . . 314 Use Highly Selective WHERE Clauses . . . . . . . . . . . . . . . . . . . . . . . . . 315 Using Indexes Effectively . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 316 Avoid Nonsargable Search Conditions . . . . . . . . . . . . . . . . . . . . . . . . 316 Avoid Arithmetic Operators on the WHERE Clause Column . . . . . . 320 Avoid Functions on the WHERE Clause Column . . . . . . . . . . . . . . . . 322 Avoiding Optimizer Hints . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 324 JOIN Hint . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 325 INDEX Hints . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 327 Using Domain and Referential Integrity . . . . . . . . . . . . . . . . . . . . . . . . . . . . 329 NOT NULL Constraint . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 329 Declarative Referential Integrity . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 332 Avoiding Resource-Intensive Queries. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 334 Avoid Data Type Conversion. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 335 Use EXISTS over COUNT(*) to Verify Data Existence . . . . . . . . . . . . 337 Use UNION ALL Instead of UNION . . . . . . . . . . . . . . . . . . . . . . . . . . . . 338 Use Indexes for Aggregate and Sort Conditions . . . . . . . . . . . . . . . . 339 Avoid Local Variables in a Batch Query . . . . . . . . . . . . . . . . . . . . . . . 339 Be Careful Naming Stored Procedures . . . . . . . . . . . . . . . . . . . . . . . . 344 Reducing the Number of Network Round-Trips . . . . . . . . . . . . . . . . . . . . . 345 Execute Multiple Queries Together . . . . . . . . . . . . . . . . . . . . . . . . . . . 346 Use SET NOCOUNT . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 346 Reducing the Transaction Cost . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 346 Reduce Logging Overhead . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 347 Reduce Lock Overhead . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 348 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 350
CHAPTER 12
Blocking Analysis . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 351 Blocking Fundamentals . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 351 Understanding Blocking . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 352 Atomicity . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 353 Consistency . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 355 Isolation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 356 Durability . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 356
xiii
xiv
NCO NTENT S
Database Locks . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 357 Lock Granularity . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 357 Lock Escalation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 362 Lock Modes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 362 Lock Compatibility . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 369 Isolation Levels. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 369 Read Uncommitted . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 370 Read Committed. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 371 Repeatable Read . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 372 Serializable . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 375 Snapshot . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 381 Effect of Indexes on Locking . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 381 Effect of a Nonclustered Index . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 382 Effect of a Clustered Index . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 384 Effect of Indexes on the Serializable Isolation Level . . . . . . . . . . . . 385 Capturing Blocking Information . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 385 Capturing Blocking Information with SQL . . . . . . . . . . . . . . . . . . . . . 386 Profiler Trace and the Blocked Process Report Event . . . . . . . . . . . 388 Blocking Resolutions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 390 Optimize the Queries . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 390 Decrease the Isolation Level . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 391 Partition the Contended Data . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 392 Covering Index on Contended Data . . . . . . . . . . . . . . . . . . . . . . . . . . . 392 Recommendations to Reduce Blocking . . . . . . . . . . . . . . . . . . . . . . . . . . . . 393 Automation to Detect and Collect Blocking Information . . . . . . . . . . . . . . 394 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 399
CHAPTER 13
Deadlock Analysis
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 401
Deadlock Fundamentals . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 401 Choosing the Deadlock Victim . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 402 Using Error Handling to Catch a Deadlock . . . . . . . . . . . . . . . . . . . . . . . . . 403 Deadlock Analysis . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 403 Collecting Deadlock Information . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 404 Analyzing the Deadlock . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 405 Avoiding Deadlocks . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 410 Accessing Resources in the Same Chronological Order . . . . . . . . . 410 Decreasing the Number of Resources Accessed . . . . . . . . . . . . . . . 411 Minimizing Lock Contention . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 411 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 413
NC O N T E N T S
CHAPTER 14
Cursor Cost Analysis . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 415 Cursor Fundamentals . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 415 Cursor Location . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 417 Cursor Concurrency . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 418 Cursor Types . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 419 Cursor Cost Comparison . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 422 Cost Comparison on Cursor Location . . . . . . . . . . . . . . . . . . . . . . . . . 422 Cost Comparison on Cursor Concurrency . . . . . . . . . . . . . . . . . . . . . 424 Cost Comparison on Cursor Type . . . . . . . . . . . . . . . . . . . . . . . . . . . . 426 Default Result Set . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 428 Benefits . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 429 Drawbacks . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 429 Analyzing SQL Server Overhead with Cursors . . . . . . . . . . . . . . . . . . . . . . 432 Analyzing SQL Server Overhead with T-SQL Cursors . . . . . . . . . . . 432 Cursor Recommendations . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 436 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 437
CHAPTER 15
Database Workload Optimization . . . . . . . . . . . . . . . . . . . . . . . . . 439 Workload Optimization Fundamentals . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 439 Workload Optimization Steps. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 440 Sample Workload . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 441 Capturing the Workload . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 444 Analyzing the Workload . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 446 Identifying the Costliest Query. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 447 Determining the Baseline Resource Use of the Costliest Query . . . . . . . 449 Overall Resource Use . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 449 Detailed Resource Use . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 449 Analyzing and Optimizing External Factors . . . . . . . . . . . . . . . . . . . . . . . . . 452 Analyzing the Batch-Level Options Used by the Application . . . . . 452 Analyzing the Effectiveness of Statistics . . . . . . . . . . . . . . . . . . . . . . 453 Analyzing the Need for Defragmentation . . . . . . . . . . . . . . . . . . . . . . 454 Analyzing the Internal Behavior of the Costliest Query . . . . . . . . . . . . . . . 458 Analyzing the Query Execution Plan . . . . . . . . . . . . . . . . . . . . . . . . . . 458 Identifying the Costly Steps in the Execution Plan . . . . . . . . . . . . . . 459 Analyzing the Effectiveness of the Processing Strategy . . . . . . . . . 460 Optimizing the Costliest Query . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 460 Modifying an Existing Index . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 461 Analyzing the Application of a Join Hint . . . . . . . . . . . . . . . . . . . . . . . 463 Avoiding the Clustered Index Scan Operation . . . . . . . . . . . . . . . . . . 466 Modifying the Procedure . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 466
xv
xvi
NCO NTENT S
Analyzing the Effect on Database Workload . . . . . . . . . . . . . . . . . . . . . . . . 469 Iterating Through Optimization Phases . . . . . . . . . . . . . . . . . . . . . . . . . . . . 471 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 473
CHAPTER 16
SQL Server Optimization Checklist. . . . . . . . . . . . . . . . . . . . . . . . 475 Database Design . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 475 Balancing Under- and Overnormalization . . . . . . . . . . . . . . . . . . . . . 476 Benefiting from Entity-Integrity Constraints . . . . . . . . . . . . . . . . . . . 477 Benefiting from Domain and Referential Integrity Constraints . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 479 Adopting Index-Design Best Practices . . . . . . . . . . . . . . . . . . . . . . . . 480 Avoiding the Use of the sp_ Prefix for Stored Procedure Names . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 482 Minimizing the Use of Triggers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 482 Query Design . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 482 Use the Command SET NOCOUNT ON . . . . . . . . . . . . . . . . . . . . . . . . 483 Explicitly Define the Owner of an Object . . . . . . . . . . . . . . . . . . . . . . 483 Avoid Nonsargable Search Conditions . . . . . . . . . . . . . . . . . . . . . . . . 483 Avoid Arithmetic Operators on the WHERE Clause Column . . . . . . 484 Avoid Optimizer Hints . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 485 Stay Away from Nesting Views . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 485 Ensure No Implicit Data Type Conversions . . . . . . . . . . . . . . . . . . . . 485 Minimize Logging Overhead. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 485 Adopt Best Practices for Reusing Execution Plans . . . . . . . . . . . . . . 486 Adopt Best Practices for Database Transactions . . . . . . . . . . . . . . . 487 Eliminate or Reduce the Overhead of Database Cursors . . . . . . . . 488 Configuration Settings. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 488 Affinity Mask . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 488 Memory Configuration Options . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 488 Cost Threshold for Parallelism . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 489 Max Degree of Parallelism . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 489 Optimize for Ad Hoc Workloads . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 489 Query Governor Cost Limit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 490 Fill Factor (%) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 490 Blocked Process Threshold . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 490 Database File Layout . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 490 Database Compression . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 491
NC O N T E N T S
Database Administration . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 491 Keep the Statistics Up-to-Date . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 491 Maintain a Minimum Amount of Index Defragmentation. . . . . . . . . 492 Cycle the SQL Error Log File. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 492 Avoid Automatic Database Functions Such As AUTO_CLOSE or AUTO_SHRINK . . . . . . . . . . . . . . . . . . . . . . . . . . . 492 Minimize the Overhead of SQL Tracing . . . . . . . . . . . . . . . . . . . . . . . 493 Database Backup . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 493 Incremental and Transaction Log Backup Frequency . . . . . . . . . . . 493 Backup Distribution . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 494 Backup Compression. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 495 Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 495
INDEX . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 497
xvii
About the Author NGRANT FRITCHEY works for FM Global, an industry-leading engineering and insurance company, as a principal DBA. He’s done development of large-scale applications in languages such as VB, C#, and Java and has worked in SQL Server since version 6.0. In addition, he has worked in finance and consulting and for three failed dot coms. He is the author of Dissecting SQL Server Execution Plans (Simple Talk Publishing, 2008).
NSAJAL DAM has a master’s of technology degree in computer science from the Indian Institute of Science, Bangalore, and has been working with Microsoft technologies for more than 16 years. He has developed an extensive background in designing database applications and managing software development. Sajal also possesses significant experience in troubleshooting and optimizing the performance of Microsoft-based applications, from front-end web pages to back-end databases. He has extensive experience in working with large, Fortune 500 customers to design scalable database solutions and maximize the performance of their database environments.
xix
About the Technical Reviewer NJOSEPH SACK works for Microsoft as a dedicated support engineer on the Premier Field Engineering team. Since 1997, he has been developing and supporting SQL Server environments for clients in the financial services, IT consulting, manufacturing, retail, and real estate industries. He is the author of SQL Server 2008 Transact-SQL Recipes (Apress, 2008), SQL Server 2005 T-SQL Recipes (Apress, 2005), and SQL Server 2000 Fast Answers for DBAs and Developers (Glasshaus, 2003).
xxi
Acknowledgments H
oly cats! Writing a book is really hard. Fortunately, there’s this incredible support team that gathers around as you write these things. From Apress: Jonathan Gennick, thank you for the opportunity and support; Richard Dal Porto, thanks for keeping everything organized; Douglas Pundick, nice work on the edits, and I’m sorry there was so much for you to do; Kim Wimpsett and Kelly Winquist, same thing. My technical editor, well, “I’m not worthy” is the best that can be said, but my sincere appreciation goes to Joseph Sack for the absolutely outstanding work he did on this book. You’ve all done amazing work. Anything that’s wrong with the book now is my responsibility, not yours. I have to also show appreciation to all the folks over at the SQL Server Central forums. Steve Jones and Andy Warren created quite a community over there. Steve, Gail, Jeff, Jack, Barry, Lynn, and all the rest I haven’t mention specifically, thank you for all the help over the years. I work for a fantastic company with great people where I’ve had opportunities to learn and grow. Thanks to Jack Dewar, my manager, and Bill Hawkins, his boss. My thanks to the DBA team who have taught me so much: Peter, Ted, Basem, AJ, Dave M., Dave H., Chris K., Chris P., Yuting, Ray, Raj, and Det. I want to thank the folks at Red Gate for making terrific tools including SQL Prompt, which I used to write and format all the T-SQL code in the book. Finally, before someone starts playing music and pushing me off the stage, I want to show appreciation to the family—my wife and kids. This is the second book you’ve allowed me to write. Thank you for putting up with me so well. Grant Fritchey
xxiii
Introduction P
erformance is frequently one of the last things on anyone’s minds when they’re developing a system. Unfortunately, that means it usually becomes the biggest problem after that system goes to production. You can’t simply rely on getting a phone call that tells you that procedure X on database Y that runs on server Z is running slow. You have to have a mechanism in place to find this information for yourself. You also can’t work off the general word slow. Slow compared to what? Last week? Last month? The way it ran in development? And once you’ve identified something as running slow, you need to identify why. Does it need an index? Does it have an index that it isn’t using? Is it the CPU, the disk, the memory, the number of users, the amount of data? Now you’ve identified what and why, but you have to do something about it. How? Rewrite the query? Change the SDANA clause? The questions that will come your way when you start performance tuning are endless. This book provides you with the tools you need to answer those questions. I’ll show you how to set up mechanisms for collecting performance data on your server for the SQL Server instances and databases living there. I’ll go over the more tactical methods of collecting data on individual T-SQL calls. Along the way, I’ll be discussing index structure, choice, and maintenance; how best to write your T-SQL code; and a whole slew of other topics. One of my goals when writing this book was to deliver all these things using examples that resemble the types of queries you’ll see in the real world, at your desk. The tools and methods presented are mostly available with SQL Server Standard Edition, although some are available only with SQL Server Enterprise Edition. These are called out whenever you might encounter them. The main point is to learn how to answer all those questions that are going to be presented to you. This book gives you the tools to do that and to answer those questions in a methodical manner that eliminates much of the guesswork that is so common in performance optimization today. Performance problems aren’t something to be feared. With the right tools, you can tackle performance problems with a calmness and reliability that will earn the respect of your peers and your clients and that will contribute directly to their success.
Who This Book Is For This book is for just about anyone responsible for the performance of the system. Database administrators, certainly, are targeted because they’re responsible for setting up the systems, creating the infrastructure, and monitoring it over time. Developers are too, because who else is going to generate all the well-formed and highly performant T-SQL code? Database developers, more than anyone, are the target audience, if only because that’s what I do for work. Anyone who has the capability to write T-SQL, design tables, implement indexes, or manipulate server settings on the SQL Server system is going to need this information to one degree or another. xxv
xxvi
NINT ROD UCTIO N
How This Book Is Structured The purpose of this was to use as many “real-looking” queries as possible. To do this, I needed a “real” database. I could have created one and forced everyone to track down the download. Instead, I chose to use the sample database created by Microsoft, called =`rajpqnaSkngo.,,4. This is available through CodePlex (dppl6++sss*_k`alhat*_ki+IOBP@>Lnk`O]ilhao). I suggest keeping a copy of the restore handy and resetting your sample database after you have read a couple of topics from the book. Microsoft updates these databases over time, so you might see different sets of data or different behavior of some of the queries than that listed in the book. To a degree, the book builds on the knowledge presented from previous chapters. However, most of the chapters present information unique within that topic, so it is possible for you to jump in and out of particular chapters. You will still receive the most benefit by a sequential read of Chapter 1 through Chapter 16. Ê
UÊ
>«ÌiÀÊ£]ʺ-+Ê+ÕiÀÞÊ*iÀvÀ>ViÊ/Õ}]»ÊÌÀ`ÕViÃÊÌ
iÊÌiÀ>ÌÛiÊ«ÀViÃÃÊvÊ performance tuning. You’ll get a first glimpse at establishing a performance baseline, identifying bottlenecks, resolving the problems, and quantifying the improvements.
Ê
UÊ
>«ÌiÀÊÓ]ʺ-ÞÃÌiÊ*iÀvÀ>ViÊ>ÞÃÃ]»ÊÃÌ>ÀÌÃÊÞÕÊvvÊÜÌ
ÊÌÀ}ÊÌ
iÊ7`ÜÃÊ system on which SQL Server runs. Performance Monitor and user-defined functions are shown as a mechanism for collecting data.
Ê
UÊ
>«ÌiÀÊÎ]ʺ-+Ê+ÕiÀÞÊ*iÀvÀ>ViÊ>ÞÃÃ]»Ê`iviÃÊÌ
iÊLiÃÌÊÜ>ÞÃÊÌÊʺÕ`iÀÊÌ
iÊ hood” and see what kind of queries are being run on your system. It provides a detailed look at the Profiler and trace tools. Several of the most useful dynamic management views and functions used to monitor queries are first identified in this chapter.
Ê
UÊ
>«ÌiÀÊ{]ʺ`iÝÊ>ÞÃÃ]»ÊiÝ«>ÃÊ`iÝiÃÊ>`Ê`iÝÊ>ÀV
ÌiVÌÕÀi°ÊÌÊ`iviÃÊÌ
iÊ differences between clustered and nonclustered indexes. It shows which types of indexes work best with different types of querying. Basic index maintenance is also introduced.
Ê
UÊ
>«ÌiÀÊx]ʺ >Ì>L>ÃiÊ }iÊ/Õ}Ê`ÛÃÀ]»ÊVÛiÀÃÊÌ
iÊVÀÃvÌÊÌÊ >Ì>L>ÃiÊ Engine Tuning Advisor. The chapter goes over in detail how to use the Database Engine Tuning Advisor; you’re introduced to the various mechanisms for calling the tool and shown how it works under real loads.
Ê
UÊ
>«ÌiÀÊÈ]ʺ >ÀÊÕ«Ê>ÞÃÃ]»ÊÌ>iÃÊÊÌ
iÊV>ÃÃVÊ«iÀvÀ>ViÊ«ÀLi]Ê the key lookup, which is also known as the bookmark lookup. This chapter explores various solutions to the bookmark lookup operation.
Ê
UÊ
>«ÌiÀÊÇ]ʺ-Ì>ÌÃÌVÃÊ>ÞÃÃ]»ÊÌÀ`ÕViÃÊÌ
iÊVVi«ÌÊvÊÃÌ>ÌÃÌVðÊ/
iÊ«ÌâiÀÊÕÃiÃÊ statistics to make decisions regarding the execution of the query. Maintaining statistics, understanding how they’re stored, learning how they work, and learning how they affect your queries are all topics within this chapter.
Ê
UÊ
>«ÌiÀÊn]ʺÀ>}iÌ>ÌÊ>ÞÃÃ]»ÊÃ
ÜÃÊ
ÜÊ`iÝiÃÊvÀ>}iÌÊÛiÀÊÌi°Ê9Õ½Ê learn how to identify when an index is fragmented. You’ll see what happens to your queries as indexes fragment. You’ll learn mechanisms to eliminate index fragmentation.
NI N T R O D U C T I O N
Ê
UÊ
>«ÌiÀÊ]ʺ ÝiVÕÌÊ*>Ê >V
iÊ>ÞÃÃ]»Ê«ÀiÃiÌÃÊÌ
iÊiV
>ÃÃÊÌ
>ÌÊ-+Ê-iÀÛiÀÊ uses to store execution plans. Plan reuse is an important concept within SQL Server. You’ll learn how to identify whether plans are being reused. You’ll get various mechanisms for looking at the cache. This chapter also introduces new dynamic management views that allow more access to the cache than ever before.
Ê
UÊ
>«ÌiÀÊ£ä]ʺ-ÌÀi`Ê*ÀVi`ÕÀiÊ,iV«>Ì]»Ê`ë>ÞÃÊ
ÜÊ>`ÊÜ
iÊ-+Ê-iÀÛiÀÊ will recompile plans that were stored in cache. You’ll learn how plan recompiles can hurt or help the performance of your system. You’ll pick up mechanisms for forcing a recompile and for preventing one.
Ê
UÊ
>«ÌiÀÊ££]ʺ+ÕiÀÞÊ iÃ}Ê>ÞÃÃ]»ÊÀiÛi>ÃÊ
ÜÊÌÊÜÀÌiʵÕiÀiÃÊÌ
>ÌÊ«iÀvÀÊÜiÊ within your system. Common mistakes are explored, and solutions are provided. You’ll learn several best practices to avoid common bottlenecks.
Ê
UÊ
>«ÌiÀÊ£Ó]ʺ V}Ê>ÞÃÃ]»ÊÌi>V
iÃÊÌ
iÊLiÃÌÊÜ>ÞÃÊÌÊÀiV}âiÊÜ
iÊÛ>ÀÕÃÊ sessions on your server are in contention for resources. You’ll learn how to monitor for blocking along with methods and techniques to avoid blocked sessions.
Ê
UÊ
>«ÌiÀÊ£Î]ʺ i>`VÊ>ÞÃÃ]»ÊÃ
ÜÃÊ
ÜÊ`i>`VÃÊVVÕÀÊÊÞÕÀÊÃÞÃÌi°Ê9Õ½Ê get methods for identifying sessions involved with deadlocks. The chapter also presents best practices for avoiding deadlocks or fixing your code if deadlocks are already occurring.
Ê
UÊ
>«ÌiÀÊ£{]ʺ ÕÀÃÀÊ ÃÌÊ>ÞÃÃ]»Ê`>}À>ÃÊÌ
iÊ
iÀiÌÊVÃÌÃÊÌ
>ÌÊVÕÀÃÀÃÊ«ÀiÃiÌÊ to set-oriented T-SQL code. However, when cursors are unavoidable, you need to understand how they work, what they do, and how best to tune them within your environment if eliminating them outright is not an option.
Ê
UÊ
>«ÌiÀÊ£x]ʺ >Ì>L>ÃiÊ7À>`Ê"«Ìâ>Ì]»Ê`iÃÌÀ>ÌiÃÊ
ÜÊÌÊÌ>iÊÌ
iÊvÀmation presented in all the previous chapters and put it to work on a real database workload. You’ll identify the worst-performing procedures and put them through various tuning methods to arrive at better performance.
Ê
UÊ
>«ÌiÀÊ£È]ʺ-+Ê-iÀÛiÀÊ"«Ìâ>ÌÊ
iVÃÌ]»ÊÃÕ>ÀâiÃÊ>ÊÌ
iÊ«ÀiVi`}ÊV
>«ters into a set of checklists and best practices. The goal of the chapter is to enable you to have a place for quickly reviewing all you have learned from the rest of the book.
Downloading the Code You can download the code examples used in this book from the Source Code section of the Apress website (dppl6++sss*]lnaoo*_ki). Most of the code is straight T-SQL stored in *omh files, which can be opened and used in any SQL Server T-SQL editing tool. There is one PowerShell script that will have to be run through a PowerShell command line.
Contacting the Author You can contact the author, Grant Fritchey, at cn]jpa``
xxvii
CHAPTER
1
SQL Query Performance Tuning Q
uery performance tuning is an important part of today’s database applications. Often you can achieve large savings in both time and money with proper query performance tuning. Hardware performance is constantly improving, which can lead to an attitude suggesting that other methods of performance tuning are no longer important. Upgrades to SQL Server— especially to the optimizer, which helps determine how a query is executed, and the query engine, which executes the query—lead to better performance all on their own. The beauty of query performance tuning is that, in many cases, a small change to an index or a SQL query can result in a far more efficient application at a very low cost. There are, however, many pitfalls for the unwary. As a result, a proven process is required to ensure that you correctly identify and resolve performance bottlenecks. To whet your appetite for the types of topics essential to honing your query optimization skills, here is a quick list of the query optimization aspects I cover in this book: Ê
UÊ `iÌvÞ}Ê«ÀLi>ÌVÊ-+ʵÕiÀiÃ
Ê
UÊ >Þâ}Ê>ʵÕiÀÞÊiÝiVÕÌÊ«>
Ê
UÊ Û>Õ>Ì}ÊÌ
iÊivviVÌÛiiÃÃÊvÊÌ
iÊVÕÀÀiÌÊ`iÝiÃ
Ê
UÊ Û`}ÊL>ÀÊÕ«Ã
Ê
UÊ Û>Õ>Ì}ÊÌ
iÊivviVÌÛiiÃÃÊvÊÌ
iÊVÕÀÀiÌÊÃÌ>ÌÃÌVÃ
Ê
UÊ >Þâ}Ê>`ÊÀiÃÛ}ÊvÀ>}iÌ>Ì
Ê
UÊ "«Ìâ}ÊiÝiVÕÌÊ«>ÊV>V
}
Ê
UÊ >Þâ}Ê>`Ê>Û`}ÊÃÌÀi`Ê«ÀVi`ÕÀiÊÀiV«>Ì
Ê
UÊ â}ÊLV}Ê>`Ê`i>`VÃ
Ê
UÊ >Þâ}ÊÌ
iÊivviVÌÛiiÃÃÊvÊVÕÀÃÀÊÕÃi
Ê
UÊ ««Þ}Ê«iÀvÀ>ViÌÕ}Ê«ÀViÃÃiÃ]ÊÌÃ]Ê>`Ê«Ìâ>ÌÊÌiV
µÕiÃÊÌÊ optimize SQL workload
Before jumping straight in to these topics, let’s first examine why we go about performance tuning the way we do. In this chapter, I discuss the basic concepts of performance 1
2
CHAPTER 1 N SQ L QU ER Y P ER FOR MA NC E TU NING
tuning for a SQL Server database system. I detail the main performance bottlenecks and show just how important it is to design a database-friendly application, which is the consumer of the data, as well as how to optimize the database. Specifically, I cover the following topics: Ê
UÊ /
iÊ«iÀvÀ>ViÌÕ}Ê«ÀViÃÃ
Ê
UÊ *iÀvÀ>ViÊÛðʫÀVi
Ê
UÊ /
iÊ«iÀvÀ>ViÊL>Ãii
Ê
UÊ 7
iÀiÊÌÊvVÕÃÊivvÀÌÃÊÊÌÕ}
Ê
UÊ /
iÊ̫ʣ£Ê-+Ê-iÀÛiÀÊ«iÀvÀ>ViÊiÀÃ
The Performance-Tuning Process The performance-tuning process consists of identifying performance bottlenecks, troubleshooting their causes, applying different resolutions, and then quantifying performance improvements. It is necessary to be a little creative, since most of the time there is no one silver bullet to improve performance. The challenge is to narrow down the list of possible causes and evaluate the effects of different resolutions. You can even undo modifications as you iterate through the tuning process.
The Core Process During the tuning process, you must examine various hardware and software factors that can affect the performance of a SQL Server–based application. You should be asking yourself the following general questions during the performance analysis: Ê
UÊ ÃÊ>ÞÊÌ
iÀÊÀiÃÕÀViÌiÃÛiÊ>««V>ÌÊÀÕ}ÊÊÌ
iÊÃ>iÊÃiÀÛiÀ¶
Ê
UÊ ÃÊÌ
iÊ
>À`Ü>ÀiÊÃÕLÃÞÃÌiÊV>«>LiÊvÊÜÌ
ÃÌ>`}ÊÌ
iÊ>ÝÕÊÜÀ>`¶
Ê
UÊ ÃÊ-+Ê-iÀÛiÀÊVv}ÕÀi`Ê«À«iÀÞ¶
Ê
UÊ ÃÊÌ
iÊ`>Ì>L>ÃiÊViVÌÊLiÌÜiiÊ-+Ê-iÀÛiÀÊ>`ÊÌ
iÊ`>Ì>L>ÃiÊ>««V>ÌÊivvVi̶
Ê
UÊ iÃÊÌ
iÊ`>Ì>L>ÃiÊ`iÃ}ÊÃÕ««ÀÌÊÌ
iÊv>ÃÌiÃÌÊ`>Ì>ÊÀiÌÀiÛ>Ê>`Ê`vV>ÌÊvÀÊ>Ê Õ«`>Ì>LiÊ`>Ì>L>Ãi®¶
Ê
UÊ ÃÊÌ
iÊÕÃiÀÊÜÀ>`]ÊVÃÃÌ}ÊvÊ-+ʵÕiÀiÃ]Ê«Ìâi`ÊÌÊÀi`ÕViÊÌ
iÊ>`ÊÊ -+Ê-iÀÛiÀ¶
Ê
UÊ 7
>ÌÊ«ÀViÃÃiÃÊ>ÀiÊV>ÕÃ}ÊÌ
iÊÃÞÃÌiÊÌÊÃÜÊ`ÜÊ>ÃÊÀiviVÌi`ÊÊÌ
iÊi>ÃÕÀiiÌÊ vÊÛ>ÀÕÃÊÜ>ÌÊÃÌ>Ìiö
Ê
UÊ iÃÊÌ
iÊÜÀ>`ÊÃÕ««ÀÌÊÌ
iÊ>ÝÕÊVVÕÀÀiVÞ¶
If any of these factors is not configured properly, then the overall system performance may suffer. Let’s briefly examine these factors. Having another resource-intensive application on the same server can limit the resources >Û>>LiÊÌÊ-+Ê-iÀÛiÀ°Ê ÛiÊ>Ê>««V>ÌÊÀÕ}Ê>ÃÊ>ÊÃiÀÛViÊV>ÊVÃÕiÊ>Ê}`Ê«>ÀÌÊvÊ the system resources and limit the resources available to SQL Server. For example, running 7`ÜÃÊ/>ÃÊ>>}iÀÊVÌÕÕÃÞÊÊÌ
iÊÃiÀÛiÀÊÃÊÌÊÀiVi`i`°Ê7`ÜÃÊ/>ÃÊ
C H A P T E R 1 N S Q L Q U E R Y P E R F O R M A N C E T U N I N G
>>}iÀÊÃÊ>ÃÊ>Ê>««V>Ì]Êp]ogicn*ata, which runs at a higher priority than the SQL Server process. Priority is the weight given to a resource that pushes the processor to give it greater preference when executing. To determine the priority of a process, follow these steps: 1. >ÕV
Ê7`ÜÃÊ/>ÃÊ>>}iÀ° 2. Select View ¢ Select Columns. 3. -iiVÌÊÌ
iÊ >ÃiÊ*ÀÀÌÞÊV
iVÊLݰ 4. Click the OK button. These steps will add the >]oaLneknepu column to the list of processes. Subsequently, ÞÕÊÜÊLiÊ>LiÊÌÊ`iÌiÀiÊÌ
>ÌÊÌ
iÊ-+Ê-iÀÛiÀÊ«ÀViÃÃÊomhoanrn*ata) by default runs at À>Ê«ÀÀÌÞ]ÊÜ
iÀi>ÃÊÌ
iÊ7`ÜÃÊ/>ÃÊ>>}iÀÊ«ÀViÃÃÊp]ogicn*ata) runs at High priority. Therefore, to allow SQL Server to maximize the use of available resources, you should look for all the nonessential applications/services running on the SQL Server machine and ensure that they are not acting as resource hogs. Improperly configuring the hardware can prevent SQL Server from gaining the maximum benefit from the available resources. The main hardware resources to be considered are processor, memory, disk, and network. For example, in a server with more than 4GB of memory, an improper memory configuration will prevent SQL Server from using the memory beyond 4GB. Furthermore, if the capacity of a particular resource is small, then it can soon become a performance bottleneck for SQL Server. Chapter 2 covers these hardware bottlenecks in detail. You should also look at the configuration of SQL Server, since proper configuration is essential for an optimized application. There is a long list of SQL Server configurations that define the generic behavior of a SQL Server installation. These configurations can be viewed and modified using a system stored procedure, ol[_kjbecqna°Ê>ÞÊvÊÌ
iÃiÊVv}ÕÀ>ÌÃÊ V>ÊLiÊ>>}i`ÊÌiÀ>VÌÛiÞÊÌ
ÀÕ}
Ê-+Ê-iÀÛiÀÊ ÌiÀ«ÀÃiÊ>>}iÀ° Since the SQL Server configurations are applicable for the complete SQL Server installation, a standard configuration is usually preferred. The good news is that, generally, you need not modify these configurations; the default settings work best for most situations. In fact, the general recommendation is to keep the SQL Server configurations at the default values. I discuss the configuration parameters in detail throughout the book. *ÀÊViVÌÛÌÞÊLiÌÜii SQL Server and the database application can hurt application performance. One of the questions you should ask yourself is, How good is the database coniV̶ÊÀÊiÝ>«i]ÊÌ
iʵÕiÀÞÊiÝiVÕÌi`ÊLÞÊÌ
iÊ>««V>ÌÊ>ÞÊLiÊ
}
ÞÊ«Ìâi`]ÊLÕÌÊÌ
iÊ database connection used to submit this query may add considerable overhead to the query performance. Based on the distribution of the application and the database, different network protocols should be used to reduce the network overhead. Additionally, the data access layer used to manage the database connectivity above the network connection may not be efficient. The data access layer technology or the way the data access layer is used by the application may not be optimal. The design of the database should also be analyzed while troubleshooting performance. This helps you understand not only the entity-relationship model of the database but also why a query may be written in a certain way. Although it may not always be possible to modify a database design because of wider implications on the database application, a good understanding of the database design helps you focus in the right direction and understand the impact of a resolution. This is especially true of the primary and foreign keys and the clustered indexes used in the tables.
3
4
CHAPTER 1 N SQ L QU ER Y P ER FOR MA NC E TU NING
The application may be slow because of poorly built queries, the queries might not be able to use the indexes, or perhaps even the indexes themselves are incorrect or missing. If any of the queries are not optimized sufficiently, they can seriously impact other queries’ performance. I cover index optimization in depth in Chapters 3, 4, 5, and 6. The next question at this stage should be, Is a query slow because of its resource intensiveness or because of concurrency ÃÃÕiÃÊÜÌ
ÊÌ
iÀʵÕiÀiöÊ9ÕÊV>Êv`Ê`i«Ì
ÊvÀ>ÌÊÊLV}Ê>>ÞÃÃÊÊ
>«ÌiÀÊ£Ó° 7
iÊ«ÀViÃÃiÃÊÀÕÊÊ>ÊÃiÀÛiÀ]ÊiÛiÊiÊÜÌ
ÊÕÌ«iÊ«ÀViÃÃÀÃ]Ê>ÌÊÌiÃÊiÊ«ÀViÃÃÊ will be waiting on another to complete. You can get a fundamental understanding of the root cause of slowdowns by identifying what is waiting and what is causing it to wait. You can realize this through operating system counters that you access through dynamic management ÛiÜÃÊÜÌ
Ê-+Ê-iÀÛiÀ°ÊÊVÛiÀÊÌ
ÃÊvÀ>ÌÊÊ
>«ÌiÀÊÓÊ>`ÊÊ
>«ÌiÀÊ£Ó° The challenge is to find out which factor is causing the performance bottleneck. For example, with slow-running SQL queries and high pressure on the hardware resources, you may find that both poor database design and a nonoptimized workload are to blame. In such a case, you must diagnose the symptoms further and correlate the findings with possible causes. Because performance tuning can be time consuming and tiresome, you should ideally take a preventive approach by designing the system for optimum performance from the outset. To strengthen the preventive approach, every lesson that you learn during the optimization of poor performance should be considered an optimization guideline when implementing new database applications. There are also proven best practices that you should consider while implementing database applications. I present these best practices in detail throughout Ì
iÊL]Ê>`Ê
>«ÌiÀÊ£nÊÃÊ`i`V>Ìi`ÊÌÊÕÌ}Ê>ÞÊvÊÌ
iÊ«Ìâ>ÌÊLiÃÌÊ«À>VÌVið *i>ÃiÊiÃÕÀiÊÌ
>ÌÊÞÕÊÌ>iÊÌ
iÊ«iÀvÀ>ViÊ«Ìâ>ÌÊÌiV
µÕiÃÊÌÊVÃ`iÀ>ÌÊ at the early stages of your database application development. Doing so will help you roll out your database projects without big surprises later. Unfortunately, we rarely live up to this ideal and often find database applications needing performance tuning. Therefore, it is important to understand not only how to improve the performance of a SQL Server–based application but also how to diagnose the causes of poor performance.
Iterating the Process *iÀvÀ>ViÊÌÕ}ÊÃÊ>Êiterative process, where you identify major bottlenecks, attempt to resolve them, measure the impact of your changes, and return to the first step until pervÀ>ViÊÃÊ>VVi«Ì>Li°Ê7
iÊ>««Þ}ÊÞÕÀÊÃÕÌÃ]ÊÞÕÊÃ
Õ`ÊvÜÊÌ
iÊ}`iÊÀÕiÊvÊ making only one change at a time. Any change usually affects other parts of the system, so you must reevaluate the effect of each change on the performance of the overall system. As an example, adding an index may fix the performance of a specific query, but it could cause other queries to run more slowly, as explained in Chapter 4. Consequently, it is preferable to conduct a performance analysis in a test environment to shield users from your diagnosis attempts and intermediate optimization steps. In such a case, evaluating one change at a time also helps in prioritizing the implementation order of the changes on the production server, based on their relative contributions. You can keep on chipping away at performance bottlenecks and improving the system performance gradually. Initially, you will be able to resolve big performance bottlenecks and achieve significant performance improvements, but as you proceed through the iterations, your returns will gradually diminish. Therefore, to use your time efficiently, it is worthwhile to quanÌvÞÊÌ
iÊ«iÀvÀ>ViÊLiVÌÛiÃÊvÀÃÌÊvÀÊiÝ>«i]Ê>ÊnäÊ«iÀViÌÊÀi`ÕVÌÊÊÌ
iÊÌiÊÌ>iÊvÀÊ a certain query, with no adverse effect anywhere else on the server) and then work toward them.
C H A P T E R 1 N S Q L Q U E R Y P E R F O R M A N C E T U N I N G
The performance of a SQL Server application is highly dependent on the amount and `ÃÌÀLÕÌÊvÊÕÃiÀÊ>VÌÛÌÞÊÀÊÜÀ>`®Ê>`Ê`>Ì>°Ê Ì
ÊÌ
iÊ>ÕÌÊ>`Ê`ÃÌÀLÕÌÊvÊ workload and data change over time, and differing data can cause SQL Server to execute SQL queries differently. The performance resolution applicable for a certain workload and data may lose its effect over a period of time. Therefore, to ensure an optimum system performance Ê>ÊVÌÕ}ÊL>ÃÃ]ÊÞÕÊÜÊii`ÊÌÊ>>ÞâiÊ«iÀvÀ>ViÊ>ÌÊÀi}Õ>ÀÊÌiÀÛ>ðÊ*iÀvÀ>ViÊ ÌÕ}ÊÃÊ>ÊiÛiÀi`}Ê«ÀViÃÃ]Ê>ÃÊÃ
ÜÊÊ}ÕÀiÊ££°
Figure 1-1. Performance-tuning process
5
6
CHAPTER 1 N SQ L QU ER Y P ER FOR MA NC E TU NING
You can see that the steps to optimize the costliest query make for a complex process, which also requires multiple iterations to troubleshoot the performance issues within the µÕiÀÞÊ>`Ê>««ÞÊiÊV
>}iÊ>ÌÊ>ÊÌi°Ê}ÕÀiÊ£ÓÊÃ
ÜÃÊÌ
iÊÃÌi«ÃÊÛÛi`ÊÊÌ
iÊoptimization of the costliest query.
Figure 1-2. Optimization of the costliest query
C H A P T E R 1 N S Q L Q U E R Y P E R F O R M A N C E T U N I N G
As you can see from this process, there is quite a lot to do to ensure that you correctly tune the performance of a given query. It is important to use a solid process like this in performance tuning to focus on the main identified issues. Having said this, it also helps to keep a broader perspective about the problem as a whole, since sometimes you may believe that you are trying to solve the correct performance bottleneck when in reality something else is causing the problem.
Performance vs. Price One of the points I touched on earlier is that to gain increasingly small performance increments, you need to spend increasingly large amounts of time and money. Therefore, to ensure the best return on your investment, you should be very objective while optimizing performance. Always consider the following two aspects: Ê
UÊ 7
>ÌÊÃÊÌ
iÊ>VVi«Ì>LiÊ«iÀvÀ>ViÊvÀÊÞÕÀÊ>««V>̶
Ê
UÊ ÃÊÌ
iÊÛiÃÌiÌÊÜÀÌ
ÊÌ
iÊ«iÀvÀ>ViÊ}>¶
Performance Targets To derive maximum efficiency, you must realistically estimate your performance requirements. You can follow many best practices to improve performance—for example, you can have your database files on the most efficient disk subsystem. However, before applying a best practice, you should consider how much you may gain from it and whether the gain will be worth the investment. Sometimes it is really difficult to estimate the performance gain without actually making the enhancement. That makes properly identifying the source of your performance bottleiVÃÊiÛiÊÀiÊ«ÀÌ>̰ÊÀiÊÞÕÊ *1]ÊiÀÞ]ÊÀÊ`ÃÊLÕ`¶ÊÃÊÌ
iÊV>ÕÃiÊV`i]Ê`>Ì>Ê ÃÌÀÕVÌÕÀi]ÊÀÊ`iÝ}]ÊÀÊ>ÀiÊÞÕÊëÞÊ>ÌÊÌ
iÊÌÊvÊÞÕÀÊ
>À`Ü>Ài¶Ê iÊÃÕÀiÊÞÕÊV>Ê>iÊ these possibly costly decisions from a known point rather than guessing. A practical approach can be to increase a resource in increments and analyze the application’s scalability with the added resource. A scalable application will proportionately benefit from an incremental increase of the resource, if the resource was truly causing the scalability bottleneck. If the ÀiÃÕÌÃÊ>««i>ÀÊÌÊLiÊÃ>ÌÃv>VÌÀÞ]ÊÌ
iÊÞÕÊV>ÊVÌÊÌÊÌ
iÊvÕÊi
>ViiÌ°Ê Ý«iÀiViÊ also plays a very important role here.
“Good Enough” Tuning Instead of tuning a system to the theoretical maximum performance, the goal should be to tune until the system performance is “good enough.” This is a commonly adopted performancetuning approach. The cost investment after such a point usually increases exponentially in V«>ÀÃÊÌÊÌ
iÊ«iÀvÀ>ViÊ}>°Ê/
iÊnä\ÓäÊÀÕiÊÜÀÃÊÛiÀÞÊÜi\ÊLÞÊÛiÃÌ}ÊÓäÊ«iÀViÌÊ vÊÞÕÀÊÀiÃÕÀViÃ]ÊÞÕÊ>ÞÊ}iÌÊnäÊ«iÀViÌÊvÊÌ
iÊ«ÃÃLiÊ«iÀvÀ>ViÊi
>ViiÌ]ÊLÕÌÊvÀÊ Ì
iÊÀi>}ÊÓäÊ«iÀViÌÊ«ÃÃLiÊ«iÀvÀ>ViÊ}>]ÊÞÕÊ>ÞÊ
>ÛiÊÌÊÛiÃÌÊ>Ê>``Ì>ÊnäÊ percent of resources. It is therefore important to be realistic when setting your performance objectives. A business benefits not by considering pure performance but by considering price pervÀ>Vi°ÊÜiÛiÀ]ÊvÊÌ
iÊÌ>À}iÌÊÃÊÌÊv`ÊÌ
iÊÃV>>LÌÞÊÌÊvÊÞÕÀÊ>««V>ÌÊvÀÊÛ>ÀÕÃÊ
7
8
CHAPTER 1 N SQ L QU ER Y P ER FOR MA NC E TU NING
reasons, including marketing the product against its competitors), then it may be worthwhile ÛiÃÌ}Ê>ÃÊÕV
Ê>ÃÊÞÕÊV>°Ê ÛiÊÊÃÕV
ÊV>ÃiÃ]ÊÕÃ}Ê>ÊÌ
À`«>ÀÌÞÊÃÌÀiÃÃÊÌiÃÌÊ>LÊ>ÞÊLi a better investment decision.
Performance Baseline One of the main objectives of performance analysis is to understand the underlying level of system use or pressure on different hardware and software subsystems. This knowledge helps you in the following ways: Ê
UÊ ÜÃÊÞÕÊÌÊ>>ÞâiÊÀiÃÕÀViÊLÌÌiiVð
Ê
UÊ >LiÃÊÞÕÊÌÊÌÀÕLiÃ
ÌÊLÞÊV«>À}ÊÃÞÃÌiÊÕÌâ>ÌÊ«>ÌÌiÀÃÊÜÌ
Ê>Ê preestablished baseline.
Ê
UÊ ÃÃÃÌÃÊÞÕÊÊ>}Ê>VVÕÀ>ÌiÊiÃÌ>ÌiÃÊÊV>«>VÌÞÊ«>}Ê>`ÊÃV
i`Õ}Ê hardware upgrades.
Ê
UÊ `ÃÊÞÕÊÊ`iÌvÞ}ÊÜÕÌâ>ÌÊ«iÀ`ÃÊÜ
iÊÌ
iÊ`>Ì>L>ÃiÊ>`ÃÌÀ>ÌÛiÊ activities can be executed.
Ê
UÊ i«ÃÊÞÕÊiÃÌ>ÌiÊÌ
iÊ>ÌÕÀiÊvÊ«ÃÃLiÊ
>À`Ü>ÀiÊ`ÜÃâ}ÊÀÊÃiÀÛiÀÊVÃ`>̰Ê7
ÞÊÜÕ`Ê>ÊV«>ÞÊ`ÜÃâi¶Ê7i]ÊÊÌ
iÊ«>ÃÌ]ÊÃiÊV«>iÃÊi>Ãi`ÊÛiÀÞÊ high-end systems expecting strong growth, but because of poor growth, they are now vÀVi`ÊÌÊ`ÜÃâiÊÌ
iÀÊÃÞÃÌiÊÃiÌիðÊ`ÊVÃ`>Ì¶Ê «>iÃÊ>ÞÊÃitimes buy too many servers or realize that the maintenance and licensing costs are too high. This would make using fewer servers very attractive.
Therefore, to better understand your application’s resource requirements, you should create a baseline for your application’s hardware and software usage. A baseline serves as a statistic of your system’s current usage pattern and as a reference with which to compare future statistics. Baseline analysis helps you understand your application’s behavior during a stable period, how hardware resources are used during such periods, and what the software V
>À>VÌiÀÃÌVÃÊ>Ài°Ê7Ì
Ê>ÊL>ÃiiÊÊ«>Vi]ÊÞÕÊV>Ê`ÊÌ
iÊvÜ}\ Ê
UÊ i>ÃÕÀiÊVÕÀÀiÌÊ«iÀvÀ>ViÊ>`ÊiÝ«ÀiÃÃÊÞÕÀÊ>««V>̽ÃÊ«iÀvÀ>ViÊ}>ð
Ê
UÊ «>ÀiÊÌ
iÀÊ
>À`Ü>ÀiÊÀÊÃvÌÜ>ÀiÊVL>ÌÃÊ>}>ÃÌÊÌ
iÊL>Ãii°
Ê
UÊ i>ÃÕÀiÊ
ÜÊÌ
iÊÜÀ>`Ê>`ÉÀÊ`>Ì>ÊV
>}iÃÊÛiÀÊÌi°
Ê
UÊ Û>Õ>ÌiÊÌ
iÊ«i>Ê>`Ê«i>ÊÕÃ>}iÊ«>ÌÌiÀÊvÊÌ
iÊ>««V>̰Ê/
ÃÊvÀ>ÌÊ can be used to effectively distribute database administration activities, such as full database backup and database defragmentation during nonpeak hours.
You can use the -ÞÃÌiÊÌÀÊÌÊ>ÃÊÀiviÀÀi`ÊÌÊ>ÃÊ*iÀvÀ>ViÊÌÀ®ÊÌÊVÀi>ÌiÊ a baseline for SQL Server’s hardware and software resource utilization. Similarly, you may baseline the SQL Server workload using the -+Ê*ÀviÀÊÌ]ÊÜ
V
ÊV>Ê
i«ÊÞÕÊÕ`iÀÃÌ>`Ê the average resource utilization and execution time of SQL queries when conditions are stable. You can also get snapshots of this information by using dynamic management views and dynamic management functions. You will learn in detail how to use these tools and queries in Chapters 2 and 3.
C H A P T E R 1 N S Q L Q U E R Y P E R F O R M A N C E T U N I N G
One other option is to take advantage of one of the many tools that can generate an artificial load on a given server or database. Numerous third-party tools are available. VÀÃvÌÊvviÀÃÊ-+"Ê>Û>>LiÊ>ÌÊdppl6++sss*ie_nkokbp*_ki+`ksjhk]`o+`ap]eho* ]olt;b]iehue`95]4^,,1^)40a0)0b.0)4`21)_^1/00.`5a-5"`eolh]uh]jc9aj), which measures Ì
iÊÉ"ÊV>«>VÌÞÊvÊÞÕÀÊÃÞÃÌi°ÊVÀÃvÌÊ>ÃÊ
>ÃÊ-+"-]Ê>ÊÌÊvÀÊ}iiÀ>Ì}Ê-+Ê -iÀÛiÀqëiVvVÊV>ÃÊ>`ÊÃÕ>Ìi`Ê>`ÃÊ>Û>>LiÊ>ÌÊdppl6++`ksjhk]`*ie_nkokbp*_ki+ `ksjhk]`+/+4+,+/4,0_^-_)]5--)0`-.)41.1)a134,-53a,^1+OMHEKOeiT42*ata®°Ê>ÞÊÌ
À`«>ÀÌÞÊ tools are available that can also help with this. If your system is not yet in production, using one of these tools to simulate a load to test the system is a very good idea.
Where to Focus Efforts 7
iÊÞÕÊÌÕi a particular system, pay special attention to the >««V>ÌÊ>ÞiÀÊÌ
iÊ`>Ì>base queries and stored procedures executed by Visual Basic/ADO or otherwise that are used to access the database). You will usually find that you can positively affect performance in the application layer far more than if you spend an equal amount of time figuring out how to tune the hardware, operating system, or SQL Server configuration. Although a proper configuration of hardware, operating system, and SQL Server is essential for the best performance of a database application, these fields have standardized so much that you usually need to spend only a limited amount of time configuring them properly for performance. Application design issues such as query design and indexing strategies, on the other hand, are application dependent. Consequently, there is usually more to optimize in the application layer than in the hardware, operating system, or SQL Server configuration. Thus, for a unit of time spent in each area, work in the application layer usually yields the maximum performance benefit, as ÕÃÌÀ>Ìi`ÊÊ}ÕÀiʣΰ
Figure 1-3. Time spent vs. performance gain
9
10
CHAPTER 1 N SQ L QU ER Y P ER FOR MA NC E TU NING
In my experience, you can obtain the greatest improvement in database application performance by looking first at the area of application design, including logical/physical database design, query design, and index design. Sure, if you concentrate on hardware configuration and upgrades, you may obtain a satisfactory performance gain. However, a bad SQL query sent by the application can consume all the hardware resources available, no matter how much you have. Therefore, a poor application design can make the hardware upgrade requirements very high, even beyond your limits. In the presence of a heavy SQL workload, concentrating on hardware configurations and upgrades usually produces a poor return on investment. You should analyze the stress created by an application on a SQL Server database at two levels: Ê
UÊ High level: Analyze how much stress the database application is creating on individual hardware resources and what the overall behavior of the SQL Server installation is. The best measures for this are the various wait states. This information can help you in two ways. First, it helps you identify the area to concentrate on within a SQL Server application where there is poor performance. Second, it helps you identify any lack of proper configuration at the higher levels. You can then decide which hardware resource may LiÊÕ«}À>`i`ÊvÊÞÕÊ>ÀiÊÌÊ>LiÊÌÊÌÕiÊÌ
iÊ>««V>ÌÊÕÃ}ÊÌ
iÊ*iÀvÀ>ViÊÌÀÊ tool, as explained in Chapter 2.
Ê
UÊ Low level: Identify the exact culprits within the application—in other words, the SQL queries that are creating most of the pressure visible at the overall higher level. This V>ÊLiÊ`iÊÕÃ}ÊÌ
iÊ-+Ê*ÀviÀÊÌÊ>` various dynamic management views, as explained in Chapter 3.
SQL Server Performance Killers Let’s now consider the major problem areas that can degrade SQL Server performance. By being aware of the main performance killers in SQL Server in advance, you will be able to focus your tuning efforts on the likely causes. Once you have optimized the hardware, operating system, and SQL Server settings, the >Ê«iÀvÀ>ViÊiÀÃÊÊ-+Ê-iÀÛiÀÊ>ÀiÊ>ÃÊvÜÃ]ÊÊ>ÊÀÕ}
ÊÀ`iÀÊÜÌ
ÊÌ
iÊÜÀÃÌÊ>««i>Àing first): Ê
UÊ *ÀÊ`iÝ}
Ê
UÊ >VVÕÀ>ÌiÊÃÌ>ÌÃÌVÃ
Ê
UÊ ÝViÃÃÛiÊLV}Ê>`Ê`i>`VÃ
Ê
UÊ
Ê
UÊ *ÀʵÕiÀÞÊ`iÃ}
Ê
UÊ *ÀÊ`>Ì>L>ÃiÊ`iÃ}
Ê
UÊ ÝViÃÃÛiÊvÀ>}iÌ>Ì
Ê
UÊ
Ê
UÊ *ÀÊiÝiVÕÌÊ«>Ã]ÊÕÃÕ>ÞÊV>ÕÃi`ÊLÞÊ«>À>iÌiÀÊÃvv}
ÃiÌL>Ãi`Ê«iÀ>ÌÃ]ÊÕÃÕ>ÞÊ/-+ÊVÕÀÃÀÃ
ÀiÕÃ>LiÊiÝiVÕÌÊ«>Ã
C H A P T E R 1 N S Q L Q U E R Y P E R F O R M A N C E T U N I N G
Ê
UÊ ÀiµÕiÌÊÀiV«>ÌÊvÊiÝiVÕÌÊ«>Ã
Ê
UÊ «À«iÀÊÕÃiÊvÊVÕÀÃÀÃ
Ê
UÊ «À«iÀÊVv}ÕÀ>ÌÊvÊÌ
iÊ`>Ì>L>ÃiÊ}
Ê
UÊ ÝViÃÃÛiÊÕÃiÊÀÊ«À«iÀÊVv}ÕÀ>ÌÊvÊpail`^
Let’s take a quick look at each of these, before considering them in more depth in later chapters.
Poor Indexing *ÀÊ`iÝ}ÊÃ usually one of the biggest performance killers in SQL Server. In the absence of proper indexing for a query, SQL Server has to retrieve and process much more data while iÝiVÕÌ}ÊÌ
iʵÕiÀÞ°Ê/
ÃÊV>ÕÃiÃÊ
}
Ê>ÕÌÃÊvÊÃÌÀiÃÃÊÊÌ
iÊ`Ã]ÊiÀÞ]Ê>`Ê *1]Ê increasing the query execution time significantly. Increased query execution time then leads to excessive blocking and deadlocks in SQL Server. You will learn how to determine the indexing strategies and resolve indexing problems in Chapters 4, 5, and 6. Generally, indexes are considered to be the responsibility of the database administrator ®°ÊÜiÛiÀ]ÊÌ
iÊ ÊV>ÌÊ`iviÊ
ÜÊÌÊÕÃiÊÌ
iÊ`iÝiÃ]ÊÃViÊÌ
iÊÕÃiÊvÊ`iÝiÃÊÃÊ determined by the database queries and stored procedures written by the developers. Therefore, defining the indexes must be a shared responsibility since the developers usually have more knowledge of the data to be retrieved and the DBAs have a better understanding of how indexes work. Indexes created without the knowledge of the queries serve little purpose.
NNote Because indexes created without the knowledge of the queries serve little purpose, database developers need to understand indexes at least as well as they know T-SQL.
Inaccurate Statistics SQL Server relies heavily on cost-based optimization, so accurate data-distribution statisÌVÃÊ>ÀiÊiÝÌÀiiÞÊ«ÀÌ>ÌÊvÀÊÌ
iÊivviVÌÛiÊÕÃiÊvÊ`iÝiðÊ7Ì
ÕÌÊ>VVÕÀ>ÌiÊÃÌ>ÌÃÌVÃ]Ê-+Ê Server’s built-in query optimizer cannot accurately estimate the number of rows affected by a query. Because the amount of data to be retrieved from a table is highly important in deciding how to optimize the query execution, the query optimizer is much less effective if the data distribution statistics are not maintained accurately. You will look at how to analyze statistics in Chapter 7.
Excessive Blocking and Deadlocks BecauseÊ-+Ê-iÀÛiÀÊÃÊvÕÞÊ>ÌVÌÞ]ÊVÃÃÌiVÞ]ÊÃ>Ì]Ê>`Ê`ÕÀ>LÌÞÊ ®ÊV«>Ì]Ê the database engine ensures that modifications made by concurrent transactions are properly isolated from one another. By default, a transaction sees the data either in the state before another concurrent transaction modified the data or after the other transaction completed—it does not see an intermediate state.
11
12
CHAPTER 1 N SQ L QU ER Y P ER FOR MA NC E TU NING
Because of this isolation, when multiple transactions try to access a common resource concurrently in a noncompatible way, blocking occurs in the database. A deadlock occurs when two resources attempt to escalate or expand locked resources and conflict with one another. The query engine determines which process is the least costly to roll back and chooses it as the deadlock victim. This requires that the database request be resubmitted for successful execution. The execution time of a query is adversely affected by the amount of blocking and deadlock it faces. For scalable performance of a multiuser database application, properly controlling the isolation levels and transaction scopes of the queries to minimize blocking and deadlock is critical; otherwise, the execution time of the queries will increase significantly, even though the hardware resources may be highly underutilized. I cover this problem in depth in
>«ÌiÀÃÊ£ÓÊ>`ʣΰ
Non-Set-Based Operations Transact-SQL is a set-based scripting language, which means it operates on sets of data. This forces you to think in terms of columns rather than in terms of rows. Non-set-based thinking leads to excessive use of cursors and loops rather than exploring more efficient joins and subqueries. The T-SQL language offers rich mechanisms for manipulating sets of data. For performance to shine, you need to take advantage of these mechanisms rather than trying to vÀViÊ>ÊÀÜLÞÀÜÊ>««À>V
ÊÌÊÞÕÀÊV`i]ÊÜ
V
ÊÜÊÊ«iÀvÀ>Vi°Ê Ý>«iÃÊvÊ
ÜÊÌÊ`Ê Ì
ÃÊ>ÀiÊ>Û>>LiÊÌ
ÀÕ}
ÕÌÊÌ
iÊLÆÊ>Ã]ÊÊ>``ÀiÃÃÊ/-+ÊLiÃÌÊ«À>VÌViÃÊÊ
>«ÌiÀÊ££Ê>`Ê VÕÀÃÀÃÊÊ
>«ÌiÀÊ£{°
Poor Query Design The effectiveness of indexes depends entirely on the way you write SQL queries. Retrieving excessively large numbers of rows from a table, or specifying a filter criterion that returns a larger result set from a table than is required, renders the indexes ineffective. To improve performance, you must ensure that the SQL queries are written to make the best use of new or existing indexes. Failing to write cost-effective SQL queries may prevent SQL Server from choosing proper indexes, which increases query execution time and database blocking.
>«ÌiÀÊ££ÊVÛiÀÃÊ
ÜÊÌÊÜÀÌiÊivviVÌÛiʵÕiÀið Query design covers not only single queries but also sets of queries often used to implement database functionalities such as a queue management among queue readers and writers.
ÛiÊÜ
iÊÌ
iÊ«iÀvÀ>ViÊvÊ`Û`Õ>ʵÕiÀiÃÊÕÃi`ÊÊÌ
iÊ`iÃ}ÊÃÊvi]ÊÌ
iÊÛiÀ>Ê«iÀformance of the database can be very poor. Resolving this kind of bottleneck requires a broad understanding of different characteristics of SQL Server, which can affect the performance of database functionalities. You will see how to design effective database functionality using SQL queries throughout the book.
Poor Database Design A database should be adequately normalized to increase the performance of data retrieval and reduce blocking. For example, if you have an undernormalized database with customer and order information in the same table, then the customer information will be repeated in all the order rows of the customer. This repetition of information in every row will increase the I/Os required to fetch all the orders placed by a customer. At the same time, a data writer working
C H A P T E R 1 N S Q L Q U E R Y P E R F O R M A N C E T U N I N G
on a customer’s order will reserve all the rows that include the customer information and thus block all other data writers/data readers trying to access the customer profile. Overnormalization of a database is as bad as undernormalization. Overnormalization increases the number and complexity of joins required to retrieve data. An overnormalized database contains a large number of tables with a very small number of columns. Having too many joins in a query may also be because database entities have not been partitioned very distinctly or the query is serving a very complex set of requirements that could perhaps be better served by creating a new view or stored procedure. >Ì>L>ÃiÊ`iÃ}ÊÃÊ>Ê>À}iÊÃÕLiV̰ÊÊÜÊ«ÀÛ`iÊ>ÊviÜÊ«ÌiÀÃÊÊ
>«ÌiÀÊ£nÊ>`ÊÌ
ÀÕ}
out the rest of the book. Because of the size of the topic, I won’t be able to treat it in the complete manner it requires. However, if you want to read a book on database design, with an emphasis on introducing the subject, I recommend reading Data Modeling for Everyone by -
>ÀÊiÊ«ÀiÃÃ]ÊÓääή°
Excessive Fragmentation 7
iÊ>>Þâ}Ê`>Ì> retrieval operations, you can usually assume that the data is organized in an orderly way, as indicated by the index used by the data retrieval operation. However, if the pages containing the data are fragmented in a nonorderly fashion or if they contain a small amount of data because of frequent page splits, then the number of read operations required by the data retrieval operation will be much higher than might otherwise be required. The increase in the number of read operations caused by fragmentation hurts query performance. Ê
>«ÌiÀÊn]ÊÞÕÊÜÊi>ÀÊ
ÜÊÌÊ>>ÞâiÊ>`ÊÀiÛiÊvÀ>}iÌ>̰
Nonreusable Execution Plans To execute a query in an efficient way, SQL Server’s query optimizer spends a fair amount of
*1ÊVÞViÃÊVÀi>Ì}Ê>ÊVÃÌivviVÌÛiÊiÝiVÕÌÊ«>°Ê/
iÊ}`ÊiÜÃÊÃÊÌ
>ÌÊÌ
iÊ«>ÊÃÊV>V
i`ÊÊ memory, so you can reuse it once created. However, if the plan is designed so that you cannot plug variable values into it, SQL Server creates a new execution plan every time the same query is resubmitted with different variable values. So, for better performance, it is extremely important to submit SQL queries in forms that help SQL Server cache and reuse the execution plans. I will also address topics such as plan freezing, forcing query plans, and the problems associated with parameter sniffing. You will see in detail how to improve the reusability of execution plans in Chapter 9.
Poor Execution Plans The same mechanisms that allow SQL Server to establish an efficient stored procedure and reuse that procedure again and again instead of recompiling can, in some cases, work against you. A bad execution plan can be a real performance killer. Bad plans are frequently caused by a process called parameter sniffing, which comes from the mechanisms that the query optimizer uses to determine the best plan based on statistics. It’s important to understand how statistics and parameters combine to create execution plans and what you can do to control them. I cover statistics in depth in Chapter 7 and execution plan analysis in Chapter 9.
13
14
CHAPTER 1 N SQ L QU ER Y P ER FOR MA NC E TU NING
Frequent Recompilation of Execution Plans One of the standard ways of ensuring a reusable execution plan, independent of variable values used in a query, is to use a stored procedure. Using a stored procedure to execute a set of SQL queries allows SQL Server to create a parameterized execution plan. A parameterized execution plan is independent of the parameter values supplied during the execution of the stored procedure, and it is consequently highly reusable. However, the execution plan of the stored procedure can be reused only if SQL Server does not have to recompile the execution plan every time the stored procedure is run. Frequent recompilation vÊ>ÊÃÌÀi`Ê«ÀVi`ÕÀiÊVÀi>ÃiÃÊ«ÀiÃÃÕÀiÊÊÌ
iÊ *1Ê>`ÊÌ
iʵÕiÀÞÊiÝiVÕÌÊÌi°Ê-+Ê-iÀÛiÀÊ has partially addressed this problem with the addition of statement-level recompilation, but it’s not a complete fix to the issue. I will discuss in detail the various causes and resolutions of ÃÌÀi`Ê«ÀVi`ÕÀi]Ê>`ÊÃÌ>ÌiiÌ]ÊÀiV«>ÌÊÊ
>«ÌiÀÊ£ä°
Improper Use of Cursors By preferring aÊVÕÀÃÀL>Ãi`ÊÀÜ>Ì>Ìi®ÊÀiÃÕÌÊÃiÌpÀÊ>ÃÊivvÊ`iÊ
>ÃÊÃÊ>«ÌÞÊÌiÀi`Ê Ì]Ê,ÜÊ ÞÊ}â}Ê,ÜÊ, ,ÆÊ«ÀÕVi`ʺÀiiL>À»®pÃÌi>`ÊvÊ>ÊÀi}Õ>ÀÊÃiÌL>Ãi`Ê-+Ê query, you add a large amount of overhead on SQL Server. Use set-based queries whenever possible, but if you are forced to deal with cursors, be sure to use efficient cursor types such as v>ÃÌvÀÜ>À`ÊÞ°Ê ÝViÃÃÛiÊÕÃiÊvÊivvViÌÊVÕÀÃÀÃÊVÀi>ÃiÃÊÃÌÀiÃÃÊÊ-+Ê-iÀÛiÀÊÀiÃÕÀViÃ]Ê slowing down system performance. I discuss how to work with cursors, if you must, properly Ê
>«ÌiÀÊ£{°
Improper Configuration of the Database Log By failing to follow the general recommendations in configuring a database log, you can >`ÛiÀÃiÞÊ>vviVÌÊÌ
iÊ«iÀvÀ>ViÊvÊ>Ê"iÊ/À>Ã>VÌÊ*ÀViÃÃ}Ê"/*®qL>Ãi`Ê-+Ê Server database. For optimal performance, SQL Server heavily relies on accessing the database logs effectively. Chapter 2 covers how to configure the database log properly.
Excessive Use or Improper Configuration of tempdb There is only one pail`^ for any SQL Server instance. Since temporary storage such as operations involving user objects such as temporary tables and table variables, system objects such as cursors or hash tables for joins, and operations such as sorts and row versioning all use the pail`^ database, pail`^ can become quite a bottleneck. All these options and others that you can use lead to space, I/O, and contention issues within pail`^. I cover some configuration options to help with this in Chapter 2 and other options in other chapters appropriate to the issues addressed by that chapter.
C H A P T E R 1 N S Q L Q U E R Y P E R F O R M A N C E T U N I N G
Summary In this introductory chapter, you have seen that SQL Server performance tuning is an iterative process, consisting of identifying performance bottlenecks, troubleshooting their cause, applying different resolutions, quantifying performance improvements, and then going back to the start until your required performance level is reached. To assist in this process, you should create a system baseline to compare with your modifications. Throughout the performance-tuning process, you need to be very objective about the amount of tuning you want to perform—you can always make a query run a little bit faster, LÕÌÊÃÊÌ
iÊivvÀÌÊÜÀÌ
ÊÌ
iÊVÃ̶Ê>Þ]ÊÃViÊ«iÀvÀ>ViÊ`i«i`ÃÊÊÌ
iÊ«>ÌÌiÀÊvÊÕÃiÀÊ activity and data, you must reevaluate the database server performance on a regular basis. To derive the optimal performance from a SQL Server database system, it is extremely important that you understand the stresses on the server created by the database application. In the next two chapters, I discuss how to analyze these stresses, both at a higher system level and at a lower SQL Server activities level. Then I show how to combine the two. In the rest of the book, you will examine in depth the biggest SQL Server performance killers, as mentioned earlier in the chapter. You will learn how these individual factors can affect performance if used incorrectly and how to resolve or avoid these traps.
15
CHAPTER
2
System Performance Analysis I
n the first chapter, I stressed the importance of having a performance baseline that you can use to measure performance changes. In fact, this is one of the first things you should do when starting the performance-tuning process, since without a baseline you will not be able to quantify improvements. In this chapter, you will learn how to use the Performance Monitor tool to accomplish this and how to use the different performance counters that are required to create a baseline. Specifically, I cover the following topics: Ê
UÊ /
iÊL>ÃVÃÊvÊÌ
iÊ*iÀvÀ>ViÊÌÀÊÌ
Ê
UÊ ÜÊÌÊ>>ÞâiÊ
>À`Ü>ÀiÊÀiÃÕÀViÊLÌÌiiVÃÊÕÃ}Ê*iÀvÀ>ViÊÌÀ
Ê
UÊ ÜÊÌÊÀiÌÀiÛiÊ*iÀvÀ>ViÊÌÀÊ`>Ì>ÊÜÌ
Ê-+Ê-iÀÛiÀÊÕÃ}Ê`Þ>VÊ management views
Ê
UÊ ÜÊÌÊÀiÃÛiÊ
>À`Ü>ÀiÊÀiÃÕÀViÊLÌÌiiVÃ
Ê
UÊ ÜÊÌÊ>>ÞâiÊÌ
iÊÛiÀ>Ê«iÀvÀ>ViÊvÊ-+Ê-iÀÛiÀ
Ê
UÊ ÜÊÌÊVÀi>ÌiÊ>ÊL>ÃiiÊvÀÊÌ
iÊÃÞÃÌi
Performance Monitor Tool Windows Server 2008 provides a tool called Performance Monitor, although all the documentation for it refers to System Monitor°Ê/
ÃÊÌÊÃÊ«>ÀÌÊvÊ>Ê>À}iÀÊÃÕÌiÊvÊ`>Ì>ÊViVÌÊÌÃÊ called the Reliability and Performance Monitor. Performance Monitor collects detailed infor>ÌÊ>LÕÌÊÌ
iÊÕÌâ>ÌÊvÊ«iÀ>Ì}ÊÃÞÃÌiÊÀiÃÕÀViðÊÌÊ>ÜÃÊÞÕÊÌÊÌÀ>VÊi>ÀÞÊiÛiÀÞÊ >ëiVÌÊvÊÃÞÃÌiÊ«iÀvÀ>Vi]ÊVÕ`}ÊiÀÞ]Ê`Ã]Ê«ÀViÃÃÀ]Ê>`ÊÌ
iÊiÌÜÀ°ÊÊ>``Ì]Ê-+Ê-iÀÛiÀÊÓäänÊ«ÀÛ`iÃÊiÝÌiÃÃÊÌÊÌ
iÊ*iÀvÀ>ViÊÌÀÊÌÊÌÊÌÀ>VÊ>ÊÛ>ÀiÌÞÊ vÊvÕVÌ>Ê>Ài>ÃÊÜÌ
Ê-+Ê-iÀÛiÀ° *iÀvÀ>ViÊÌÀÊÌÀ>VÃÊÀiÃÕÀViÊLi
>ÛÀÊLÞÊV>«ÌÕÀ}Ê«iÀvÀ>ViÊ`>Ì>Ê}iiÀ>Ìi`Ê by hardware and software components of the system such as a processor, a process, a thread, >`ÊÃʰÊ/
iÊ«iÀvÀ>ViÊ`>Ì>Ê}iiÀ>Ìi`ÊLÞÊ>ÊÃÞÃÌiÊV«iÌÊÃÊÀi«ÀiÃiÌi`ÊLÞÊ>Êperformance object. A performance object provides counters that represent specific aspects of a component, such as !Lnk_aooknPeia for a Lnk_aookn object.
17
18
CHAPTER 2 N SYS TEM P ER FOR MA NC E A NA L YS IS
/
iÀiÊV>ÊLiÊÕÌ«iÊÃÌ>ViÃÊvÊ>ÊÃÞÃÌiÊV«ḭÊÀÊÃÌ>Vi]ÊÌ
iÊLnk_aookn object in a computer with two processors will have two instances represented as instances 0 and 1. Performance objects with multiple instances may also have an instance called [Pkp]h to Ài«ÀiÃiÌÊÌ
iÊÌÌ>ÊÛ>ÕiÊvÀÊ>ÊÌ
iÊÃÌ>ViðÊÀÊiÝ>«i]ÊÌ
iÊ«ÀViÃÃÀÊÕÃ>}iÊvÊ>ÊV«ÕÌiÀÊ with four processors can be determined using the following performance object, counter, and ÃÌ>ViÊ>ÃÊÃ
ÜÊÊ}ÕÀiÊÓ£®\ Ê
UÊ Performance object: Lnk_aookn
Ê
UÊ Counter: !Lnk_aooknPeia
Ê
UÊ Instance: [Pkp]h
Figure 2-1. Adding a Performance Monitor counter -ÞÃÌiÊLi
>ÛÀÊV>ÊLiÊiÌ
iÀÊÌÀ>Vi`ÊÊÀi>ÊÌiÊÊÌ
iÊvÀÊvÊ}À>«
ÃÊÀÊV>«ÌÕÀi`Ê>ÃÊ>Ê log (called a counter log®ÊvÀÊvviÊ>>ÞÃð /ÊÀÕÊÌ
iÊ*iÀvÀ>ViÊÌÀÊÌ]ÊiÝiVÕÌiÊlanbikj from a command prompt, which ÜÊ«iÊÌ
iÊ,i>LÌÞÊ>`Ê*iÀvÀ>ViÊÌÀÊÃÕÌi°Ê9ÕÊV>Ê>ÃÊÀ}
ÌVVÊÌ
iÊ «ÕÌiÀÊ VÊÊÌ
iÊ`iÃÌ«ÊÀÊ-Ì>ÀÌÊiÕ]ÊiÝ«>`Ê >}ÃÌVÃ]Ê>`ÊÌ
iÊiÝ«>`ÊÌ
iÊ,i>LÌÞÊ>`Ê Performance Monitor. Both will allow you to open the Performance Monitor utility. 9ÕÊÜÊi>ÀÊ
ÜÊÌÊÃiÌÊÕ«ÊÌ
iÊ`Û`Õ>ÊVÕÌiÀÃÊÊÌ
iʺ Ài>Ì}Ê>Ê >Ãii»ÊÃiVÌÊ >ÌiÀÊÊÌ
ÃÊV
>«ÌiÀ°ÊÀÃÌÊÞÕÊÜÊiÝ>iÊÜ
V
ÊVÕÌiÀÃÊÞÕÊÃ
Õ`ÊV
ÃiÊÊÀ`iÀÊÌÊ`iÌvÞÊÃÞÃÌiÊLÌÌiiVÃÊ>`Ê>ÃÊ
ÜÊÞÕÊV>ÊÀiÃÛiÊÃiÊvÊÌ
iÃiÊLÌÌiiVð
C H A P T E R 2 N S Y S T E M P E R F O R M A N C E A N A LY S I S
Dynamic Management Views /Ê}iÌÊ> immediate snapshot of a large amount of data that was formerly available only Ê*iÀvÀ>ViÊÌÀ]Ê-+Ê-iÀÛiÀÊÜÊvviÀÃÊÌ
iÊÃ>iÊ`>Ì>ÊÌiÀ>ÞÊÌ
ÀÕ}
Ê>ÊÃiÌ of `Þ>VÊ>>}iiÌÊÛiÜÃÊ 6îÊ>`Ê`Þ>VÊ>>}iiÌÊvÕVÌÃÊ Ã®°Ê/
iÃiÊ>ÀiÊ iÝÌÀiiÞÊÕÃivÕÊiV
>ÃÃÊvÀÊV>«ÌÕÀ}Ê>ÊÃ>«Ã
ÌÊvÊÌ
iÊVÕÀÀiÌÊ«iÀvÀ>ViÊvÊÞÕÀÊ ÃÞÃÌi°Ê½ÊÌÀ`ÕViÊÃiÛiÀ>ÊvÊÌ
iÃiÊÌ
ÀÕ}
ÕÌÊÌ
iÊL]ÊLÕÌʽÊvVÕÃÊÊ>ÊviÜÊÌ
>ÌÊ>ÀiÊÌ
iÊ most important for monitoring performance and for establishing a baseline. /
iÊouo*`i[ko[lanbkni]j_a[_kqjpano view displaysÊÌ
iÊ-+Ê-iÀÛiÀÊVÕÌiÀÃÊÜÌ
Ê>Ê µÕiÀÞ]Ê>Ü}ÊÞÕÊÌÊ>««ÞÊÌ
iÊvÕÊÃÌÀi}Ì
ÊvÊ/-+ÊÌÊÌ
iÊ`>Ì>Êi`>ÌiÞ°ÊÀÊiÝ>«i]Ê this simple query will return the current value for Hkcejo+oa_: OAHA?P_jpn[r]hqa BNKIouo*`i[ko[lanbkni]j_a[_kqjpano SDANAK>FA?P[J=IA9#IOOMH CB.,,46Cajan]hOp]peope_o# =J@_kqjpan[j]ia9#Hkcejo+oa_# /
ÃÊÀiÌÕÀÃÊÌ
iÊÛ>ÕiÊvÊ£xÊvÀÊÞÊÃiÀÛiÀ°ÊÀÊÞÕÀÊÃiÀÛiÀ]ÊÞÕ½Êii`ÊÌÊÃÕLÃÌÌÕÌiÊÌ
iÊ appropriate server name in the K>FA?P[J=IA comparison. /
iÀiÊ>ÀiÊ>Ê>À}iÊÕLiÀÊvÊ 6ÃÊ>`Ê ÃÊÌ
>ÌÊV>ÊLiÊÕÃi`ÊÌÊ}>Ì
iÀÊvÀ>ÌÊ about the server. Rather than cover them all, I’ll introduce one more that you will find yourself accessing on a regular basis, ouo*`i[ko[s]ep[op]po°Ê/
ÃÊ 6ÊÃ
ÜÃÊ>Ê>}}Ài}>Ìi`ÊÛiÜÊvÊ Ì
iÊÌ
Ài>`ÃÊÜÌ
Ê-+Ê-iÀÛiÀÊÌ
>ÌÊ>ÀiÊÜ>Ì}ÊÊÛ>ÀÕÃÊÀiÃÕÀViÃ]ÊViVÌi`ÊÃViÊÌ
iÊ>ÃÌÊ ÌiÊ-+Ê-iÀÛiÀÊÜ>ÃÊÃÌ>ÀÌi`ÊÀÊÌ
iÊVÕÌiÀÃÊÜiÀiÊÀiÃḭÊ`iÌvÞ}ÊÌ
iÊÌÞ«iÃÊvÊÜ>ÌÃÊÌ
>ÌÊ>ÀiÊ occurring within your system is one of the easiest mechanisms to begin identifying the source vÊÞÕÀÊLÌÌiiVðÊ9ÕÊV>ÊÃÀÌÊÌ
iÊ`>Ì>ÊÊÛ>ÀÕÃÊÜ>ÞÃ]ÊLÕÌÊvÀÊÌ
iÊvÀÃÌÊiÝ>«i]ʽÊÊ>ÌÊ the waits that have the longest current count using this simple query: OAHA?PPKL$-,% & BNKIouo*`i[ko[s]ep[op]po KN@AN>Us]ep[peia[io@AO? }ÕÀiÊÓÓÊ`ë>ÞÃÊÌ
iÊÕÌ«Õ̰
Figure 2-2. Output from ouo*`i[ko[s]ep[op]po You can see not only the cumulative time that particular waits have occurred but also >ÊVÕÌÊvÊ
ÜÊvÌiÊÌ
iÞÊ
>ÛiÊVVÕÀÀi`Ê>`ÊÌ
iÊ>ÝÕÊÌiÊÌ
>ÌÊÃiÌ
}Ê
>`ÊÌÊ Ü>̰ÊÀÊ
iÀi]ÊÞÕÊV>Ê`iÌvÞÊÌ
iÊÜ>ÌÊÌÞ«iÊ>`ÊLi}ÊÌÀÕLiÃ
Ì}°Ê"iÊvÊÌ
iÊÃÌÊ
19
20
CHAPTER 2 N SYS TEM P ER FOR MA NC E A NA L YS IS
VÊÌÞ«iÃÊvÊÜ>ÌÃÊÃÊÉ"°ÊvÊÞÕÊÃiiÊ=OUJ?D[EK[?KILHAPEKJ, EK[?KILHAPEKJ, HKCICN, SNEPAHKC, or L=CAEKH=P?D in your top tenÊÜ>ÌÊÌÞ«iÃ]ÊÞÕÊ>ÞÊLiÊiÝ«iÀiV}ÊÉ"ÊVÌiÌ]Ê >`ÊÞÕÊÜÊÜÊÜ
iÀiÊÌÊÃÌ>ÀÌÊÜÀ}°ÊÀÊ>ÊÀiÊ`iÌ>i`Ê>>ÞÃÃÊvÊÜ>ÌÊÌÞ«iÃÊ>`Ê
ÜÊ ÌÊÕÃiÊÌ
iÊ>ÃÊ>ÊÌÀ}ÊÌÊÜÌ
Ê-+Ê-iÀÛiÀ]ÊÀi>`ÊÌ
iÊVÀÃvÌÊÜ
ÌiÊ«>«iÀʺ-+Ê -iÀÛiÀÊÓääxÊ7>ÌÃÊ>`Ê+ÕiÕiûÊdppl6++sss*ie_nkokbp*_ki+pa_djap+lnk`pa_djkh+omh+ ^aopln]_pe_a+lanbkni]j_a[pqjejc[s]epo[mqaqao*iolt®°ÊÌ
Õ}
ÊÌÊÜ>ÃÊÜÀÌÌiÊvÀÊ-+Ê -iÀÛiÀÊÓääx]ÊÌÊÃÊiµÕ>ÞÊ>««V>LiÊÌÊ-+Ê-iÀÛiÀÊÓään°
Hardware Resource Bottlenecks /Þ«V>Þ]Ê-+Ê-iÀÛiÀÊ`>Ì>L>Ãi performance is affected by stress on the following hardware resources: Ê
UÊ iÀÞ
Ê
UÊ ÃÊÉ"
Ê
UÊ *ÀViÃÃÀ
Ê
UÊ
iÌÜÀ
-ÌÀiÃÃÊLiÞ`ÊÌ
iÊV>«>VÌÞÊvÊ>Ê
>À`Ü>ÀiÊÀiÃÕÀViÊvÀÃÊ>ÊLÌÌiiV°Ê/Ê>``ÀiÃÃÊÌ
iÊ ÛiÀ>Ê«iÀvÀ>ViÊvÊ>ÊÃÞÃÌi]ÊÞÕÊii`ÊÌÊ`iÌvÞÊÌ
iÃiÊLÌÌiiVÃ]ÊLiV>ÕÃiÊÌ
iÞÊvÀÊÌ
iÊ limit on overall system performance.
Identifying Bottlenecks /
iÀiÊÃÊÕÃÕ>ÞÊ>ÊÀi>ÌÃ
«ÊLiÌÜiiÊÀiÃÕÀViÊLÌÌiiVðÊÀÊiÝ>«i]Ê>Ê«ÀViÃÃÀÊ LÌÌiiVÊ>ÞÊLiÊ>ÊÃÞ«ÌÊvÊiÝViÃÃÛiÊ«>}}ÊiÀÞÊLÌÌiiV®ÊÀÊ>ÊÃÜÊ`ÃÊ`ÃÊ LÌÌiiV®°ÊvÊ>ÊÃÞÃÌiÊÃÊÜÊÊiÀÞ]ÊV>ÕÃ}ÊiÝViÃÃÛiÊ«>}}]Ê>`Ê
>ÃÊ>ÊÃÜÊ`Ã]ÊÌ
iÊ iÊvÊÌ
iÊi`ÊÀiÃÕÌÃÊÜÊLiÊ>Ê«ÀViÃÃÀÊÜÌ
Ê
}
ÊÕÌâ>ÌÊÃViÊÌ
iÊ«ÀViÃÃÀÊ
>ÃÊÌÊëi`Ê >ÊÃ}vV>ÌÊÕLiÀÊvÊ *1ÊVÞViÃÊÌÊÃÜ>«Ê«>}iÃÊÊ>`ÊÕÌÊvÊÌ
iÊiÀÞÊ>`ÊÌÊ>>}iÊ Ì
iÊÀiÃÕÌ>ÌÊ
}
ÊÕLiÀÊvÊÉ"ÊÀiµÕiÃÌðÊ,i«>V}ÊÌ
iÊ«ÀViÃÃÀÊÜÌ
Ê>Êv>ÃÌiÀÊiÊ>ÞÊ
i«Ê >ÊÌÌi]ÊLÕÌÊÌÊÜÕ`ÊÌÊLiÊÌ
iÊLiÃÌÊÛiÀ>ÊÃÕ̰ÊÊ>ÊV>ÃiÊiÊÌ
Ã]ÊVÀi>Ã}ÊiÀÞÊÃÊ>Ê ÀiÊ>««À«À>ÌiÊÃÕÌ]ÊLiV>ÕÃiÊÌÊÜÊ`iVÀi>ÃiÊ«ÀiÃÃÕÀiÊÊÌ
iÊ`ÃÊ>`Ê«ÀViÃÃÀÊ>ÃÊÜi°Ê
ÛiÊÕ«}À>`}ÊÌ
iÊ`ÃÊÜÊ«ÀL>LÞÊLiÊ>ÊLiÌÌiÀÊÃÕÌÊÌ
>ÊÕ«}À>`}ÊÌ
iÊ«ÀViÃÃÀ°
NNote The most common performance problem is usually I/O, either from memory or from the disk.
"iÊvÊÌ
iÊLiÃÌÊÜ>ÞÃÊvÊV>Ì}Ê>ÊLÌÌiiVÊÃÊÌÊ`iÌvÞÊÀiÃÕÀViÃÊÌ
>ÌÊ>ÀiÊÜ>Ì}ÊvÀÊ some other resource to complete its operation. You can use Performance Monitor counters ÀÊ 6ÃÊÃÕV
Ê>ÃÊouo*`i[ko[s]ep[op]poÊÌÊ}>Ì
iÀÊÌ
>ÌÊvÀ>̰Ê/
iÊÀiëÃiÊÌiÊvÊ>Ê request served by a resource includes the time the request had to wait in the resource queue, >ÃÊÜiÊ>ÃÊÌ
iÊÌiÊÌ>iÊÌÊiÝiVÕÌiÊÌ
iÊÀiµÕiÃÌ]ÊÃÊi`ÊÕÃiÀÊÀiëÃiÊÌiÊÃÊ`ÀiVÌÞÊ«À«Àtional to the amount of queuing in a system.
C H A P T E R 2 N S Y S T E M P E R F O R M A N C E A N A LY S I S
ÀÊiÝ>«i]ÊVÃ`iÀÊÌ
>ÌÊÌ
iÊ`ÃÊÃÕLÃÞÃÌiÊ
>ÃÊ>Ê`ÃʵÕiÕiÊi}Ì
ÊvÊ£ä°Ê-ViÊÌ
iÊ `ÃÊÃÕLÃÞÃÌiÊ>Ài>`ÞÊ
>ÃÊ«i`}Ê`ÃÊÀiµÕiÃÌÃÊÊÌ]Ê>ÊiÜÊ`ÃÊÀiµÕiÃÌÊ
>ÃÊÌÊÜ>ÌÊÕÌÊÌ
iÊ «ÀiÛÕÃÊ`ÃÊÀiµÕiÃÌÃÊV«iÌi°ÊvÊÌ
iÊÌiÊÌ>iÊLÞÊ>Ê>ÛiÀ>}iÊ`ÃÊÌÀ>ÃviÀÊÃÊiÊÃiV`]Ê Ì
iÊÌ
iÊiÜÊ`ÃÊÀiµÕiÃÌÊ
>ÃÊÌÊÜ>ÌÊvÀÊ>LÕÌÊÌiÊÃiV`ÃÊLivÀiÊ}iÌÌ}ÊÌ
iÊ>ÌÌiÌÊvÊÌ
iÊ `ÃÊÃÕLÃÞÃÌi°Ê/
iÀivÀi]ÊÌ
iÊÌÌ>ÊÀiëÃiÊÌiÊvÊÌ
iÊ`ÃÊÀiµÕiÃÌÊÜÊLiÊÌiÊÃiV`ÃÊÜ>ÌÊ Ìi]Ê«ÕÃÊiÊÃiV`Ê`ÃÊÌÀ>ÃviÀÊÌi° iÊ>Ü>ÀiÊÌ
>ÌÊÌ
iÊ>LÃiViÊvÊ>ʵÕiÕiÊ`iÃÊÌÊi>ÊÌ
>ÌÊÌ
iÀiÊÃÊÊLÌÌiiV°Ê7
iÊ µÕiÕiÊi}Ì
ÃÊÃÌ>ÀÌÊ}ÀÜ}]Ê
ÜiÛiÀ]ÊÌÊÃÊ>ÊÃÕÀiÊÃ}ÊÌ
>ÌÊÌ
iÊÃÞÃÌiÊÃÊÌÊ>LiÊÌÊii«ÊÕ«Ê with the demand. ÌÊ>ÊÀiÃÕÀViÃÊ
>ÛiÊëiVvVÊVÕÌiÀÃÊÌ
>ÌÊÃ
ÜʵÕiÕ}ÊiÛiÃ]ÊLÕÌÊÃÌÊÀiÃÕÀViÃÊ
>ÛiÊÃiÊVÕÌiÀÃÊÌ
>ÌÊÀi«ÀiÃiÌÊ>ÊÛiÀVÌÌ>ÊvÊÌ
>ÌÊÀiÃÕÀVi°ÊÀÊiÝ>«i]ÊiÀÞÊ has no such counter, but a large number of hard page faults represents the overcommittal of «
ÞÃV>ÊiÀÞÊ
>À`Ê«>}iÊv>ÕÌÃÊ>ÀiÊiÝ«>i`Ê>ÌiÀÊÊÌ
iÊV
>«ÌiÀÊÊÌ
iÊÃiVÌʺ*>}iÃÉÃiVÊ >`Ê*>}iÊ>ÕÌÃÉÃiVÊ ÕÌiÀû®°Ê"Ì
iÀÊÀiÃÕÀViÃ]ÊÃÕV
Ê>ÃÊÌ
iÊ«ÀViÃÃÀÊ>`Ê`Ã]Ê
>ÛiÊëiVvVÊ VÕÌiÀÃÊÌÊ`V>ÌiÊÌ
iÊiÛiÊvʵÕiÕ}°ÊÀÊiÝ>«i]ÊÌ
i counter L]caHebaAtla_p]j_u `V>ÌiÃÊ
ÜÊ}Ê>Ê«>}iÊÜÊÃÌ>ÞÊÊÌ
iÊLÕvviÀÊ«ÊÜÌ
ÕÌÊLi}ÊÀiviÀiVi`°Ê/
ÃÊÃÊ>Ê `V>ÌÀÊvÊ
ÜÊÜiÊ-+Ê-iÀÛiÀÊÃÊ>LiÊÌÊ>>}iÊÌÃÊiÀÞ]ÊÃViÊ>Ê}iÀÊviÊi>ÃÊÌ
>ÌÊ >Ê«iViÊvÊ`>Ì>ÊÊÌ
iÊLÕvviÀÊÜÊLiÊÌ
iÀi]Ê>Û>>Li]ÊÜ>Ì}ÊvÀÊÌ
iÊiÝÌÊÀiviÀiVi°ÊÜiÛiÀ]Ê >ÊÃ
ÀÌiÀÊviÊi>ÃÊÌ
>ÌÊ-+Ê-iÀÛiÀÊÃÊÛ}Ê«>}iÃÊÊ>`ÊÕÌÊvÊÌ
iÊLÕvviÀʵÕVÞ]Ê«ÃÃLÞÊ ÃÕ}}iÃÌ}Ê>ÊiÀÞÊLÌÌiiV° You will see which counters to use inÊ>>Þâ}Êi>V
ÊÌÞ«iÊvÊLÌÌiiVÊÃ
ÀÌÞ°
Bottleneck Resolution "ViÊÞÕÊ
>ÛiÊ`iÌvi`ÊLÌÌiiVÃ]ÊÞÕÊV>ÊÀiÃÛiÊÌ
iÊÊÌÜÊÜ>ÞÃ\ Ê
UÊ 9ÕÊV>ÊVÀi>ÃiÊÀiÃÕÀViÊÌ
ÀÕ}
«Õ̰
Ê
UÊ 9ÕÊV>Ê`iVÀi>ÃiÊÌ
iÊ>ÀÀÛ>ÊÀ>ÌiÊvÊÀiµÕiÃÌÃÊÌÊÌ
iÊÀiÃÕÀVi°
VÀi>Ã}ÊÌ
iÊÌ
ÀÕ}
«ÕÌÊÕÃÕ>ÞÊÀiµÕÀiÃÊiÝÌÀ>ÊÀiÃÕÀViÃÊÃÕV
Ê>ÃÊiÀÞ]Ê`ÃÃ]Ê«ÀViÃÃÀÃ]ÊÀÊiÌÜÀÊ>`>«ÌiÀðÊ9ÕÊV>Ê`iVÀi>ÃiÊÌ
iÊ>ÀÀÛ>ÊÀ>ÌiÊLÞÊLi}ÊÀiÊÃiiVÌÛiÊ>LÕÌÊ Ì
iÊÀiµÕiÃÌÃÊÌÊ>ÊÀiÃÕÀVi°ÊÀÊiÝ>«i]ÊÜ
iÊÞÕÊ
>ÛiÊ>Ê`ÃÊÃÕLÃÞÃÌiÊLÌÌiiV]ÊÞÕÊV>Ê iÌ
iÀÊVÀi>ÃiÊÌ
iÊÌ
ÀÕ}
«ÕÌÊvÊÌ
iÊ`ÃÊÃÕLÃÞÃÌiÊÀÊ`iVÀi>ÃiÊÌ
iÊ>ÕÌÊvÊÉ"ÊÀiµÕiÃÌð VÀi>Ã}ÊÌ
iÊÌ
ÀÕ}
«ÕÌÊi>ÃÊ>``}ÊÀiÊ`ÃÃÊÀÊÕ«}À>`}ÊÌÊv>ÃÌiÀÊ`ÃÃ°Ê iVÀi>Ã}ÊÌ
iÊ>ÀÀÛ>ÊÀ>ÌiÊi>ÃÊ`iÌvÞ}ÊÌ
iÊV>ÕÃiÊvÊ
}
ÊÉ"ÊÀiµÕiÃÌÃÊÌÊÌ
iÊ`ÃÊÃÕLÃÞÃÌiÊ>`Ê >««Þ}ÊÀiÃÕÌÃÊÌÊ`iVÀi>ÃiÊÌ
iÀÊÕLiÀ°Ê9ÕÊ>ÞÊLiÊ>LiÊÌÊ`iVÀi>ÃiÊÌ
iÊÉ"ÊÀiµÕiÃÌÃ]Ê vÀÊiÝ>«i]ÊLÞÊ>``}Ê>««À«À>ÌiÊ`iÝiÃÊÊ>ÊÌ>LiÊÌÊÌÊÌ
iÊ>ÕÌÊvÊ`>Ì>Ê>VViÃÃi`ÊÀÊ by partitioning a tableÊLiÌÜiiÊÕÌ«iÊ`Ãð
Memory Bottleneck Analysis Memory can be aÊ«ÀLi>ÌVÊLÌÌiiVÊLiV>ÕÃiÊ>ÊLÌÌiiVÊÊiÀÞÊÜÊ>viÃÌÊÊ Ì
iÀÊÀiÃÕÀViÃ]Ḛ̂Ê/
ÃÊÃÊ«>ÀÌVÕ>ÀÞÊÌÀÕiÊvÀÊ>ÊÃÞÃÌiÊÀÕ}Ê-+Ê-iÀÛiÀ°Ê7
iÊ-+Ê -iÀÛiÀÊÀÕÃÊÕÌÊvÊV>V
iÊÀÊiÀÞ®]Ê>Ê«ÀViÃÃÊÜÌ
Ê-+Ê-iÀÛiÀÊV>i`Êlazy writer®Ê
>Ã ÌÊÜÀÊiÝÌiÃÛiÞÊÌÊ>Ì>ÊiÕ}
ÊvÀiiÊÌiÀ>ÊiÀÞÊ«>}iÃÊÜÌ
Ê-+Ê-iÀÛiÀ°Ê/
ÃÊ VÃÕiÃÊiÝÌÀ>Ê *1ÊVÞViÃÊ>`Ê«iÀvÀÃÊ>``Ì>Ê«
ÞÃV>Ê`ÃÊÉ"ÊÌÊÜÀÌiÊiÀÞÊ«>}iÃÊ L>VÊÌÊ`ð
21
22
CHAPTER 2 N SYS TEM P ER FOR MA NC E A NA L YS IS
SQL Server Memory Management -+Ê-iÀÛiÀÊ>>}ià memory for databases, including memory requirements for data and µÕiÀÞÊiÝiVÕÌÊ«>Ã]ÊÊ>Ê>À}iÊ«ÊvÊiÀÞÊV>i`ÊÌ
iÊmemory pool°Ê/
iÊiÀÞÊ«Ê consists of a collection of 8KB buffers to manage data pages and plan cache pages, free pages, >`ÊÃÊvÀÌ
°Ê/
iÊiÀÞÊ«ÊÃÊÕÃÕ>ÞÊÌ
iÊ>À}iÃÌÊ«ÀÌÊvÊ-+Ê-iÀÛiÀÊiÀÞ°Ê-+Ê -iÀÛiÀÊ>>}iÃÊiÀÞÊLÞÊ}ÀÜ}ÊÀÊÃ
À}ÊÌÃÊiÀÞÊ«ÊÃâiÊ`Þ>V>Þ° You canÊVv}ÕÀiÊ-+Ê-iÀÛiÀÊvÀÊ`Þ>VÊiÀÞÊ>>}iiÌÊÊ-+Ê-iÀÛiÀÊ>>}iiÌÊ-ÌÕ`Ê---®°ÊÊÌÊÌ
iÊiÀÞÊv`iÀÊvÊÌ
iÊ-iÀÛiÀÊ*À«iÀÌiÃÊ`>}ÊLÝ]Ê>ÃÊÃ
ÜÊÊ }ÕÀiÊÓΰ
Figure 2-3. SQL Server memory configuration /
iÊ`Þ>VÊiÀÞÊÀ>}iÊÃÊVÌÀi`ÊÌ
ÀÕ}
ÊÌÜÊVv}ÕÀ>ÌÊ«À«iÀÌiÃ\Ê Iejeiqi$I>% and I]teiqi$I>%.
C H A P T E R 2 N S Y S T E M P E R F O R M A N C E A N A LY S I S
Ê
UÊ Iejeiqi$I>%]Ê>ÃÊÜÊ>ÃÊiejoanraniaiknu]ÊÜÀÃÊ>ÃÊ>ÊvÀÊÛ>ÕiÊvÀÊÌ
iÊiÀÞÊ «°Ê"ViÊÌ
iÊiÀÞÊ«ÊÀi>V
iÃÊÌ
iÊÃ>iÊÃâiÊ>ÃÊÌ
iÊvÀÊÛ>Õi]Ê-+Ê-iÀÛiÀÊV>Ê VÌÕiÊVÌÌ}Ê«>}iÃÊÊÌ
iÊiÀÞÊ«]ÊLÕÌÊÌÊV>ÌÊLiÊÃ
ÀÕÊÌÊiÃÃÊÌ
>Ê Ì
iÊvÀÊÛ>Õi°Ê ÌiÊÌ
>ÌÊ-+Ê-iÀÛiÀÊ`iÃÊÌÊÃÌ>ÀÌÊÜÌ
ÊÌ
iÊiejoanraniaiknu configuration value but commits memory dynamically, as needed.
Ê
UÊ I]teiqi$I>%]Ê>ÃÊÜÊ>Ã i]toanraniaiknu, serves as a ceiling value to limit the >ÝÕÊ}ÀÜÌ
ÊvÊÌ
iÊiÀÞÊ«°Ê/
iÃiÊVv}ÕÀ>ÌÊÃiÌÌ}ÃÊÌ>iÊivviVÌÊidiately and do not require a restart.
VÀÃvÌÊÀiVi`ÃÊÌ
>ÌÊÞÕÊÕÃiÊ`Þ>VÊiÀÞÊVv}ÕÀ>ÌÊvÀÊ-+Ê-iÀÛiÀ]Ê where iejoanraniaiknu will be 0 and i]toanraniaiknuÊÜÊLiÊÌ
iÊ>ÝÕÊ«
ÞÃV>Ê memory of the system, assuming a single instance on the machine. You should not run other iÀÞÌiÃÛiÊ>««V>ÌÃÊÊÌ
iÊÃ>iÊÃiÀÛiÀÊ>ÃÊ-+Ê-iÀÛiÀ]ÊLÕÌÊvÊÞÕÊÕÃÌ]ÊÊÀiVmend you first get estimates on how much memory is needed by other applications and then Vv}ÕÀiÊ-+Ê-iÀÛiÀÊÜÌ
Ê>Êi]toanraniaiknu value set to prevent the other applications vÀÊÃÌ>ÀÛ}Ê-+Ê-iÀÛiÀÊvÊiÀÞ°Ê"Ê>ÊÃÞÃÌiÊÜ
iÀiÊ-+Ê-iÀÛiÀÊÃÊÀÕ}ÊÊÌÃÊÜ]ÊÊ «ÀiviÀÊÌÊÃiÌÊÌ
iÊÕÊÃiÀÛiÀÊiÀÞÊiµÕ>ÊÌÊÌ
iÊ>ÝÊÛ>ÕiÊ>`ÊëÞÊ`ë>ÌV
ÊÜÌ
Ê `Þ>VÊ>>}iḭÊ"Ê>ÊÃiÀÛiÀÊÜÌ
ÊÕÌ«iÊ-+Ê-iÀÛiÀÊÃÌ>ViÃ]ÊÞÕ½Êii`ÊÌÊ>`ÕÃÌÊ Ì
iÃiÊiÀÞÊÃiÌÌ}ÃÊÌÊiÃÕÀiÊi>V
ÊÃÌ>ViÊ
>ÃÊ>Ê>`iµÕ>ÌiÊÛ>Õi°ÊÕÃÌÊ>iÊÃÕÀiÊÞÕ½ÛiÊ ivÌÊiÕ}
ÊiÀÞÊvÀÊÌ
iÊ«iÀ>Ì}ÊÃÞÃÌiÊ>`ÊiÝÌiÀ>Ê«ÀViÃÃiÃ]Ê>ÃÊÜiÊ>ÃÊLÕvviÀ pool memory (which used to be called IaiPkHa]ra®° iÀÞÊÜÌ
Ê-+Ê-iÀÛiÀÊV>ÊLiÊÀÕ}
ÞÊ`Û`i`ÊÌÊLÕvviÀÊ«ÊiÀÞ]ÊÜ
V
ÊÀi«ÀiÃiÌÃÊ`>Ì>Ê«>}iÃÊ>`ÊvÀiiÊ«>}iÃ]Ê>`ÊLÕvviÀÊiÀÞ]ÊÜ
V
ÊVÃÃÌÃÊvÊÌ
Ài>`Ã]Ê Ã]Ê i`ÊÃiÀÛiÀÃ]Ê>`ÊÌ
iÀðÊÃÌÊvÊÌ
iÊiÀÞÊÕÃi`ÊLÞÊ-+Ê-iÀÛiÀÊ}iÃÊÌÊÌ
iÊLÕvviÀÊ«°
NNote SQL Server does consume more memory than simply that specified by the i]t[oanran[iaiknu setting.
You can also manage the configuration values for iejoanraniaiknu and i]toanran iaiknu by using the ol[_kjbecqnaÊÃÞÃÌiÊÃÌÀi`Ê«ÀVi`ÕÀi°Ê/ÊÃiiÊÌ
iÊVv}ÕÀ>ÌÊÛ>ÕiÃÊvÀÊ Ì
iÃiÊ«>À>iÌiÀÃ]ÊiÝiVÕÌiÊÌ
iÊol[_kjbecqna stored procedure as follows: ata_ol[_kjbecqna#iejoanraniaiknu$I>%# ata_ol[_kjbecqna#i]toanraniaiknu$I>%# }ÕÀiÊÓ{ÊÃ
ÜÃÊÌ
iÊÀiÃÕÌÊvÊÀÕ}ÊÌ
iÃiÊV>`ð
Figure 2-4. SQL Server memory configuration properties
23
24
CHAPTER 2 N SYS TEM P ER FOR MA NC E A NA L YS IS
ÌiÊÌ
>ÌÊÌ
iÊ`iv>ÕÌÊÛ>ÕiÊvÀÊÌ
iÊiejoanraniaiknu setting is 0MB, and for the i]t oanraniaiknuÊÃiÌÌ}ÊÌÊÃÊÓ£{Ç{nÎÈ{Ç °ÊÃ]Êi]toanraniaiknu cannot be set to less Ì
>Ê{ ° You can also modify these configuration values using the ol[_kjbecqna stored procedure. ÀÊiÝ>«i]ÊÌÊÃiÌÊi]toanraniaiknu to 200MB and iejoanraniaiknuÊÌÊ£ää ]ÊiÝiVÕÌiÊ the following set of statements (oap[iaiknu*omhÊÊÌ
iÊ`Ü>`®\ QOAi]opan ATA?ol[_kjbecqna#odks]`r]j_a`klpekj#(#-# NA?KJBECQNA ata_ol[_kjbecqna#iejoanraniaiknu$I>%#(-,, ata_ol[_kjbecqna#i]toanraniaiknu$I>%#(.,, NA?KJBECQNASEPDKRANNE@A /
iÊiejoanraniaiknu and i]toanraniaiknu configurations are classified as advanced options. By default, the ol[_kjbecqna stored procedure does not affect/display the advanced options. Setting odks]`r]j_a`klpekj to - as shown previously enables the ol[_kjbecqna stored procedure to affect/display the advanced options. /
iÊNA?KJBECQNA statement updates the memory configuration values set by ol[_kjbecqna. Since ad hoc updates to the system catalog containing the memory configuration values are not recommended, the KRANNE@A flag is used with the NA?KJBECQNA statement to force the memory configuration. If you do the memory configuration through Management Studio, >>}iiÌÊ-ÌÕ`Ê>ÕÌ>ÌV>ÞÊiÝiVÕÌiÃÊÌ
iÊNA?KJBECQNASEPDKRANNE@A statement after the configuration setting. ÊÃiÊÀ>ÀiÊVÀVÕÃÌ>ViÃ]ÊÞÕÊ>ÞÊii`ÊÌÊ>ÜÊvÀÊ-+Ê-iÀÛiÀÊÃ
>À}Ê>ÊÃÞÃÌi½ÃÊ iÀÞ°Ê/Êi>LÀ>Ìi]ÊVÃ`iÀÊ>ÊV«ÕÌiÀÊÜÌ
Ê-+Ê-iÀÛiÀÊ>`Ê ÝV
>}iÊ-iÀÛiÀÊÀÕ}Ê ÊÌ°Ê Ì
ÊÃiÀÛiÀÃÊ>ÀiÊ
i>ÛÞÊÕÃiÀÃÊvÊiÀÞÊ>`ÊÌ
ÕÃÊii«Ê«ÕÃ
}Êi>V
ÊÌ
iÀÊvÀÊiÀÞ°Ê /
iÊ`Þ>ViÀÞÊLi
>ÛÀÊvÊ-+Ê-iÀÛiÀÊ>ÜÃÊÌÊÌÊÀii>ÃiÊiÀÞÊÌÊ ÝV
>}iÊ-iÀÛiÀÊ >ÌÊiÊÃÌ>ViÊ>`Ê}À>LÊÌÊL>VÊ>ÃÊ ÝV
>}iÊ-iÀÛiÀÊÀii>ÃiÃḚ̂Ê9ÕÊV>Ê>Û`ÊÌ
ÃÊ`Þ>V iÀÞÊ>>}iiÌÊÛiÀ
i>`ÊLÞÊVv}ÕÀ}Ê-+Ê-iÀÛiÀÊvÀÊ>ÊvÝi`ÊiÀÞÊÃâi°ÊÜiÛiÀ]Ê «i>ÃiÊii«ÊÊ`ÊÌ
>ÌÊÃViÊ-+Ê-iÀÛiÀÊÃÊ>ÊiÝÌÀiiÞÊÀiÃÕÀViÌiÃÛiÊ«ÀViÃÃ]ÊÌÊÃÊ
}
ÞÊÀiVi`i`ÊÌ
>ÌÊÞÕÊ
>ÛiÊ>Ê`i`V>Ìi`Ê-+Ê-iÀÛiÀÊ«À`ÕVÌÊ>V
i° ÜÊÌ
>ÌÊÞÕÊÕ`iÀÃÌ>`Ê-+Ê-iÀÛiÀÊiÀÞÊ>>}iiÌ]Êi̽ÃÊVÃ`iÀÊÌ
iÊperfor>ViÊVÕÌiÀÃÊÞÕÊV>ÊÕÃiÊÌÊ>>ÞâiÊÃÌÀiÃÃÊÊiÀÞ]Ê>ÃÊÃ
ÜÊÊ/>LiÊÓ£° Table 2-1. Performance Monitor Counters to Analyze Memory Pressure
Object(Instance[,InstanceN])
Counter
Description
Values
Iaiknu
=r]eh]^ha>upao
Àii physical memory
System dependent
L]cao+oa_
Rate of hard page faults
Average Û>ÕiÊÊxä
L]caB]qhpo+oa_
Rate of total page faults
«>ÀiÊÜÌ
Ê its baseline value for trend analysis
L]caoEjlqp+oa_
Rate of input page faults
L]caoKqplqp+oa_
Rate of output page faults
C H A P T E R 2 N S Y S T E M P E R F O R M A N C E A N A LY S I S
Object(Instance[,InstanceN])
Counter
Description
Values
OMHOanran6>qbbanI]j]can
>qbban_]_da depn]pek
Percentage of requests served out of buffer cache
Average value q 90%
L]caHeba Atla_p]j_u
/i page spends in buffer
Average Û>ÕiÊÊÎää
?da_glkejp L]cao+oa_
PagesÊÜÀÌÌiÊÌÊ`ÃÊLÞÊ V
iV«Ì
Average Û>ÕiÊÊÎä
H]vusnepao+oa_
ÀÌÞ aged pages flushed from buffer
Average value < 20
IaiknuCn]jpo Laj`ejc
ÕLiÀ of processes waiting for memory grant
Average value = 0
P]ncapOanran Iaiknu$G>%
>ÝÕ physical iÀÞÊ-+Ê-iÀÛiÀÊV>Ê VÃÕiÊÊÌ
iÊLÝ
ÃiÊÌÊÃâiÊ of physical memory
Pkp]hOanran Iaiknu$G>%
Physical memory curÀiÌÞÊ>ÃÃ}i`ÊÌÊ-+Ê Server
P]ncapOanran Iaiknu$G>%
Lner]pa>upao
-âi]Ê bytes, of memory that this process has allocated that cannot be shared with other processes
OMHOanran6IaiknuI]j]can
Lnk_aoo
ÃiÊÌÊ
½ÊÜÊÜ>ÊÞÕÊÌ
ÀÕ}
ÊÌ
iÃiÊVÕÌiÀÃÊÌÊ}iÌÊ>ÊLiÌÌiÀÊ`i> of what you can use them for.
Available Bytes /
iÊ=r]eh]^ha>upaoÊVÕÌiÀÊÀi«ÀiÃiÌÃÊvÀiiÊ«
ÞÃV>ÊiÀÞÊÊÌ
iÊÃÞÃÌi°ÊÀÊ}`Ê«iÀvÀ>Vi]ÊÌ
ÃÊVÕÌiÀÊÛ>ÕiÊÃ
Õ`ÊÌÊLiÊÌÊܰÊvÊ-+Ê-iÀÛiÀÊÃÊVv}ÕÀi`ÊvÀÊ`Þ>VÊ memory usage, then this value will be controlled by calls to a Windows API that determines Ü
iÊ>`Ê
ÜÊÕV
ÊiÀÞÊÌÊÀii>Ãi°Ê ÝÌi`i`Ê«iÀ`ÃÊvÊÌiÊÜÌ
ÊÌ
ÃÊÛ>ÕiÊÛiÀÞÊÜÊ>`Ê -+Ê-iÀÛiÀÊiÀÞÊÌÊV
>}}Ê`V>ÌiÃÊÌ
>ÌÊÌ
iÊÃiÀÛiÀÊÃÊÕ`iÀÊÃiÛiÀiÊiÀÞÊÃÌÀiÃð
Pages/sec and Page Faults/sec Counters /ÊÕ`iÀÃÌ>`ÊÌ
iÊ«ÀÌ>ViÊvÊÌ
iÊL]cao+oa_ and L]caB]qhpo+oa_ counters, you first need to learn about page faults. A page fault occurs when a process requires code or data that is not in its working setÊÌÃÊë>ViÊÊ«
ÞÃV>ÊiÀÞ®°ÊÌÊ>ÞÊi>`ÊÌÊ>ÊÃvÌÊ«>}iÊv>ÕÌÊÀÊ>Ê
>À`Ê«>}iÊ fault. If the faulted page is found elsewhere in physical memory, then it is called a soft page fault. A hard page faultÊVVÕÀÃÊÜ
iÊ>Ê«ÀViÃÃÊÀiµÕÀiÃÊV`iÊÀÊ`>Ì>ÊÌ
>ÌÊÃÊÌÊÊÌÃÊÜÀ}Ê ÃiÌÊÀÊiÃiÜ
iÀiÊÊ«
ÞÃV>ÊiÀÞÊ>`ÊÕÃÌÊLiÊÀiÌÀiÛi`ÊvÀÊ`ð /
iÊëii`ÊvÊ>Ê`ÃÊ>VViÃÃÊÃÊÊÌ
iÊÀ`iÀÊvÊÃiV`Ã]ÊÜ
iÀi>ÃÊ>ÊiÀÞÊ>VViÃÃÊÃÊÊ Ì
iÊÀ`iÀÊvÊ>ÃiV`ðÊ/
ÃÊ
Õ}iÊ`vviÀiViÊÊÌ
iÊëii`ÊLiÌÜiiÊ>Ê`ÃÊ>VViÃÃÊ>`Ê>ÊiÀÞÊ>VViÃÃÊ>iÃÊÌ
iÊivviVÌÊvÊ
>À`Ê«>}iÊv>ÕÌÃÊÃ}vV>ÌÊV«>Ài`ÊÌÊÌ
>ÌÊvÊÃvÌÊ«>}iÊv>ÕÌð /
iÊL]cao+oa_ÊVÕÌiÀÊÀi«ÀiÃiÌÃÊÌ
iÊÕLiÀÊvÊ«>}iÃÊÀi>`ÊvÀÊÀÊÜÀÌÌiÊÌÊ`ÃÊ«iÀÊ ÃiV`ÊÌÊÀiÃÛiÊ
>À`Ê«>}iÊv>ÕÌðÊ/
iÊL]caB]qhpo+oa_ performance counter indicates the total page faults per second—soft page faults plus hard page faults—handled by the system.
25
26
CHAPTER 2 N SYS TEM P ER FOR MA NC E A NA L YS IS
>À`Ê«>}iÊv>ÕÌÃ]Ê`V>Ìi`ÊLÞÊL]cao+oa_, should not be consistently high. If this counter ÃÊVÃÃÌiÌÞÊÛiÀÞÊ
}
]ÊÌ
iÊ-+Ê-iÀÛiÀÊÃÊ«ÀL>LÞÊÃÌ>ÀÛ}ÊÌ
iÀÊ>««V>ÌðÊ/
iÀiÊ>ÀiÊÊ hard and fast numbers for what indicates a problem, because these numbers will vary widely LiÌÜiiÊÃÞÃÌiÃÊL>Ãi`ÊÊÌ
iÊ>ÕÌÊ>`ÊÌÞ«iÊvÊiÀÞÊ>ÃÊÜiÊ>ÃÊÌ
iÊëii`ÊvÊ`ÃÊ>VViÃÃÊ on the system. If the L]cao+oa_ counter isÊÛiÀÞÊ
}
]ÊÌ
iÊÞÕÊV>ÊLÀi>ÊÌÊÕ«ÊÌÊL]caoEjlqp+oa_ and L]caoKqplqp+oa_: Ê
UÊ L]caoEjlqp+oa_: An application will wait only on an input page, not on an output page.
Ê
UÊ L]caoKqplqp+oa_: Page output will stress the system, but an application usually does not see this stress. Pages output are usually represented by the application’s dirty «>}iÃÊÌ
>ÌÊii`ÊÌÊLiÊL>Vi`ÊÕÌÊÌÊÌ
iÊ`ðÊL]caoKqplqp+oa_ is an issue only when `ÃÊ>`ÊLiViÊ>ÊÃÃÕi°
Ã]ÊV
iVÊLnk_aoo6L]caB]qhpo+oa_ÊÌÊv`ÊÕÌÊÜ
V
Ê«ÀViÃÃÊÃÊV>ÕÃ}ÊiÝViÃÃÛiÊ paging in case of high L]cao+oa_°Ê/
iÊLnk_aoo object is the system component that provides performance data for the processes running on the system, which are individually represented by their corresponding instance name. ÀÊiÝ>«i]ÊÌ
iÊ-+Ê-iÀÛiÀÊ«ÀViÃÃÊÃÊÀi«ÀiÃiÌi`ÊLÞÊÌ
iÊomhoanrn instance of the Lnk_aooÊLiV̰Ê}
ÊÕLiÀÃÊvÀÊÌ
ÃÊVÕÌiÀÊÕÃÕ>ÞÊ`ÊÌÊi>ÊÕV
ÊÕiÃÃÊL]cao+oa_ is high. L]caB]qhpo+oa_ can range all over the spectrum with normal application behavior, with Û>ÕiÃÊvÀÊäÊÌÊ£]äääÊ«iÀÊÃiV`ÊLi}Ê>VVi«Ì>Li°Ê/
ÃÊiÌÀiÊ`>Ì>ÊÃiÌÊi>ÃÊ>ÊL>ÃiiÊÃÊ iÃÃiÌ>ÊÌÊ`iÌiÀiÊÌ
iÊiÝ«iVÌi`ÊÀ>ÊLi
>ÛÀ°
Buffer Cache Hit Ratio /
iÊbuffer cache is the pool of buffer pages into which data pages are read, and it is often the L}}iÃÌÊ«>ÀÌÊvÊÌ
iÊ-+Ê-iÀÛiÀÊiÀÞÊ«°Ê/
ÃÊVÕÌiÀÊÛ>ÕiÊÃ
Õ`ÊLiÊ>ÃÊ
}
Ê>ÃÊ«ÃÃLi]Ê iëiV>ÞÊvÀÊ"/*ÊÃÞÃÌiÃÊÌ
>ÌÊÃ
Õ`Ê
>ÛiÊv>ÀÞÊÀi}iÌi`Ê`>Ì>Ê>VViÃÃ]ÊÕiÊ>ÊÜ>Ài
ÕÃiÊÀÊÀi«ÀÌ}ÊÃÞÃÌi°ÊÌÊÃÊiÝÌÀiiÞÊVÊÌÊv`ÊÌ
ÃÊVÕÌiÀÊÛ>ÕiÊ>ÃÊÊ«iÀViÌÊ or more for most production servers. A low >qbban_]_dadepn]pek value indicates that few requests could be served out of the buffer cache, with the rest of the requests being served vÀÊ`ð 7
iÊÌ
ÃÊ
>««iÃ]ÊiÌ
iÀÊ-+Ê-iÀÛiÀÊÃÊÃÌÊÜ>À}ÊÕ«ÊÀÊÌ
iÊiÀÞÊÀiµÕÀiiÌÊvÊ Ì
iÊLÕvviÀÊV>V
iÊÃÊÀiÊÌ
>ÊÌ
iÊ>ÝÕÊiÀÞÊ>Û>>LiÊvÀÊÌÃÊ}ÀÜÌ
°ÊvÊÌ
ÃÊÃÊVÃÃtently low, you should consider getting more memory for the system.
Page Life Expectancy L]caHebaAtla_p]j_u indicates how long a page will stay in the buffer pool without being refiÀiVi`°ÊiiÀ>Þ]Ê>ÊÜÊÕLiÀÊvÀÊÌ
ÃÊVÕÌiÀÊi>ÃÊÌ
>ÌÊ«>}iÃÊ>ÀiÊLi}ÊÀiÛi`ÊvÀÊ the buffer, lowering the efficiency of the cache and indicating the possibility of memory presÃÕÀi°Ê"ÊÀi«ÀÌ}ÊÃÞÃÌiÃ]Ê>ÃÊ««Ãi`ÊÌÊ"/*ÊÃÞÃÌiÃ]ÊÌ
ÃÊÕLiÀÊ>ÞÊÀi>Ê>ÌÊ>ÊÜiÀÊ Û>ÕiÊÃViÊÀiÊ`>Ì>ÊÃÊ>VViÃÃi`ÊvÀÊÀi«ÀÌ}ÊÃÞÃÌiðÊÊÀi>Ã>LiÊÛ>ÕiÊÌÊiÝ«iVÌÊÌÊÃiiÊ
iÀiÊÃÊÎääÊÃiV`ÃÊÀÊÀi°
C H A P T E R 2 N S Y S T E M P E R F O R M A N C E A N A LY S I S
Checkpoint Pages/sec /
iÊ?da_glkejpL]cao+oa_ÊVÕÌiÀÊÀi«ÀiÃiÌÃÊÌ
iÊÕLiÀÊvÊ«>}iÃÊÌ
>ÌÊ>ÀiÊÛi`ÊÌÊ`ÃÊLÞÊ >ÊV
iV«ÌÊ«iÀ>̰Ê/
iÃiÊÕLiÀÃÊÃ
Õ`ÊLiÊÀi>ÌÛiÞÊÜ]ÊvÀÊiÝ>«i]ÊiÃÃÊÌ
>ÊÎäÊ«iÀÊ ÃiV`ÊvÀÊÃÌÊÃÞÃÌiðÊÊ
}
iÀÊÕLiÀÊi>ÃÊÀiÊ«>}iÃÊ>ÀiÊLi}Ê>Ài`Ê>ÃÊ`ÀÌÞÊÊÌ
iÊ V>V
i°ÊÊ`ÀÌÞÊ«>}iÊÃÊiÊÌ
>ÌÊÃÊ`vi`ÊÜ
iÊÊÌ
iÊLÕvviÀ°Ê7
iÊ̽ÃÊ`vi`]Ê̽ÃÊ>Ài`Ê >ÃÊ`ÀÌÞÊ>`ÊÜÊ}iÌÊÜÀÌÌiÊL>VÊÌÊÌ
iÊ`ÃÊ`ÕÀ}ÊÌ
iÊiÝÌÊV
iV«Ì°Ê}
iÀÊÛ>ÕiÃÊÊÌ
ÃÊ counter indicate a larger number of writes occurring within the system, possibly indicative of É"Ê«ÀLið
Lazy writes/sec /
iÊH]vusnepao+oa_ counter records the number of buffers written each second by the buffer >>}iÀ½ÃÊ>âÞÊÜÀÌiÊ«ÀViÃðÊ/
ÃÊ«ÀViÃÃÊÃÊÜ
iÀiÊÌ
iÊ`ÀÌÞ]Ê>}i`ÊLÕvviÀÃÊ>ÀiÊÀiÛi`ÊvÀÊ the buffer by a system process that frees the memory up for other uses. A dirty, aged buffer ÃÊiÊÌ
>ÌÊ
>ÃÊV
>}iÃÊ>`Êii`ÃÊÌÊLiÊÜÀÌÌiÊÌÊÌ
iÊ`ðÊ}
iÀÊÛ>ÕiÃÊÊÌ
ÃÊVÕÌiÀÊ «ÃÃLÞÊ`V>ÌiÊÉ"ÊÃÃÕiÃÊÀÊiÛiÊiÀÞÊ«ÀLiðÊ/
iÊH]vusnepao+oa_ values should consistently be less than 20 for the average system.
Memory Grants Pending /
iÊIaiknuCn]jpoLaj`ejc counter represents the number of processes pending for a memÀÞÊ}À>ÌÊÜÌ
Ê-+Ê-iÀÛiÀÊiÀÞ°ÊvÊÌ
ÃÊVÕÌiÀÊÛ>ÕiÊÃÊ
}
]ÊÌ
iÊ-+Ê-iÀÛiÀÊÃÊÃ
ÀÌÊvÊ iÀÞ°Ê1`iÀÊÀ>ÊV`ÌÃ]ÊÌ
ÃÊVÕÌiÀÊÛ>ÕiÊÃ
Õ`ÊVÃÃÌiÌÞÊLiÊäÊvÀÊÃÌÊ«Àduction servers. Another way to retrieve this value, on the fly, is to run queries against theÊ 6Êouo*`i[ ata_[mqanu[iaiknu[cn]jpo. A jqhh value in the column cn]jp[peia indicates that the process ÃÊÃÌÊÜ>Ì}ÊvÀÊ>ÊiÀÞÊ}À>̰Ê/
ÃÊÃÊiÊiÌ
`ÊÞÕÊV>ÊÕÃiÊÌÊÌÀÕLiÃ
ÌʵÕiÀÞÊ ÌiÕÌÃÊLÞÊ`iÌvÞ}ÊÌ
>ÌÊ>ʵÕiÀÞÊÀʵÕiÀiîÊÃÊÜ>Ì}ÊÊiÀÞÊÊÀ`iÀÊÌÊiÝiVÕÌi°
Target Server Memory (KB) and Total Server Memory (KB) P]ncapOanranIaiknu$G>% indicatesÊÌ
iÊÌÌ>Ê>ÕÌÊvÊ`Þ>VÊiÀÞÊ-+Ê-iÀÛiÀÊÃÊ willing to consume. Pkp]hOanranIaiknu$G>% indicates the amount of memory currently >ÃÃ}i`ÊÌÊ-+Ê-iÀÛiÀ°Ê/
iÊPkp]hOanranIaiknu$G>% counter value can be very high if the ÃÞÃÌiÊÃÊ`i`V>Ìi`ÊÌÊ-+Ê-iÀÛiÀ°ÊvÊPkp]hOanranIaiknu$G>% is much less than P]ncap OanranIaiknu$G>%]ÊÌ
iÊiÌ
iÀÊÌ
iÊ-+Ê-iÀÛiÀÊiÀÞÊÀiµÕÀiiÌÊÃÊÜ]ÊÌ
iÊi]toanran iaiknuÊVv}ÕÀ>ÌÊ«>À>iÌiÀÊvÊ-+Ê-iÀÛiÀÊÃÊÃiÌÊ>ÌÊÌÊÜÊ>ÊÛ>Õi]ÊÀÊÌ
iÊÃÞÃÌiÊÃÊÊ warm-up phase°Ê/
iÊÜ>ÀÕ«Ê«
>ÃiÊÃÊÌ
iÊ«iÀ`Ê>vÌiÀÊ-+Ê-iÀÛiÀÊÃÊÃÌ>ÀÌi`ÊÜ
iÊÌ
iÊ`>Ì>L>ÃiÊÃiÀÛiÀÊÃÊÊÌ
iÊ«ÀViÃÃÊvÊiÝ«>`}ÊÌÃÊiÀÞÊ>V>ÌÊ`Þ>V>ÞÊ>ÃÊÀiÊ`>Ì>Ê sets are accessed, bringing more data pages into memory. 9ÕÊV>ÊVvÀÊ>ÊÜÊiÀÞÊÀiµÕÀiiÌÊvÀÊ-+Ê-iÀÛiÀÊLÞÊÌ
iÊ«ÀiÃiViÊvÊ>Ê>À}iÊ ÕLiÀÊvÊvÀiiÊ«>}iÃ]ÊÕÃÕ>ÞÊx]äääÊÀÊÀi°
Memory Bottleneck Resolutions When there is high stress on memory, indicated by a large number of hard page faults, you can ÀiÃÛiÊiÀÞÊLÌÌiiVÊÕÃ}ÊÌ
iÊvÜV
>ÀÌÊÃ
ÜÊÊ}ÕÀiÊÓx°
27
28
CHAPTER 2 N SYS TEM P ER FOR MA NC E A NA L YS IS
Figure 2-5. Memory bottleneck resolution chart ÊviÜÊvÊÌ
iÊVÊÀiÃÕÌÃÊvÀÊiÀÞÊLÌÌiiVÃÊ>ÀiÊ>ÃÊvÜÃ\ Ê
UÊ "«Ìâ}Ê>««V>ÌÊÜÀ>`
Ê
UÊ V>Ì}ÊÀiÊiÀÞÊÌÊ-+Ê-iÀÛiÀ
Ê
UÊ VÀi>Ã}ÊÃÞÃÌiÊiÀÞ
Ê
UÊ
>}}ÊvÀÊ>ÊÎÓLÌÊÌÊ>ÊÈ{LÌÊ«ÀViÃÃÀ
Ê
UÊ >L}ÊÎ ÊvÊ«ÀViÃÃÊë>Vi
Ê
UÊ 1Ã}ÊiÀÞÊLiÞ`Ê{ ÊÜÌ
ÊÎÓLÌÊ-+Ê-iÀÛiÀ i̽ÃÊÌ>iÊ>ÊÊ>ÌÊi>V
ÊvÊÌ
iÃiÊÊÌÕÀ°
C H A P T E R 2 N S Y S T E M P E R F O R M A N C E A N A LY S I S
Optimizing Application Workload "«Ìâ}Ê>««V>ÌÊÜÀ>`ÊÃÊÌ
iÊÃÌÊivviVÌÛiÊÀiÃÕÌÊÃÌÊvÊÌ
iÊÌi]ÊLÕÌÊLiV>ÕÃiÊ vÊÌ
iÊV«iÝÌÞÊ>`ÊV
>i}iÃÊÛÛi`ÊÊÌ
ÃÊ«ÀViÃÃ]ÊÌÊÃÊÕÃÕ>ÞÊVÃ`iÀi`Ê>Ã̰Ê/Ê`iÌvÞÊÌ
iÊiÀÞÌiÃÛiʵÕiÀiÃ]ÊV>«ÌÕÀiÊ>ÊÌ
iÊ-+ʵÕiÀiÃÊÕÃ}Ê-+Ê*ÀviÀÊÜ
V
ÊÞÕÊ ÜÊi>ÀÊ
ÜÊÌÊÕÃiÊÊ
>«ÌiÀÊή]Ê>`ÊÌ
iÊ}ÀÕ«ÊÌ
iÊÌÀ>ViÊÕÌ«ÕÌÊÊÌ
iÊNa]`oÊVÕ°Ê/
iÊ queries with the highest number of logical reads contribute to most of the memory stress. You ÜÊÃiiÊ
ÜÊÌÊ«ÌâiÊÌ
ÃiʵÕiÀiÃÊÊÀiÊ`iÌ>ÊÌ
ÀÕ}
ÕÌÊÌ
ÃÊL°
Allocating More Memory to SQL Server As you learned in theʺ-+Ê-iÀÛiÀÊiÀÞÊ>>}iiÌ»ÊÃiVÌ]ÊÌ
i i]toanraniaiknu Vv}ÕÀ>ÌÊV>ÊÌÊÌ
iÊ>ÝÕÊÃâiÊvÊÌ
iÊ-+Ê-iÀÛiÀÊiÀÞÊ«°ÊvÊÌ
iÊiÀÞÊ ÀiµÕÀiiÌÊvÊ-+Ê-iÀÛiÀÊÃÊÀiÊÌ
>ÊÌ
iÊi]toanraniaiknu value, which you can tell through the number of hard page faults, then increasing the value will allow the memory pool ÌÊ}ÀܰÊ/ÊLiivÌÊvÀÊVÀi>Ã}ÊÌ
iÊi]toanraniaiknu value, ensure that enough physical memory is available in the system.
Increasing System Memory /
iÊiÀÞÊÀiµÕÀiiÌÊvÊ-+Ê-iÀÛiÀÊ`i«i`ÃÊÊÌ
iÊÌÌ>Ê>ÕÌÊvÊ`>Ì>Ê«ÀViÃÃi`ÊLÞÊ -+Ê>VÌÛÌiðÊÌÊÃÊÌÊ`ÀiVÌÞÊVÀÀi>Ìi`ÊÌÊÌ
iÊÃâiÊvÊÌ
iÊ`>Ì>L>ÃiÊÀÊÌ
iÊÕLiÀÊvÊV}Ê-+ʵÕiÀiðÊÀÊiÝ>«i]ÊvÊ>ÊiÀÞÌiÃÛiʵÕiÀÞÊ«iÀvÀÃÊ>ÊVÀÃÃÊÊLiÌÜiiÊÌÜÊ small tables without any filter criteria to narrow down the result set, it can cause high stress on the system memory. "iÊvÊÌ
iÊi>ÃiÃÌÊ>`ÊV
i>«iÃÌÊÀiÃÕÌÃÊÃÊÌÊëÞÊVÀi>ÃiÊÃÞÃÌiÊiÀÞ°ÊÜiÛiÀ]Ê it is still important to find out what is consuming the physical memory, because if the appliV>ÌÊÜÀ>`ÊÃÊiÝÌÀiiÞÊiÀÞÊÌiÃÛi]ÊÞÕÊÜÊÃÊLiÊÌi`ÊLÞÊÌ
iÊ>ÝÕÊ >ÕÌÊvÊiÀÞÊ>ÊÃÞÃÌiÊV>Ê>VViÃðÊ/Ê`iÌvÞÊÜ
V
ʵÕiÀiÃÊ>ÀiÊÕÃ}ÊÀiÊiÀÞ]Ê you can query the ouo*`i[ata_[mqanu[iaiknu[cn]jpoÊ 6°ÊÕÃÌÊLiÊV>ÀivÕÊÜ
iÊÀÕ}ʵÕiÀiÃÊ>}>ÃÌÊÌ
ÃÊ 6ÊÕÃ}Ê>ÊFKEJ or an KN@AN>U statement because, if your system is already under memory stress, these actions can lead to your query needing its own memory grant. -iÌ
}ÊÌÊii«ÊÊ`ÊÃÊÌ
iÊiÀÞÊii`i`ÊLÞÊ-+Ê-iÀÛiÀÊÕÌÃ`iÊÌ
iÊLÕvviÀÊ«°Ê /
ÃÊÃÊÀiviÀÀi`ÊÌÊ>ÃÊnonbuffer memory and used to be called IaiPkHa]ra°Ê"iÊvÊÌ
iÊÜÀÃÌÊ iÀÞÊ«ÀLiÃÊvÀʵÕiÀiÃÊVVÕÀÃÊÜ
iÊÌ
iÊÃâiÊvÊÌ
iʵÕiÀÞÊÌÃiv]ÊÌÊÌ
iÊ`>Ì>ÊÀiÌÕÀi`Ê LÕÌÊÌ
iÊ/-+ÊV`i]ÊiÝVii`ÃÊn ÊÊÃâi°Ê/
ÃÊiÀÞÊÃÊ>V>Ìi`Ê>ÌÊÃÌ>ÀÌÕ«ÊÕÃ}ÊÌ
iÊÌc ÃÜÌV
°ÊvÊÞÕÀʵÕiÀiÃÊ>ÀiÊ>À}iÊ>}>]Ê}Ài>ÌiÀÊÌ
>Ên ÊÊÃâi®]ÊÌ
iÞÊÜÊii`ÊÌÊÌ>iÊ>`Û>Ì>}iÊvÊÌ
ÃÊiÀÞÊ>V>̰Ê9ÕÊÜÊii`ÊÌÊ>iÊÃÕÀiÊÞÕÊ
>ÛiÊiÕ}
ÊiÀÞÊ>Û>>LiÊ ÊÞÕÀÊÃiÀÛiÀÊvÀÊÌ
ðÊ1Ã}ÊiÝÌi`i`ÊÃÌÀi`Ê«ÀVi`ÕÀiÃ]Ê`ÃÌÀLÕÌi`ʵÕiÀiÃ]Ê>`Ê>ÕÌ>tion objects also places a strain on this memory space.
Changing from a 32-bit to a 64-bit Processor Switching theʫ
ÞÃV>ÊÃiÀÛiÀÊvÀÊ>ÊÎÓLÌÊ«ÀViÃÃÀÊÌÊ>ÊÈ{LÌÊ«ÀViÃÃÀÊ>`ÊÌ
iÊ>ÌÌi`>ÌÊ 7`ÜÃÊ-iÀÛiÀÊÃvÌÜ>ÀiÊÕ«}À>`i®ÊÀ>`V>ÞÊV
>}iÃÊÌ
iÊiÀÞÊ>>}iiÌÊV>«>LÌiÃÊvÊ -+Ê-iÀÛiÀ°Ê/
iÊÌ>ÌÃÊÊ-+Ê-iÀÛiÀÊvÀÊiÀÞÊ}ÊvÀÊÎ ÊÌÊVÕÌ}ÊÕ«ÊÌÊÈ{ Ê of additional data cache available throughÊ``ÀiÃÃÊ7`Ü}Ê ÝÌiÃÃÊQ7 R®ÊÌÊ>ÊÌÊvÊ Õ«ÊÌÊn/ Ê`i«i`}ÊÊÌ
iÊÛiÀÃÊvÊÌ
iÊ«iÀ>Ì}ÊÃÞÃÌiÊ>`ÊÌ
iÊëiVvVÊ«ÀViÃÃÀÊÌÞ«i°Ê
29
30
CHAPTER 2 N SYS TEM P ER FOR MA NC E A NA L YS IS
Ì>ÌÃÊiÝ«iÀiVi`ÊÜÌ
ÊÌ
iÊ7 Ê>ÀiÊÀiÛi`ÊÃViÊÌ
iÊiÀÞÊÕÃi`ÊÊÈ{LÌÊÃÞÃÌiÃÊ V>ÊLiÊÕÃi`ÊvÀÊLÌ
Ê`>Ì>Ê>`ÊÌ
iÊ«ÀVi`ÕÀiÊV>V
i]ÊÕiÊ7 ÊiÀÞ°Ê ÊÌ
iÀÊÃÜÌV
iÃÊÀÊ iÝÌÀ>ÊiV
>ÃÃÊ>ÀiÊii`i`ÊvÀÊ-+Ê-iÀÛiÀÊÌÊÌ>iÊ>`Û>Ì>}iÊvÊÌ
ÃÊiÀÞÊë>Vi°
Enabling 3GB of Process Space -Ì>`>À`ÊÎÓLÌÊ>``ÀiÃÃiÃÊV>Ê>«Ê>Ê>ÝÕÊvÊ{ ÊvÊiÀÞ°Ê/
iÊÃÌ>`>À`Ê>``ÀiÃÃÊ Ã«>ViÃÊvÊÎÓLÌÊ7`ÜÃÊ«iÀ>Ì}ÊÃÞÃÌiÃÊ«ÀViÃÃiÃÊ>ÀiÊÌ
iÀivÀiÊÌi`ÊÌÊ{ °Ê"ÕÌÊvÊ Ì
ÃÊ{ Ê«ÀViÃÃÊë>Vi]ÊLÞÊ`iv>ÕÌÊÌ
iÊÕ««iÀÊÓ ÊÃÊÀiÃiÀÛi`ÊvÀÊÌ
iÊ«iÀ>Ì}ÊÃÞÃÌi]Ê>`ÊÌ
iÊ ÜiÀÊÓ ÊÃÊ>`iÊ>Û>>LiÊÌÊÌ
iÊ>««V>̰ÊvÊÞÕ specify a +/C> switch in the ^kkp*eje file vÊÌ
iÊÎÓLÌÊ"-]ÊÌ
iÊ«iÀ>Ì}ÊÃÞÃÌiÊÀiÃiÀÛiÃÊÞÊ£ ÊvÊÌ
iÊ>``ÀiÃÃÊë>Vi]Ê>`ÊÌ
iÊ>««V>ÌÊV>Ê>VViÃÃÊÕ«ÊÌÊÎ °Ê/
ÃÊÃÊ>Ã called 4-gig tuningÊ{/®°Ê ÊiÜÊ*ÃÊ>ÀiÊÀiµÕÀi`Ê for this purpose. /
iÀivÀi]ÊÊ>Ê>V
iÊÜÌ
Ê{ ÊvÊ«
ÞÃV>ÊiÀÞÊ>`ÊÌ
iÊ`iv>ÕÌÊ7`ÜÃÊVv}ÕÀ>Ì]ÊÞÕÊÜÊv`Ê>Û>>LiÊiÀÞÊvÊ>LÕÌÊÓ ÊÀÊÀi°Ê/ÊiÌÊ-+Ê-iÀÛiÀÊÕÃiÊÕ«ÊÌÊÎ Ê of the available memory, you can add the +/C> switch in the ^kkp*eje file as follows: W^kkphk]`anY peiakqp9/, `ab]qhp9iqhpe$,%`eog$,%n`eog$,%l]npepekj$-%XSEJJP Wklan]pejcouopaioY iqhpe$,%`eog$,%n`eog$,%l]npepekj$-%XSEJJP9 Ie_nkokbpSej`ksoOanran.,,4=`r]j_a`Oanran +b]op`apa_p+/C> /
iÊ+/C>ÊÃÜÌV
ÊÃ
Õ`ÊÌÊLiÊÕÃi`ÊvÀÊÃÞÃÌiÃÊÜÌ
ÊÀiÊÌ
>Ê£È ÊvÊ«
ÞÃV>ÊiÀÞ]Ê >ÃÊiÝ«>i`ÊÊÌ
iÊvÜ}ÊÃiVÌ]ÊÀÊvÀÊÃÞÃÌiÃÊÌ
>ÌÊÀiµÕÀiÊ>Ê
}
iÀÊ>ÕÌÊvÊiÀiÊ memory. -+Ê-iÀÛiÀÊÓäänÊÊÈ{LÌÊÃÞÃÌiÃÊV>ÊÃÕ««ÀÌÊÕ«ÊÌÊn/ ÊÊ>ÊÝÈ{Ê«>ÌvÀÊ>`ÊÕ«ÊÌÊ Ç/ ÊÊÌ
iÊÈ{°Ê/
iÀiÊÃÊ>ÊvÕÀÌ
iÀÊÌÊÊ7`ÜÃÊÓääÎÊvÊ£/ °ÊÃÊÀiÊiÀÞÊÃÊ>Û>>LiÊ vÀÊÌ
iÊ"-]ÊÌ
iÊÌÃÊ«Ãi`ÊLÞÊ-+Ê-iÀÛiÀÊ>ÀiÊÀi>V
i`°Ê/
ÃÊÃÊÜÌ
ÕÌÊ
>Û}ÊÌÊÕÃiÊ>ÞÊ Ì
iÀÊÃÜÌV
iÃÊÀÊiÝÌi`i`ÊiÀÞÊÃV
iið
Using Memory Beyond 4GB Within SQL Server All processors basedÊÊÌ
iÊÎÓÊ>ÀV
ÌiVÌÕÀi]ÊLi}}ÊÜÌ
ÊÌ
iÊÌiÊ*iÌÕÊ*À]ÊÃÕ««ÀÌÊ a new ÎÈLÌÊ«
ÞÃV>Ê>``ÀiÃÃ}Ê`iÊV>i`Ê*
ÞÃV>Ê``ÀiÃÃÊ ÝÌiÃÊ* ®°Ê* Ê>ÜÃÊ Õ«ÊÌÊÈ{ ÊvÊ«
ÞÃV>ÊiÀÞ]Ê`i«i`}ÊÕ«ÊÌ
iÊ«iÀ>Ì}ÊÃÞÃÌi°Ê/
iÊ* Ê`iÊiÀiÊ ÀiµÕÀiÃÊ>ÊÌiÊÀV
ÌiVÌÕÀiÊ«ÀViÃÃÀÊ*iÌÕÊ*ÀÊÀÊiÜiÀ®Ê>`ÊÀiÊÌ
>Ê{ ÊvÊ,° 7`ÜÃÊ-iÀÛiÀÊ«iÀ>Ì}ÊÃÞÃÌiÃÊÌ
>ÌÊ>ÀiÊÎÓLÌÊ«iiÌÊAddress Windowing
ÝÌiÃÃÊÌÊ>VViÃÃÊÕ«ÊÌÊÈ{ ÊvÊ«
ÞÃV>ÊiÀÞÊÜÌ
ÊÌ
iÊ* Ê>``ÀiÃÃ}Ê`i°Ê/
ÃÊ memory is available for the buffer cache, for data storage only, but cannot be used by operaÌÃÊ>`ÊÌ>ÃÃÊÌÊVÀi>ÃiÊÌ
iÊÕLiÀÊv]ÊvÀÊiÝ>«i]Ê`>Ì>L>ÃiÃÊ>Û>>LiÊÊÌ
iÊÃÞÃÌi°
C H A P T E R 2 N S Y S T E M P E R F O R M A N C E A N A LY S I S
VViÃÃ}ÊiÀÞÊLiÞ`Ê{ ÊÊ>ÊÎÓLÌÊ7`ÜÃÊ-iÀÛiÀÊ"-ÊÀiµÕÀiÃÊVv}ÕÀ>ÌÊ>ÌÊ ÌÜÊiÛiÃ\ÊÌ
iÊ«iÀ>Ì}ÊÃÞÃÌiÊiÛiÊ>`ÊÌ
iÊ>««V>ÌÊiÛi°Ê/Êi>LiÊÌ
iÊ«iÀ>Ì}ÊÃÞÃÌiÊ ÌÊ>VViÃÃÊÀiÊÌ
>Ê{ ÊvÊ«
ÞÃV>ÊiÀÞ]Ê>``Ê>Ê+L=A switch in the ^kkp*eje file as follows: W^kkphk]`anY peiakqp9/, `ab]qhp9iqhpe$,%`eog$,%n`eog$,%l]npepekj$-%XSEJJP Wklan]pejcouopaioY iqhpe$,%`eog$,%n`eog$,%l]npepekj$-%XSEJJP9 Ie_nkokbpSej`ksoOanran.,,4=`r]j_a`Oanran +b]op`apa_p+L=A "ViÊÞÕÊ
>ÛiÊ`vi`Ê^kkp*eje, you can use ol[_kjbecqnaÊÌÊi>LiÊ-+Ê-iÀÛiÀÊÓäänÊÌÊ >VViÃÃÊÀiÊÌ
>Ê{ ÊvÊ«
ÞÃV>ÊiÀÞ°Ê/
iÊvÜ}ÊiÝ>«iÊoap[1CecIaiknu*omh in the `Ü>`®ÊÃ
ÜÃÊ
ÜÊÌÊi>LiÊx \ ol[_kjbecqna#odks]`r]j_a`klpekjo#(NA?KJBECQNA CK ol[_kjbecqna#]saaj]^ha`#(NA?KJBECQNA CK ol[_kjbecqna#i]toanraniaiknu#(1-., NA?KJBECQNA CK /
iÊ«iÀ>Ì}ÊÃÞÃÌiÊÕÃÌÊLiÊÀiÃÌ>ÀÌi`ÊvÀÊÌ
iÊ+L=AÊÃÜÌV
ÊÌÊÌ>iÊivviV̰Ê/
iÊ>ÃÃV>Ìi`Ê ÀiÃÌ>ÀÌÊvÊ-+Ê-iÀÛiÀÊ«ÕÌÃÊÌ
iÊ7 ÊVv}ÕÀ>ÌÊÌÊivviV̰ ÃÌ>ViÃÊvÊ-+Ê-iÀÛiÀÊÓäänÊ`ÊÌÊ`Þ>V>ÞÊ>>}iÊÌ
iÊÃâiÊvÊÌ
iÊ>``ÀiÃÃÊë>ViÊ when AWE memory is enabled; therefore, setting the i]toanraniaiknu configuration «>À>iÌiÀÊvÊ-+Ê-iÀÛiÀÊÃÊ>`>ÌÀÞÊÜ
iÊÕÃ}Ê7 ÊiÀÞ°Ê"Ê>Ê`i`V>Ìi`Ê-+Ê-iÀÛiÀÊ machine, i]toanraniaiknu may be set to $pkp]hlduoe_]hiaiknuÌ.,,I>% so that enough «
ÞÃV>ÊiÀÞÊÃÊi«ÌÊ>Ã`iÊvÀÊÌ
iÊ«iÀ>Ì}ÊÃÞÃÌiÊ>`ÊÌ
iÀÊiÃÃiÌ>ÊÌÃÉ>««V>Ìð 7
iÊÀÕ}ÊÕÌ«iÊÃÌ>ViÃÊvÊ-+Ê-iÀÛiÀÊÓäänÊÊÌ
iÊÃ>iÊV«ÕÌiÀ]ÊÞÕÊÕÃÌÊ ensure the following with each instance using AWE memory: Ê
UÊ >V
ÊÃÌ>ViÊ
>ÃÊ>Êi]toanraniaiknu setting.
Ê
UÊ 7
iÊÃiÌÌ}Êi]toanraniaiknu]ÊÞÕÊii`ÊÌÊÌ>iÊÌÊ>VVÕÌÊÌ
iÊ>ÕÌÊvÊ nonbuffer pool memory (formerly called IaiPkHa]ra®ÊÌ
>ÌÊÞÕÊ>ÞÊii`ÊvÀÊ-+Ê Server to operate.
Ê
UÊ /
iÊÃÕÊvÊÌ
iÊi]toanraniaiknu values for all the instances should be less than the amount of physical memory in the computer.
Ã`iÀ}ÊÌ
iÊ«ÀiVi`}Êv>VÌÀÃ]ÊÊ>Ê-+Ê-iÀÛiÀÊÓäänÊVÕÃÌiÀÊiÛÀiÌÊÜÌ
ÊÌÜÊ nodes and AWE/PAE enabled, the i]toanraniaiknu value on each node should be less than half the system physical memory.
31
32
CHAPTER 2 N SYS TEM P ER FOR MA NC E A NA L YS IS
NNote If you use the +/C> feature along with AWE/PAE, then an instance of SQL Server will be limited to a maximum of 16GB of extended memory. This limitation is because of the internal design of the Windows Server operating system. Limiting the system space within the process space to 1GB using the +/C> switch allows the Windows Server operating system to manage physical memory up to 16GB. Therefore, to access memory beyond 16GB, you should not use the +/C> switch.
Disk Bottleneck Analysis -+Ê-iÀÛiÀÊÃÊ>Ê
i>ÛÞÊÕÃiÀÊvÊÌ
iÊ
>À`Ê`Ã]Ê>`ÊÃViÊ`ÃÊëii`ÃÊ>ÀiÊV«>À>ÌÛiÞÊÕV
Ê ÃÜiÀÊÌ
>ÊiÀÞÊ>`Ê«ÀViÃÃÀÊëii`Ã]Ê>ÊVÌiÌÊÊ`ÃÊÀiÃÕÀViÃÊV>ÊÃ}vV>ÌÞÊ `i}À>`iÊ-+Ê-iÀÛiÀÊ«iÀvÀ>Vi°Ê>ÞÃÃÊ>`ÊÀiÃÕÌÊvÊ>ÞÊ`ÃÊÀiÃÕÀViÊLÌÌiiVÊV>Ê «ÀÛiÊ-+Ê-iÀÛiÀÊ«iÀvÀ>ViÊÃ}vV>ÌÞ°
Disk Counters /Ê>>ÞâiÊ`ÃÊ«iÀvÀ>Vi]ÊÞÕÊV>ÊÕÃiÊÌ
iÊVÕÌiÀÃÊÃ
ÜÊÊ/>LiÊÓÓ° Table 2-2. Performance Monitor Counters to Analyze I/O Pressure
Object(Instance[,InstanceN]) Counter
Description
Value
Lduoe_]h@eog$@]p])`eog( !@eogPeia Hkc)`eog%
Percentage of time `ÃÊÜ>ÃÊLÕÃÞ
Average Û>ÕiÊÊnx¯
?qnnajp@eogMqaqaHajcpd
ÕLiÀ of ÕÌÃÌ>`}Ê`ÃÊ requests at the time performance data is collected
Average value < 2 «iÀÊ`Ã
=rc*@eogMqaqaHajcpd
Average number vʵÕiÕi`Ê`ÃÊ requests during the sample interval
Average value < 2 «iÀÊ`Ã
@eogPn]jobano+oa_
Rate of read/write «iÀ>ÌÃÊÊ`Ã
>ÝÕÊ value < 100 «iÀÊ`Ã
@eog>upao+oa_
Amount of data transfer to/from «iÀÊ`ÃÊ«iÀÊÃiV`
>ÝÕÊ value < 10MB per second
=rc*@eogOa_+Na]`
Average time in ms ÌÊÀi>`ÊvÀÊ`Ã
Average value < 10 ms
=rc*@eogOa_+Snepa
Average time in ms ÌÊÜÀÌiÊÌÊ`Ã
Average value < 10 ms
C H A P T E R 2 N S Y S T E M P E R F O R M A N C E A N A LY S I S
/
iÊLduoe_]h@eogÊVÕÌiÀÃÊÀi«ÀiÃiÌÊÌ
iÊ>VÌÛÌiÃÊÊ>Ê«
ÞÃV>Ê`ðÊHkce_]h@eog counÌiÀÃÊÀi«ÀiÃiÌÊ}V>ÊÃÕLÕÌÃÊÀÊ«>ÀÌÌîÊVÀi>Ìi`ÊÊ>Ê«
ÞÃV>Ê`ðÊvÊÞÕÊVÀi>ÌiÊÌÜÊ partitions, say ?6 and @6ÊÊ>Ê«
ÞÃV>Ê`Ã]ÊÌ
iÊÞÕÊV>ÊÌÀÊÌ
iÊ`ÃÊ>VÌÛÌiÃÊvÊÌ
iÊ `Û`Õ>Ê}V>Ê`ÃÃÊÕÃ}Ê}V>Ê`ÃÊVÕÌiÀðÊÜiÛiÀ]ÊLiV>ÕÃiÊ>Ê`ÃÊLÌÌiiVÊÕÌ>ÌiÞÊVVÕÀÃÊÊÌ
iʫ
ÞÃV>Ê`Ã]ÊÌÊÊÌ
iÊ}V>Ê`Ã]ÊÌÊÃÊÕÃÕ>ÞÊ«ÀiviÀ>LiÊÌÊÕÃiÊÌ
iÊ Lduoe_]h@eog counters. ÌiÊÌ
>ÌÊvÀÊ>Ê
>À`Ü>ÀiÊÀi`Õ`>ÌÊ>ÀÀ>ÞÊvÊ`i«i`iÌÊ`ÃÃÊ, ®ÊÃÕLÃÞÃÌiÊÃiiÊ Ì
iʺ1Ã}Ê>Ê, ÊÀÀ>Þ»ÊÃiVÌÊvÀÊÀiÊÊ, ®]ÊÌ
iÊVÕÌiÀÃÊÌÀi>ÌÊÌ
iÊ>ÀÀ>ÞÊ>ÃÊ>ÊÃ}iÊ «
ÞÃV>Ê`ðÊÀÊiÝ>«i]ÊiÛiÊvÊÞÕÊ
>ÛiÊÌiÊ`ÃÃÊÊ>Ê, ÊVv}ÕÀ>Ì]ÊÌ
iÞÊÜÊ>ÊLiÊ Ài«ÀiÃiÌi`Ê>ÃÊiÊ«
ÞÃV>Ê`ÃÊÌÊÌ
iÊ«iÀ>Ì}ÊÃÞÃÌi]Ê>`ÊÃÕLÃiµÕiÌÞÊÞÕÊÜÊ
>ÛiÊÞÊ one set of Lduoe_]h@eogÊVÕÌiÀÃÊvÀÊÌ
>ÌÊ, ÊÃÕLÃÞÃÌi°Ê/
iÊÃ>iÊ«ÌÊ>««iÃÊÌÊstorage >Ài>ÊiÌÜÀÊ- ®Ê`ÃÃÊÃiiÊÌ
iʺ1Ã}Ê>Ê- Ê-ÞÃÌi»ÊÃiVÌÊvÀÊëiVvVî°
% Disk Time /
iÊ!@eogPeia counter monitorsÊÌ
iÊ«iÀViÌ>}iÊvÊÌiÊÌ
iÊ`ÃÊÃÊLÕÃÞÊÜÌ
ÊÀi>`ÉÜÀÌiÊ >VÌÛÌiðÊÌÊÃ
Õ`ÊÌÊLiÊVÌÕÕÃÞÊ
}
°ÊvÊÌ
ÃÊVÕÌiÀÊÛ>ÕiÊÃÊVÃÃÌiÌÞÊÀiÊÌ
>ÊnxÊ «iÀViÌ]ÊÌ
iÊÞÕÊÕÃÌÊÌ>iÊÃÌi«ÃÊÌÊLÀ}ÊÌÊ`ܰÊ9ÕÊVÕ`ÊÕ«}À>`iÊÌ
iÊ`ÃÊÃÕLÃÞÃÌi]ÊLÕÌÊ >ÊÀiÊivviVÌÛiÊÃÕÌÊÃÊÌÊ>Û`Ê}}ÊÌÊÌ
iÊ`>Ì>Ê`ÃÊ>ÃÊÕV
Ê>ÃÊ«ÃÃLi°Ê ÛiÊ>ÊVÃÃÌiÌÊ`ÃÊÕÃ>}iÊvÊxÊ«iÀViÌÊ>ÞÊ>`ÛiÀÃiÞÊ>vviVÌÊ«iÀvÀ>Vi° ÀÊ«iÀvÀ>Vi]ÊÌÊÃÊ>Ü>ÞÃÊLiivV>ÊÌÊV>V
iÊÌ
iÊ`ÃÊVÌiÌÃÊÊiÀÞÊÃViÊ`ÃÊ access is in the order of milliseconds, whereas memory access is in the order of nanoseconds. -+Ê-iÀÛiÀÊ>`«ÌÃÊÌ
ÃÊÃÌÀ>Ìi}ÞÊLÞÊV>V
}ÊÌ
iÊ`>Ì>Ê«>}iÃÊÊÌÃÊLÕvviÀÊV>V
i°Ê ÕÌÊvÊ-+Ê-iÀÛiÀÊ
>ÃÊÌÊ}ÊÌÊ`ÃÊvÌi]Ê>ÃÊ`V>Ìi`ÊLÞÊ>Ê
}
Ê!@eogPeia value, then the slow access time of Ì
iÊ`ÃÊV«>Ài`ÊÌÊÌ
>ÌÊvÊÌ
iÊiÀÞÊÜÊ
ÕÀÌÊÌ
iÊ«iÀvÀ>ViÊvÊÌ
iÊ`>Ì>L>Ãi°
Current Disk Queue Length ?qnnajp@eogMqaqaHajcpd isÊÌ
iÊÕLiÀÊvÊÀiµÕiÃÌÃÊÕÌÃÌ>`}ÊÊÌ
iÊ`ÃÊÃÕLÃÞÃÌiÊ>ÌÊ the time the performance data is collected. It includes requests in service at the time of the Ã>«Ã
̰ÊÊ`ÃÊÃÕLÃÞÃÌiÊÜÊ
>ÛiÊÞÊiÊ`ÃʵÕiÕi°Ê9ÕÊÃ
Õ`ÊÕÃiÊÌ
ÃÊVÕÌiÀÊÛ>ÕiÊ to support the conclusion made from the !@eogPeiaÊVÕÌiÀ°ÊÊVÃÃÌiÌÊ`ÃʵÕiÕiÊi}Ì
Ê vÊÌÜÊ«iÀÊ`ÃÊ`V>ÌiÃÊÌ
iÊ«ÃÃLÌÞÊvÊ>ÊLÌÌiiVÊÊÌ
iÊ`ð ÀÊiÝ>«i]ÊÊ>Ê, ÊVv}ÕÀ>ÌÊÜÌ
Ê£äÊ`ÃÃ]Ê>ÊVÃÃÌiÌÊ`ÃʵÕiÕiÊi}Ì
ÊvÊ ÓÊ«iÀÊ`Ã]ÊÀÊÓäÊrÊ£äÊ`ÃÃÊÊÓÊ«iÀÊ`Ãî]ÊvÀÊÌ
iÊV«iÌiÊ`ÃÊÃÕLÃÞÃÌiÊ`V>ÌiÃÊ>Ê`ÃÊ LÌÌiiV°Ê, ÊVÌÀiÀÃÊÕÃÕ>ÞÊ`ÃÌÀLÕÌiÊ`ÃÊÉ"ÃÊÕvÀÞÊ>VÀÃÃÊ>Ê`ÃÃÊÊÌ
iÊ`ÃÊ ÃÕLÃÞÃÌi]ÊLÕÌÊLiV>ÕÃiÊvÊÌ
iÊLÛÕÃÊV
>ViÃÊvÊÕvÀÊ`ÃÊÉ"]ÊVÃ`iÀÊÌ
iÊvÜ}Ê range of values for the ?qnnajp@eogMqaqaHajcpd counter: Ê
UÊ iÃÃÊÌ
>ÊÓʳÊÊvÊë`iîÊÃÊ>Êexcellent value to see. In the worst case, the two ÀiµÕiÃÌÃÊÊÌ
iʵÕiÕiÊ>ÞÊLiÊ«i`}ÊÊÌ
iÊÃ>iÊ`ð
Ê
UÊ iÃÃÊÌ
>ÊÓÊÊÊvÊë`iîÊÃÊ>Êfair value to see. In the best case, the queued requests >ÞÊLiÊÕvÀÞÊ`ÃÌÀLÕÌi`Ê>VÀÃÃÊ>ÊÌ
iÊ`ÃÃÊÊÌ
iÊ`ÃÊÃÕLÃÞÃÌiÊÜÌ
ÊÌÜÊ ÀiµÕiÃÌÃÊ«i`}Ê«iÀÊ`ð
33
34
CHAPTER 2 N SYS TEM P ER FOR MA NC E A NA L YS IS
vÊÞÕÊ`ÊÌÊÃÕëiVÌÊ`Þ>VÊÛ>À>ÌÊÊ`ÃÊ>`Ã]ÊÌ
iÊÞÕÊ>ÞÊÕÃiÊÌ
iÊ=rc*@eog MqaqaHajcpd counter since this counter represents the average of the instantaneous values provided by the ?qnnajp@eogMqaqaHajcpdÊVÕÌiÀ°ÊvÊÞÕÊÌ
ÊÞÕÊ>ÞÊ
>ÛiÊ>Ê`ÃÊÃÃÕiÊ L>Ãi`ÊÊÌ
iʵÕiÕiÊi}Ì
]ÊÞÕÊÃ
Õ`Ê>ÃÊV
iVÊÌ
iÊ=ran]ca@eogOa_kj`oLanPn]joban valueÊvÀÊÌ
iÊÃ>iÊ`ðÊ>Þ]ÊV
iVÊÌ
iÊÜ>ÌÊÃÌ>ÌiÃÊvÊÞÕÀÊ«ÀViÃÃiÃÊÌÊÃiiÊÜ
iÌ
iÀÊÞÕÊ>ÀiÊ iÝ«iÀiV}ÊÉ"ÊÜ>Ìð
Disk Transfers/sec @eogPn]jobano+oa_ monitorsÊÌ
iÊÀ>ÌiÊvÊÀi>`Ê>`ÊÜÀÌiÊ«iÀ>ÌÃÊÊÌ
iÊ`ðÊÊÌÞ«V>Ê`à Ì`>ÞÊV>Ê`Ê>LÕÌÊ£näÊ`ÃÊÌÀ>ÃviÀÃÊ«iÀÊÃiV`ÊvÀÊÃiµÕiÌ>ÊÉ"Ê>`Ê£ääÊ`ÃÊÌÀ>ÃviÀÃÊ«iÀÊ ÃiV`ÊvÀÊÀ>`ÊÉ"°ÊÊÌ
iÊV>ÃiÊvÊÀ>`ÊÉ"]Ê@eogPn]jobano+oa_ is lower because more `ÃÊ>ÀÊ>`Ê
i>`ÊÛiiÌÃÊ>ÀiÊÛÛi`°Ê"/*ÊÜÀ>`Ã]ÊÜ
V
Ê>ÀiÊÜÀ>`ÃÊvÀÊ`}Ê mainly singleton operations, small operations, and random access, are typically constrained LÞÊ`ÃÊÌÀ>ÃviÀÃÊ«iÀÊÃiV`°Ê-]ÊÊÌ
iÊV>ÃiÊvÊ>Ê"/*ÊÜÀ>`]ÊÞÕÊ>ÀiÊÀiÊVÃÌÀ>i`ÊLÞÊ Ì
iÊv>VÌÊÌ
>ÌÊ>Ê`ÃÊV>Ê`ÊÞÊ£ääÊ`ÃÊÌÀ>ÃviÀÃÊ«iÀÊÃiV`ÊÌ
>ÊLÞÊÌÃÊÌ
ÀÕ}
«ÕÌÊëiVvV>tion of 10MB per second. iV>ÕÃiÊvÊÌ
iÊ
iÀiÌÊÃÜiÃÃÊvÊ>Ê`Ã]ÊÌÊÃÊÀiVi`i`ÊÌ
>ÌÊÞÕÊii«Ê`ÃÊÌÀ>ÃviÀÃÊ «iÀÊÃiV`Ê>ÃÊÜÊ>ÃÊ«ÃÃLi°Ê9ÕÊÜÊÃiiÊ
ÜÊÌÊ`ÊÌ
ÃÊiÝ̰
Disk Bytes/sec /
iÊ@eog>upao+oa_ counter monitors the rate at which bytes are transferred to or from the `ÃÊ`ÕÀ}ÊÀi>`ÊÀÊÜÀÌiÊ«iÀ>ÌðÊÊÌÞ«V>Ê`ÃÊV>ÊÌÀ>ÃviÀÊ>LÕÌÊ£ä Ê«iÀÊÃiV`°Ê iiÀ>Þ]Ê"/*Ê>««V>ÌÃÊ>ÀiÊÌÊVÃÌÀ>i`ÊLÞÊÌ
iÊ`ÃÊÌÀ>ÃviÀÊV>«>VÌÞÊvÊÌ
iÊ`ÃÊ ÃÕLÃÞÃÌiÊÃViÊÌ
iÊ"/*Ê>««V>ÌÃÊ>VViÃÃÊÃ>Ê>ÕÌÃÊvÊ`>Ì>ÊÊ`Û`Õ>Ê`>Ì>L>ÃiÊ ÀiµÕiÃÌðÊvÊÌ
iÊ>ÕÌÊvÊ`>Ì>ÊÌÀ>ÃviÀÊiÝVii`ÃÊÌ
iÊV>«>VÌÞÊvÊÌ
iÊ`ÃÊÃÕLÃÞÃÌi]ÊÌ
iÊ >ÊL>V}ÊÃÌ>ÀÌÃÊ`iÛi«}ÊÊÌ
iÊ`ÃÊÃÕLÃÞÃÌi]Ê>ÃÊÀiviVÌi`ÊLÞÊÌ
iÊ@eogMqaqaHajcpd counters.
Avg. Disk Sec/Read and Avg. Disk Sec/Write =rc*@eogOa_+Na]` and =rc*@eogOa_+SnepaÊÌÀ>VÊÌ
i average amount of timeÊÌÊÌ>iÃÊÊ ÃiV`ÃÊÌÊÀi>`ÊvÀÊÀÊÜÀÌiÊÌÊ>Ê`ðÊ>Û}Ê>ÊÕ`iÀÃÌ>`}ÊvÊÕÃÌÊ
ÜÊÜiÊÌ
iÊ `ÃÃÊ>ÀiÊ
>`}ÊÌ
iÊÜÀÌiÃÊ>`ÊÀi>`ÃÊÌ
>ÌÊÌ
iÞÊÀiViÛiÊV>ÊLiÊ>ÊÛiÀÞÊÃÌÀ}Ê`V>ÌÀÊvÊ Ü
iÀiÊ«ÀLiÃÊi°ÊvÊ̽ÃÊÌ>}ÊÀiÊÌ
>Ê>LÕÌÊ£äÊÃÊÌÊÛiÊÌ
iÊ`>Ì>ÊvÀÊÀÊÌÊÞÕÀÊ `Ã]ÊÞÕÊ>ÞÊii`ÊÌÊÌ>iÊ>ÊÊ>ÌÊÌ
iÊ
>À`Ü>ÀiÊ>`ÊVv}ÕÀ>ÌÊÌÊLiÊÃÕÀiÊiÛiÀÞÌ
}Ê ÃÊÜÀ}ÊVÀÀiVÌÞ°Ê9Õ½Êii`ÊÌÊ}iÌÊiÛiÊLiÌÌiÀÊÀiëÃiÊÌiÃÊvÀÊÌ
iÊÌÀ>Ã>VÌÊ}ÊÌÊ perform well.
C H A P T E R 2 N S Y S T E M P E R F O R M A N C E A N A LY S I S
Disk Bottleneck Resolutions A few of the commonÊ`ÃÊLÌÌiiVÊÀiÃÕÌÃÊ>ÀiÊ>ÃÊvÜÃ\ Ê
UÊ "«Ìâ}Ê>««V>ÌÊÜÀ>`
Ê
UÊ 1Ã}Ê>Êv>ÃÌiÀÊ`ÃÊ`ÀÛi
Ê
UÊ 1Ã}Ê>Ê, Ê>ÀÀ>Þ
Ê
UÊ 1Ã}Ê>Ê- ÊÃÞÃÌi
Ê
UÊ }}Ê`ÃÃÊ«À«iÀÞ
Ê
UÊ 1Ã}Ê>ÊL>ÌÌiÀÞL>Vi`ÊVÌÀiÀÊV>V
i
Ê
UÊ ``}ÊÃÞÃÌiÊiÀÞ
Ê
UÊ Ài>Ì}ÊÕÌ«iÊviÃÊ>`Êvi}ÀÕ«Ã
Ê
UÊ *>V}ÊÌ
iÊÌ>LiÊ>`ÊÌ
iÊ`iÝÊvÀÊÌ
>ÌÊÌ>LiÊÊ`vviÀiÌÊ`ÃÃ
Ê
UÊ ->Û}ÊÌ
iÊ}ÊviÊÌÊ>ÊÃi«>À>ÌiÊ«
ÞÃV>Ê`ÀÛi
Ê
UÊ 1Ã}Ê«>ÀÌÌi`ÊÌ>Lià ½ÊÜÊÜ>ÊÞÕÊÌ
ÀÕ}
Êi>V
ÊvÊÌ
iÃiÊÀiÃÕÌÃÊÊÌÕÀ°
Optimizing Application Workload I cannot stressÊiÕ}
Ê
ÜÊ«ÀÌ>ÌÊÌÊÃÊÌÊ«ÌâiÊ>Ê>««V>̽ÃÊÜÀ>`ÊÊÀiÃÛ}Ê >Ê«iÀvÀ>ViÊÃÃÕi°Ê/
iʵÕiÀiÃÊÜÌ
ÊÌ
iÊ
}
iÃÌÊÕLiÀÊvÊÀi>`ÃÊÜÊLiÊÌ
iÊiÃÊÌ
>ÌÊV>ÕÃiÊ >Ê}Ài>ÌÊ`i>ÊvÊ`ÃÊÉ"°Ê½ÊVÛiÀÊÌ
iÊÃÌÀ>Ìi}iÃÊvÀÊ«Ìâ}ÊÌ
ÃiʵÕiÀiÃÊÊÀiÊ`iÌ>Ê Ì
ÀÕ}
ÕÌÊÌ
iÊÀiÃÌÊvÊÌ
ÃÊL°
Using a Faster Disk Drive "iÊvÊÌ
iÊi>ÃiÃÌÊÀiÃÕÌÃ]Ê>`ÊiÊÌ
>ÌÊÞÕÊÜÊ>`«ÌÊÃÌÊvÊÌ
iÊÌi]ÊÃÊÌÊÕÃiÊ`ÃÊ `ÀÛiÃÊÜÌ
Êv>ÃÌiÀÊ`ÃÊÌÀ>ÃviÀÃÊ«iÀÊÃiV`°ÊÜiÛiÀ]ÊÞÕÊÃ
Õ`ÊÌÊÕÃÌÊÕ«}À>`iÊ`ÃÊ`ÀÛiÃÊ ÜÌ
ÕÌÊvÕÀÌ
iÀÊÛiÃÌ}>ÌÆÊÞÕÊii`ÊÌÊv`ÊÕÌÊÜ
>ÌÊÃÊV>ÕÃ}ÊÌ
iÊÃÌÀiÃÃÊÊÌ
iÊ`ð
Using a RAID Array "iÊÜ>ÞÊvÊLÌ>}Ê`ÃÊÉ"Ê«>À>iÃÊÃÊÌÊVÀi>ÌiÊ>ÊÃ}iÊ«ÊvÊ`ÀÛiÃÊÌÊÃiÀÛiÊ>Ê-+Ê -iÀÛiÀÊ`>Ì>L>ÃiÊviÃ]ÊiÝVÕ`}ÊÌÀ>Ã>VÌÊ}ÊviðÊ/
iÊ«ÊV>ÊLiÊ>ÊÃ}iÊ, Ê>ÀÀ>Þ]Ê Ü
V
ÊÃÊÀi«ÀiÃiÌi`ÊÊ7`ÜÃÊ-iÀÛiÀÊÓäänÊ>ÃÊ>ÊÃ}iÊ«
ÞÃV>Ê`ÃÊ`ÀÛi°Ê/
iÊivviVÌÛiiÃÃÊ vÊ>Ê`ÀÛiÊ«Ê`i«i`ÃÊÊÌ
iÊVv}ÕÀ>ÌÊvÊÌ
iÊ, Ê`Ãð "ÕÌÊvÊ>Ê>Û>>LiÊ, ÊVv}ÕÀ>ÌÃ]ÊÌ
iÊÃÌÊVÞÊÕÃi`Ê, ÊVv}ÕÀ>ÌÃÊ >ÀiÊÌ
iÊvÜ}ÊÃiiÊ}ÕÀiÊÓÈ®\ Ê
UÊ RAID 0: Striping with no fault tolerance
Ê
UÊ RAID 1: Mirroring
Ê
UÊ RAID 5: Striping with parity
Ê
UÊ RAID 1+0: Striping with mirroring
35
36
CHAPTER 2 N SYS TEM P ER FOR MA NC E A NA L YS IS
Figure 2-6. RAID configurations
RAID 0 Since thisÊ, ÊVv}ÕÀ>ÌÊ
>ÃÊÊv>ÕÌÊÌiÀ>Vi]ÊÞÕÊV>ÊÕÃiÊÌÊÞÊÊÃÌÕ>ÌÃÊÜ
iÀiÊ Ì
iÊÀi>LÌÞÊvÊ`>Ì>ÊÃÊÌÊ>ÊVViÀ°Ê/
iÊv>ÕÀiÊvÊ>ÞÊ`ÃÊÊÌ
iÊ>ÀÀ>ÞÊÜÊV>ÕÃiÊV«iÌiÊ `>Ì>ÊÃÃÊÊÌ
iÊ`ÃÊÃÕLÃÞÃÌi°Ê/
iÀivÀi]ÊÞÕÊV>ÌÊÕÃiÊÌÊvÀÊ>ÞÊ`>Ì>ÊviÊÀÊÌÀ>Ã>VÌÊ}Ê viÊÌ
>ÌÊVÃÌÌÕÌiÃÊ>Ê`>Ì>L>Ãi]ÊiÝVi«ÌÊvÀÊÌ
iÊÃÞÃÌiÊÌi«À>ÀÞÊ`>Ì>L>ÃiÊV>i`Êpail`^. /
iÊÕLiÀÊvÊÉ"ÃÊ«iÀÊ`ÃÊÊ, ÊäÊÃÊÀi«ÀiÃiÌi`ÊLÞÊÌ
iÊvÜ}ÊiµÕ>Ì\
É"ÃÊ«iÀÊ`ÃÊrÊ,i>`ÃʳÊ7ÀÌiîÊÉÊ ÕLiÀÊvÊ`ÃÃÊÊÌ
iÊ>ÀÀ>Þ In this equation, Na]`oÊÃÊÌ
iÊÕLiÀÊvÊÀi>`ÊÀiµÕiÃÌÃÊÌÊÌ
iÊ`ÃÊÃÕLÃÞÃÌi]Ê>`ÊSnepao is Ì
iÊÕLiÀÊvÊÜÀÌiÊÀiµÕiÃÌÃÊÌÊÌ
iÊ`ÃÊÃÕLÃÞÃÌi°
RAID 1 , ʣʫÀÛ`iÃÊ
}
Êv>ÕÌÊÌiÀ>ViÊvÀÊVÀÌV>Ê`>Ì>ÊLÞÊÀÀÀ}ÊÌ
iÊ`>Ì>Ê`ÃÊÌÊ>ÊÃi«>À>ÌiÊ `ðÊÌÊV>ÊLiÊÕÃi`ÊÜ
iÀiÊÌ
iÊV«iÌiÊ`>Ì>ÊV>ÊLiÊ>VV`>Ìi`ÊÊiÊ`ÃÊÞ°Ê >Ì>L>ÃiÊ ÌÀ>Ã>VÌÊ}ÊviÃÊvÀÊÕÃiÀÊ`>Ì>L>ÃiÃ]Ê«iÀ>Ì}ÊÃÞÃÌiÊviÃ]Ê>`Ê-+Ê-iÀÛiÀÊÃÞÃÌiÊ`>Ì>L>ÃiÃÊ (i]opan and io`^®Ê>ÀiÊÕÃÕ>ÞÊÃ>ÊiÕ}
ÊÌÊÕÃiÊ, Ê£° /
iÊÕLiÀÊvÊÉ"ÃÊ«iÀÊ`ÃÊÊ, Ê£ÊÃÊÀi«ÀiÃiÌi`ÊLÞÊÌ
iÊvÜ}ÊiµÕ>Ì\
É"ÃÊ«iÀÊ`ÃÊr,i>`ÃʳÊÓÊÊ7ÀÌiîÊÉÊÓ
RAID5 /
à configuration is a good option in many cases. It provides reasonable fault tolerance by ivviVÌÛiÞÊÕÃ}ÊÞÊiÊiÝÌÀ>Ê`ÃÊÌÊÃ>ÛiÊÌ
iÊV«ÕÌi`Ê«>ÀÌÞÊvÊÌ
iÊ`>Ì>ÊÊÌ
iÀÊ`ÃÃ]Ê>ÃÊ Ã
ÜÊÊ}ÕÀiÊÓȰÊ7
iÊÌ
iÀiÊÃÊ>Ê`ÃÊv>ÕÀiÊÊ, ÊxÊVv}ÕÀ>Ì]ÊÉ"Ê«iÀvÀ>ViÊ becomes terrible, although the system does remain usable while operating with the failed drive.
C H A P T E R 2 N S Y S T E M P E R F O R M A N C E A N A LY S I S
ÞÊ`>Ì>ÊÜ
iÀiÊÜÀÌiÃÊ>iÊÕ«ÊÀiÊÌ
>Ê£äÊ«iÀViÌÊvÊÌ
iÊÌÌ>Ê`ÃÊÀiµÕiÃÌÃÊÃÊÌÊ>Ê }`ÊV>``>ÌiÊvÀÊ, Êx°Ê/
ÕÃ]ÊÕÃiÊ, ÊxÊÊÀi>`ÞÊÛÕiÃÊÀÊÛÕiÃÊÜÌ
Ê>ÊÜÊ«iÀViÌ>}iÊvÊ`ÃÊÜÀÌið /
iÊÕLiÀÊvÊÉ"ÃÊ«iÀÊ`ÃÊÊ, ÊxÊÃÊÀi«ÀiÃiÌi`ÊLÞÊÌ
iÊvÜ}ÊiµÕ>Ì\
É"ÃÊ«iÀÊ`ÃÊrÊ,i>`ÃʳÊ{ÊÊ7ÀÌiîÊÉÊ ÕLiÀÊvÊ`ÃÃÊÊÌ
iÊ>ÀÀ>Þ ÃÊÃ
ÜÊÊÌ
iÊ«ÀiVi`}ÊiµÕ>Ì]ÊÌ
iÊÜÀÌiÊ«iÀ>ÌÃÊÊÌ
iÊ, ÊxÊ`ÃÊÃÕLÃÞÃÌiÊ >ÀiÊ>}vi`ÊvÕÀÊÌiðÊÀÊi>V
ÊV}ÊÜÀÌiÊÀiµÕiÃÌ]ÊÌ
iÊvÜ}Ê>ÀiÊÌ
iÊvÕÀÊVÀÀië`}ÊÉ"ÊÀiµÕiÃÌÃÊÊÌ
iÊ`ÃÊÃÕLÃÞÃÌi\ Ê
UÊ "iÊÀi>`ÊÉ"ÊÌÊÀi>`ÊiÝÃÌ}Ê`>Ì>ÊvÀÊÌ
iÊ`>Ì>Ê`ÃÊÜ
ÃiÊVÌiÌÊÃÊÌÊLiÊ`vi`
Ê
UÊ "iÊÀi>`ÊÉ"ÊÌÊÀi>`ÊiÝÃÌ}Ê«>ÀÌÞÊvÀ>ÌÊvÀÊÌ
iÊVÀÀië`}Ê«>ÀÌÞÊ`Ã
Ê
UÊ "iÊÜÀÌiÊÉ"ÊÌÊÜÀÌiÊÌ
iÊiÜÊ`>Ì>ÊÌÊÌ
iÊ`>Ì>Ê`ÃÊÜ
ÃiÊVÌiÌÊÃÊÌÊLiÊ`vi`
Ê
UÊ "iÊÜÀÌiÊÉ"ÊÌÊÜÀÌiÊÌ
iÊiÜÊ«>ÀÌÞÊvÀ>ÌÊÌÊÌ
iÊVÀÀië`}Ê«>ÀÌÞÊ`Ã
/
iÀivÀi]ÊÌ
iÊvÕÀÊÉ"ÃÊvÀÊi>V
ÊÜÀÌiÊÀiµÕiÃÌÊVÃÃÌÊvÊÌÜÊÀi>`ÊÉ"ÃÊ>`ÊÌÜÊÜÀÌiÊÉ"ð Ê>Ê"/*Ê`>Ì>L>Ãi]Ê>ÊÌ
iÊ`>Ì>Ê`vV>ÌÃÊ>ÀiÊi`>ÌiÞÊÜÀÌÌiÊÌÊÌ
iÊÌÀ>Ã>VÌÊ }ÊviÊ>ÃÊ«>ÀÌÊvÊÌ
iÊ`>Ì>L>ÃiÊÌÀ>Ã>VÌ]ÊLÕÌÊÌ
iÊ`>Ì>ÊÊÌ
iÊ`>Ì>ÊviÊÌÃivÊÃÊÃÞV
Àâi`Ê ÜÌ
ÊÌ
iÊÌÀ>Ã>VÌÊ}ÊviÊVÌiÌÊ>ÃÞV
ÀÕÃÞÊÊL>ÌV
Ê«iÀ>ÌðÊ/
ÃÊ«iÀ>ÌÊÃÊ >>}i`ÊLÞÊÌ
iÊÌiÀ>Ê«ÀViÃÃÊvÊ-+Ê-iÀÛiÀÊV>i`ÊÌ
iÊcheckpoint process°Ê/
iÊvÀiµÕiVÞÊvÊ this operation can be controlled by using the na_kranuejpanr]h$iej% configuration paramiÌiÀÊvÊ-+Ê-iÀÛiÀ° Because of the continuous write operation in the transaction log file for a highly transacÌ>Ê"/*Ê`>Ì>L>Ãi]Ê«>V}ÊÌÀ>Ã>VÌÊ}ÊviÃÊÊ>Ê, ÊxÊ>ÀÀ>ÞÊÜÊÃ}vV>ÌÞÊ`i}À>`iÊ Ì
iÊ>ÀÀ>Þ½ÃÊ«iÀvÀ>Vi°ÊÌ
Õ}
ÊÞÕÊÃ
Õ`ÊÌÊ«>ViÊÌ
iÊÌÀ>Ã>VÌ>Ê}ÊviÃÊÊ>Ê, Ê xÊ>ÀÀ>Þ]ÊÌ
iÊ`>Ì>ÊviÃÊ>ÞÊLiÊ«>Vi`ÊÊ, Êx]ÊÃViÊÌ
iÊÜÀÌiÊ«iÀ>ÌÃÊÌÊÌ
iÊ`>Ì>ÊviÃÊ>ÀiÊ intermittent and batched together to improve the efficiency of the write operation.
RAID 1+0 (RAID 10) , Ê£³äÊ>ÃÊÀiviÀÀi`ÊÌÊ>ÃÊä³£Ê>`Ê£ä®ÊVv}ÕÀ>ÌÊvviÀÃÊ>Ê
}
Ê`i}ÀiiÊvÊv>ÕÌÊÌiÀ>ViÊ LÞÊÀÀÀ}ÊiÛiÀÞÊ`>Ì>Ê`ÃÊÊÌ
iÊ>ÀÀ>Þ°ÊÌÊÃÊ>ÊÕV
ÊÀiÊiÝ«iÃÛiÊÃÕÌÊÌ
>Ê, Ê x]ÊÃViÊ`ÕLiÊÌ
iÊÕLiÀÊvÊ`>Ì>Ê`ÃÃÊ>ÀiÊÀiµÕÀi`ÊÌÊ«ÀÛ`iÊv>ÕÌÊÌiÀ>Vi°Ê/
ÃÊ, Ê configuration should be used where a large volume is required to save data and more than 10 «iÀViÌÊvÊ`ÃÊÀiµÕiÃÌÃÊ>ÀiÊÜÀÌiðÊ-ViÊ, Ê£³äÊÃÕ««ÀÌÃÊsplit seeks (the ability to distribute Ì
iÊÀi>`Ê«iÀ>ÌÃÊÌÊÌ
iÊ`>Ì>Ê`ÃÊ>`ÊÌ
iÊÀÀÀÊ`ÃÊ>`ÊÌ
iÊVÛiÀ}iÊÌ
iÊÌÜÊ`>Ì>Ê ÃÌÀi>î]ÊÀi>`Ê«iÀvÀ>ViÊÃÊ>ÃÊÛiÀÞÊ}`°Ê/
ÕÃ]ÊÕÃiÊ, Ê£³äÊÜ
iÀiÛiÀÊ«iÀvÀ>ViÊÃÊ critical. /
iÊÕLiÀÊvÊÉ"ÃÊ«iÀÊ`ÃÊÊ, Ê£³äÊÃÊÀi«ÀiÃiÌi`ÊLÞÊÌ
iÊvÜ}ÊiµÕ>Ì\
É"ÃÊ«iÀÊ`ÃÊrÊ,i>`ÃʳÊÓÊÊ7ÀÌiîÊÉÊ ÕLiÀÊvÊ`ÃÃÊÊÌ
iÊ>ÀÀ>Þ
Using a SAN System -ÌÀ>}iÊ>Ài>ÊiÌÜÀÃÊ- îÊÀi> largely the domain of large-scale enterprise systems, >Ì
Õ}
ÊÌ
iÊVÃÌÊÃÊV}Ê`ܰÊÊ- ÊV>ÊLiÊÕÃi`ÊÌÊVÀi>ÃiÊ«iÀvÀ>ViÊvÊ>ÊÃÌÀ>}iÊ ÃÕLÃÞÃÌiÊLÞÊëÞÊ«ÀÛ`}ÊÀiÊë`iÃÊ>`Ê`ÃÊ`ÀÛiÃÊÌÊÀi>`ÊvÀÊ>`ÊÜÀÌiḚ̂Ê
37
38
CHAPTER 2 N SYS TEM P ER FOR MA NC E A NA L YS IS
ʺ`ÀÛi»Ê>ÃÊÃiiÊLÞÊÌ
iÊ«iÀ>Ì}ÊÃÞÃÌiÊÃÊÃÌÀ«i`Ê>VÀÃÃÊÕÌ«iÊÀi`Õ`>ÌÊ`ÀÛiÃÊÊ>Ê- Ê Ê>Ê>iÀÊÃ>ÀÊÌÊÌ
>ÌÊvÊ, ÊxÊLÕÌÊÜÌ
ÕÌÊÌ
iÊÛiÀ
i>`ÊvÊ, ÊxÊÀÊÌ
iÊ«iÀvÀ>ViÊ ÃÃÊV>ÕÃi`ÊLÞÊ`ÃÊv>ÕÀiÃ°Ê iV>ÕÃiÊvÊÌ
iÀÊÃâi]ÊV«iÝÌÞ]Ê>`ÊVÃÌ]Ê- ÃÊ>ÀiÊÌÊiViÃÃ>Àily a good solution in all cases. Also, depending on the amount of data, direct attached storage -®ÊV>ÊLiÊVv}ÕÀi`ÊÌÊÀÕÊv>ÃÌiÀ°Ê/
iÊ«ÀV«>ÊÃÌÀi}Ì
ÊvÊ- ÊÃÞÃÌiÃÊÃÊÌÊÀiviVÌi`ÊÊ performance but rather in the areas of scalability, availability, and maintenance.
Aligning Disks Properly Aligning a `ÃÊ`ÀÛiÊÃÊÌÊ>ÊÜiÜÊÃÃÕi]ÊLÕÌÊÀiÊ>`ÊÀiÊ ÃÊ>ÀiÊLiV}Ê>Ü>ÀiÊ vḚ̂Ê/
iÊÜ>ÞÊ`>Ì>ÊÃÊÃÌÀi`ÊÊ>Ê`ÃÊÃÊÊ>ÊÃiÀiÃÊvÊÃiVÌÀÃÊ>ÃÊÀiviÀÀi`ÊÌÊ>ÃÊblocks®ÊÌ
>ÌÊ >ÀiÊÃÌÀi`ÊÊÌÀ>VðÊÊ`ÃÊÃÊÕÌÊvÊ>}iÌÊÜ
iÊÌ
iÊÃâiÊvÊÌ
iÊÌÀ>V]Ê`iÌiÀi`ÊLÞÊÌ
iÊ Ûi`À]ÊVÃÃÌÃÊvÊ>ÊÕLiÀÊvÊÃiVÌÀÃÊ`vviÀiÌÊvÀÊÌ
iÊ`iv>ÕÌÊÃâiÊÌ
>ÌÊÞÕ½ÀiÊÜÀÌ}ÊÌ°Ê /
ÃÊÜÊi>ÊÌ
>ÌÊiÊÃiVÌÀÊÜÊLiÊÜÀÌÌiÊVÀÀiVÌÞ]ÊLÕÌÊÌ
iÊiÝÌÊiÊÜÊ
>ÛiÊÌÊVÀÃÃÊ ÌÜÊÌÀ>VðÊ/
ÃÊV>ÊÀiÊÌ
>Ê`ÕLiÊÌ
iÊ>ÕÌÊvÊÉ"ÊÀiµÕÀi`ÊÌÊÜÀÌiÊÀÊÀi>`ÊvÀÊÌ
iÊ `ðÊ/
iÊiÞÊÃÊÌÊ>}ÊÌ
iÊ«>ÀÌÌÊÃÊÌ
>ÌÊÞÕ½ÀiÊÃÌÀ}ÊÌ
iÊVÀÀiVÌÊÕLiÀÊvÊÃiVÌÀÃÊvÀÊ Ì
iÊÌÀ>V°
Using a Battery-Backed Controller Cache ÀÊLiÃÌÊ«iÀvÀ>Vi]ÊÕÃiÊ>ÊV>V
}Ê, ÊVÌÀiÀÊvÀÊ-+Ê-iÀÛiÀÊviÃ]ÊLiV>ÕÃiÊÌ
iÊVÌÀiÀÊ}Õ>À>ÌiiÃÊÌ
>ÌÊ`>Ì>ÊiÌÀÕÃÌi`ÊÌÊÌÊÃÊÜÀÌÌiÊÌÊ`ÃÊiÛiÌÕ>Þ]ÊÊ>ÌÌiÀÊ
ÜÊ>À}iÊ the queue is or even if power fails. When using a controller cache, the write requests to the , ÊVÌÀiÀÊ>ÀiÊÜÀÌÌiÊÌÊÌ
iÊVÌÀiÀÊV>V
i]Ê>`ÊÌ
iÊÃÕLÃiµÕiÌÊ`ÃÊ«iÀ>ÌÃÊ>ÀiÊ ÃV
i`Õi`Ê>ÃÞV
ÀÕÃÞ°Ê/
ÃÊ>ÃÞV
ÀÕÃÊÌiV
µÕiÊvÊ`}ÊÉ"ÃÊvÀÊÌ
iÊÜÀÌiÊÀiµÕiÃÌÃÊ «ÀÛiÃÊÌ
iÊ«iÀvÀ>ViÊvÊ`>Ì>L>ÃiʵÕiÀiÃÊÃ}vV>ÌÞ°Ê/
iÊ>ÕÌÊvÊ«iÀvÀ>ViÊ «ÀÛiiÌÊ`i«i`ÃÊÊÌ
iÊÃâiÊvÊÌ
iÊVÌÀiÀÊV>V
iÊ>`ÊÕLiÀÊvÊÜÀÌiÊÀiµÕiÃÌð 7
iÊÌ
iÀiÊÃÊ>ÊÕÀi>LiÊL>ÌÌiÀÞÊL>VÕ«]Ê«ÜiÀÊ`ÃÀÕ«ÌÃÊÜÊV>ÕÃiÊ>ÊÃÃÊvÊiÜÉ `vi`Ê`>Ì>ÊÌ
>ÌÊÃ]Ê`ÀÌÞÊ`>Ì>®ÊÊÌ
iÊVÌÀiÀÊV>V
iÊÌ
>ÌÊÜ>ÃÊÌÊvÕÃ
i`ÊÌÊÌ
iÊ`ðÊvÊÌ
ÃÊ dirty data belonged to a committed transaction, then the consistency of the data will be compromised. ÀÊiÝ>«i]ÊvÊ>ÊÕÃiÀÊVÌÃÊ>Ê`>Ì>L>ÃiÊÌÀ>Ã>VÌ]ÊÌ
iÊ`ÀÌÞÊ`>Ì>ÊvÀÊÌ
iÊÌÀ>Ã>VÌÊ will be written to the controller cache, and the user will be informed about a successful completion of the transaction. But should the power go down before this committed data is written ÌÊÌ
iÊ`Ã]ÊÌ
iÊ>ÊÌ
iÊ`>Ì>ÊÊÌ
iÊV>V
iÊÜÊLiÊÃÌÊÕiÃÃÊÌ
iÊVÌÀiÀÊ
>ÃÊ>ÊÀi>LiÊL>ÌÌiÀÞÊ L>VÕ«° /Ê>Û`Ê`>Ì>ÊVÃÃÌiVÞ]ÊiÃÕÀiÊvÀÊÞÕÀÊ`ÃÊÛi`ÀÊÌ
>ÌÊÌ
iÊVÌÀiÀÊV>V
iÊ
>ÃÊ>Ê Ài>LiÊL>ÌÌiÀÞÊL>VÕ«°
Adding System Memory When physical iÀÞÊÃÊÃV>ÀVi]ÊÌ
iÊÃÞÃÌiÊÃÌ>ÀÌÃÊÜÀÌ}ÊÌ
iÊVÌiÌÃÊvÊiÀÞÊL>VÊÌÊ `ÃÊ>`ÊÀi>`}ÊÃ>iÀÊLVÃÊvÊ`>Ì>ÊÀiÊvÀiµÕiÌÞ]ÊV>ÕÃ}Ê>ÊÌÊvÊ«>}}°Ê/
iÊiÃÃÊ iÀÞÊÌ
iÊÃÞÃÌiÊ
>Ã]ÊÌ
iÊÀiÊÌ
iÊ`ÃÊÃÕLÃÞÃÌiÊÃÊÕÃi`°Ê/
ÃÊV>ÊLiÊÀiÃÛi`ÊÕÃ}ÊÌ
iÊ iÀÞÊLÌÌiiVÊÀiÃÕÌÃÊiÕiÀ>Ìi`ÊÊÌ
iÊ«ÀiÛÕÃÊÃiV̰
C H A P T E R 2 N S Y S T E M P E R F O R M A N C E A N A LY S I S
Creating Multiple Files and Filegroups Ê-+Ê-iÀÛiÀ]Êi>V
user database consists of one or more data files and one or more transacÌÊ}ÊviðÊ/
iÊ`>Ì>ÊviÃÊLi}}ÊÌÊ>Ê`>Ì>L>ÃiÊV>ÊLiÊ}ÀÕ«i`ÊÌ}iÌ
iÀÊÊiÊÀÊÀiÊ vi}ÀÕ«ÃÊvÀÊ>`ÃÌÀ>ÌÛiÊ>`Ê`>Ì>Ê>V>ÌÉ«>ViiÌÊ«ÕÀ«ÃiðÊÀÊiÝ>«i]ÊvÊ>Ê`>Ì>Ê file is placed in a separate filegroup, then write access to all the tables in the filegroup can be VÌÀi`ÊViVÌÛiÞÊLÞÊ>}ÊÌ
iÊvi}ÀÕ«ÊÀi>`ÞÊÌÀ>Ã>VÌÊ}ÊviÃÊ`ÊÌÊLi}ÊÌÊ >ÞÊvi}ÀÕ«®° 9ÕÊV>ÊVÀi>ÌiÊ>Êvi}ÀÕ«ÊvÀÊ>Ê`>Ì>L>ÃiÊvÀÊ-+Ê-iÀÛiÀÊ>>}iiÌÊ-ÌÕ`]Ê>ÃÊÃ
ÜÊ Ê}ÕÀiÊÓǰÊ/
iÊvi}ÀÕ«ÃÊvÊ>Ê`>Ì>L>ÃiÊ>ÀiÊ«ÀiÃiÌi`ÊÊÌ
iÊi}ÀÕ«ÃÊ«>iÊvÊÌ
iÊ >Ì>L>ÃiÊ*À«iÀÌiÃÊ`>}ÊLݰ
Figure 2-7. Filegroups configuration Ê}ÕÀiÊÓÇ]ÊÞÕÊV>ÊÃiiÊÌ
>ÌÊÌ
iÀiÊÃÊÞÊ>ÊÃ}iÊvi}ÀÕ«ÊVÀi>Ìi`ÊLÞÊ`iv>ÕÌÊÜÌ
Ê `ÛiÌÕÀi7ÀÃÓään°Ê9ÕÊV>Ê>``ÊÕÌ«iÊviÃÊÌÊÕÌ«iÊvi}ÀÕ«ÃÊ`ÃÌÀLÕÌi`Ê>VÀÃÃÊ `ÀÛiÃÊÃÊÌ
>ÌÊÜÀÊV>ÊLiÊ`iÊÊ«>À>iÊ>VÀÃÃÊÌ
iÊ}ÀÕ«ÃÊ>`Êvið 9ÕÊV>Ê>``Ê>Ê`>Ì>ÊviÊÌÊ>Êvi}ÀÕ«ÊÊÌ
iÊ >Ì>L>ÃiÊ*À«iÀÌiÃÊ`>}ÊLÝÊÊÌ
iÊiÃÊ Ü`ÜÊLÞÊÃiiVÌ}ÊvÀÊÌ
iÊ`À«`ÜÊÃÌ]Ê>ÃÊÃ
ÜÊÊ}ÕÀiÊÓn°
39
40
CHAPTER 2 N SYS TEM P ER FOR MA NC E A NA L YS IS
Figure 2-8. Data files configuration You can also do this programmatically, as follows (behacnkql*omhÊÊÌ
iÊ`Ü>`®\ =HPAN@=P=>=OA=`rajpqnaSkngo.,,4=@@BEHACNKQLEJ@ATAO7 =HPAN@=P=>=OA=`rajpqnaSkngo.,,4=@@BEHA $J=IA9=`rajpqnaSkngo.,,4[@]p].( BEHAJ=IA9#?6XLnkcn]iBehaoXIe_nkokbpOMH£ OanranXIOOMH-,*CB.,,4XIOOMHX@=P=X=`rajpqnaSkngo.,,4[.*j`b#( OEVA9-I>( BEHACNKSPD9-,!%PKBEHACNKQLEj`atao7 Ê>ÊÃÞÃÌiÊÜÌ
ÊÕÌ«iÊ«ÀViÃÃÀÃ]Ê-+Ê-iÀÛiÀÊV>ÊÌ>iÊ>`Û>Ì>}iÊvÊÕÌ«iÊviÃÊ>`Ê filegroups by performing multiple parallel scans on the data files. As tables in a file are accessed sequentially, a thread is created to read the associated data file. If a filegroup consists of four data files, then four threads will be created to read data in parallel. If the data files are placed on vÕÀÊ`vviÀiÌÊ«
ÞÃV>Ê`ÃÃ]ÊÌ
iÊ>ÊÌ
iÊ`ÃÊë`iÃÊV>ÊÜÀÊÃÕÌ>iÕÃÞ°Ê/
iÀivÀi]ÊÊ>Ê ÕÌ`ÃÊÃÕLÃÞÃÌi]ÊÌÊÃÊÕÃÕ>ÞÊ>`Û>Ì>}iÕÃÊÌÊ
>ÛiÊÕÌ«iÊviÃÊÊÌ
iÊvi}ÀÕ«° 1Ã}ÊÕÌ«iÊviÃÊ>`Êvi}ÀÕ«ÃÊ>ÃÊi>LiÃÊÞÕÊÌÊ«ÀÛiÊÌ
iÊ«iÀvÀ>ViÊvÊÊ operations. By separating tables that are frequently joined into separate filegroups and then «ÕÌÌ}ÊviÃÊÜÌ
ÊÌ
iÊvi}ÀÕ«ÃÊÊÃi«>À>ÌiÊ`ÃÃÊÀÊ1 -]ÊÌ
iÊÃi«>À>Ìi`ÊÉ"Ê«>Ì
ÃÊV>Ê ÀiÃÕÌÊÊ«ÀÛi`Ê«iÀvÀ>Vi°ÊÀÊiÝ>«i]ÊVÃ`iÀÊÌ
iÊvÜ}ʵÕiÀÞ\
C H A P T E R 2 N S Y S T E M P E R F O R M A N C E A N A LY S I S
OAHA?P& BNKIpEJJANFKEJp. KJp-*_-9p.*_. If the tables p- and p. are placed in separate filegroups containing one file each, then on a multiple-processor system a separate thread will be created for each table. With the files vÊÌ
iÊÌÜÊvi}ÀÕ«ÃÊÊ`vviÀiÌÊ«
ÞÃV>Ê`ÃÃ]Ê`>Ì>ÊvÀÊLÌ
ÊÌ
iÊÌ>LiÃÊV>ÊLiÊÀiÌÀiÛi`Ê simultaneously. It is recommended for performance and recovery purposes that, if multiple filegroups are to be used, the primary filegroup should be used only for system objects and secondary vi}ÀÕ«ÃÊÃ
Õ`ÊLiÊÕÃi`ÊÞÊvÀÊÕÃiÀÊLiVÌðÊ/
ÃÊ>««À>V
Ê«ÀÛiÃÊ`ÃÊÉ"Ê>`ÊÌ
iÊ>LÌÞÊÌÊÀiVÛiÀÊvÀÊVÀÀի̰Ê/
iÊÀiVÛiÀ>LÌÞÊvÊ>Ê`>Ì>L>ÃiÊÃÊ
}
iÀÊvÊÌ
iÊ«À>ÀÞÊ`>Ì>Ê viÊ>`ÊÌ
iÊ}ÊviÃÊ>ÀiÊÌ>V̰Ê1ÃiÊÌ
iÊ«À>ÀÞÊvi}ÀÕ«ÊvÀÊÃÞÃÌiÊLiVÌÃÊÞ]Ê>`ÊÃÌÀiÊ>Ê user-related objects on a secondary filegroup. Spreading a databaseÊÌÊÕÌ«iÊviÃ]ÊiÛiÊÌÊÌ
iÊÃ>iÊ`ÀÛi]Ê>iÃÊÌÊi>ÃÞÊÌÊÛiÊ Ì
iÊ`>Ì>L>ÃiÊviÃÊÌÊÃi«>À>ÌiÊ`ÀÛiÃ]Ê>}ÊvÕÌÕÀiÊ`ÃÊÕ«}À>`iÃÊi>ÃiÀ°ÊÀÊiÝ>«i]ÊÌÊ move a user database file (=`rajpqnaSkngo.,,4[.*j`b®ÊÌÊ>ÊiÜÊ`ÃÊÃÕLÃÞÃÌiÊB6®]ÊÞÕÊV>Ê follow these steps: 1. iÌ>V
ÊÌ
iÊÕÃiÀÊ`>Ì>L>ÃiÊ>ÃÊvÜÃÊbeha[ikra*omhÊÊÌ
iÊV`iÊ`Ü>`®\ QOAi]opan7 CK ol[`ap]_d[`^#=`rajpqnaSkngo.,,4#7 CK 2. «ÞÊÌ
iÊ`>Ì>ÊviÊ=`rajpqnaSkngo.,,4[.*j`b to a folder B6X@]p]XÊÊÌ
iÊiÜÊ`ÃÊÃÕLsystem. 3. Reattach the user database by referring files at appropriate locations, as shown here: QOAi]opan7 CK ol[]pp]_d[`^#=`rajpqnaSkngo.,,4# (#?6XLnkcn]iBehaoXIe_nkokbpOMH OanranXIOOMH-,*CB.,,4XIOOMHX@=P=X=`rajpqnaSkngo.,,4*i`b# (#B6X@=P=X=`rajpqnaSkngo.,,4[.*j`b# (#?6XLnkcn]iBehaoXIe_nkokbpOMH OanranXIOOMH-,*CB.,,4XIOOMHX@=P=X=`rajpqnaSkngo.,,4*h`b#7 CK 4. /ÊÛiÀvÞÊÌ
iÊviÃÊLi}}ÊÌÊ>Ê`>Ì>L>Ãi]ÊiÝiVÕÌiÊÌ
iÊvÜ}ÊV>`Ã\ QOA=`rajpqnaskngo.,,4 CK OAHA?P&BNKIouo*`]p]^]oa[behao CK
41
42
CHAPTER 2 N SYS TEM P ER FOR MA NC E A NA L YS IS
9ÕÊ>ÞÊ
>ÛiÊÌVi`ÊÊ}ÕÀiÊÓnÊÌ
>ÌÊiÊvÊÌ
iÊviÃÊÊÌ
iÊ`ÛiÌÕÀi7ÀÃÓäänÊ`>Ì>base is of type BehaOpna]i°Ê/
iÊBehaOpna]iÊ`>Ì>ÊÃÌÀ>}iÊÜ>ÃÊÌÀ`ÕVi`ÊÜÌ
Ê-+Ê-iÀÛiÀÊÓään°Ê ÌÃÊ«ÕÀ«ÃiÊÃÊÌÊÃi«>À>ÌiÊÌ
iÊÜÀÊvÊ>Ì>}ÊÕÃÌÀÕVÌÕÀi`Ê`>Ì>]Ê>À}iÊL>ÀÞÊviÃ]Ê8]Ê >`ÊÃÊ]ÊvÀÊÌ
iÊ`>Ì>L>Ãi]ÊÛ}ÊÌ
>ÌÊÜÀÊÌÊÌ
iÊ7`ÜÃÊ-iÀÛiÀÊviÊÃÞÃÌi°Ê-]Ê>Ì
Õ}
Ê the data in a BehaOpna]i column can be treated as a column in a table, the processing of the `>Ì>ÊÃÊÌÊ
>`i`ÊLÞÊÌ
iÊ`>Ì>L>ÃiÊi}i]ÊÌ
iÀiLÞÊ`iVÀi>Ã}ÊÌ
iÊÜÀ>`ÊvÊÌ
iÊ-+Ê-iÀÛiÀ° 9ÕÊV>ÊÃiÀÌ]ÊÕ«`>Ìi]Ê>`Ê`iiÌiÊviÃÌÀi>Ê`>Ì>ÊÌ
ÀÕ}
Ê/-+ÊV>`ÃÊÀÊLÞÊV>}Ê directly to streaming interfaces using KlajBeha$% and SnepaBeha$%ÊvÀʰ /Ê«À}À>}Ê >}Õ>}iðÊÀÊ>À}iÀÊÃiÌÃÊvÊÕÃÌÀÕVÌÕÀi`Ê`>Ì>]ÊÜ
iÊÌ
iÊ`>Ì>ÊÃÌÀi`ÊÊÌ
iÊ>À}iÊLiVÌÊVumn is larger than 1MB, using BehaOpna]i provides a serious increase in performance. Smaller data sets are better off being stored within the database using more traditional types such as R=N?D=N$I=T% or R=N>EJ=NU$I=T%.
Placing the Table and Index on Separate Disks If a system hasÊÕÌ«iÊ«ÀViÃÃÀÃ]Ê-+Ê-iÀÛiÀÊV>ÊÌ>iÊ>`Û>Ì>}iÊvÊÕÌ«iÊvi}ÀÕ«ÃÊLÞÊ >VViÃÃ}ÊÌ>LiÃÊ>`ÊVÀÀië`}ÊVÕÃÌiÀi`Ê`iÝiÃÊÕÃ}ÊÃi«>À>ÌiÊÉ"Ê«>Ì
ðÊÊVÕÃÌiÀi`Ê`iÝÊV>ÊLiÊVÀi>Ìi`ÊÊ>ÊëiVvVÊvi}ÀÕ«Ê>ÃÊvÜÃ\ ?NA=PAEJ@ATe-KJp-$_-%KJEj`atao
NTip The nonclustered index and other types of indexes are explained in Chapter 4.
Saving Log Files to a Separate Physical Disk -+Ê-iÀÛiÀÊ}ÊviÃÊÃ
Õ`Ê>Ü>ÞÃ]ÊÜ
iÀiÊ«ÃÃLi]ÊLiÊV>Ìi`ÊÊ>ÊÃi«>À>ÌiÊ
>À`Ê`ÃÊ `ÀÛiÊvÀÊ>ÊÌ
iÀÊ-+Ê-iÀÛiÀÊ`>Ì>L>ÃiÊviðÊ/À>Ã>VÌÊ}Ê>VÌÛÌÞÊ«À>ÀÞÊVÃÃÌÃÊvÊ ÃiµÕiÌ>ÊÜÀÌiÊÉ"]ÊÕiÊÌ
iÊÃiµÕiÌ>ÊÀÊÀ>`®ÊÉ"ÊÀiµÕÀi`ÊvÀÊÌ
iÊ`>Ì>ÊviÃ°Ê -i«>À>Ì}ÊÌÀ>Ã>VÌÊ}Ê>VÌÛÌÞÊvÀÊÌ
iÀÊÃiµÕiÌ>Ê`ÃÊÉ"Ê>VÌÛÌÞÊV>ÊÀiÃÕÌÊÊÉ"Ê «iÀvÀ>ViÊ«ÀÛiiÌÃ]ÊLiV>ÕÃiÊÌÊ>ÜÃÊÌ
iÊ
>À`Ê`ÃÊ`ÀÛiÃÊVÌ>}Ê}ÊviÃÊÌÊVViÌÀ>ÌiÊÊÃiµÕiÌ>ÊÉ"° /
iÊ>ÀÊ«ÀÌÊvÊÌiÊÀiµÕÀi`ÊÌÊ>VViÃÃÊ`>Ì>ÊvÀÊ>Ê
>À`Ê`ÃÊÃÊëiÌÊÊÌ
iʫ
ÞÃV>ÊÛiiÌÊvÊÌ
iÊ`ÃÊë`iÊ
i>`ÊÌÊV>ÌiÊÌ
iÊ`>Ì>°Ê"ViÊÌ
iÊ`>Ì>ÊÃÊV>Ìi`]ÊÌ
iÊ`>Ì>ÊÃÊ read electronically, which is much faster than the physical movement of the head. With only ÃiµÕiÌ>ÊÉ"Ê«iÀ>ÌÃÊÊÌ
iÊ}Ê`Ã]ÊÌ
iÊë`iÊ
i>`ÊvÊÌ
iÊ}Ê`ÃÊV>ÊÜÀÌiÊÌÊÌ
iÊ }Ê`ÃÊÜÌ
Ê>ÊÕÊvÊ«
ÞÃV>ÊÛiḭÊvÊÌ
iÊÃ>iÊ`ÃÊÃÊÕÃi`ÊvÀÊ`>Ì>ÊviÃ]Ê
ÜiÛiÀ]ÊÌ
iÊë`iÊ
i>`Ê
>ÃÊÌÊÛiÊÌÊÌ
iÊVÀÀiVÌÊV>ÌÊLivÀiÊÜÀÌ}ÊÌÊÌ
iÊ}Êvi°Ê/
ÃÊ increases the time required to write to the log file and thereby hurts performance. ÕÀÌ
iÀÀi]ÊvÀÊ-+Ê-iÀÛiÀÊÜÌ
ÊÕÌ«iÊ"/*Ê`>Ì>L>ÃiÃ]ÊÌ
iÊÌÀ>Ã>VÌÊ}ÊviÃÊ should be physically separated from each other on different physical drives to improve pervÀ>Vi°ÊÊiÝVi«ÌÊÌÊÌ
ÃÊÀiµÕÀiiÌÊÃÊ>ÊÀi>`ÞÊ`>Ì>L>ÃiÊÀÊ>Ê`>Ì>L>ÃiÊÜÌ
ÊÛiÀÞÊ few database changes. Since no online changes are made to the read-only database, no write «iÀ>ÌÃÊ>ÀiÊ«iÀvÀi`ÊÊÌ
iÊ}Êvi°Ê/
iÀivÀi]Ê
>Û}ÊÌ
iÊ}ÊviÊÊ>ÊÃi«>À>ÌiÊ`ÃÊÃÊÌÊ required for the read-only databases.
C H A P T E R 2 N S Y S T E M P E R F O R M A N C E A N A LY S I S
Partitioning Tables In addition toÊëÞÊ>``}ÊviÃÊÌÊvi}ÀÕ«ÃÊ>`ÊiÌÌ}Ê-+Ê-iÀÛiÀÊ`ÃÌÀLÕÌiÊÌ
iÊ`>Ì>Ê LiÌÜiiÊÌ
i]Ê̽ÃÊ«ÃÃLiÊÌÊ`iviÊ>Ê
ÀâÌ>ÊÃi}iÌ>ÌÊvÊ`>Ì>ÊV>i`Ê>Êpartition so that data is divided up between multiple files by the partition. A filtered set of data is a segmentÆÊvÀÊiÝ>«i]ÊvÊÌ
iÊ«>ÀÌÌÊÃÊLÞÊÌ
]ÊÌ
iÊÃi}iÌÊvÊ`>Ì>ÊÃÊ>ÞÊ}ÛiÊÌ
°Ê
Ài>Ì}Ê>Ê«>ÀÌÌÊÛiÃÊÌ
iÊÃi}iÌÊvÊ`>Ì>ÊÌÊ>Ê«>ÀÌVÕ>ÀÊviÊ>`ÊÞÊÌ
>ÌÊvi°Ê/
ÃÊ provides a massive increase in speed because, when querying against well-defined partitions, only the files with the partitions of data you’re interested in will be accessed during a given query. If you assume for a moment that data is partitioned by month, then each Ì
½ÃÊ`>Ì>ÊviÊV>ÊLiÊÃiÌÊÌÊÀi>`ÞÊ>ÃÊi>V
ÊÌ
Êi`ðÊ/
>ÌÊÀi>`ÞÊÃÌ>ÌÕÃÊi>ÃÊ Ì
>ÌÊÊV}ÊÜÊVVÕÀÊÊ>ÊviÊ>ÃÊÀi>`ʵÕiÀiÃÊ>ÀiÊÀÕÊ>}>ÃÌÊÌ]ÊvÕÀÌ
iÀÊVÀi>Ã}ÊÌ
iÊ performance of your queries. Partitions can actually be mapped to the same files within a filegroup, which won’t offer any performance improvements but can be used for easier maintenance of the data.
Processor Bottleneck Analysis -+Ê-iÀÛiÀÊÓäänÊ>iÃÊ
i>ÛÞÊÕÃiÊvÊ>ÞÊprocessor resource available. You can use the Perfor>ViÊÌÀÊVÕÌiÀÃÊÊ/>LiÊÓÎÊÌÊ>>ÞâiÊ«ÀiÃÃÕÀiÊÊÌ
iÊ«ÀViÃÃÀÊÀiÃÕÀVi° Table 2-3. Performance Monitor Counters to Analyze CPU Pressure
Object(Instance[,InstanceN])
Counter
Description
Value
Lnk_aookn$[Pkp]h%
!Lnk_aooknPeia
Percentage of time processor was busy
Average value < 80%
!Lnerehaca`Peia
Percentage of processor time spent in privileged mode
Average value < 10%
Lnk_aooknMqaqaHajcpd
ÕLiÀ of requests outstanding on the processor
Average value < 2
?kjpatpOsep_dao+oa_
Rate at which Average processor is switched value < 1,000 per processor from one thread to another
>]p_dNamqaopo+oa_
-+ÊV>` batches received per second
Based on your standard ÜÀ>`
OMH?kileh]pekjo+oa_
ÕLiÀ of times -+ÊÃÊV«i`
Average value > 100
OMHNa_kileh]pekjo+oa_
ÕLiÀ of recompiles
Ouopai
OMHOanran6OMHOp]peope_o
½ÊÜÊÜ>ÊÞÕÊÌ
ÀÕ}
ÊÌ
iÃiÊVÕÌiÀÃÊÊÀiÊ`iÌ>°
43
44
CHAPTER 2 N SYS TEM P ER FOR MA NC E A NA L YS IS
% Processor Time !Lnk_aooknPeia should not beÊVÃÃÌiÌÞÊ
}
Ê}Ài>ÌiÀÊÌ
>ÊÇxÊ«iÀViÌ®°Ê/
iÊivviVÌÊvÊ any sustained processor time greater than 90 percent is the same as that with 100 percent. If !Lnk_aooknPeiaÊÃÊVÃÃÌiÌÞÊ
}
Ê>`Ê`ÃÊ>`ÊiÌÜÀÊVÕÌiÀÊÛ>ÕiÃÊ>ÀiÊÜ]ÊÞÕÀÊvÀÃÌÊ priority must be to reduce the stress on the processor. ÀÊiÝ>«i]ÊvÊ!Lnk_aooknPeiaÊÃÊnxÊ«iÀViÌÊ>`Ê!@eogPeiaÊÃÊxäÊ«iÀViÌ]ÊÌ
iÊÌÊÃÊ µÕÌiÊiÞÊÌ
>ÌÊ>Ê>ÀÊ«>ÀÌÊvÊÌ
iÊ«ÀViÃÃÀÊÌiÊÃÊëiÌÊÊ>>}}ÊÌ
iÊ`ÃÊ>VÌÛÌiÃ°Ê /
ÃÊÜÊLiÊÀiviVÌi`ÊÊÌ
iÊ!Lnerehaca`PeiaÊVÕÌiÀÊvÊÌ
iÊ«ÀViÃÃÀ]Ê>ÃÊiÝ«>i`ÊÊÌ
iÊ iÝÌÊÃiV̰ÊÊÌ
>ÌÊV>Ãi]ÊÌÊÜÊLiÊ>`Û>Ì>}iÕÃÊÌÊ«ÌâiÊÌ
iÊ`ÃÊLÌÌiiVÊvÀÃ̰ÊÕÀÌ
iÀ]ÊÀiiLiÀÊÌ
>ÌÊÌ
iÊ`ÃÊLÌÌiiVÊÊÌÕÀÊV>ÊLiÊLiV>ÕÃiÊvÊ>ÊiÀÞÊLÌÌiiV]Ê>ÃÊ iÝ«>i`Êi>ÀiÀÊÊÌ
iÊV
>«ÌiÀ° 9ÕÊV>ÊÌÀ>VÊ«ÀViÃÃÀÊÌiÊ>ÃÊ>Ê>}}Ài}>ÌiÊvÊ>ÊÌ
iÊ«ÀViÃÃÀÃÊÊÌ
iÊ>V
i]ÊÀÊÞÕÊ V>ÊÌÀ>VÊÌ
iÊ«iÀViÌ>}iÊÕÌâ>ÌÊ`Û`Õ>ÞÊÌÊ«>ÀÌVÕ>ÀÊ«ÀViÃÃÀðÊ/
ÃÊ>ÜÃÊÞÕÊÌÊ Ãi}Ài}>ÌiÊÌ
iÊ`>Ì>ÊViVÌÊÊÌ
iÊiÛiÌÊÌ
>ÌÊ-+Ê-iÀÛiÀÊÀÕÃÊÊÌ
ÀiiÊ«ÀViÃÃÀÃÊvÊ>ÊvÕÀ processor machine.
% Privileged Time Processing on a Windows server is done in two modes: user mode and privileged (or kernel®Ê `i°ÊÊÃÞÃÌiiÛiÊ>VÌÛÌiÃ]ÊVÕ`}Ê`ÃÊ>VViÃÃ]Ê>ÀiÊ`iÊÊ«ÀÛi}i`Ê`i°ÊvÊÞÕÊ find that !Lnerehaca`PeiaÊÊ>Ê`i`V>Ìi`Ê-+Ê-iÀÛiÀÊÃÞÃÌiÊÃÊÓäÊÌÊÓxÊ«iÀViÌÊÀÊÀi]Ê Ì
iÊÌ
iÊÃÞÃÌiÊÃÊ«ÀL>LÞÊ`}Ê>ÊÌÊvÊÉ"piÞÊÀiÊÌ
>ÊÞÕÊii`°Ê/
iÊ!Lnerehaca` PeiaÊVÕÌiÀÊÊ>Ê`i`V>Ìi`Ê-+Ê-iÀÛiÀÊÃÞÃÌiÊÃ
Õ`ÊLiÊ>ÌÊÃÌÊxÊÌÊ£äÊ«iÀVḭ
Processor Queue Length Lnk_aooknMqaqaHajcpd isÊÌ
iÊÕLiÀÊvÊÌ
Ài>`ÃÊÊÌ
iÊ«ÀViÃÃÀʵÕiÕi°Ê/
iÀiÊÃÊ>ÊÃ}iÊ «ÀViÃÃÀʵÕiÕi]ÊiÛiÊÊV«ÕÌiÀÃÊÜÌ
ÊÕÌ«iÊ«ÀViÃÃÀð®Ê1iÊÌ
iÊ`ÃÊVÕÌiÀÃ]ÊÌ
iÊ Lnk_aooknMqaqaHajcpdÊVÕÌiÀÊ`iÃÊÌÊÀi>`ÊÌ
Ài>`ÃÊÌ
>ÌÊ>ÀiÊ>Ài>`ÞÊÀÕ}°Ê"ÊÃÞÃÌiÃÊ ÜÌ
ÊÜiÀÊ *1ÊÕÌâ>Ì]ÊÌ
iÊLnk_aooknMqaqaHajcpd counter is typically 0 or 1. A sustained Lnk_aooknMqaqaHajcpd counter of greater than 2 generally indicates proViÃÃÀÊV}iÃÌ°Ê iV>ÕÃiÊvÊÕÌ«iÊ«ÀViÃÃÀÃ]ÊÞÕÊ>ÞÊii`ÊÌÊÌ>iÊÌÊ>VVÕÌÊÌ
iÊ number of schedulers dealing with the processor queue length. A processor queue length ÀiÊÌ
>ÊÌÜÊÌiÃÊÌ
iÊÕLiÀÊvÊÃV
i`ÕiÀÃÊÕÃÕ>ÞÊ£\£ÊÜÌ
Ê«ÀViÃÃÀîÊV>Ê>ÃÊ`V>ÌiÊ >Ê«ÀViÃÃÀÊLÌÌiiV°ÊÌ
Õ}
Ê>Ê
}
Ê!Lnk_aooknPeia counter indicates a busy processor, a sustained high Lnk_aooknMqaqaHajcpd counter is a more certain indicator. If the recomi`i`ÊÛ>ÕiÊÃÊiÝVii`i`]ÊÌ
ÃÊ}iiÀ>ÞÊ`V>ÌiÃÊÌ
>ÌÊÌ
iÀiÊ>ÀiÊÀiÊÌ
Ài>`ÃÊÀi>`ÞÊÌÊÀÕÊ than the current number of processors can service in an optimal way.
Context Switches/sec /
iÊ?kjpatpOsep_dao+oa_ counter monitors the combined rate at which all processors on the V«ÕÌiÀÊ>ÀiÊÃÜÌV
i`ÊvÀÊiÊÌ
Ài>`ÊÌÊ>Ì
iÀ°ÊÊVÌiÝÌÊÃÜÌV
ÊVVÕÀÃÊÜ
iÊ>ÊÀÕ}Ê thread voluntarily relinquishes the processor, is preempted by a higher-priority ready thread, ÀÊÃÜÌV
iÃÊLiÌÜiiÊÕÃiÀÊ`iÊ>`Ê«ÀÛi}i`Ê`iÊÌÊÕÃiÊ>ÊiÝiVÕÌÛiÊÀÊ>ÊÃÕLÃÞÃÌiÊÃiÀÛVi°Ê It is the sum of Pdna]`6?kjpatpOsep_dao+oa_ for all threads running on all processors in the computer, and it is measured in numbers of switches.
C H A P T E R 2 N S Y S T E M P E R F O R M A N C E A N A LY S I S
Êv}ÕÀiÊvÊÎääÊÌÊ£]äääÊ?kjpatpOsep_dao+oa_Ê«iÀÊ«ÀViÃÃÀÊÃÊiÝViiÌÊÌÊv>À°ÊLÀmally high rates, greater than 20,000 per second, can be caused by page faults due to memory starvation.
Batch Requests/sec >]p_dNamqaopo+oa_ gives you a good indicator of just how much load is being placed on the system, which has a direct correlation to how much load is being placed on the processor. Since you could see a lot of low-cost queries on your system or a few high-cost queries, you V>½ÌÊÊ>ÌÊÌ
ÃÊÕLiÀÊLÞÊÌÃivÊLÕÌÊÕÃÌÊÀiviÀiViÊÌ
iÊÌ
iÀÊVÕÌiÀÃÊ`ivi`ÊÊÌ
ÃÊÃiVÌÆÊ£]äääÊÀiµÕiÃÌÃÊÊ>ÊÃiV`ÊÜÕ`ÊLiÊVÃ`iÀi`Ê>ÊLÕÃÞÊÃÞÃÌi°ÊÀi>ÌiÀÊÛ>ÕiÃÊ>ÞÊLiÊ V>ÕÃiÊvÀÊVViÀ°Ê/
iÊLiÃÌÊÜ>ÞÊÌÊÜÊÜ
V
ÊÛ>ÕiÊ
>ÃÊi>}ÊÜÌ
ÊÞÕÀÊÜÊÃÞÃÌiÃÊÃÊ to establish a baseline and then monitor from there.
SQL Compilations/sec /
iÊOMH?kileh]pekjo+oa_ counter shows both batch compiles and statement recompiles >ÃÊ«>ÀÌÊvÊÌÃÊ>}}Ài}>̰Ê/
ÃÊÕLiÀÊV>ÊLiÊiÝÌÀiiÞÊ
}
ÊÜ
iÊ>ÊÃiÀÛiÀÊÃÊvÀÃÌÊÌÕÀi`ÊÊ ÀÊ>vÌiÀÊ>Êv>ÛiÀÊÀÊ>ÞÊÌ
iÀÊÃÌ>ÀÌÕ«ÊÌÞ«iÊiÛiÌ®]ÊLÕÌÊÌÊÜÊÃÌ>LâiÊÛiÀÊÌi°Ê"ViÊÃÌ>Li]Ê 100 or more compilations in one second will certainly manifest as problems in the processor.
>«ÌiÀÊÊVÛiÀÃÊ-+ÊV«>ÌÊÊ`iÌ>°
SQL Recompilations/sec OMHNa_kileh]pekjo+oa_ is a measure of the recompiles of both batches and statements. A high number of recompiles will lead to processor stress. Because statement recompiles are part of Ì
ÃÊVÕÌ]ÊÌÊV>ÊLiÊÕV
Ê
}
iÀÊÌ
>ÊÊÛiÀÃÃÊvÊ-+Ê-iÀÛiÀÊ«ÀÀÊÌÊÓääx°Ê
>«ÌiÀÊ£äÊVÛers query recompilation in detail.
Processor Bottleneck Resolutions A few of theÊVÊ«ÀViÃÃÀÊLÌÌiiVÊÀiÃÕÌÃÊ>ÀiÊ>ÃÊvÜÃ\ Ê
UÊ "«Ìâ}Ê>««V>ÌÊÜÀ>`
Ê
UÊ >Ì}ÊÀÊÀi`ÕV}ÊiÝViÃÃÛiÊV«iÃÉÀiV«iÃ
Ê
UÊ 1Ã}ÊÀiÊÀÊv>ÃÌiÀÊ«ÀViÃÃÀÃ
Ê
UÊ 1Ã}Ê>Ê>À}iÊÓÉÎÊV>V
i
Ê
UÊ ,Õ}ÊÜÌ
ÊÀiÊivvViÌÊVÌÀiÀÃÉ`ÀÛiÀÃ
Ê
UÊ
ÌÊÀÕ}ÊÕiViÃÃ>ÀÞÊÃvÌÜ>Ài
i̽ÃÊVÃ`iÀÊi>V
ÊvÊÌ
iÃiÊÀiÃÕÌÃÊÊÌÕÀ°
Optimizing Application Workload /Ê`iÌvÞÊÌ
iÊ«ÀViÃÃÀÌiÃÛiʵÕiÀiÃ]ÊV>«ÌÕÀiÊ>ÊÌ
iÊ-+ʵÕiÀiÃÊÕÃ}Ê-+Ê*ÀviÀÊ Ü
V
ÊÊÜÊ`ÃVÕÃÃÊÊÌ
iÊiÝÌÊV
>«ÌiÀ®]Ê>`ÊÌ
iÊ}ÀÕ«ÊÌ
iÊ*ÀviÀÊÌÀ>ViÊÕÌ«ÕÌÊÊÌ
iÊ?LQ
45
46
CHAPTER 2 N SYS TEM P ER FOR MA NC E A NA L YS IS
VÕ°Ê/
iʵÕiÀiÃÊÜÌ
ÊÌ
iÊ
}
iÃÌÊ>ÕÌÊvÊ *1ÊÌiÊVÌÀLÕÌiÊÌ
iÊÃÌÊÌÊÌ
iÊ *1Ê ÃÌÀiÃðÊ9ÕÊÃ
Õ`ÊÌ
iÊ>>ÞâiÊ>`Ê«ÌâiÊÌ
ÃiʵÕiÀiÃÊÌÊÀi`ÕViÊÃÌÀiÃÃÊÊÌ
iÊ *1°Ê9ÕÊ can also query directly against the ouo*`i[ata_[mqanu[op]po view to see immediate issues in Ài>ÊÌi°Ê>Þ]ÊÕÃ}ÊLÌ
Ê>ʵÕiÀÞÊ
>Ã
Ê>`Ê>ʵÕiÀÞÊ«>Ê
>Ã
]ÊÞÕÊV>Ê`iÌvÞÊ>`ÊÌÕiÊ VʵÕiÀiÃÊÀÊVÊiÝiVÕÌÊ«>ÃÊÌ
ÃÊÃÊ`ÃVÕÃÃi`ÊÊ`iÌ>ÊÊ
>«ÌiÀÊ®°ÊÃÌÊvÊ Ì
iÊÀiÃÌÊvÊÌ
iÊV
>«ÌiÀÃÊÊÌ
ÃÊLÊ>ÀiÊVViÀi`ÊÜÌ
Ê«Ìâ}Ê>««V>ÌÊÜÀ>`°
Eliminating Excessive Compiles/Recompiles A certain number of µÕiÀÞÊV«iÃÊ>`ÊÀiV«iÃÊÃÊëÞÊÌÊLiÊiÝ«iVÌi`°Ê̽ÃÊÜ
iÊÌ
iÀiÊÃÊ >Ê>À}iÊÕLiÀÊvÊÌ
iÃiÊÛiÀÊÃÕÃÌ>i`Ê«iÀ`ÃÊÌ
>ÌÊ>Ê«ÀLiÊiÝÃÌðÊ̽ÃÊ>ÃÊÜÀÌ
ÊÌ}ÊÌ
iÊ ratio between them. A high number of compiles and a very low number of recompiles means Ì
>ÌÊviÜʵÕiÀiÃÊ>ÀiÊLi}ÊÀiÕÃi`ÊÜÌ
ÊÌ
iÊÃÞÃÌiʵÕiÀÞÊÀiÕÃiÊÃÊVÛiÀi`ÊÊ`iÌ>ÊÊ
>«ÌiÀÊ®°ÊÊ
}
ÊÕLiÀÊvÊÀiV«iÃÊÜÊV>ÕÃiÊ
}
Ê«ÀViÃÃÀÊÕÃi°ÊiÌ
`ÃÊvÀÊ>``ÀiÃÃ}Ê ÀiV«iÃÊ>ÀiÊVÛiÀi`ÊÊ
>«ÌiÀÊ£ä°
Using More or Faster Processors "iÊvÊÌ
i easiest resolutions, and one that you will adopt most of the time, is to increase sysÌiÊ«ÀViÃÃ}Ê«ÜiÀ°ÊÜiÛiÀ]ÊLiV>ÕÃiÊvÊÌ
iÊ
}
ÊVÃÌÊÛÛi`ÊÊ>Ê«ÀViÃÃÀÊÕ«}À>`i]ÊÞÕÊ Ã
Õ`ÊvÀÃÌÊ«ÌâiÊ *1ÌiÃÛiÊ«iÀ>ÌÃÊ>ÃÊÕV
Ê>ÃÊ«ÃÃLi° /
iÊÃÞÃÌi½ÃÊ«ÀViÃÃ}Ê«ÜiÀÊV>ÊLiÊVÀi>Ãi`ÊLÞÊVÀi>Ã}ÊÌ
iÊ«ÜiÀÊvÊ`Û`Õ>Ê processors or by adding more processors. When you have a high !Lnk_aooknPeia counter and a low Lnk_aooknMqaqaHajcpdÊVÕÌiÀ]ÊÌÊ>iÃÊÃiÃiÊÌÊVÀi>ÃiÊÌ
iÊ«ÜiÀÊvÊ`Û`Õ>Ê processors. In the case of both a high !Lnk_aooknPeia counter and a high Lnk_aooknMqaqa Hajcpd counter, you should consider adding more processors. Increasing the number of proViÃÃÀÃÊ>ÜÃÊÌ
iÊÃÞÃÌiÊÌÊiÝiVÕÌiÊÀiÊÀiµÕiÃÌÃÊÃÕÌ>iÕÃÞ°
Using a Large L2/L3 Cache Modern processors have become so much faster than memory that they need at least two iÛiÃÊvÊiÀÞÊV>V
iÊÌÊÀi`ÕViÊ>ÌiVÞ°Ê"Ê*iÌÕV>ÃÃÊ>V
iÃ]ÊÌ
iÊv>ÃÌÊ£ÊV>V
iÊ
`ÃÊ n ÊvÊ`>Ì>Ê>`Ên ÊvÊÃÌÀÕVÌÃ]ÊÜ
iÊÌ
iÊÃÜiÀÊÓÊV>V
iÊ
`ÃÊÕ«ÊÌÊÈ ÊvÊÝi`ÊV`iÊ >`Ê`>Ì>°Ê iÜÊ«ÀViÃÃÀÃÊ>ÀiÊÃ
««}ÊÜÌ
Ê>ÊÎÊV>V
iÊÊÌ«ÊvÊÌ
iÊÌ
iÀÃÊÛ>ÀÞ}ÊÊÃâiÊ vÀÊÈ ÊÌÊÓxÈ Ê>`ÊÓ Ê ÊÀiviÀÃÊÌÊmebibyte, a binary representation, 220, that is ÛiÀÞÊÃ>ÀÊÊÃâiÊÌÊLÕÌÊÌÊiÝ>VÌÞÊÌ
iÊÃ>iÊ>ÃÊ>Êi}>LÞÌi]ÊÜ
V
ÊÃÊ£äÈ®°Ê,iviÀiViÃÊÌÊ VÌiÌÊvÕ`ÊÊÌ
iÊ£ÊV>V
iÊVÃÌÊiÊVÞVi]ÊÀiviÀiViÃÊÌÊÌ
iÊÓÉÎÊV>V
iÊVÃÌÊvÕÀÊÌÊÃiÛiÊ VÞViÃ]Ê>`ÊÀiviÀiViÃÊÌÊÌ
iÊ>ÊiÀÞÊVÃÌÊ`âiÃÊvÊ«ÀViÃÃÀÊVÞViðÊ7Ì
ÊÌ
iÊVÀi>ÃiÊ Ê«ÀViÃÃ}Ê«ÜiÀ]ÊÌ
iÊ>ÌÌiÀÊv}ÕÀiÊÜÊÃÊiÝVii`Ê£ääÊVÞViðÊÊ>ÞÊÜ>ÞÃ]ÊÌ
iÊV>V
iÊÃÊ iÊÃ>]Êv>ÃÌ]ÊÛÀÌÕ>ÊiÀÞÊÃ`iÊÌ
iÊ«ÀViÃÃÀ° >Ì>L>ÃiÊi}iÃÊiÊÓÉÎÊV>V
iÃÊLiV>ÕÃiÊÌ
iÞÊii«Ê«ÀViÃÃ}ÊvvÊÌ
iÊÃÞÃÌiÊLÕðÊ/
iÊ «ÀViÃÃÀÊ`iÃÊÌÊ
>ÛiÊÌÊ}ÊÌ
ÀÕ}
ÊÌ
iÊÃÞÃÌiÊLÕÃÊÌÊ>VViÃÃÊiÀÞÆÊÌÊV>ÊÜÀÊÕÌÊvÊÌ
iÊ ÓÉÎÊV>V
i°Ê ÌÊ
>Û}ÊiÕ}
ÊÓÉÎÊV>V
iÊV>ÊV>ÕÃiÊÌ
iÊ«ÀViÃÃÀÊÌÊÜ>ÌÊ>Ê}iÀÊ«iÀ`Ê vÊÌiÊvÀÊÌ
iÊ`>Ì>ÉV`iÊÌÊÛiÊvÀÊÌ
iÊ>ÊiÀÞÊÌÊÌ
iÊÓÉÎÊV>V
i°ÊÊ«ÀViÃÃÀÊÜÌ
Ê >Ê
}
ÊVVÊëii`ÊLÕÌÊ>ÊÜÊÓÉÎÊV>V
iÊ>ÞÊÜ>ÃÌiÊ>Ê>À}iÊÕLiÀÊvÊ *1ÊVÞViÃÊÜ>Ì}ÊÊ Ì
iÊÃ>ÊÓÉÎÊV>V
i°ÊÊ>À}iÊÓÉÎÊV>V
iÊ
i«ÃÊ>ÝâiÊÌ
iÊÕÃiÊvÊ *1ÊVÞViÃÊvÀÊ>VÌÕ>Ê «ÀViÃÃ}ÊÃÌi>`ÊvÊÜ>Ì}ÊÊÌ
iÊÓÉÎÊV>V
i°
C H A P T E R 2 N S Y S T E M P E R F O R M A N C E A N A LY S I S
/`>Þ]ÊÌÊÃÊÛiÀÞÊVÊÌÊ
>ÛiÊi}>LÞÌiÊV>V
iÃÊÊvÕÀÜ>ÞÊÃÞÃÌiðÊ7Ì
ÊiÜÊvÕÀÊ >`Êi}
ÌÜ>ÞÊÃÞÃÌiÃ]ÊÞÕÊÜÊvÌiÊ}iÌÊÕ«ÊÌÊ>ÊÈ ÊÓÊV>V
iÊ>`]Ê>ÃÊiÌi`Êi>ÀiÀ]Ê ÓxÈ ÊÊΰÊÀÊiÝ>«i]ÊÃiÌiÃÊÞÕÊ>ÞÊ}iÌÊ>Ê«iÀvÀ>ViÊ«ÀÛiiÌÊvÊÓäÊ «iÀViÌÊÀÊÀiÊëÞÊLÞÊÕÃ}Ê>Êx£Ó ÊÓÊV>V
iÊÃÌi>`ÊvÊ>ÊÓxÈ ÊÓÊV>V
i°
Running More Efficient Controllers/Drivers /
iÀi is a big difference in !Lnerehaca`Peia consumption between different controllers and VÌÀiÀÊ`ÀÛiÀÃÊÊÌ
iÊ>ÀiÌÊÌ`>Þ°Ê/
iÊÌiV
µÕiÃÊÕÃi`ÊLÞÊVÌÀiÀÊ`ÀÛiÀÃÊÌÊ`ÊÉ"Ê>ÀiÊ µÕÌiÊ`vviÀiÌÊ>`ÊVÃÕiÊ`vviÀiÌÊ>ÕÌÃÊvÊ *1ÊÌi°ÊvÊÞÕÊV>ÊV
>}iÊÌÊ>ÊVÌÀiÀÊ Ì
>ÌÊvÀiiÃÊÕ«Ê{ÊÌÊxÊ«iÀViÌÊvÊ!Lnerehaca`Peia, you can improve performance.
Not Running Unnecessary Software If you have screen savers running on your system, they may use a large amount of procesÃÀÊÌi°Ê/
ÃÊÃÊ«>ÀÌVÕ>ÀÞÊÌÀÕiÊvÊÃVÀiiÊÃ>ÛiÀÃÊÌ
>ÌÊÕÃiÊ"«iÊ>ÊÃvÌÜ>ÀiÊÌiÀv>ViÊÌ
>ÌÊ ÃÕ««ÀÌÃÊÌ
iÊ«À`ÕVÌÊvÊ
}
µÕ>ÌÞ]ÊÌ
Àii`iÃ>ÊVÀÊ>}}®]ÊLiV>ÕÃiÊÌ
iÞÊV>Ê i>ÃÞÊÌ>iÊ>Ü>ÞÊ£xÊ«iÀViÌÊvÊ«ÀViÃÃÀÊÌiÊÊ>ÊÃiÀÛiÀÊÌ
>ÌÊ>VÃÊ>Ê"«iÊ}À>«
VÃÊV>À`°Ê Because you can run your servers with the monitor switched off for the majority of the time, ëÞÊ`Ã>LiÊ>ÞÊÃVÀiiÊÃ>ÛiÀÃÊÊÞÕÀÊÃiÀÛiÀÊÀ]Ê>ÃÊ>Ê«ÀiÛiÌÛiÊi>ÃÕÀi]ÊÕÃiÊÌ
iÊL>Ê ÃVÀiiÊÃ>ÛiÀ°Ê*i>ÃiÊ`ÊÀiiLiÀÊÌÊVÊÞÕÀÊÃiÀÛiÀÊvÀÊÃiVÕÀÌÞ° 7
iÊ«ÃÃLi]ÊÊÕiViÃÃ>ÀÞÊÃvÌÜ>ÀiÊÃ
Õ`ÊLiÊÀÕ}ÊÊÌ
iÊÃ>iÊÃiÀÛiÀÊ>ÃÊ-+Ê -iÀÛiÀ°Ê ÝÌiÀÀÊ>««V>ÌÃÊÌ
>ÌÊ
>ÛiÊÌ
}ÊÌÊ`ÊÜÌ
Ê>Ì>}ÊÌ
iÊ7`ÜÃÊ-iÀÛiÀÊÀÊ -+Ê-iÀÛiÀÊ>ÀiÊLiÃÌÊ«>Vi` on a different machine.
Network Bottleneck Analysis Ê-+Ê-iÀÛiÀÊ"/*Ê«À`ÕVÌÊiÛÀiÌÃ]ÊÞÕÊv`ÊviÜÊ«iÀvÀ>ViÊÃÃÕiÃÊÌ
>ÌÊ>ÀiÊ because of problems with the iÌÜÀ°ÊÃÌÊvÊÌ
iÊiÌÜÀÊÃÃÕiÃÊÞÕÊv>ViÊÊÌ
iÊ"/*ÊiÛronment are in fact hardware or driver limitations or issues with switches or routers. Most of Ì
iÃiÊÃÃÕiÃÊV>ÊLiÊLiÃÌÊ`>}Ãi`ÊÜÌ
ÊÌ
iÊ iÌÜÀÊÌÀḚ̂ÊÜiÛiÀ]Ê*iÀvÀ>ViÊ ÌÀÊ>ÃÊ«ÀÛ`iÃÊLiVÌÃÊÌ
>ÌÊViVÌÊ`>Ì>ÊÊiÌÜÀÊ>VÌÛÌÞ]Ê>ÃÊÃ
ÜÊÊ/>LiÊÓ{° Table 2-4. Performance Monitor Counters to Analyze Network Pressure
Object(Instance[,InstanceN])
Counter
Description
Value
JapskngEjpanb]_a$Japskng_]n`%
>upaoPkp]h+oa_
Rate at which bytes are transferred on Ì
iÊ
Average Û>ÕiÊÊxä¯ÊvÊ ÊV>«>VÌÞ
JapskngOaciajp
!JapQpehev]pekj
Percentage of iÌÜÀ bandwidth in use on a iÌÜÀÊÃi}iÌ
Average value < 80% vÊiÌÜÀÊ bandwidth
47
48
CHAPTER 2 N SYS TEM P ER FOR MA NC E A NA L YS IS
Bytes Total/sec You can use the >upaoPkp]h+oa_ÊVÕÌiÀÊÌÊ`iÌiÀiÊ
ÜÊÌ
iÊiÌÜÀÊÌiÀv>ViÊV>À`Ê ®Ê ÀÊiÌÜÀÊ>`>«ÌiÀÊÃÊ«iÀvÀ}°Ê/
iÊ>upaoPkp]h+oa_ counter should report high values to `V>ÌiÊ>Ê>À}iÊÕLiÀÊvÊÃÕVViÃÃvÕÊÌÀ>ÃÃÃÃ°Ê «>ÀiÊÌ
ÃÊÛ>ÕiÊÜÌ
ÊÌ
>ÌÊÀi«ÀÌi`ÊLÞ the JapskngEjpanb]_aX?qnnajp>]j`se`pd performance counter, which reflects each adapter’s bandwidth. /Ê>ÜÊ
i>`ÀÊvÀÊëiÃÊÊÌÀ>vvV]ÊÞÕÊÃ
Õ`ÊÕÃÕ>ÞÊ>ÛiÀ>}iÊÊÀiÊÌ
>ÊxäÊ«iÀcent of capacity. If this number is close to the capacity of the connection and if processor and memory use are moderate, then the connection may well be a problem.
% Net Utilization /
iÊ!JapQpehev]pekj counter represents theÊ«iÀViÌ>}iÊvÊiÌÜÀÊL>`Ü`Ì
ÊÊÕÃiÊÊ>Ê iÌÜÀÊÃi}ḭÊ/
iÊÌ
ÀiÃ
`ÊvÀÊÌ
ÃÊVÕÌiÀÊ`i«i`ÃÊÊÌ
iÊÌÞ«iÊvÊiÌÜÀ°ÊÀÊ Ì
iÀiÌÊiÌÜÀÃ]ÊvÀÊiÝ>«i]ÊÎäÊ«iÀViÌÊÃÊÌ
iÊÀiVi`i`ÊÌ
ÀiÃ
`ÊÜ
iÊ-+Ê-iÀÛiÀÊÃÊÊ>Ê Ã
>Ài`ÊiÌÜÀÊ
ÕL°ÊÀÊ-+Ê-iÀÛiÀÊÊ>Ê`i`V>Ìi`ÊvÕ`Õ«iÝÊiÌÜÀ]ÊiÛiÊÌ
Õ}
Êi>ÀÊ£ääÊ «iÀViÌÊÕÃ>}iÊvÊÌ
iÊiÌÜÀÊÃÊ>VVi«Ì>Li]ÊÌÊÃÊ>`Û>Ì>}iÕÃÊÌÊii«ÊÌ
iÊiÌÜÀÊÕÌâ>ÌÊ LiÜÊ>Ê>VVi«Ì>LiÊÌ
ÀiÃ
`ÊÌÊii«ÊÀÊvÀÊÌ
iÊëiÃÊÊÌ
iÊ>`°
NNote You must install the Network Monitor Driver to collect performance data using the Japskng Oaciajp object counters.
Ê7`ÜÃÊ-iÀÛiÀÊÓään]ÊÞÕÊV>ÊÃÌ>ÊÌ
iÊ iÌÜÀÊÌÀÊ ÀÛiÀÊvÀÊÌ
iÊV>Ê>Ài>Ê ViVÌÊ«À«iÀÌiÃÊvÀÊÌ
iÊiÌÜÀÊ>`>«ÌiÀ°Ê/
iÊ iÌÜÀÊÌÀÊ ÀÛiÀÊÃÊ>Û>>LiÊÊÌ
iÊ iÌÜÀÊ«ÀÌVÊÃÌÊvÊiÌÜÀÊV«iÌÃÊvÀÊÌ
iÊiÌÜÀÊ>`>«ÌiÀ°
Network Bottleneck Resolutions A few of theÊVÊiÌÜÀÊLÌÌiiVÊÀiÃÕÌÃÊ>ÀiÊ>ÃÊvÜÃ\ Ê
UÊ "«Ìâ}Ê>««V>ÌÊÜÀ>`
Ê
UÊ ``}ÊiÌÜÀÊ>`>«ÌiÀÃ
Ê
UÊ `iÀ>Ì}Ê>`Ê>Û`}ÊÌiÀÀÕ«Ìà i̽ÃÊVÃ`iÀÊÌ
iÃiÊÀiÃÕÌÃÊÊÀiÊ`iÌ>°
Optimizing Application Workload /Ê«ÌâiÊiÌÜÀÊÌÀ>vvVÊLiÌÜiiÊ>Ê`>Ì>L>ÃiÊ>««V>ÌÊ>`Ê>Ê`>Ì>L>ÃiÊÃiÀÛiÀ]Ê>iÊÌ
iÊ following design changes in the application:
C H A P T E R 2 N S Y S T E M P E R F O R M A N C E A N A LY S I S
Ê
UÊ ÃÌi>`ÊvÊÃi`}Ê>Ê}Ê-+ÊÃÌÀ}]ÊVÀi>ÌiÊ>ÊÃÌÀi`Ê«ÀVi`ÕÀiÊvÀÊÌ
iÊ-+ʵÕiÀÞ°Ê /
i]ÊÞÕÊÕÃÌÊii`ÊÌÊÃi`ÊÛiÀÊÌ
iÊiÌÜÀÊÌ
iÊ>iÊvÊÌ
iÊÃÌÀi`Ê«ÀVi`ÕÀiÊ>`Ê its parameters.
Ê
UÊ ÀÕ«ÊÕÌ«iÊ`>Ì>L>ÃiÊÀiµÕiÃÌÃÊÌÊiÊÃÌÀi`Ê«ÀVi`ÕÀi°Ê/
i]ÊÞÊiÊ`>Ì>L>ÃiÊ ÀiµÕiÃÌÊÜÊLiÊÀiµÕÀi`Ê>VÀÃÃÊÌ
iÊiÌÜÀÊvÀÊÌ
iÊÃiÌÊvÊ-+ʵÕiÀiÃÊ«iiÌi`ÊÊ the stored procedure.
Ê
UÊ ,iµÕiÃÌÊ>ÊÃ>Ê`>Ì>ÊÃiÌ°Ê ÊÌÊÀiµÕiÃÌÊÌ>LiÊVÕÃÊÌ
>ÌÊ>ÀiÊÌÊÕÃi`ÊÊÌ
iÊ application logic.
Ê
UÊ ÛiÊ`>Ì>ÌiÃÛiÊLÕÃiÃÃÊ}VÊÌÊÌ
iÊ`>Ì>L>ÃiÊ>ÃÊÃÌÀi`Ê«ÀVi`ÕÀiÃÊÀÊ `>Ì>L>ÃiÊÌÀ}}iÀÃÊÌÊÀi`ÕViÊiÌÜÀÊÀÕ`ÌÀ«Ã°
Adding Network Adapters You can add iÌÜÀÊ>`>«ÌiÀÃÊÃÊÌ
>ÌÊÞÕÊ
>ÛiÊiÊiÌÜÀÊ>`>«ÌiÀÊ«iÀÊ«ÀViÃÃÀ°ÊiiÀ>Þ]ÊÞÕÊÃ
Õ`Ê>``Ê>ÊiÌÜÀÊ>`>«ÌiÀÊÞÊvÊÞÕÊii`ÊÌ
iÊVÀi>Ãi`ÊL>`Ü`Ì
]ÊLiV>ÕÃiÊ i>V
Ê>``Ì>ÊiÌÜÀÊ>`>«ÌiÀÊ
>ÃÊÃiÊÌÀÃVÊÛiÀ
i>`°ÊÜiÛiÀ]ÊvÊiÊvÊÌ
iÊ«Àcessors is nearly always active (that is, Lnk_aooknX!Lnk_aooknPeia consistently equals 100 «iÀViÌ®Ê>`ÊÀiÊÌ
>Ê
>vÊvÊÌÃÊÌiÊÃÊëiÌÊÃiÀÛV}Ê`iviÀÀi`Ê«ÀVi`ÕÀiÊV>ÃÊÌ
>ÌÊÃ]Ê Lnk_aooknX!@L?PeiaÊiÝVii`ÃÊxäÊ«iÀViÌ®]ÊÌ
iÊ>``}Ê>ÊiÌÜÀÊV>À`ÊÃÊiÞÊÌÊ«ÀÛiÊ system performance. If aÊiÌÜÀÊ>`>«ÌiÀÊ`iÃÊÌÊÕÃiÊ iÌÜÀÊ ÀÛiÀÊÌiÀv>ViÊ-«iVvV>ÌÊ -®Ê«ÀÌÊ drivers, you cannot modify the distribution of deferredÊ«ÀVi`ÕÀiÊV>ÃÊ * îÊvÀÊLiÌÌiÀÊ «iÀvÀ>Vi°ÊÊ - miniport driver (also called a miniport driver®Ê>>}iÃÊ>Ê Ê>`Ê ÌiÀv>ViÃÊÜÌ
Ê
}
iÀiÛiÊ`ÀÛiÀðÊÌÊVÕV>ÌiÃÊÜÌ
ÊÌÃÊ Ê>`Ê
}
iÀiÛiÊ`ÀÛiÀÃÊ Ì
ÀÕ}
ÊÌ
iÊ -ÊLÀ>ÀÞ° /ÊLiÊ>LiÊÌÊ`vÞÊÌ
iÊ`ÃÌÀLÕÌÊvÊ * ÃÊ>`ÊLiV>ÕÃiÊÌ
iÀÊ -Ê«Ìâ>ÌÃÊ }
ÌÊLiÊÕ>Û>>Li]ÊÞÕÊ>ÞÊVÃ`iÀÊÕ«}À>`}Ê>Ê`Û`Õ>ÊiÌÜÀÊ>`>«ÌiÀÊÃÌi>`ÊvÊ adding a new adapter.
Moderating and Avoiding Interruptions When addingÊÀÊÕ«}À>`}ÊiÌÜÀÊ>`>«ÌiÀÃ]ÊV
ÃiÊ>`>«ÌiÀÃÊÜÌ
Ê`ÀÛiÀÃÊÌ
>ÌÊÃÕ««ÀÌÊ interrupt moderation and/or interrupt avoidance. Interrupt moderation allows a processor to process interrupts more efficiently by grouping several interrupts into a single hardware interrupt. Interrupt avoidance allows a processor to continue processing interrupts without new interrupts queued until all pending interrupts are completed.
SQL Server Overall Performance /Ê>>ÞâiÊÌ
iÊÛiÀ>Ê«iÀvÀ>ViÊvÊ>Ê-+Ê-iÀÛiÀ]ÊLiÃ`iÃÊiÝ>}Ê
>À`Ü>ÀiÊÀiÃÕÀViÊ ÕÌâ>Ì]ÊÞÕÊÃ
Õ`Ê>ÃÊiÝ>iÊÃiÊ}iiÀ>Ê>ëiVÌÃÊvÊ-+Ê-iÀÛiÀÊÌÃiv°Ê9ÕÊV>ÊÕÃiÊÌ
iÊ «iÀvÀ>ViÊVÕÌiÀÃÊ«ÀiÃiÌi`ÊÊ/>LiÊÓx°
49
50
CHAPTER 2 N SYS TEM P ER FOR MA NC E A NA L YS IS
Table 2-5. Performance Monitor Counters to Analyze Generic SQL Pressure
Object(Instance[,InstanceN])
Counter
OMHOanran6=__aooIapdk`o
BnaaOl]_aO_]jo+oa_ BqhhO_]jo+oa_ P]^haHk_gAo_]h]pekjo+oa_ Skngp]^hao?na]pa`+oa_
OMHOanran6H]p_dao
Pkp]hH]p_dS]epPeia$io%
OMHOanran6Hk_go$[Pkp]h%
Hk_gPeiakqpo+oa_ Hk_gS]epPeia$io% Jqi^ankb@a]`hk_go+oa_
OMHOanran6OMHOp]peope_o
>]p_dNamqaopo+oa_ OMHNa)?kileh]pekjo+oa_
OMHOanran6Cajan]hOp]peope_o
Lnk_aooao>hk_ga` Qoan?kjja_pekjo
i̽ÃÊÌ>iÊ>ÊÊ>ÌÊi>V
ÊvÊÌ
iÃiÊVÕÌiÀÃÊÊVÌiÝ̰
Missing Indexes /Ê>>ÞâiÊÌ
iÊ«ÃÃLÌÞÊvÊÃÃ}Ê`iÝiÃÊV>ÕÃ}ÊÌ>LiÊÃV>ÃÊÀÊ>À}iÊ`>Ì>ÊÃiÌÊÀiÌÀiÛ>Ã]Ê ÞÕÊV>ÊÕÃiÊÌ
iÊVÕÌiÀÃÊÊ/>LiÊÓȰ Table 2-6. Performance Monitor Counter to Analyze Excessive Data Scans
Object(Instance[,InstanceN])
Counter
OMHOanran6=__aooIapdk`o
BnaaOl]_aO_]jo+oa_ BqhhO_]jo+oa_
FreeSpace Scans/sec /
ÃÊVÕÌiÀ represents inserts into a table with no physical ordering of its rows—such a table is also called a heap table°Ê ÝÌÀ>Ê«ÀViÃÃ}ÊÃÊÀiµÕÀi`ÊÌÊ`iviÊ>`ÊÃÌÀiÊ>Ê
i>«ÊÌ>LiÊÃViÊ -+Ê-iÀÛiÀÊÀ>ÞÊÕÃiÃÊÌ
iÊVÕÃÌiÀi`Ê`iÝÊ>ÃÊ>ÊÃÌÀ>}iÊiV
>ÃÊvÀÊÌ
iÊÌ>LiÊ`>Ì>°ÊÊ heap table requires an additional, internal column called a uniquifier to be generated for each ÀÜÊÃiÀÌi`°Ê/
iÀivÀi]ÊÌÊÃÊÕÃÕ>ÞÊÀiVi`i`ÊÌ
>ÌÊÞÕÊ«
ÞÃV>ÞÊÀ`iÀÊÌ
iÊÌ>LiÊÀÜÃÊLÞÊ ÕÃ}Ê>ÊVÕÃÌiÀi`Ê`iÝÊÊÌ
iÊÌ>Li°Ê9ÕÊÜÊi>ÀÊ>LÕÌÊ
i>«ÊÌ>LiÃÊ>`ÊVÕÃÌiÀi`Ê`iÝiÃÊÊ
>«ÌiÀÊx°
Full Scans/sec /
ÃÊVÕÌiÀÊÌÀÃÊÌ
iÊÕLiÀÊvÊÕÀiÃÌÀVÌi`ÊvÕÊÃV>ÃÊÊL>ÃiÊÌ>LiÃÊÀÊ`iÝiðÊÊviÜÊvÊ the main causes of high BqhhO_]jo+oa_ are as follows: Ê
UÊ ÃÃ}Ê`iÝiÃ
Ê
UÊ /Ê>ÞÊÀÜÃÊÀiµÕiÃÌi`
C H A P T E R 2 N S Y S T E M P E R F O R M A N C E A N A LY S I S
/ÊvÕÀÌ
iÀÊÛiÃÌ}>ÌiʵÕiÀiÃÊ«À`ÕV}ÊÌ
iÊ«ÀiVi`}Ê«ÀLiÃ]ÊÕÃiÊ-+Ê*ÀviÀÊÌÊ `iÌvÞÊÌ
iʵÕiÀiÃÊÊÜÊVÛiÀÊÌ
ÃÊÌÊÊÌ
iÊiÝÌÊV
>«ÌiÀ®°Ê+ÕiÀiÃÊÜÌ
ÊÃÃ}Ê`iÝiÃÊÀÊ ÌÊ>ÞÊÀÜÃÊÀiµÕiÃÌi`ÊÜÊ
>ÛiÊ>Ê>À}iÊÕLiÀÊvÊ}V>ÊÀi>`ÃÊ>`Ê>ÊVÀi>Ãi`Ê *1ÊÌi° Be aware of the fact that full scans may be performed for the temporary tables used in a ÃÌÀi`Ê«ÀVi`ÕÀi]ÊLiV>ÕÃiÊÃÌÊvÊÌ
iÊÌiÊÞÕÊÜÊÌÊ
>ÛiÊ`iÝiÃÊÀÊÞÕÊÜÊÌÊii`Ê `iÝiîÊÊÌi«À>ÀÞÊÌ>LiðÊ-Ì]Ê>``}ÊÌ
ÃÊVÕÌiÀÊÌÊÌ
iÊL>ÃiiÊ
i«ÃÊ`iÌvÞÊÌ
iÊ«Ãsible increase in the use of temporary tables, which are usually not good for performance.
Dynamic Management Views Another wayÊÌÊV
iVÊvÀÊÃÃ}Ê`iÝiÃÊÃÊÌÊÌ
iʵÕiÀÞÊÌ
i dynamic management view ouo* `i[`^[ieooejc[ej`at[`ap]eho°Ê/
ÃÊ>>}iiÌÊÛiÜÊÀiÌÕÀÃÊvÀ>ÌÊÌ
>ÌÊV>ÊÃÕ}}iÃÌÊ V>``>ÌiÃÊvÀÊ`iÝiÃÊL>Ãi`ÊÊÌ
iÊiÝiVÕÌÊ«>ÃÊvÊÌ
iʵÕiÀiÃÊLi}ÊÀÕÊ>}>ÃÌÊÌ
iÊ `>Ì>L>Ãi°Ê/
iÊÛiÜÊouo*`i[`^[ieooejc[ej`at[`ap]ehoÊÃÊ«>ÀÌÊvÊ>ÊÃiÀiÃÊvÊ 6ÃÊViVÌÛiÞÊ referred to as the missing indexes feature°Ê/
iÃiÊ 6ÃÊ>ÀiÊL>Ãi`ÊÊ`>Ì>Ê}iiÀ>Ìi`ÊvÀÊ iÝiVÕÌÊ«>ÃÊÃÌÀi`ÊÊÌ
iÊV>V
i°Ê9ÕÊV>ʵÕiÀÞÊ`ÀiVÌÞÊ>}>ÃÌÊÌ
ÃÊÛiÜÊÌÊ}>Ì
iÀÊ`>Ì>ÊÌÊ `iV`iÊÜ
iÌ
iÀÊÞÕÊÜ>ÌÊÌÊLÕ`Ê`iÝiÃÊL>Ãi`ÊÊÌ
iÊvÀ>ÌÊ>Û>>LiÊvÀÊÜÌ
ÊÌ
iÊ ÛiܰÊÃÃ}Ê`iÝiÃÊÜÊ>ÃÊLiÊÃ
ÜÊÜÌ
ÊÌ
iÊ8ÊiÝiVÕÌÊ«>ÊvÀÊ>Ê}ÛiʵÕiÀÞ]ÊLÕÌÊ ½ÊVÛiÀÊÌ
>ÌÊÀiÊÊÌ
iÊiÝÌÊV
>«ÌiÀ° /
iÊ««ÃÌiÊ«ÀLiÊÌÊ>ÊÃÃ}Ê`iÝÊÃÊiÊÌ
>ÌÊÃÊiÛiÀÊÕÃi`°Ê/
iÊ 6Êouo*`i[ `^[ej`at[qo]ca[op]po showsÊÜ
V
Ê`iÝiÃÊ
>ÛiÊLiiÊÕÃi`]Ê>ÌÊi>ÃÌÊÃViÊÌ
iÊ>ÃÌÊÀiLÌÊvÊ Ì
iÊÃÞÃÌi°Ê9ÕÊV>Ê>ÃÊÛiÜÊÌ
iÊ`iÝiÃÊÊÕÃiÊÜÌ
Ê>ÊÜiÀiÛiÊ 6]Êouo*`i[`^[ej`at[ klan]pekj]h[op]po°ÊÌÊÜÊ
i«ÊÌÊÃ
ÜÊÜ
iÀiÊ`iÝiÃÊ>ÀiÊÃÜ}Ê`ÜÊLiV>ÕÃiÊvÊVÌiÌÊ ÀÊÉ"°Ê½ÊVÛiÀÊÌ
iÃiÊLÌ
ÊÊÀiÊ`iÌ>ÊÊ
>«ÌiÀÊ£ä°
Database Blocking /Ê>>ÞâiÊÌ
iÊ«>VÌÊvÊ`>Ì>L>ÃiÊLV}ÊÊÌ
iÊ«iÀvÀ>ViÊvÊ-+Ê-iÀÛiÀ]ÊÞÕÊV>ÊÕÃiÊÌ
iÊ VÕÌiÀÃÊÃ
ÜÊÊ/>LiÊÓǰ Table 2-7. Performance Monitor Counters to Analyze SQL Server Locking
Object(Instance[,InstanceN])
Counter
OMHOanran6H]p_dao
Pkp]hH]p_dS]epPeia$io%
OMHOanran6Hk_go$[Pkp]h%
Hk_gPeiakqpo+oa_ Hk_gS]epPeia$io% Jqi^ankb@a]`hk_go+oa_
Total Latch Wait Time (ms) >ÌV
iÃÊ>ÀiÊÕÃi`ÊÌiÀ>ÞÊLÞÊ-+Ê-iÀÛiÀÊÌÊ«ÀÌiVÌÊÌ
iÊÌi}ÀÌÞÊvÊÌiÀ>ÊÃÌÀÕVÌÕÀiÃ]ÊÃÕV
Ê >ÃÊ>ÊÌ>LiÊÀÜ]Ê>`Ê>ÀiÊÌÊ`ÀiVÌÞÊVÌÀi`ÊLÞÊÕÃiÀðÊ/
ÃÊVÕÌiÀÊÌÀÃÊÌÌ>Ê>ÌV
ÊÜ>ÌÊ ÌiÊÊÃiV`îÊvÀÊ>ÌV
ÊÀiµÕiÃÌÃÊÌ
>ÌÊ
>`ÊÌÊÜ>ÌÊÊÌ
iÊ>ÃÌÊÃiV`°ÊÊ
}
ÊÛ>ÕiÊvÀÊ Ì
ÃÊVÕÌiÀÊ`V>ÌiÃÊÌ
>ÌÊ-+Ê-iÀÛiÀÊÃÊëi`}ÊÌÊÕV
ÊÌiÊÜ>Ì}ÊÊÌÃÊÌiÀ>ÊÃÞV
Àâ>ÌÊiV
>ð
51
52
CHAPTER 2 N SYS TEM P ER FOR MA NC E A NA L YS IS
Lock Timeouts/sec and Lock Wait Time (ms) 9ÕÊÃ
Õ`ÊiÝ«iVÌÊHk_gPeiakqpo+oa_ to be 0 and Hk_gS]epPeia$io% to be very low. A âiÀÊÛ>ÕiÊvÀÊHk_gPeiakqpo+oa_ and a high value for Hk_gS]epPeia$io% indicate that iÝViÃÃÛiÊLV}ÊÃÊVVÕÀÀ}ÊÊÌ
iÊ`>Ì>L>Ãi° /ÜÊ>««À>V
iÃÊV>ÊLiÊ>`«Ìi`ÊÊÌ
ÃÊV>Ãi\ Ê
UÊ 9ÕÊV>Ê`iÌvÞÊÌ
iÊVÃÌÞʵÕiÀiÃÊÕÃ}Ê`>Ì>ÊvÀÊ-+Ê*ÀviÀÊÀÊLÞʵÕiÀÞ}Êouo* `i[ata_[mqanu[op]po andÊÌ
iÊ«ÌâiÊÌ
iʵÕiÀiÃÊ>««À«À>ÌiÞ°
Ê
UÊ 9ÕÊV>ÊÕÃiÊLV}Ê>>ÞÃÃÊÌÊ`>}ÃiÊÌ
iÊV>ÕÃiÊvÊiÝViÃÃÛiÊLV}°ÊÌÊÃÊÕÃÕ>ÞÊ>`Û>Ì>}iÕÃÊÌÊVViÌÀ>ÌiÊÊ«Ìâ}ÊÌ
iÊVÃÌÞʵÕiÀiÃÊvÀÃÌ]ÊLiV>ÕÃiÊÌ
Ã]Ê ÊÌÕÀ]ÊÀi`ÕViÃÊLV}ÊvÀÊÌ
iÀðÊÊ
>«ÌiÀÊ£Ó]ÊÞÕÊÜÊi>ÀÊ
ÜÊÌÊ>>ÞâiÊ>`Ê ÀiÃÛiÊLV}°
Number of Deadlocks/sec You shouldÊiÝ«iVÌÊÌÊÃiiÊ>ÊäÊÛ>ÕiÊvÀÊÌ
ÃÊVÕÌiÀ°ÊvÊÞÕÊv`Ê>ÊâiÀÊÛ>Õi]ÊÌ
iÊÞÕÊ Ã
Õ`Ê`iÌvÞÊÌ
iÊÛVÌâi`ÊÀiµÕiÃÌÊ>`ÊiÌ
iÀÊÀiÃÕLÌÊÌ
iÊ`>Ì>L>ÃiÊÀiµÕiÃÌÊ>ÕÌ>ÌV>ÞÊ or suggest that the user do so. More important, an attempt should be made to troubleshoot >`ÊÀiÃÛiÊÌ
iÊ`i>`V°Ê}>]Ê
>«ÌiÀÊ£ÓÊÃ
ÜÃÊ
ÜÊÌÊ`ÊÌ
ð
Nonreusable Execution Plans Since generating an iÝiVÕÌÊ«>ÊvÀÊ>ÊÃÌÀi`Ê«ÀVi`ÕÀiʵÕiÀÞÊÀiµÕÀiÃÊ *1ÊVÞViÃ]ÊÞÕÊV>Ê Ài`ÕViÊÌ
iÊÃÌÀiÃÃÊÊÌ
iÊ *1ÊLÞÊÀiÕÃ}ÊÌ
iÊiÝiVÕÌÊ«>°Ê/Ê>>ÞâiÊÌ
iÊÕLiÀÊvÊÃÌÀi`Ê «ÀVi`ÕÀiÃÊÌ
>ÌÊ>ÀiÊÀiV«}]ÊÞÕÊV>ÊÊ>ÌÊÌ
iÊVÕÌiÀÊÊ/>LiÊÓn° Table 2-8. Performance Monitor Counter to Analyze Execution Plan Reusability
Object(Instance[,InstanceN])
Counter
OMHOanran6OMHOp]peope_o
OMHNa)?kileh]pekjo+oa_
Recompilations of stored procedures add overhead on the processor. You should see a Û>ÕiÊVÃiÊÌÊâiÀÊvÀÊÌ
iÊOMHNa)?kileh]pekjo+oa_ÊVÕÌiÀ°ÊvÊÞÕÊVÃÃÌiÌÞÊÃiiÊâiÀÊ Û>ÕiÃ]ÊÌ
iÊÞÕÊÃ
Õ`ÊÕÃiÊ-+Ê*ÀviÀÊÌÊvÕÀÌ
iÀÊÛiÃÌ}>ÌiÊÌ
iÊÃÌÀi`Ê«ÀVi`ÕÀiÃÊÕ`iÀ}}ÊÀiV«>ÌðÊ"ViÊÞÕÊ`iÌvÞÊÌ
iÊÀiiÛ>ÌÊÃÌÀi`Ê«ÀVi`ÕÀiÃ]ÊÞÕÊÃ
Õ`Ê>ÌÌi«ÌÊÌÊ >>ÞâiÊ>`ÊÀiÃÛiÊÌ
iÊV>ÕÃiÊvÊÀiV«>ÌðÊÊ
>«ÌiÀÊ£ä]ÊÞÕÊÜÊi>ÀÊ
ÜÊÌÊ>>ÞâiÊ and resolve various causes of recompilation.
General Behavior -+Ê-iÀÛiÀÊ«ÀÛ`iÃÊ>``Ì>Ê«iÀvÀ>ViÊVÕÌiÀÃÊÌÊÌÀ>VÊÃiÊ}iiÀ>Ê>ëiVÌÃÊvÊ>Ê-+Ê -iÀÛiÀÊÃÞÃÌi°Ê/>LiÊÓÊÃÌÃÊ>ÊviÜÊvÊÌ
iÊÃÌÊVÞÊÕÃi`ÊVÕÌiÀð
C H A P T E R 2 N S Y S T E M P E R F O R M A N C E A N A LY S I S
Table 2-9. Performance Monitor Counters to Analyze Volume of Incoming Requests
Object(Instance[,InstanceN])
Counter
OMHOanran6Cajan]hOp]peope_o
Qoan?kjja_pekjo
OMHOanran6OMHOp]peope_o
>]p_dNamqaopo+oa_
User Connections Multiple read-onlyÊ-+Ê-iÀÛiÀÃÊV>ÊÜÀÊÌ}iÌ
iÀÊÊ>Ê>`L>>V}ÊiÛÀiÌÊÜ
iÀiÊ -+Ê-iÀÛiÀÊÃÊëÀi>`ÊÛiÀÊÃiÛiÀ>Ê>V
iîÊÌÊÃÕ««ÀÌÊ>Ê>À}iÊÕLiÀÊvÊ`>Ì>L>ÃiÊÀiµÕiÃÌÃ°Ê In such cases, it is better to monitor the Qoan?kjja_pekjo counter to evaluate the distribution vÊÕÃiÀÊViVÌÃÊ>VÀÃÃÊÕÌ«iÊ-+Ê-iÀÛiÀÊÃÌ>ViðÊQoan?kjja_pekjo can range all over Ì
iÊëiVÌÀÕÊÜÌ
ÊÀ>Ê>««V>ÌÊLi
>ÛÀ°Ê/
ÃÊÃÊÜ
iÀiÊ>ÊÀ>ÊL>ÃiiÊÃÊiÃÃiÌ>ÊÌÊ `iÌiÀiÊÌ
iÊiÝ«iVÌi`ÊLi
>ÛÀ°Ê9ÕÊÜÊÃiiÊ
ÜÊÞÕÊV>ÊiÃÌ>LÃ
ÊÌ
ÃÊL>ÃiiÊÃ
ÀÌÞ°
Batch Requests/sec /
ÃÊVÕÌiÀÊÃÊ>Ê}`Ê`V>ÌÀÊvÊÌ
iÊ>`ÊÊ-+Ê-iÀÛiÀ°Ê >Ãi`ÊÊÌ
iÊiÛiÊvÊÃÞÃÌiÊ ÀiÃÕÀViÊÕÌâ>ÌÊ>`Ê>]p_dNamqaopo+oa_]ÊÞÕÊV>ÊiÃÌ>ÌiÊÌ
iÊÕLiÀÊvÊÕÃiÀÃÊ-+Ê-iÀÛiÀÊ >ÞÊLiÊ>LiÊÌÊÌ>iÊÜÌ
ÕÌÊ`iÛi«}ÊÀiÃÕÀViÊLÌÌiiVðÊ/
ÃÊVÕÌiÀÊÛ>Õi]Ê>ÌÊ`vviÀiÌÊ load cycles, also helps you understand its relationship with the number of database connecÌðÊ/
ÃÊ>ÃÊ
i«ÃÊÞÕÊÕ`iÀÃÌ>`Ê-+Ê-iÀÛiÀ½ÃÊÀi>ÌÃ
«ÊÜÌ
ÊSa^Namqaop+oa_, that is, =_peraOanranL]cao*Namqaopo+oa_ for web applications using Microsoft Internet Information -iÀÛViÃÊ-®Ê>`ÊVÌÛiÊ-iÀÛiÀÊ*>}iÃÊ-*®°ÊÊÌ
ÃÊ>>ÞÃÃÊ
i«ÃÊÞÕÊLiÌÌiÀÊÕ`iÀÃÌ>`Ê>`Ê predict system behavior as the user load changes. /
iÊÛ>ÕiÊvÊÌ
ÃÊVÕÌiÀÊV>ÊÀ>}iÊÛiÀÊ>ÊÜ`iÊëiVÌÀÕÊÜÌ
ÊÀ>Ê>««V>ÌÊLi
>Ûior. A normal baselineÊÃÊiÃÃiÌ>ÊÌÊ`iÌiÀiÊÌ
iÊiÝ«iVÌi`ÊLi
>ÛÀ°Êi̽ÃÊÛiÊÊÌÊÊ>ÌÊ creating one now.
Creating a Baseline Ü that youÊ
>ÛiÊi`Ê>ÌÊ>ÊviÜÊvÊÌ
iÊ>Ê«iÀvÀ>ViÊVÕÌiÀÃ]Êi̽ÃÊÃiiÊ
ÜÊÌÊLÀ}Ê Ì
iÃiÊVÕÌiÀÃÊÌ}iÌ
iÀÊÌÊVÀi>ÌiÊ>ÊÃÞÃÌiÊL>Ãii°Ê/
iÃiÊ>ÀiÊÌ
iÊÃÌi«ÃÊÞÕÊii`ÊÌÊvÜ\ 1. Ài>ÌiÊ>ÊÀiÕÃ>LiÊÃÌÊvÊ«iÀvÀ>ViÊVÕÌiÀð 2. Ài>ÌiÊ>ÊVÕÌiÀÊ}ÊÕÃ}ÊÞÕÀÊÃÌÊvÊ«iÀvÀ>ViÊVÕÌiÀð 3. âiÊ*iÀvÀ>ViÊÌÀÊÛiÀ
i>`°
Creating a Reusable List of Performance Counters Run the Performance Monitor tool on a Windows Server 2008 machine connected to the same iÌÜÀÊ>ÃÊÌ
>ÌÊvÊÌ
iÊ-+Ê-iÀÛiÀÊÃÞÃÌi°Ê``Ê«iÀvÀ>ViÊVÕÌiÀÃÊÌÊÌ
iÊ6iÜÊ
>ÀÌÊ`ë>ÞÊ of the Performance Monitor through the Properties ¢Ê >Ì>Ê¢Ê``Ê ÕÌiÀÃÊ`>}ÊLÝ]Ê>ÃÊ Ã
ÜÊÊ}ÕÀiÊÓ°
53
54
CHAPTER 2 N SYS TEM P ER FOR MA NC E A NA L YS IS
Figure 2-9. Adding Performance Monitor counters ÀÊiÝ>«i]ÊÌ add the performance counter Lnk_aookn$[Pkp]h%X!Lnk_aooknPeia, follow these steps: 1. -iiVÌÊÌ
iÊ«ÌÊ-iiVÌÊ ÕÌiÀÃÊvÀÊ «ÕÌiÀ]Ê>`ÊëiVvÞÊÌ
iÊV«ÕÌiÀÊ>iÊ ÀÕ}Ê-+Ê-iÀÛiÀÊÊÌ
iÊVÀÀië`}ÊiÌÀÞÊvi`° 2. VÊÌ
iÊLÝÊiÝÌÊÌÊÌ
iÊ«iÀvÀ>ViÊLiVÌÊLnk_aookn. 3.
ÃiÊÌ
iÊ!Lnk_aooknPeia counter from the list of performance counters. 4.
ÃiÊejop]j_a[Pkp]hÊvÀÊÌ
iÊÃÌ>ViÃÊÊÌ
iÊÃÌ>ViÃÊvÊ-iiVÌi`Ê"LiVÌÊÃ̰ 5. VÊÌ
iÊ``ÊLÕÌÌÊÌÊ>``ÊÌ
ÃÊ«iÀvÀ>ViÊVÕÌiÀÊÌÊÌ
iÊÃÌÊvÊVÕÌiÀÃÊÌÊLiÊ added. 6. ÌÕiÊ>ÃÊii`i`ÊÜÌ
ÊÌ
iÀÊVÕÌiÀðÊ7
iÊvÃ
i`]ÊVVÊÌ
iÊ"ÊLÕḬ̀ When creating a reusable list for your baseline, you can repeat the preceding steps to add >ÊÌ
iÊ«iÀvÀ>ViÊVÕÌiÀÃÊÃÌi`ÊÊ/>LiÊÓ£ä°
C H A P T E R 2 N S Y S T E M P E R F O R M A N C E A N A LY S I S
Table 2-10. Performance Monitor Counters to Analyze SQL Server Performance
Object(Instance[,InstanceN])
Counter
Iaiknu
=r]eh]^haI>upao L]cao+oa_
Lduoe_]h@eog$@]p])`eog(Hkc)`eog%
!@eogPeia ?qnnajp@eogMqaqaHajcpd @eogPn]jobano+oa_ @eog>upao+oa_
Lnk_aookn$[Pkp]h%
!Lnk_aooknPeia !Lnerehaca`Peia
Ouopai
Lnk_aooknMqaqaHajcpd ?kjpatpOsep_dao+oa_
JapskngEjpanb]_a$Japskng_]n`%
>upaoPkp]h+oa_
JapskngOaciajp
!JapQpehev]pekj
OMHOanran6=__aooIapdk`o
BnaaOl]_aO_]jo+oa_ BqhhO_]jo+oa_
OMHOanran6>qbbanI]j]can
>qbban_]_dadepn]pek Bnaal]cao
OMHOanran6H]p_dao
Pkp]hH]p_dS]epPeia$io%
OMHOanran6Hk_go$[Pkp]h%
Hk_gPeiakqpo+oa_ Hk_gS]epPeia$io% Jqi^ankb@a]`hk_go+oa_
OMHOanran6IaiknuI]j]can
IaiknuCn]jpoLaj`ejc P]ncapOanranIaiknu$G>% Pkp]hOanranIaiknu$G>%
OMHOanran6OMHOp]peope_o
>]p_dNamqaopo+oa_ OMHNa)?kileh]pekjo+oa_
OMHOanran6Cajan]hOp]peope_o
Qoan?kjja_pekjo
"ViÊÞÕÊ
>ÛiÊ>``i`Ê>ÊÌ
iÊ«iÀvÀ>ViÊVÕÌiÀÃ]ÊVÃiÊÌ
iÊ``Ê ÕÌiÀÃÊ`>}ÊLݰÊ/Ê save the list of counters as an *dpiÊvi]ÊÀ}
ÌVVÊ>ÞÜ
iÀiÊÊÌ
iÊÀ}
ÌÊvÀ>iÊvÊ*iÀvÀ>ViÊ Monitor, and select the Save As menu item. /
iÊ*dpi file lists all the performance counters that can be used as a base set of counters ÌÊVÀi>ÌiÊ>ÊVÕÌiÀÊ}]ÊÀÊÌÊÛiÜÊ*iÀvÀ>ViÊÌÀÊ}À>«
ÃÊÌiÀ>VÌÛiÞ]ÊvÀÊÌ
iÊÃ>iÊ-+Ê -iÀÛiÀÊ>V
i°Ê/Ê>ÃÊÕÃiÊÌ
ÃÊÃÌÊvÊVÕÌiÀÃÊvÀÊÌ
iÀÊ-+Ê-iÀÛiÀÊ>V
iÃ]Ê«iÊÌ
iÊ*dpi viÊÊ>Êi`ÌÀÊÃÕV
Ê>ÃÊ Ìi«>`]Ê>`ÊÀi«>ViÊ>ÊÃÌ>ViÃÊvÊXXOMHOanranI]_dejaJ]ia with ‘’ >ÊL>ÊÃÌÀ}®]ÊÜÌ
ÕÌÊÌ
iʵÕÌið You can also use this counter list file to view Performance Monitor graphs interactively in >ÊÌiÀiÌÊLÀÜÃiÀ]Ê>ÃÊÃ
ÜÊÊ}ÕÀiÊÓ£ä°
55
56
CHAPTER 2 N SYS TEM P ER FOR MA NC E A NA L YS IS
Figure 2-10. Performance Monitor in Internet browser
Creating a Counter Log Using the List of Performance Counters Performance Monitor provides a counter log facility to save the performance data of multiple counters over a period of time. You can view the saved counter log using Performance Monitor ÌÊ>>ÞâiÊÌ
iÊ«iÀvÀ>ViÊ`>Ì>°ÊÌÊÃÊÕÃÕ>ÞÊVÛiiÌÊÌÊVÀi>ÌiÊ>ÊVÕÌiÀÊ}ÊvÀÊ>Ê`ivi`Ê ÃÌÊvÊ«iÀvÀ>ViÊVÕÌiÀðÊ-«ÞÊViVÌ}ÊÌ
iÊ`>Ì>ÊÀ>Ì
iÀÊÌ
>ÊÛiÜ}ÊÌÊÌ
ÀÕ}
ÊÌ
iÊ1Ê is the preferred method of automation to prepare for troubleshooting your server’s performance or establishing a baseline.
Ý«>`Ê >Ì>Ê iVÌÀÊ-iÌÃÊ¢Ê1ÃiÀÊ ivi`°Ê,}
ÌVV]Ê>`ÊÃiiVÌÊ iÜÊ¢Ê >Ì>Ê iVÌÀÊ -iÌ°Ê iviÊÌ
iÊ>iÊvÊÌ
iÊÃiÌ]Ê>`Ê>iÊÌ
ÃÊ>Ê>Õ>ÊVÀi>ÌÊLÞÊVV}ÊÌ
iÊ>««À«À>ÌiÊ À>`ÊLÕÌÌÆÊÌ
iÊVVÊ iÝ̰Ê9Õ½Ê
>ÛiÊÌÊ`iviÊÜ
>ÌÊÌÞ«iÊvÊ`>Ì>ÊÞÕ½ÀiÊViVÌ}°ÊÊÌ
ÃÊ V>Ãi]ÊÃiiVÌÊÌ
iÊV
iVÊLÝÊ*iÀvÀ>ViÊ ÕÌiÀÃÊÕ`iÀÊÌ
iÊ Ài>ÌiÊ >Ì>Ê}ÃÊÀ>`ÊLÕÌÌ]Ê >`ÊÌ
iÊVVÊ iÝ̰ÊiÀiÊÞÕÊV>Ê`iviÊÌ
iÊ«iÀvÀ>ViÊLiVÌÃÊÞÕÊÜ>ÌÊÌÊViVÌÊÕÃ}Ê Ì
iÊÃ>iÊ``Ê ÕÌiÀÃÊ`>}ÊLÝÊ>ÃÊÃ
ÜÊi>ÀiÀÊÊ}ÕÀiÊÓ°Ê V}Ê iÝÌÊ>ÜÃÊÞÕÊÌÊ `iviÊÌ
iÊ`iÃÌ>ÌÊv`iÀ°Ê VÊ iÝÌ]ÊÌ
iÊÃiiVÌÊÌ
iÊÀ>`ÊLÕÌÌÊ"«iÊ*À«iÀÌiÃÊvÀÊ/
ÃÊ >Ì>Ê iVÌÀÊ-iÌ]Ê>`ÊVVÊÃ
°Ê9ÕÊV>ÊÃV
i`ÕiÊÌ
iÊVÕÌiÀÊ}ÊÌÊ>ÕÌ>ÌV>ÞÊÃÌ>ÀÌÊ at a specific time and stop after a certain time period or at a specific time. You can configure Ì
iÃiÊÃiÌÌ}ÃÊÌ
ÀÕ}
ÊÌ
iÊ-V
i`ÕiÊ«>i°Ê}ÕÀiÊÓ££ÊÃ
ÜÃÊÌ
iÊÃÕ>ÀÞÊvÊÜ
V
ÊVÕÌiÀÃÊ have been selected as well as the frequency with which the counters will be collected.
C H A P T E R 2 N S Y S T E M P E R F O R M A N C E A N A LY S I S
Figure 2-11. Defining a Performance Monitor counter log
NTip I’ll offer additional suggestions for these settings in the section that follows.
ÀÊ>``Ì>ÊvÀ>ÌÊÊ
ÜÊÌÊVÀi>ÌiÊVÕÌiÀÊ}ÃÊÕÃ}Ê*iÀvÀ>ViÊÌÀ]Ê «i>ÃiÊÀiviÀÊÌÊÌ
iÊVÀÃvÌÊÜi`}iÊ >ÃiÊ>ÀÌViʺ*iÀvÀ>ViÊ/Õ}ÊÕ`iiÃÊvÀÊ 7`ÜÃÊ-iÀÛiÀÊÓään» at dppl6++`ksjhk]`*ie_nkokbp*_ki+`ksjhk]`+5+_+1+ 5_1^.-23)4,-3)0^]a)5b`a)`155^]_4-40]+Lanb)pqj)onr*`k_t.
Minimizing Performance Monitor Overhead /
i Performance Monitor tool is designed to add as little overhead as possible, if used VÀÀiVÌÞ°Ê/ÊâiÊÌ
iÊ«>VÌÊvÊÕÃ}ÊÌ
ÃÊÌÊÊ>ÊÃÞÃÌi]ÊVÃ`iÀÊÌ
iÊvÜ}Ê suggestions: Ê
UÊ ÌÊÌ
iÊÕLiÀÊvÊVÕÌiÀÃ]ÊëiVvV>ÞÊ«iÀvÀ>ViÊLiVÌð
Ê
UÊ 1ÃiÊVÕÌiÀÊ}ÃÊÃÌi>`ÊvÊÛiÜ}Ê*iÀvÀ>ViÊÌÀÊ}À>«
ÃÊÌiÀ>VÌÛiÞ°
Ê
UÊ ,ÕÊ*iÀvÀ>ViÊÌÀÊÀiÌiÞÊÜ
iÊÛiÜ}Ê}À>«
ÃÊÌiÀ>VÌÛiÞ°
Ê
UÊ ->ÛiÊÌ
iÊVÕÌiÀÊ}ÊviÊÌÊ>Ê`vviÀiÌÊV>Ê`ð
Ê
UÊ VÀi>ÃiÊÌ
iÊÃ>«}ÊÌiÀÛ>° i̽ÃÊVÃ`iÀÊi>V
ÊvÊÌ
iÃiÊ«ÌÃÊÊÀiÊ`iÌ>°
57
58
CHAPTER 2 N SYS TEM P ER FOR MA NC E A NA L YS IS
Limit the Number of Counters Monitoring large numbers of performance counters with small sampling intervals could incur ÃiÊ>ÕÌÊvÊÛiÀ
i>`ÊÊÌ
iÊÃÞÃÌi°Ê/
iÊLÕÊvÊÌ
ÃÊÛiÀ
i>`ÊViÃÊvÀÊÌ
iÊÕLiÀÊvÊ «iÀvÀ>ViÊLiVÌÃÊÞÕÊ>ÀiÊÌÀ}]ÊÃÊÃiiVÌ}ÊÌ
iÊÜÃiÞÊÃÊ«ÀÌ>̰Ê/
iÊÕLiÀÊ of counters for the selected performance objects does not add much overhead, because it gives ÞÊ>Ê>ÌÌÀLÕÌiÊvÊÌ
iÊLiVÌÊÌÃiv°Ê/
iÀivÀi]ÊÌÊÃÊ«ÀÌ>ÌÊÌÊÜÊÜ
>ÌÊLiVÌÃÊÞÕÊÜ>ÌÊ to monitor and why.
Prefer Counter Logs 1Ãi counter logs instead of viewing a Performance Monitor graph interactively, because Performance Monitor graphing is more costly in terms of overhead. Monitoring current activities should be limited to short-term viewing of data, troubleshooting, and diagnosis. Performance data reported via a counter log is sampled, meaning that data is collected periodically rather than traced, whereas the Performance Monitor graph is updated in real time as events occur. 1Ã}ÊVÕÌiÀÊ}ÃÊÜÊÀi`ÕViÊÌ
>ÌÊÛiÀ
i>`°
View Performance Monitor Graphs Remotely Since viewing the live performance data using Performance Monitor graphs creates a fair amount of overhead on the system, run the tool remotely on a different machine, and connect ÌÊÌ
iÊ-+Ê-iÀÛiÀÊÃÞÃÌiÊÌ
ÀÕ}
ÊÌ
iḚ̂Ê/ÊÀiÌiÞÊViVÌÊÌÊÌ
iÊ-+Ê-iÀÛiÀÊ>V
i]Ê ÀÕÊÌ
iÊ*iÀvÀ>ViÊÌÀÊÌÊÊ>Ê>V
iÊViVÌi`ÊÌÊÌ
iÊiÌÜÀÊÌÊÜ
V
ÊÌ
iÊ-+Ê Server machine is connected. ÃÊÃ
ÜÊÊ}ÕÀiÊÓ]ÊÌÞ«iÊÌ
iÊV«ÕÌiÀÊ>iÊÀÊ*Ê>``ÀiÃîÊvÊÌ
iÊ-+Ê-iÀÛiÀÊ >V
iÊÊÌ
iÊ-iiVÌÊ ÕÌiÀÃÊvÀÊ «ÕÌiÀÊLÝ°Ê iÊ>Ü>ÀiÊÌ
>ÌÊvÊÞÕÊViVÌÊÌÊÌ
iÊ production server through a Windows Server 2008 terminal service session, the major part of the tool will still run on the server.
Save Counter Log Locally
iVÌ}ÊÌ
i performance data for the counter log does not incur the overhead of displaying any graph. So, while using counter log mode, it is more efficient to log counter values locally ÊÌ
iÊ-+Ê-iÀÛiÀÊÃÞÃÌiÊÃÌi>`ÊvÊÌÀ>ÃviÀÀ}ÊÌ
iÊ«iÀvÀ>ViÊ`>Ì>Ê>VÀÃÃÊÌ
iÊiÌÜÀ°Ê*ÕÌÊ Ì
iÊVÕÌiÀÊ}ÊviÊÊ>ÊV>Ê`ÃÊÌ
iÀÊÌ
>ÊÌ
iÊiÃÊÌ
>ÌÊ>ÀiÊÌÀi`°
Increase the Sampling Interval BecauseÊÞÕÊ>ÀiÊ>ÞÊÌiÀiÃÌi`ÊÊÌ
iÊÀiÃÕÀViÊÕÌâ>ÌÊ«>ÌÌiÀÊ`ÕÀ}ÊL>ÃiiÊÌÀ}]ÊÞÕÊV>Êi>ÃÞÊVÀi>ÃiÊÌ
iÊ«iÀvÀ>ViÊ`>Ì>ÊÃ>«}ÊÌiÀÛ>ÊÌÊÈäÊÃiV`ÃÊÀÊÀiÊ ÌÊ`iVÀi>ÃiÊÌ
iÊ}ÊviÊÃâiÊ>`ÊÀi`ÕViÊ`i>`ÊÊ`ÃÊÉ"ðÊ9ÕÊV>ÊÕÃiÊ>ÊÃ
ÀÌÊÃ>«}Ê interval to detect and diagnose timing issues. Even while viewing Performance Monitor graphs interactively, increase the sampling interval from the default value of one second per sample. ÕÃÌÊÀiiLiÀ]ÊV
>}}ÊÌ
iÊÃ>«}ÊÃâi]ÊÕ«ÊÀÊ`Ü]ÊV>Ê>vviVÌÊÌ
i granularity of the data as well as the quantity. You have to weigh these choices carefully.
C H A P T E R 2 N S Y S T E M P E R F O R M A N C E A N A LY S I S
System Behavior Analysis Against Baseline /
i default behavior of a database application changes over time because of various factors such as the following: Ê
UÊ
>}iÊvÊ`>Ì>
Ê
UÊ
>}iÊvÊÕÃiÀÊ}ÀÕ«
Ê
UÊ
>}iÊÊÕÃ>}iÊ«>ÌÌiÀÊvÊÌ
iÊ>««V>Ì
Ê
UÊ ``ÌÊÌÊÀÊV
>}iÊÊ>««V>ÌÊvÕVÌ>ÌiÃ
Ê
UÊ
>}iÊÊÃvÌÜ>ÀiÊiÛÀiÌÊLiV>ÕÃiÊvÊÌ
iÊÃÌ>>ÌÊvÊiÜÊÃiÀÛViÊ«>VÃÊÀÊ software upgrades
Ê
UÊ
>}iÊÊ
>À`Ü>ÀiÊiÛÀiÌ
Because of the preceding changes, the baseline created for the database server slowly loses its significance. It may not always be accurate to compare the current behavior of the ÃÞÃÌiÊÜÌ
Ê>Ê`ÊL>Ãii°Ê/
iÀivÀi]ÊÌÊÃÊ«ÀÌ>ÌÊÌÊii«ÊÌ
iÊL>ÃiiÊÕ«Ì`>ÌiÊLÞÊ creating a new baseline at regular time intervals. It is also beneficial to archive the previous baseline logs so that they can be referred to later, if required. /
iÊVÕÌiÀÊ}ÊvÀÊÌ
iÊL>ÃiiÊÀÊÌ
iÊVÕÀÀiÌÊLi
>ÛÀÊvÊÌ
iÊÃÞÃÌiÊV>ÊLiÊ>>Þâi`Ê using the Performance Monitor tool by following these steps: 1. "«iÊÌ
iÊVÕÌiÀÊ}°Ê1ÃiÊ*iÀvÀ>ViÊÌÀ½ÃÊÌL>ÀÊÌiÊ6iÜÊ}ÊiÊ >Ì>]Ê>`Ê select the log file name. 2. ``Ê>ÊÌ
iÊ«iÀvÀ>ViÊVÕÌiÀÃÊÌÊ>>ÞâiÊÌ
iÊ«iÀvÀ>ViÊ`>Ì>°Ê ÌiÊÌ
>ÌÊÞÊÌ
iÊ performance objects, counters, and instances selected during the counter log creation are shown in the selection lists. 3. >ÞâiÊÌ
iÊÃÞÃÌiÊLi
>ÛÀÊ>ÌÊ`vviÀiÌÊ«>ÀÌÃÊvÊÌ
iÊ`>ÞÊLÞÊ>`ÕÃÌ}ÊÌ
iÊÌiÊÀ>}iÊ >VVÀ`}Þ]Ê>ÃÊÃ
ÜÊÊ}ÕÀiÊÓ£Ó°
Figure 2-12. Defining time range for log analysis
59
60
CHAPTER 2 N SYS TEM P ER FOR MA NC E A NA L YS IS
ÕÀ}Ê>Ê«iÀvÀ>ViÊÀiÛiÜ]ÊÞÕÊV>Ê>>ÞâiÊÌ
iÊÃÞÃÌiiÛiÊLi
>ÛÀÊvÊÌ
iÊ`>Ì>L>ÃiÊ LÞÊV«>À}ÊÌ
iÊVÕÀÀiÌÊÛ>ÕiÊvÊ«iÀvÀ>ViÊVÕÌiÀÃÊÜÌ
ÊÌ
iÊ>ÌiÃÌÊL>Ãii°Ê/>iÊÌ
iÊvlowing considerations into account while comparing the performance data: Ê
UÊ 1ÃiÊÌ
iÊÃ>iÊÃiÌÊvÊ«iÀvÀ>ViÊVÕÌiÀÃÊÊLÌ
ÊV>Ãið
Ê
UÊ «>ÀiÊÌ
iÊÕ]Ê>ÝÕ]ÊÀÊ>ÛiÀ>}iÊÛ>ÕiÊvÊÌ
iÊVÕÌiÀÃÊ>ÃÊ>««V>LiÊvÀÊ Ì
iÊ`Û`Õ>ÊVÕÌiÀðÊÊiÝ«>i`ÊÌ
iÊëiVvVÊÛ>ÕiÃÊvÀÊÌ
iÊVÕÌiÀÃÊi>ÀiÀ°
Ê
UÊ -iÊVÕÌiÀÃÊ
>ÛiÊ>Ê>LÃÕÌiÊ}`ÉL>`ÊÛ>ÕiÊ>ÃÊiÌi`Ê«ÀiÛÕÃÞ°Ê/
iÊVÕÀÀiÌÊ Û>ÕiÊvÊÌ
iÃiÊVÕÌiÀÃÊii`ÊÌÊLiÊV«>Ài`ÊÜÌ
ÊÌ
iÊL>ÃiiÊÛ>ÕiðÊÀÊiÝ>«i]Ê if the current average value of the L]cao+oa_ counter is 100, then it indicates that the ÃÞÃÌiÊ
>ÃÊ`iÛi«i`Ê>ÊiÀÞÊLÌÌiiV°Ê ÛiÊÌ
Õ}
ÊÌÊ`iÃÊÌÊÀiµÕÀiÊ>ÊVparison with the baseline, it is still advantageous to review the corresponding baseline Û>Õi]ÊLiV>ÕÃiÊÌ
iÊiÀÞÊLÌÌiiVÊ}
ÌÊ
>ÛiÊiÝÃÌi`ÊvÀÊ>Ê}ÊÌi°Ê>Û}ÊÌ
iÊ >ÀV
Ûi`ÊL>ÃiiÊ}ÃÊ
i«ÃÊ`iÌiVÌÊÌ
iÊvÀÃÌÊVVÕÀÀiViÊvÊÌ
iÊiÀÞÊLÌÌiiV°
Ê
UÊ -iÊVÕÌiÀÃÊ`ÊÌÊ
>ÛiÊ>Ê`ivÌÛiÊ}`ÉL>`ÊÛ>Õi°Ê iV>ÕÃiÊÌ
iÀÊÛ>ÕiÊ`i«i`ÃÊ on the application, a relative comparison with the corresponding baseline counters is >ÊÕÃ̰ÊÀÊiÝ>«i]ÊÌ
iÊVÕÀÀiÌÊÛ>ÕiÊvÊÌ
iÊQoan?kjja_pekjoÊVÕÌiÀÊvÀÊ-+Ê-iÀÛiÀÊ does not signify anything good or bad with the application. But comparing it with the corresponding baseline value may reveal a big increase in the number of user connecÌÃ]Ê`V>Ì}Ê>ÊVÀi>ÃiÊÊÌ
iÊÜÀ>`°
Ê
UÊ «>ÀiÊ>ÊÀ>}iÊvÊÛ>ÕiÊvÀÊÌ
iÊVÕÌiÀÃÊvÀÊÌ
iÊVÕÀÀiÌÊ>`ÊÌ
iÊL>ÃiiÊVÕÌiÀÊ }ðÊ/
iÊvÕVÌÕ>ÌÊÊÌ
iÊ`Û`Õ>ÊÛ>ÕiÃÊvÊÌ
iÊVÕÌiÀÃÊÜÊLiÊÀ>âi`ÊLÞÊÌ
iÊ range of values.
Ê
UÊ «>ÀiÊ}ÃÊvÀÊÌ
iÊÃ>iÊ«>ÀÌÊvÊÌ
iÊ`>Þ°ÊÀÊÃÌÊ>««V>ÌÃ]ÊÌ
iÊÕÃ>}iÊ«>ÌÌiÀÊ Û>ÀiÃÊ`ÕÀ}Ê`vviÀiÌÊ«>ÀÌÃÊvÊÌ
iÊ`>Þ°Ê/ÊLÌ>ÊÌ
iÊÕ]Ê>ÝÕ]Ê>`Ê>ÛiÀage value of the counters for a specific time, adjust the time range of the counter logs as shown previously.
"ViÊÌ
iÊÃÞÃÌiiÛiÊLÌÌiiVÊÃÊ`iÌvi`]ÊÌ
iÊÌiÀ>ÊLi
>ÛÀÊvÊÌ
iÊ>««V>ÌÊ Ã
Õ`ÊLiÊ>>Þâi`ÊÌÊ`iÌiÀiÊÌ
iÊV>ÕÃiÊvÊÌ
iÊLÌÌiiV°Ê`iÌvÞ}Ê>`Ê«Ìâ}ÊÌ
iÊ ÃÕÀViÊvÊÌ
iÊLÌÌiiV will help use the system resources efficiently.
Summary In thisÊV
>«ÌiÀ]ÊÞÕÊi>Ài`ÊÌ
>ÌÊÞÕÊV>ÊÕÃiÊÌ
iÊ*iÀvÀ>ViÊÌÀÊÌÊÌÊ>>ÞâiÊÌ
iÊ effect on system resources of a slow-performing database application, as well as the overall Li
>ÛÀÊvÊ-+Ê-iÀÛiÀ°ÊÀÊiÛiÀÞÊÀiÃÕÌ>ÌÊÃÞÃÌiÊLÌÌiiV]ÊÌ
iÀiÊ>ÀiÊÌÜÊÌÞ«iÃÊvÊÀiÃÕÌÃ\Ê
>À`Ü>ÀiÊÀiÃÕÌÃÊ>`Ê>««V>ÌÊ«Ìâ>̰Ê"vÊVÕÀÃi]ÊÌÊÃÊ>Ü>ÞÃÊLiivV>ÊÌÊ «ÌâiÊÌ
iÊ`>Ì>L>ÃiÊ>««V>ÌÊLivÀiÊVÃ`iÀ}Ê>Ê
>À`Ü>ÀiÊÕ«}À>`i° ÊÌ
iÊiÝÌÊV
>«ÌiÀ]ÊÞÕÊÜÊi>ÀÊ
ÜÊÌÊ>>ÞâiÊÌ
iÊÜÀ>`ÊvÊ>Ê`>Ì>L>ÃiÊ>««V>ÌÊ for performance tuning.
CHAPTER
3
SQL Query Performance Analysis A
common cause of slow SQL Server performance is a heavy database application workload— the nature of the queries themselves. Thus, to analyze the cause of a system bottleneck, it is important to examine the database application workload and identify the SQL queries causing the most stress on system resources. To do this, you use the SQL Profiler and Management Studio tools. In this chapter, I cover the following topics: Ê
UÊ /
iÊL>ÃVÃÊvÊÌ
iÊ-+Ê*ÀviÀÊÌ
Ê
UÊ ÜÊÌÊ>>ÞâiÊ-+Ê-iÀÛiÀÊÜÀ>`Ê>`Ê`iÌvÞÊVÃÌÞÊ-+ʵÕiÀiÃÊÕÃ}Ê-+Ê*ÀviÀ
Ê
UÊ ÜÊÌÊVLiÊÌ
iÊL>ÃiiÊi>ÃÕÀiiÌÃÊÜÌ
Ê`>Ì>ÊViVÌi`ÊvÀÊ-+Ê*ÀviÀ
Ê
UÊ ÜÊÌÊ>>ÞâiÊÌ
iÊ«ÀViÃÃ}ÊÃÌÀ>Ìi}ÞÊvÊ>ÊVÃÌÞÊ-+ʵÕiÀÞÊÕÃ}Ê>>}iiÌÊ Studio
Ê
UÊ ÜÊÌÊÌÀ>VʵÕiÀÞÊ«iÀvÀ>ViÊÌ
ÀÕ}
Ê`Þ>VÊ>>}iiÌÊÛiÜÃ
Ê
UÊ ÜÊÌÊ>>ÞâiÊÌ
iÊivviVÌÛiiÃÃÊvÊ`iÝÊ>`ÊÊÃÌÀ>Ìi}iÃÊvÀÊ>Ê-+ʵÕiÀÞ
Ê
UÊ ÜÊÌÊi>ÃÕÀiÊÌ
iÊVÃÌÊvÊ>Ê-+ʵÕiÀÞÊÕÃ}Ê-+ÊÕÌÌiÃ
The SQL Profiler Tool SQL Profiler is a GUI and set of system stored procedures that can be used to do the following: Ê
UÊ À>«
V>ÞÊÌÀ SQL Server queries
Ê
UÊ iVÌʵÕiÀÞÊvÀ>ÌÊÊÌ
iÊL>V}ÀÕ`
Ê
UÊ >ÞâiÊ«iÀvÀ>Vi
Ê
UÊ >}ÃiÊ«ÀLiÃÊÃÕV
Ê>ÃÊ`i>`VÃ
Ê
UÊ iLÕ}Ê>Ê/À>Ã>VÌ-+Ê/-+®ÊÃÌ>ÌiiÌ
Ê
UÊ ,i«>ÞÊ-+Ê-iÀÛiÀÊ>VÌÛÌÞÊÊ>ÊÃÕ>Ì
61
62
CHAPTER 3 N SQ L QU ER Y P ER FOR MA NC E A NA L YS IS
You can also use SQL Profiler to capture activities performed on a SQL Server instance. Such a capture is called a Profiler trace. You can use a Profiler trace to capture events gener ated by various subsystems within SQL Server. You can run traces from the graphical front end or through direct calls to the procedures. The most efficient way to define a trace is through the system procedures, but a good place to start learning about traces is through the GUI.
Profiler Traces Open the Profiler tool from the Start ¢ Programs ¢ Microsoft SQL Server 2008 ¢ Performance Tools menu, and select File ¢ New ¢Ê/À>Vi°Ê9ÕÊV>Ê>ÃÊ«ÀiÃÃÊ ÌÀ³ ÊÀÊVVÊÌ
iÊ iÜÊ/À>ViÊ button. This opens a connection window, so choose the server instance you’ll be tracing and Ì
iÊViV̰Ê/
>ÌÊ«iÃÊÌ
iÊ/À>ViÊ*À«iÀÌiÃÊ`>}ÊLÝÊÃ
ÜÊÊ}ÕÀiÊΣ°
Figure 3-1. General trace properties You can supply a trace name to help categorize your traces later when you have lots of Ì
i°Ê vviÀiÌÊÌÀ>ViÊÌi«>ÌiÃÊ>ÀiÊ>Û>>LiÊÌ
>ÌʵÕVÞÊ
i«ÊÞÕÊÃiÌÊÕ«ÊiÜÊÌÀ>ViÃÊvÀÊ`vviÀ ent purposes. For the moment, I’ll stick with the Standard trace. Without additional changes, this trace will run as a graphical trace, but from the Trace Properties dialog box you can define the trace to save its output to a file or to a table. I’ll spend more time on these options later in this chapter. Finally, from the General tab, you can define a stop time for the trace. These options combine to give you more control over exactly what you intend to monitor and how you intend to monitor it within SQL Server.
C H A P T E R 3 N S Q L Q U E R Y P E R F O R M A N C E A N A LY S I S
With the initial setup complete, click the Events Selection tab to provide more detailed `ivÌÊÌÊÞÕÀÊÌÀ>Vi]Ê>ÃÊÃ
ÜÊÊ}ÕÀiÊÎÓ°
Figure 3-2. Events Selection tab
Events event represents various activities performed in SQL Server. These are categorized for easy classification into event classesÆÊVÕÀÃÀÊiÛiÌÃ]ÊVÊiÛiÌÃ]ÊÃÌÀi`Ê«ÀVi`ÕÀiÊiÛiÌÃ]Ê>`Ê/-+Ê events are a few common event classes. ÀÊ«iÀvÀ>ViÊ>>ÞÃÃ]ÊÞÕÊ>ÀiÊ>ÞÊÌiÀiÃÌi`ÊÊÌ
iÊiÛiÌÃÊÌ
>ÌÊ
i«ÊÞÕÊÕ`}iÊ levels of resource stress for various activities performed on SQL Server. By resource stress, I mean things such as the following: Ê
UÊ 7
>ÌÊ`ÊvÊ *1ÊÕÌâ>ÌÊÜ>ÃÊÛÛi`ÊvÀÊÌ
iÊ-+Ê>VÌÛÌÞ¶
Ê
UÊ ÜÊÕV
ÊiÀÞÊÜ>ÃÊÕÃi`¶
Ê
UÊ ÜÊÕV
ÊÉ"ÊÜ>ÃÊÛÛi`¶
Ê
UÊ ÜÊ}Ê``ÊÌ
iÊ-+Ê>VÌÛÌÞÊÌ>iÊÌÊiÝiVÕÌi¶
Ê
UÊ ÜÊvÀiµÕiÌÞÊÜ>ÃÊ>Ê«>ÀÌVÕ>ÀʵÕiÀÞÊiÝiVÕÌi`¶
Ê
UÊ 7
>ÌÊ`ÊvÊiÀÀÀÃÊ>`ÊÜ>À}ÃÊÜiÀiÊv>Vi`ÊLÞÊÌ
iʵÕiÀiö
63
64
CHAPTER 3 N SQ L QU ER Y P ER FOR MA NC E A NA L YS IS
You can calculate the resource stress of a SQL activity after the completion of an event, so the main events you use for performance analysis are those that represent the completion of a -+Ê>VÌÛÌÞ°Ê/>LiÊΣÊ`iÃVÀLiÃÊÌ
iÃiÊiÛiÌð Table 3-1. Events to Trace Query Completion
Event Class
Event
Description
Opkna`Lnk_a`qnao
NL?6?kilhapa`
Ê,* ÊV«iÌÊiÛiÌ
OL6?kilhapa`
ÊÃÌÀi` procedure completion event
OL6Opip?kilhapa`
Ê-+ÊÃÌ>ÌiiÌ completion event within a stored procedure
OMH6>]p_d?kilhapa`
Ê/-+ batch completion event
OMH6Opip?kilhapa`
Ê/-+ÊÃÌ>ÌiiÌ completion event
POMH
Ê,* ÊiÛiÌÊ`V>ÌiÃÊÌ
>ÌÊÌ
iÊÃÌÀi`Ê«ÀVi`ÕÀiÊÜ>ÃÊiÝiVÕÌi`ÊÕÃ}ÊÌ
iÊ,iÌiÊ*ÀVi `ÕÀiÊ >Ê,* ®ÊiV
>ÃÊÌ
ÀÕ}
Ê>Ê" ÊV>`°ÊvÊ>Ê`>Ì>L>ÃiÊ>««V>ÌÊiÝiVÕÌiÃÊ a ÃÌÀi`Ê«ÀVi`ÕÀiÊÕÃ}ÊÌ
iÊ/-+ÊATA?QPA statement, then that stored procedure is resolved >ÃÊ>Ê-+ÊL>ÌV
ÊÀ>Ì
iÀÊÌ
>Ê>ÃÊ>Ê,* °Ê,* ÊÀiµÕiÃÌÃÊ>ÀiÊ}iiÀ>ÞÊv>ÃÌiÀÊÌ
>ÊATA?QPA requests, since they bypass much of the statement parsing and parameter processing in SQL Server. ÊT-SQL batch is aÊÃiÌÊvÊ-+ʵÕiÀiÃÊÌ
>ÌÊ>ÀiÊÃÕLÌÌi`ÊÌ}iÌ
iÀÊÌÊ-+Ê-iÀÛiÀ°ÊÊ/-+Ê batch is usually terminated by a CK command. The CKÊV>`ÊÃÊÌÊ>Ê/-+ÊÃÌ>ÌiiÌ°Ê Instead, the CK command is recognized by the omh_i` utility, as well as by Management Studio, >`ÊÌÊÃ}>ÃÊÌ
iÊi`ÊvÊ>ÊL>ÌV
°Ê >V
Ê-+ʵÕiÀÞÊÊÌ
iÊL>ÌV
ÊÃÊVÃ`iÀi`Ê>Ê/-+ÊÃÌ>Ìi ḭÊ/
ÕÃ]Ê>Ê/-+ÊL>ÌV
ÊVÃÃÌÃÊvÊiÊÀÊÀiÊ/-+ÊÃÌ>ÌiiÌðÊ-Ì>ÌiiÌÃÊÀÊ/-+Ê ÃÌ>ÌiiÌÃÊ>ÀiÊ>ÃÊÌ
iÊ`Û`Õ>]Ê`ÃVÀiÌiÊV>`ÃÊÜÌ
Ê>ÊÃÌÀi`Ê«ÀVi`ÕÀi°Ê >«ÌÕÀ}Ê individual statements with the OL6Opip?kilhapa` or OMH6Opip?kilhapa` event can be a very expensive operation, depending on the number of individual statements within your queries. ÃÃÕiÊvÀÊ>ÊiÌÊÌ
>ÌÊi>V
ÊÃÌÀi`Ê«ÀVi`ÕÀiÊÜÌ
ÊÞÕÀÊÃÞÃÌiÊVÌ>ÃÊi]Ê>`ÊÞÊ i]Ê/-+ÊÃÌ>ÌiḭÊÊÌ
ÃÊV>Ãi]ÊÌ
iÊViVÌÊvÊV«iÌi`ÊÃÌ>ÌiiÌÃÊÃÊ«ÀiÌÌÞÊÜ°Ê ÜÊ assume that you have multiple statements within your procedures and that some of those pro Vi`ÕÀiÃÊ>ÀiÊV>ÃÊÌÊÌ
iÀÊ«ÀVi`ÕÀiÃÊÜÌ
ÊÌ
iÀÊÃÌ>ÌiiÌÃ°Ê iVÌ}Ê>ÊÌ
ÃÊiÝÌÀ>Ê`>Ì>ÊÜÊ becomes a noticeable load on the system. Statement completion events should be collected iÝÌÀiiÞÊÕ`VÕÃÞ]ÊiëiV>ÞÊÊ>Ê«À`ÕVÌÊÃÞÃÌi° vÌiÀÊÞÕ½ÛiÊÃiiVÌi`Ê>ÊÌÀ>ViÊÌi«>Ìi]Ê>Ê«ÀiÃiiVÌi`ÊÃÌÊvÊiÛiÌÃÊÜÊ>Ài>`ÞÊLiÊ`ivi`Ê on the Events Selection tab. Only the events you have selected will be displayed. To see the full ÃÌÊvÊiÛiÌÃÊ>Û>>Li]ÊVVÊÌ
iÊ-
ÜÊÊ ÛiÌÃÊV
iVÊLݰÊ/Ê>``Ê>ÊiÛiÌÊÌÊÌ
iÊÌÀ>Vi]Êv`Ê the event under an event class in the Event column, and click the check box to the left of it. To remove events not required, deselect the check box next to the event. Ì
Õ}
ÊÌ
iÊiÛiÌÃÊÃÌi`ÊÊ/>LiÊΣÊÀi«ÀiÃiÌÊÌ
iÊÃÌÊVÊiÛiÌÃÊÕÃi`ÊvÀÊ determining performance, you can sometimes use a number of additional events to diagnose Ì
iÊÃ>iÊÌ
}°ÊÀÊiÝ>«i]Ê>ÃÊiÌi`ÊÊ
>«ÌiÀÊ£]ÊÀi«i>Ìi`ÊÀiV«>ÌÊvÊ>ÊÃÌÀi`Ê procedure adds processing overhead, which hurts the performance of the database request. The Opkna`Lnk_a`qnao event class of the Profiler tool includes an event, OL6Na_kileha, to `V>ÌiÊÌ
iÊÀiV«>ÌÊvÊ>ÊÃÌÀi`Ê«ÀVi`ÕÀiÊÌ
ÃÊiÛiÌÊÃÊiÝ«>i`ÊÊ`i«Ì
ÊÊ
>«ÌiÀÊ £ä®°Ê->ÀÞ]Ê*ÀviÀÊVÕ`iÃÊ>``Ì>ÊiÛiÌÃÊÌÊ`V>ÌiÊÌ
iÀÊ«iÀvÀ>ViÀi>Ìi`ÊÃÃÕiÃÊ ÜÌ
Ê>Ê`>Ì>L>ÃiÊÜÀ>`°Ê/>LiÊÎÓÊÃ
ÜÃÊ>ÊviÜÊvÊÌ
iÃiÊiÛiÌð
C H A P T E R 3 N S Q L Q U E R Y P E R F O R M A N C E A N A LY S I S
Table 3-2. Events to Trace Query Performance
Event Class
Event
Description
Oa_qnepu=q`ep
=q`epHkcej =q`epHkckqp
Keeps track of database connections when users connect to and disconnect from SQL Server.
Oaooekjo
Ateopejc?kjja_pekj
,i«ÀiÃiÌÃ all the users connected to SQL Server before the trace was started.
?qnokno
?qnoknEilhe_ep?kjranoekj
Indicates that the cursor type created is different from the requested type.
Annkno]j`S]njejco
=ppajpekj
,i«ÀiÃiÌÃ the intermediate termination of a request caused by actions such as query cancellation by a client or a broken database connection.
At_alpekj
Indicates the occurrence of an exception in SQL Server.
Ata_qpekjS]njejco
Indicates the occurrence of any warning during the execution of a query or a stored procedure.
D]odS]njejc
Indicates the occurrence of an error in a hashing operation.
Ieooejc?khqijOp]peope_o
Indicates that the statistics of a column, required by the optimizer to decide a processing strategy, are missing.
IeooejcFkejLna`e_]pa
Indicates that a query is executed with no }Ê«Ài`V>ÌiÊLiÌÜiiÊÌÜÊÌ>Lið
OknpS]njejco
Indicates that a sort operation performed in a query such as OAHA?P did not fit into memory.
Hk_g6@a]`hk_g
Flags the presence of a deadlock.
Hk_g6@a]`hk_g?d]ej
Shows a trace of the chain of queries creating the deadlock.
Hk_g6Peiakqp
Signifies that the lock has exceeded the timeout parameter, which is set by OAP HK?G[PEIAKQPpeiakqp[lanek`$io%.
OL6Na_kileha
Indicates that an execution plan for a stored procedure had to be recompiled, because one did not exist, a recompilation was forced, or the existing execution plan could not be reused.
OL6Op]npejc
,i«ÀiÃiÌÃ the starting of a stored OL6OpipOp]npejc procedure and a SQL statement within a stored procedure, respectively. They are useful to identify queries that started but could not finish because of an operation that caused an =ppajpekj event.
OMHPn]jo]_pekj
Provides information about a database transaction, including information such as Ü
iÊ>ÊÌÀ>Ã>VÌÊÃÌ>ÀÌi`ÉV«iÌi`]ÊÌ
iÊ duration of the transaction, and so on.
Hk_go
Opkna`Lnk_a`qnao
Pn]jo]_pekjo
65
66
CHAPTER 3 N SQ L QU ER Y P ER FOR MA NC E A NA L YS IS
Data Columns Events are represented by different attributes called data columns. The data columns repre sent different attributes of an event, such as the class of the event, the SQL statement for the event, the resource cost of the event, and the source of the event. The data columns that represent the resource cost of an event are ?LQ, Na]`o, Snepao, and @qn]pekj°ÊÃÊ>ÊÀiÃÕÌ]ÊÌ
iÊ`>Ì>ÊVÕÃÊÞÕÊÜÊÕÃiÊÃÌÊvÀÊ«iÀvÀ>ViÊ>>ÞÃÃÊ>ÀiÊÃ
ÜÊ Ê/>LiÊÎΰ Table 3-3. Data Columns to Trace Query Completion
Data Column
Description
Arajp?h]oo
Type of event, for example, OMH6Op]paiajp?kilhapa`.
Patp@]p]
SQL statement for an event, such as OAHA?P&BNKIouok^fa_po.
?LQ
*1ÊVÃÌÊvÊ>ÊiÛiÌÊÊÃiV`ÃÊî°ÊÀÊiÝ>«i]Ê?LQ9-,, for a OAHA?P ÃÌ>ÌiiÌÊ`V>ÌiÃÊÌ
>ÌÊÌ
iÊÃÌ>ÌiiÌÊÌÊ£ääÊÃÊÌÊiÝiVÕÌi°
Na]`o
Number of logical reads performed for an event. For example, Na]`o94,, for a OAHA?P statement indicates that the statement required a total of 800 reads.
Snepao
Number of logical writes performed for an event.
@qn]pekj
Execution time of an event in ms.
OLE@
SQL Server process identifier used for the event.
Op]npPeia
Start time of an event.
Each logical read and write consists of an 8KB page activity in memory, which may require âiÀÊÀÊÀiÊ«
ÞÃV>ÊÉ"Ê«iÀ>ÌðÊ/Êv`ÊÌ
iÊÕLiÀÊvÊ«
ÞÃV>ÊÉ"Ê«iÀ>ÌÃÊÊ>Ê`ÃÊ subsystem, use the System Monitor tool; I’ll cover more about combining the output of Pro filer and System Monitor later in this chapter. You can add a data column to a Profiler trace by simply clicking the check box for that column. If no check box is available for a given column, then the event in question cannot collect that piece of data. Like the events, initially only the data columns defined by the tem «>ÌiÊ>ÀiÊ`ë>Þi`°Ê/ÊÃiiÊÌ
iÊV«iÌiÊÃÌÊvÊ`>Ì>ÊVÕÃ]ÊVVÊÌ
iÊ-
ÜÊÊ ÕÃÊ check box. You can use additional data columns from time to time to diagnose the cause of poor performance. For example, in the case of a stored procedure recompilation, the Profiler tool indicates the cause of the recompilation through the ArajpOq^?h]ooÊ`>Ì>ÊVÕ°Ê/
ÃÊ`>Ì>Ê VÕÊÃÊiÝ«>i`ÊÊ`i«Ì
ÊÊ
>«ÌiÀʣ䰮ÊÊviÜÊvÊÌ
iÊVÞÊÕÃi`Ê>``Ì>Ê`>Ì>ÊV umns are as follows: Ê
UÊ >ej]nu@]p]
Ê
UÊ Ejpacan@]p]
Ê
UÊ ArajpOq^?h]oo
Ê
UÊ @]p]^]oaE@
Ê
UÊ K^fa_pE@
C H A P T E R 3 N S Q L Q U E R Y P E R F O R M A N C E A N A LY S I S
Ê
UÊ Ej`atE@
Ê
UÊ Pn]jo]_pekjE@
Ê
UÊ Annkn
Ê
UÊ Aj`Peia
The >ej]nu@]p] and Ejpacan@]p] data columns provide specific information about a given SQL Server activity. For example, in the case of a cursor, they specify the type of cur ÃÀÊÀiµÕiÃÌi`Ê>`ÊÌ
iÊÌÞ«iÊvÊVÕÀÃÀÊVÀi>Ìi`°ÊÌ
Õ}
ÊÌ
iÊ>iÃÊvÊÌ
iÃiÊ>``Ì>Ê`>Ì>Ê columns indicate their purpose to a great extent, I will explain the usefulness of these data columns in later chapters as you use them.
ÕÊ`>Ì>ÊV>ÊLiÊÀi>ÀÀ>}i`ÊÌÊ>iÊÌ
iÊÃVÀiiÊÀiÊ«i>Ã}]Ê>`ÊÌ
iÞÊV>ÊLiÊ grouped to provide aggregates of the data collected. To control the column data placement, VVÊÌ
iÊ"À}>âiÊ ÕÃÊLÕḬ̀Ê/
ÃÊ«iÃÊÌ
iÊ"À}>âiÊ ÕÃÊ`>}ÊLÝÊÃ
ÜÊÊ }ÕÀiÊÎΰ
Figure 3-3. Organize Columns dialog box 9ÕÊV>ÊV
>}iÊÌ
iÊ«ÃÌÊvÊ>ÊVÕÊLÞÊVV}ÊÌ
iÊ1«Ê>`Ê ÜÊLÕÌÌðÊÛ}Ê>Ê column into the Groups category means it will become an aggregation column.
Filters In addition to defining events and data columns for a Profiler trace, you can also define various filter criteria. These help keep the trace output small, which is usually a good idea. />LiÊÎ{Ê`iÃVÀLiÃÊÌ
iÊvÌiÀÊVÀÌiÀ>ÊÌ
>ÌÊÞÕÊÜÊVÞÊÕÃiÊ`ÕÀ}Ê«iÀvÀ>ViÊ analysis.
67
68
CHAPTER 3 N SQ L QU ER Y P ER FOR MA NC E A NA L YS IS
Table 3-4. SQL Trace Filters
Events
Filter Criteria Example
Use
=llhe_]pekjJ]ia
Jkphega6OMHLnkbehan
This filters out the events generated by Profiler. This is the default behavior.
@]p]^]oaE@
Amq]ho68E@kbpda`]p]^]oa This filters out events generated by a par ticular database. You can determine the pkikjepkn:
ÊvÊ>Ê`>Ì>L>ÃiÊvÀÊÌÃÊ>iÊ>ÃÊvÜÃ\Ê OAHA?P@>[E@$#Jknpdsej`#%.
@qn]pekj
Cna]panpd]jknamq]h6.
For performance analysis, you will often capture a trace for a large workload. In a large trace, there will be many event logs with a @qn]pekj that is less than what you’re interested in. Filter out these event logs, because there is hardly any scope for optimizing these SQL activities.
Na]`o
Cna]panpd]jknamq]h6.
This is similar to the criterion on the @qn]pekj data column.
OLE@
Amq]ho68@]p]^]oaqoano pkikjepkn:
This troubleshoots queries sent by a specific database user.
}ÕÀiÊÎ{ÊÃ
ÜÃÊ>Êë«iÌÊvÊÌ
iÊ«ÀiVi`}ÊvÌiÀÊVÀÌiÀ>ÊÃiiVÌÊÊ-+Ê*ÀviÀ°
NNote For a complete list of filters available in SQL Profiler, please refer to the MSDN article “Limiting Traces” (dppl6++io`j*ie_nkokbp*_ki+he^n]nu+`ab]qhp*]ol;qnh9+he^n]nu+aj)qo+]`iejomh+ ]`[ikj[lanb[2jt`*]ol).
Figure 3-4. Trace definition with filters
C H A P T E R 3 N S Q L Q U E R Y P E R F O R M A N C E A N A LY S I S
Trace Templates In the previous section, you learned how to define a new Profiler trace to monitor activities ÕÃ}Ê-+Ê-iÀÛiÀ°ÊÜiÛiÀ]ÊÃÌi>`ÊvÊ`iv}Ê>ÊiÜÊÌÀ>ViÊiÛiÀÞÊÌiÊÞÕÊÜ>ÌÊÌÊÕÃiÊi]Ê you can create a trace template with your customized events, data columns, and filters and then reuse the trace template to capture a trace. The procedure for defining a new trace tem plate is similar to that of defining a new trace: 1. Ài>ÌiÊ>ÊiÜÊÌÀ>Vi° 2. iviÊÌ
iÊiÛiÌÃ]Ê`>Ì>ÊVÕÃ]Ê>`ÊvÌiÀÃÊÌ
iÊÃ>iÊÜ>ÞÊ>ÃÊÃ
ÜÊi>ÀiÀ° 3. Save the trace definition as a trace template from the File ¢Ê->ÛiÊÃÊiÕÊV
Vi°Ê Profiler will automatically populate its Templates list with your new template.
Trace Data vÌiÀÊÞÕ½ÛiÊ`ivi`Ê>ÊÌÀ>Vi]ÊVV}ÊÌ
iÊ,ÕÊLÕÌÌÊÜÊLi}ÊV>«ÌÕÀ}ÊiÛiÌÃÊ>`Ê`ë>Þ }ÊÌ
iÊÊÞÕÀÊÃVÀii]Ê>ÃÊÃ
ÜÊÊ}ÕÀiÊÎx°Ê9ÕÊÜÊÃiiÊ>ÊÃiÀiÃÊvÊiÛiÌÃÊÃVÀ}ÊLÞ]Ê and you can watch your system perform in what I’ve always called SQL TV. You have control ÛiÀÊÌ
iÊÌÀ>ViÊÀiÊÀÊiÃÃÊiÊ>Ê 6 Ê«>ÞiÀ]ÊÃÊÞÕÊV>Ê«>ÕÃi]ÊÃÌ>ÀÌ]Ê>`ÊÃÌ«ÊÌ
iÊÌÀ>ViÊÃÀÀÞ]Ê Êv>ÃÌvÀÜ>À`®ÊÕÃ}ÊÌ
iÊLÕÌÌÃÊÊÌ
iÊÌL>À°Ê9ÕÊV>ÊiÛiÊ«>ÕÃiÊÌ
iÊÌÀ>ViÊ>`Ê>iÊ changes to it as you work.
Figure 3-5. Events and data captured within a trace Once you finish capturing your SQL Server activities, you can save the trace output to a trace file or a trace table. Trace output saved to a trace file is in a native format and can be opened by Profiler to analyze the SQL queries. Saving the trace output to a trace table allows the SQL queries in the trace output to be analyzed by Profiler as well as by OAHA?P statements on the trace table.
69
70
CHAPTER 3 N SQ L QU ER Y P ER FOR MA NC E A NA L YS IS
Profiler creates a trace table dynamically as per the definition of the data columns in the trace output. The ability to analyze the trace output using OAHA?P statements adds great flexibility to the analysis process. For example, if you want to find out the number of query iÝiVÕÌÃÊÜÌ
Ê>ÊÀiëÃiÊÌiÊvÊiÃÃÊÌ
>ÊxääÊÃ]ÊÞÕÊV>ÊiÝiVÕÌiÊ>ÊOAHA?P statement on the trace table as follows: OAHA?P?KQJP$&%BNKI8Pn]_aP]^ha: SDANA@qn]pekj:1,, 9ÕÊÜÊÊ>ÌÊÃiÊÃ>«iʵÕiÀiÃÊÊÌ
iʺ`iÌvÞ}Ê ÃÌÞÊ+ÕiÀiûÊÃiVÌÊ>ÌiÀÊÊ the chapter, but first I’ll describe how to automate Profiler and simultaneously reduce the amount of memory and network bandwidth that the Profiler process uses.
Trace Automation The Profiler GUI makes collecting Profiler trace information easy. Unfortunately, that ease comes at a cost. The events captured by the Profiler tool go into a cache in memory in order to feed across the network to the GUI. Your GUI is dependent on your network. Network traffic can slow things down and cause the cache to fill. This will, to a small degree, impact perfor mance on the server. Further, when the cache fills, the server will begin to drop events in À`iÀÊÌÊ>Û`ÊÃiÀÕÃÞÊ«>VÌ}ÊÃiÀÛiÀÊ«iÀvÀ>Vi°ÊÜÊ`ÊÞÕÊ>Û`ÊÌ
öÊ1ÃiÊÌ
iÊÃÞÃÌiÊ stored procedures to generate a scripted trace that outputs to a file.
Capturing a Trace Using the GUI You can create a scripted trace in one of two ways, manually or with the GUI. Until you get comfortable with all the requirements of the scripts, the easy way is to use the Profiler tool’s GUI. These are the steps you’ll need to perform: 1. iviÊ>ÊÌÀ>Vi° 2. VÊÌ
iÊiÕÊiÊ¢ Export ¢Ê-VÀ«ÌÊ/À>ViÊ iv̰ 3. 9ÕÊÕÃÌÊÃiiVÌÊÌ
iÊÌ>À}iÌÊÃiÀÛiÀÊÌÞ«i]ÊÊÌ
ÃÊÃÌ>Vi]ÊÀÊ-+Ê-iÀÛiÀÊÓääxÉÓään° 4. Give the file a name, and save it. These steps will generate all the script commands that you need to capture a trace and ÕÌ«ÕÌÊÌÊÌÊ>Êvi°Ê9ÕÊV>Ê>ÃÊÃVÀ«ÌÊÌÀ>ViÊiÛiÌÃÊÌ
>ÌÊÜÊLiÊÕÃi`ÊÜÌ
ÊÌ
iÊiÜÊÓäänÊ >Ì>Ê
iVÌÀÊÃiÀÛVi]ÊLÕÌÊÌ
>ÌÊvÕVÌ>ÌÞÊÃÊLiÞ`ÊÌ
iÊÃV«iÊvÊÌ
ÃÊL° To manually launch this new trace, use Management Studio as follows: 1. Open the file. 2. ,i«>ViÊÜ
iÀiÊÌÊÃ>ÞÃÊEjoanpBehaJ]iaDana with the appropriate name and path for your system. 3. Execute the script. It will return a single column result set with the Pn]_aE`. 9ÕÊ>ÞÊÜ>ÌÊÌÊ>ÕÌ>ÌiÊÌ
iÊiÝiVÕÌÊvÊÌ
ÃÊÃVÀ«ÌÊÌ
ÀÕ}
ÊÌ
iÊ-+Ê}iÌ]ÊÀÊÞÕÊV>Ê even run the script from the command line using the omh_i`*ata utility. Whatever method you use, the script will start the trace. If you have not defined a trace stop time, you will need to stop the trace manually using the Pn]_aE`. I’ll show how to do that in the next section.
C H A P T E R 3 N S Q L Q U E R Y P E R F O R M A N C E A N A LY S I S
Capturing a Trace Using Stored Procedures If you look at the scripts defined in the previous section, you will see a series of commands, called in a specific order: Ê
UÊ ol[pn]_a[_na]pa\Ê Ài>Ìi a trace definition.
Ê
UÊ ol[pn]_a[oaparajp\Ê``ÊiÛiÌÃÊ>`ÊiÛiÌÊVÕÃÊÌÊÌ
iÊÌÀ>Vi°
Ê
UÊ ol[pn]_a[oapbehpan\Ê««ÞÊvÌiÀÃÊÌÊÌ
iÊÌÀ>Vi°
Once the SQL trace has been defined, you can run the trace using the stored procedure ol[pn]_a[oapop]pqo. The tracing of SQL activities continues until the trace is stopped. Since the SQL tracing VÌÕiÃÊ>ÃÊ>ÊL>Vi`Ê«ÀViÃÃ]ÊÌ
iÊ>>}iiÌÊ-ÌÕ`ÊÃiÃÃÊii`ÊÌÊLiÊi«ÌÊ«i°Ê9ÕÊ can identify the running traces by using theÊ-+Ê-iÀÛiÀÊLÕÌÊvÕVÌÊbj[pn]_a[capejbk, as shown in the following query: OAHA?P&BNKI66bj[pn]_a[capejbk$`ab]qhp%7 }ÕÀiÊÎÈÊÃ
ÜÃÊÌ
iÊÕÌ«ÕÌÊvÊÌ
iÊvÕV̰
Figure 3-6. Output of bj[pn]_a[capejbk The number of unique pn]_ae`s in the output of the function bj[pn]_a[capejbk indicates the number of traces active on SQL Server. The data value of the column r]hqa for the property vÊxÊ`V>ÌiÃÊÜ
iÌ
iÀÊÌ
iÊÌÀ>ViÊÃÊÀÕ}ÊÛ>ÕiÊrÊ£®ÊÀÊÃÌ««i`ÊÛ>ÕiÊrÊä®°Ê9ÕÊV>ÊÃÌ«Ê>Ê specific trace, say pn]_ae`9-, by executing the stored procedure ol[pn]_a[oapop]pqo: ATA?ol[pn]_a[oapop]pqo-(,7 vÌiÀÊ>ÊÌÀ>ViÊÃÊÃÌ««i`]ÊÌÃÊ`ivÌÊÕÃÌÊLiÊVÃi`Ê>`Ê`iiÌi`ÊvÀÊÌ
iÊÃiÀÛiÀÊLÞÊ executing ol[pn]_a[oapop]pqo: ATA?ol[pn]_a[oapop]pqo-(.7 To verify that the trace is stopped successfully, reexecute the function bj[pn]_a[capejbk, and ensure that the output of the function doesn’t contain the pn]_ae`. The format of the trace file created by this technique is the same as that of the trace file created by Profiler. Therefore, this trace file can be analyzed in the same way as a trace file created by Profiler.
>«ÌÕÀ}Ê>Ê-+ÊÌÀ>ViÊÕÃ}ÊÃÌÀi`Ê«ÀVi`ÕÀiÃÊ>ÃÊÕÌi`ÊÊÌ
iÊ«ÀiÛÕÃÊÃiVÌÊ>Û`ÃÊ the overhead associated with the Profiler GUI. It also provides greater flexibility in managing Ì
iÊÌÀ>V}ÊÃV
i`ÕiÊvÊ>Ê-+ÊÌÀ>ViÊÌ
>ÊÃÊ«ÀÛ`i`ÊLÞÊÌ
iÊ*ÀviÀḚ̂ÊÊ
>«ÌiÀÊ£È]ÊÞÕÊÜÊ learn how to control the schedule of a SQL trace while capturing the activities of a SQL work load over an extended period of time.
71
72
CHAPTER 3 N SQ L QU ER Y P ER FOR MA NC E A NA L YS IS
NNote The time captured through a trace defined as illustrated in this section is stored in microseceonds, not milliseconds. This difference between units can cause confusion if not taken into account.
Combining Trace and Performance Monitor Output Ê
>«ÌiÀÊÓ]ÊÊÃ
Üi` how to capture Performance Monitor data to a file. In the preceding section, I showed how to capture Profiler data to a file as well. If you automate the collection of both of these sets of data so that they cover the same time periods, you can use them together inside the SQL Profiler GUI. Be sure that your trace has both the Op]npPeia and Aj`peia data fields. Follow these steps: 1. Open the trace file. 2. VÊÌ
iÊiʢʫÀÌÊ*iÀvÀ>ViÊ >Ì>° 3. Select the Performance Monitor file to be imported. *iÀvÀ}ÊÌ
iÃiÊ>VÌÃÊÜÊ«iÊÌ
iÊ`>}ÊLÝÊÃ
ÜÊÊ}ÕÀiÊÎÇ]ÊÜ
V
Ê>ÜÃÊÞÕÊ to pick Performance Monitor counters for inclusion.
Figure 3-7. Counters available for display in Profiler Once you’ve selected the counters you want to include, clicking OK will open the Profiler >`Ê*iÀvÀ>ViÊÌÀÊ`>Ì>ÊÌ}iÌ
iÀ]Ê>ÃÊÞÕÊV>ÊÃiiÊÊ}ÕÀiÊÎn°Ê ÜÊÞÕÊV>ÊLi}ÊÌÊ use the trace data and the Performance Monitor data together. If you select an event in the top
C H A P T E R 3 N S Q L Q U E R Y P E R F O R M A N C E A N A LY S I S
window, it will place a red line within the Performance Monitor data, showing where the event VVÕÀÀi`ÊÜÌ
ÊÌ
iÊÌiÊvÀ>iÊvÊÌ
>ÌÊ`>Ì>°Ê ÛiÀÃiÞ]ÊÞÕÊV>ÊVVÊÜÌ
ÊÌ
iÊ*iÀvÀ>ViÊ Monitor data, and the event that represents that time frame will be selected. These capabilities work so well together that you’ll be using them regularly during tuning sessions to identify the bottlenecks and stress points and to determine which specific queries are causing them.
Figure 3-8. Profiler data next to Performance Monitor data
SQL Profiler Recommendations You have already seen how to set some filters on a trace to help minimize the impact of Pro filer on system resources. To further minimize the impact, consider the following suggestions: Ê
UÊ ÌÊÌ
iÊÕLiÀÊvÊiÛiÌÃÊ>`Ê`>Ì>ÊVÕð
Ê
UÊ ÃV>À`ÊÃÌ>ÀÌÊiÛiÌÃÊvÀÊ«iÀvÀ>ViÊ>>ÞÃð
Ê
UÊ ÌÊÌÀ>ViÊÕÌ«ÕÌÊÃâi°
Ê
UÊ Û`ÊiÊ`>Ì>ÊVÕÊÃÀÌ}°
Ê
UÊ ,ÕÊ*ÀviÀÊÀiÌiÞ° In the following sections, I cover each of these suggestions in more depth.
Limiting the Number of Events and Data Columns While tracing SQL queries, you can decide which SQL activities should be captured by filtering iÛiÌÃÊ>`Ê`>Ì>ÊVÕðÊ
Ã}ÊiÝÌÀ>ÊiÛiÌÃÊVÌÀLÕÌiÃÊÌÊÌ
iÊLÕÊvÊÌÀ>V}ÊÛiÀ
i>`°Ê >Ì>ÊVÕÃÊ`ÊÌÊ>``ÊÕV
ÊÛiÀ
i>`]ÊÃViÊÌ
iÞÊ>ÀiÊÞÊ>ÌÌÀLÕÌiÃÊvÊ>ÊiÛiÌÊV>ÃÃ°Ê Therefore, it is extremely important to know why you want to trace each event selected and to select your events based only on necessity.
73
74
CHAPTER 3 N SQ L QU ER Y P ER FOR MA NC E A NA L YS IS
Minimizing the number of events to be captured prevents SQL Server from wasting the L>`Ü`Ì
ÊvÊÛ>Õ>LiÊÀiÃÕÀViÃÊ}iiÀ>Ì}Ê>ÊÌ
ÃiÊiÛiÌÃ°Ê >«ÌÕÀ}ÊiÛiÌÃÊÃÕV
Ê>ÃÊVÃÊ and execution plans should be done with caution, because these events make the trace output very large and degrade SQL Server’s performance. It’s important to reduce the number of events while analyzing a production server, since you don’t want the profiler to add a large amount of load to the production server. On a test server, the amount of load contributed by Profiler is a lesser consideration than the ability to analyze every activity on SQL Server. Therefore, on a test server, you need not compromise so much with the information you might be interested in. Some events come with added cost. I mentioned earlier in the chapter that the statement completion events can be costly, but other events could negatively impact your system while you’re attempting to capture information about that system.
FILTERING STAGES There are two stages of filtering: prefiltering, which is performed by SQL Server, and postfiltering, which is performed by the user. Prefiltering is the online stage of capturing the SQL Server activities. Prefiltering offers several advantages: s )T REDUCES THE IMPACT ON THE PERFORMANCE OF 31, 3ERVER SINCE A LIMITED NUMBER OF EVENTS ARE GENERated. s )T REDUCES TRACE OUTPUT SIZE s )T SIMPLIFIES POSTFILTERING OPERATIONS because fewer events are being captured in the first place. The only disadvantage to prefiltering is that you may miss some vital information that might be required for thorough analysis. As with many things, choosing when and how much to prefilter involves making good judgment calls. Postfiltering IS THE STAGE WHERE YOU ANALYZE THE CAPTURED TRACE FILE )T IS AN OFFLINE STAGE INCURRING NO overhead on SQL Server. Once you filter an existing trace file, you can save the postfiltered information to a new trace file. For example, while identifying the problematic queries, you might be interested in queries with an execution time greater than 500 ms. To identify such queries, you can apply a postfilter on the trace output for a @qn]pekj greater than or equal to 500 and save this filtered trace output as a separate trace file. This means that the next time you need to look at the same criteria, you do not have to load the original trace file and do filtering again; you can just use the new trace file.
Discarding Start Events for Performance Analysis The information you want for performance analysis revolves around the resource cost of a query. Start events, such as OL6OpipOp]npejc, do not provide this information, because it is ÞÊ>vÌiÀÊ>ÊiÛiÌÊV«iÌiÃÊÌ
>ÌÊÞÕÊV>ÊV«ÕÌiÊÌ
iÊ>ÕÌÊvÊÉ"]ÊÌ
iÊ *1Ê>`]Ê>`ÊÌ
iÊ `ÕÀ>ÌÊvÊÌ
iʵÕiÀÞ°Ê/
iÀivÀi]ÊÜ
iÊÌÀ>V}ÊÃÜÀÕ}ʵÕiÀiÃÊvÀÊ«iÀvÀ>ViÊ>>Þ sis, you need not capture the start events. This information is provided by the corresponding completion events. -]ÊÜ
iÊÃ
Õ`ÊÞÕÊV>«ÌÕÀiÊÃÌ>ÀÌÊiÛiÌöÊ7i]ÊÞÕÊÃ
Õ`ÊV>«ÌÕÀiÊÃÌ>ÀÌÊiÛiÌÃÊÜ
iÊÞÕÊ don’t expect some SQL queries to finish execution because of error conditions or when you
C H A P T E R 3 N S Q L Q U E R Y P E R F O R M A N C E A N A LY S I S
find frequent =ppajpekjÊiÛiÌðÊÊ=ppajpekj event usually indicates that the user cancelled the query midway or the query timeout expired, probably because the query was running for a long time.
Limiting the Trace Output Size Besides prefiltering events and data columns, other filtering criteria limit the trace output size. }>]ÊÌ}ÊÃâiÊ>ÞÊV>ÕÃiÊÞÕÊÌÊÃÃÊÌiÀiÃÌ}ÊiÛiÌÃÊvÊÞÕ½ÀiÊ}Ê>ÌÊÛiÀ>ÊÃÞÃÌiÊ behavior. But if you’re focusing on costly queries, a filter helps. From the Edit Filter dialog box, >VViÃÃi`ÊLÞÊVV}ÊÌ
iÊ ÕÊÌiÀÃÊLÕÌÌÊÊÌ
iÊ ÛiÌÃÊ-iiVÌÊÌ>L]ÊVÃ`iÀÊÌ
iÊv lowing settings: Ê
UÊ Duration – Greater than or equal: 2: SQL queries with a @qn]pekjÊiµÕ>ÊÌÊäÊÀÊ£ÊÃÊ cannot be further optimized.
Ê
UÊ Reads – Greater than or equal: 2: SQL queries with number of logical reads equal to äÊÀÊ£ÊÃÊV>ÌÊLiÊvÕÀÌ
iÀÊ«Ìâi`°
Avoiding Online Data Column Sorting ÕÀ}Ê«iÀvÀ>ViÊ>>ÞÃÃ]ÊÞÕÊÕÃÕ>ÞÊÃÀÌÊ>ÊÌÀ>ViÊÕÌ«ÕÌÊÊ`vviÀiÌÊ`>Ì>ÊVÕÃÊÃÕV
Ê as @qn]pekj, ?LQ, and Na]`o®ÊÌÊ`iÌvÞʵÕiÀiÃÊÜÌ
ÊÌ
iÊ>À}iÃÌÊVÀÀië`}Êv}ÕÀiðÊvÊÞÕÊ sort offline, you reduce the activities Profiler has to perform while interacting with SQL Server. This is how to sort a captured SQL trace output: 1. >«ÌÕÀiÊÌ
iÊÌÀ>ViÊÜÌ
ÕÌÊ>ÞÊÃÀÌ}ÊÀÊ}ÀÕ«}®° 2. Save the trace output to a trace file. 3. "«iÊÌ
iÊÌÀ>ViÊviÊ>`ÊÃÀÌÊÀÊ}ÀÕ«®ÊÌ
iÊÌÀ>ViÊviÊÕÌ«ÕÌÊÊëiVvVÊ`>Ì>ÊVÕÃÊ as required.
Running Profiler Remotely It is usually not a good practice to run test tools directly on the production server. Profiler has a heavy user interface; therefore, it is better to run it on another machine. Similar to System ÌÀ]Ê*ÀviÀÊÃ
Õ`ÊÌÊLiÊÀÕÊÌ
ÀÕ}
Ê>ÊÌiÀ>ÊÃiÀÛViÊÃiÃÃ]ÊLiV>ÕÃiÊ>Ê>ÀÊ«>ÀÌÊ of the tool still runs on the server. When collecting a trace output directly to a file, save the file V>ÞÊÜ
iÀiÊ*ÀviÀÊÃÊLi}ÊÀÕ°Ê/
ÃÊÃÊÃÌÊ>ÊÀiÊÀiÃÕÀViÌiÃÛiÊ«iÀ>ÌÊÌ
>ÊÀÕ }Ê*ÀviÀÊÌ
ÀÕ}
ÊÌ
iÊÃÞÃÌiÊÃÌÀi`Ê«ÀVi`ÕÀiÃÊ>ÃÊ>ÊÃiÀÛiÀÃ`iÊÌÀ>Vi°Ê/
>ÌÊÀi>ÃÊÌ
iÊ best option.
Limiting the Use of Certain Events SomeÊiÛiÌÃÊ>ÀiÊÀiÊVÃÌÞÊÌ
>ÊÌ
iÀðÊÃÊÊiÌi`Ê«ÀiÛÕÃÞ]Ê`i«i`}ÊÊÌ
iÊ>ÌÕÀiÊ of the queries being generated, the statement completion events can be very costly. Other iÛiÌÃÊÌÊÕÃiÊÕ`VÕÃÞ]ÊiëiV>ÞÊÊ>ÊÃÞÃÌiÊÌ
>̽ÃÊ>Ài>`ÞÊiÝ«iÀiV}ÊÃÌÀiÃÃ]Ê>ÀiÊÌ
iÊ Showplan XML events Lanbkni]j_a6Odkslh]jTIH, Lanbkni]j_a6Odkslh]jTIHbknMqanu ?kileha, and Lanbkni]j_a6Odkslh]jTIHOp]peope_oLnkbeha°ÊÌ
Õ}
ÊÌ
iÃiÊiÛiÌÃÊV>ÊLiÊ useful, keep them off the production machine.
75
76
CHAPTER 3 N SQ L QU ER Y P ER FOR MA NC E A NA L YS IS
Query Performance Metrics Without Profiler Setting up a trace allows you to collect a lot of data for later use, but the collection can be somewhat expensive, and you have to wait on the results. If you need to immediately capture performance metrics about your system, especially as they pertain to query performance, then the dynamic management view ouo*`i[ata_[mqanu[op]po is what you need. If you still need a historical tracking of when queries were run and their individual costs, a trace is still the bet ÌiÀÊÌ°Ê ÕÌÊvÊÞÕÊÕÃÌÊii`ÊÌÊÜ]Ê>ÌÊÌ
ÃÊiÌ]ÊÌ
iÊ}iÃÌÀÕ}ʵÕiÀiÃÊÀÊÌ
iÊÃÌÊ physical reads, then you can get that information from ouo*`i[ata_[mqanu[op]po. Since ouo*`i[ata_[mqanu[op]poÊÃÊÕÃÌÊ>ÊÛiÜ]ÊÞÕÊV>ÊëÞʵÕiÀÞÊ>}>ÃÌÊÌÊ>`Ê}iÌÊ vÀ>ÌÊ>LÕÌÊÌ
iÊÃÌ>ÌÃÌVÃÊvʵÕiÀÞÊ«>ÃÊÊÌ
iÊÃiÀÛiÀ°Ê/>LiÊÎxÊÃ
ÜÃÊÃiÊvÊÌ
iÊ`>Ì>Ê returned from the query. Table 3-5. ouo*`i[ata_[mqanu[op]po Output
Column
Description
Lh]j[d]j`ha
Pointer that refers to the execution plan
?na]pekj[peia
Time that the plan was created
H]op[ata_qpekjpeia
Last time the plan was used by a query
Ata_qpekj[_kqjp
Number of times the plan has been used
Pkp]h[skngan[peia
/Ì>Ê *1ÊÌiÊÕÃi`ÊLÞÊÌ
iÊ«>ÊÃViÊÌÊÜ>ÃÊVÀi>Ìi`
Pkp]h[hkce_]h[na]`o
Total number of reads used since the plan was created
Pkp]h[hkce_]h[snepao
Total number of writes used since the plan was created
Mqanu[d]od
binary hash that can be used to identify queries with similar logic
Mqanu[lh]j[d]od
ÊL>ÀÞÊ
>Ã
ÊÌ
>ÌÊV>ÊLiÊÕÃi`ÊÌÊ`iÌvÞÊ«>ÃÊÜÌ
ÊÃ>ÀÊ}V
/>LiÊÎxÊÃÊÕÃÌÊ>ÊÃ>«}°ÊÀÊV«iÌiÊ`iÌ>Ã]ÊÃiiÊ ÃÊ"i° To filter the information returned from ouo*`i[ata_[mqanu[op]po]ÊÞÕ½Êii`ÊÌÊÊÌÊ with other dynamic management functions such as ouo*`i[ata_[omh[patp, which shows the query text associated with the plan, or ouo*`i[mqanu[lh]j, which has the execution plan for Ì
iʵÕiÀÞ°Ê"ViÊi`ÊÌÊÌ
iÃiÊÌ
iÀÊ Ã]ÊÞÕÊV>ÊÌÊÌ
iÊ`>Ì>L>ÃiÊÀÊ«ÀVi`ÕÀiÊÌ
>ÌÊÞÕÊ Ü>ÌÊÌÊvÌiÀ°Ê/
iÃiÊÌ
iÀÊ ÃÊ>ÀiÊVÛiÀi`Ê detail in other chapters of the book.
Costly Queries Now that you have seen what you need to consider when using the Profiler tool, let’s look at what the data represents: the costly queries themselves. When the performance of SQL Server goes bad, two things are most likely happening: Ê
UÊ ÀÃÌ]ÊViÀÌ>ʵÕiÀiÃÊVÀi>ÌiÊ
}
ÊÃÌÀiÃÃÊÊsystem resources. These queries affect the performance of the overall system, because the server becomes incapable of serving other SQL queries fast enough.
Ê
UÊ ``Ì>Þ]ÊÌ
iÊVÃÌÞʵÕiÀiÃÊLVÊ>ÊÌ
iÀʵÕiÀiÃÊÀiµÕiÃÌ}ÊÌ
iÊÃ>iÊ`>Ì>L>ÃiÊ resources, further degrading the performance of those queries. Optimizing the costly queries improves not only their own performance but also the performance of other queries by reducing database blocking and pressure on SQL Server resources.
C H A P T E R 3 N S Q L Q U E R Y P E R F O R M A N C E A N A LY S I S
You can use Profiler to capture the SQL Server workload, as explained previously in this V
>«ÌiÀ°Ê iviÊ>ÊÌÀ>Vi°Ê/
iÊÀÕÊÌ
iʵÕiÀiÃÊÊpn]_a[mqaneao*omh°Ê}ÕÀiÊÎÊÃ
ÜÃÊ>ÊÃ>«iÊ trace output. On a live production server, the trace output may be quite large; the solution is ÌÊÕÃiÊvÌiÀÊVÀÌiÀ>]Ê>ÃÊiÝ«>i`ÊÊÌ
iÊi>ÀiÀʺÌiÀûÊÃiVÌ]ÊÌÊÌÊÌ
iÊÃâiÊvÊÌ
iÊÌÀ>ViÊ output.
Figure 3-9. A sample trace output Once you have captured the set of SQL queries representing a complete workload, you should analyze the trace to identify two sets of queries: Ê
UÊ ÃÌÞʵÕiÀiÃÊÌ
>ÌÊ>ÀiÊV>ÕÃ}Ê>Ê}Ài>ÌÊ`i>ÊvÊ«ÀiÃÃÕÀiÊÊÌ
iÊÃÞÃÌiÊÀiÃÕÀViÃ
Ê
UÊ +ÕiÀiÃÊÌ
>ÌÊ>ÀiÊÃÜi`Ê`ÜÊÌ
iÊÃÌ
Identifying Costly Queries The goal of SQL Server is to return result sets to the user in the shortest time. To do this, SQL -iÀÛiÀÊ
>ÃÊ>ÊLÕÌ]ÊVÃÌL>Ãi`Ê«ÌâiÀÊV>i`ÊÌ
iÊquery optimizer, whichÊ}iiÀ>ÌiÃÊ>ÊVÃÌ effective strategy called a query execution plan. The query optimizer weighs many factors, VÕ`}ÊLÕÌÊÌÊÌi`ÊÌ®ÊÌ
iÊÕÃ>}iÊvÊ *1]ÊiÀÞ]Ê>`Ê`ÃÊÉ"ÊÀiµÕÀi`ÊÌÊiÝiVÕÌiÊ a query, all derived from the statistics maintained by indexes or generated on the fly, and it Ì
iÊVÀi>ÌiÃÊ>ÊVÃÌivviVÌÛiÊiÝiVÕÌÊ«>°ÊÌ
Õ}
Êâ}ÊÌ
iÊÕLiÀÊvÊÉ"ÃÊÃÊÌÊ>Ê ÀiµÕÀiiÌÊvÀÊ>ÊVÃÌivviVÌÛiÊ«>]ÊÞÕÊÜÊvÌiÊv`ÊÌ
>ÌÊÌ
iÊi>ÃÌÊVÃÌÞÊ«>Ê
>ÃÊÌ
iÊviÜ iÃÌÊÉ"ÃÊLiV>ÕÃiÊÉ"Ê«iÀ>ÌÃÊ>ÀiÊiÝ«iÃÛi°
77
78
CHAPTER 3 N SQ L QU ER Y P ER FOR MA NC E A NA L YS IS
In the data returned from a trace, the ?LQ and Na]`o columns also show where a query costs you. The ?LQÊVÕÊÀi«ÀiÃiÌÃÊÌ
iÊ *1ÊÌiÊÕÃi`ÊÌÊiÝiVÕÌiÊÌ
iʵÕiÀÞ°Ê/
iÊNa]`o col umnÊÀi«ÀiÃiÌÃÊÌ
iÊÕLiÀÊvÊ}V>Ê«>}iÃÊn ÊÊÃâi®Ê>ʵÕiÀÞÊ«iÀ>Ìi`ÊÊ>`ÊÌ
iÀiLÞÊ indicates the amount of memory stress caused by the query. It also provides an indication of disk stress, since memory pages have to be backed up in the case of action queries, populated `ÕÀ}ÊvÀÃÌÌiÊ`>Ì>Ê>VViÃÃ]Ê>`Ê`ë>Vi`ÊÌÊ`ÃÊ`ÕÀ}ÊiÀÞÊLÌÌiiVðÊ/
iÊ
}
iÀÊ Ì
iÊÕLiÀÊvÊ}V>ÊÀi>`ÃÊvÀÊ>ʵÕiÀÞ]ÊÌ
iÊ
}
iÀÊÌ
iÊ«ÃÃLiÊÃÌÀiÃÃÊÊÌ
iÊ`ÃÊVÕ`ÊLi°ÊÊ iÝViÃÃÛiÊÕLiÀÊvÊ}V>Ê«>}iÃÊ>ÃÊVÀi>ÃiÃÊ>`ÊÊÌ
iÊ *1ÊÊ>>}}ÊÌ
ÃiÊ«>}ið The queries that cause a large number of logical reads usually acquire locks on a corre ë`}ÞÊ>À}iÊÃiÌÊvÊ`>Ì>°Ê ÛiÊÀi>`}Ê>ÃÊ««Ãi`ÊÌÊÜÀÌ}®ÊÀiµÕÀiÃÊÃ
>ÀiÊVÃÊÊ>Ê Ì
iÊ`>Ì>°Ê/
iÃiʵÕiÀiÃÊLVÊ>ÊÌ
iÀʵÕiÀiÃÊÀiµÕiÃÌ}ÊÌ
ÃÊ`>Ì>ÊÀÊ>Ê«>ÀÌÊvÊÌ
iÊ`>Ì>®ÊvÀÊ the purposes of modifying it, not for reading it. Since these queries are inherently costly and require a long time to execute, they block other queries for an extended period of time. The blocked queries then cause blocks on further queries, introducing a chain of blocking in the `>Ì>L>Ãi°Ê
>«ÌiÀÊ£ÓÊVÛiÀÃÊVÊ`ið® ÃÊ>ÊÀiÃÕÌ]ÊÌÊ>iÃÊÃiÃiÊÌÊ`iÌvÞÊÌ
iÊVÃÌÞʵÕiÀiÃÊ>`Ê«ÌâiÊÌ
iÊvÀÃÌ]ÊÌ
iÀiLÞÊ doing the following: Ê
UÊ «ÀÛ}ÊÌ
iÊ«iÀvÀ>ViÊvÊÌ
iÊVÃÌÞʵÕiÀiÃÊÌ
iÃiÛiÃ
Ê
UÊ ,i`ÕV}ÊÌ
iÊÛiÀ>ÊÃÌÀiÃÃÊÊÃÞÃÌiÊÀiÃÕÀViÃ
Ê
UÊ ,i`ÕV}Ê`>Ì>L>ÃiÊLV} The costly queries can be categorized into the following two types:
Ê
UÊ Single execution\ÊÊ`Û`Õ>ÊiÝiVÕÌÊvÊÌ
iʵÕiÀÞÊÃÊVÃÌÞ°
Ê
UÊ Multiple executions\ÊʵÕiÀÞÊÌÃivÊ>ÞÊÌÊLiÊVÃÌÞ]ÊLÕÌÊÌ
iÊÀi«i>Ìi`ÊiÝiVÕÌÊvÊÌ
iÊ query causes pressure on the system resources.
You can identify these two types of costly queries using different approaches, as explained in the following sections.
Costly Queries with a Single Execution You can identify the costly queries by analyzing a SQL Profiler trace output file or by querying ouo*`i[ata_[mqanu[op]po. Since you are interested in identifying queries that perform a large number of logical reads, you should sort the trace output on the Na]`o data column. You can access the trace information by following these steps: 1. >«ÌÕÀiÊ>ÊProfiler trace that represents a typical workload. 2. Save the trace output to a trace file. 3. Open the trace file for analysis. 4. Open the Properties window for the trace, and click the Events Selection tab. 5. "«iÊÌ
iÊ"À}>âiÊ ÕÃÊÜ`ÜÊLÞÊVV}ÊÌ
>ÌÊLÕḬ̀ 6. Group the trace output on the Na]`o column. To do this, move the Na]`o column under Ì
iÊÀÕ«ÃÊÃiVÌ]Ê`iÃVÀLi`Êi>ÀiÀÊÊÌ
ÃÊV
>«ÌiÀ]Ê>ÃÊÃ
ÜÊÊ}ÕÀiÊÎ£ä° 7. You can work with the trace grouped, or you can look at it sorted by changing the `ë>ÞÊÕÃ}Ê ÌÀ³ °
C H A P T E R 3 N S Q L Q U E R Y P E R F O R M A N C E A N A LY S I S
Figure 3-10. Trace definition sorted on the Na]`o column This process will group the trace output on the Na]`oÊVÕ]Ê>ÃÊÃ
ÜÊÊ}ÕÀiÊΣ£°Ê The trace output is sorted on Na]`o in ascending order. From there, you can select a few of the costliest queries and analyze and optimize them appropriately. ÊÃiÊV>ÃiÃ]ÊÞÕÊ>ÞÊ
>ÛiÊ`iÌvi`Ê>Ê>À}iÊÃÌÀiÃÃÊÊÌ
iÊ *1ÊvÀÊÌ
iÊ-ÞÃÌiÊÌÀÊ ÕÌ«Õ̰Ê/
iÊ«ÀiÃÃÕÀiÊÊÌ
iÊ *1Ê>ÞÊLiÊLiV>ÕÃiÊvÊ>Ê>À}iÊÕLiÀÊvÊ *1ÌiÃÛiÊ«iÀ> ÌÃ]ÊÃÕV
Ê>ÃÊÃÌÀi`Ê«ÀVi`ÕÀiÊÀiV«>ÌÃ]Ê>}}Ài}>ÌiÊvÕVÌÃ]Ê`>Ì>ÊÃÀÌ}]Ê
>Ã
ÊÃ]Ê and so on. In such cases, you should sort the Profiler trace output on the ?LQ column to iden tify the queries taking up a large number of processor cycles.
Figure 3-11. Trace output sorted on the Na]`o column
79
80
CHAPTER 3 N SQ L QU ER Y P ER FOR MA NC E A NA L YS IS
Costly Queries with Multiple Executions à I mentioned earlier, sometimes a query may not be costly by itself, but the cumulative effect of multiple executions of the same query might put pressure on the system resources. Sort ing on the Na]`o column won’t help you identify this type of costly query. You instead want to know the total number of reads performed by multiple executions of the query. Unfortunately, Profiler doesn’t help here directly, but you can still get this information in the following ways: Ê
UÊ ÀÕ«ÊÌ
iÊÌÀ>ViÊÕÌ«ÕÌÊÊ*ÀviÀÊÊÌ
iÊvÜ}ÊVÕÃ\ÊArajp?h]oo, Patp@]p], and Na]`o. For the group of rows with the same Arajp?h]oo and Patp@]p], manually calculate the total of all the corresponding Na]`o. This approach doesn’t sound very user friendly!
Ê
UÊ ->ÛiÊÌ
iÊÌÀ>ViÊÕÌ«ÕÌÊÌÊ>ÊÌÀ>ViÊÌ>LiÊLÞÊÃiiVÌ}ÊiÊ¢Ê->ÛiÊÃÊ¢ Trace Table in *ÀviÀ°ÊÃ]ÊÞÕÊV>Ê«ÀÌÊÌ
iÊÌÀ>ViÊviÊÕÌ«ÕÌÊvÊ*ÀviÀÊÌÊ>ÊÌÀ>ViÊÌ>LiÊLÞÊÕÃ}Ê Ì
iÊLÕÌÊvÕVÌÊbj[pn]_a[capp]^ha.
Ê
UÊ VViÃÃÊÌ
iÊouo*`i[ata_[mqanu[op]poÊ 6ÊÌ retrieve the information from the produc tion server. This assumes that you’re dealing with an immediate issue and not looking at a historical problem.
In this case, I’ll load the data into a table on the database so that I can run queries against it using the following script: OAHA?P&EJPKPn]_a[P]^ha BNKI66bj[pn]_a[capp]^ha$#?6XLanbkni]j_aPn]_a*pn_#(`ab]qhp% Once the SQL trace is imported into a database table, execute a OAHA?P statement to find the total number of reads performed by the multiple executions of the same query as follows na]`o*omhÊÊÌ
iÊ`Ü>`®\ OAHA?P?KQJP$&%=OPkp]hAta_qpekjo(Arajp?h]oo(Patp@]p] (OQI$@qn]pekj%=O@qn]pekj[Pkp]h (OQI$?LQ%=O?LQ[Pkp]h (OQI$Na]`o%=ONa]`o[Pkp]h (OQI$Snepao%=OSnepao[Pkp]h BNKIPn]_a[P]^ha CNKQL>UArajp?h]oo(Patp@]p] KN@AN>UNa]`o[Pkp]h@AO? The Pkp]hAta_qpekjo column in the preceding script indicates the number of times a query was executed. The Na]`o[Pkp]h column indicates the total number of reads performed by the multiple executions of the query. ÜiÛiÀ]ÊÌ
iÀiÊÃÊ>ÊÌÌiÊ«ÀLi°Ê/
iÊ`>Ì>ÊÌÞ«iÊvÊÌ
iÊPatp@]p] column for the trace table created by Profiler is JPATP, which can’t be specified in the CNKQL>U clause—SQL Server 2008 doesn’t support grouping on a column with the JPATP data type. Therefore, you may create a table similar to the trace table, with the only exception being that the data type of the Patp@]p] column should be JR=N?D=N$I=T% instead of JPATP. Using the I=T length for the JR=N?D=N data type allows you not to worry about how long the JPATP data is.
C H A P T E R 3 N S Q L Q U E R Y P E R F O R M A N C E A N A LY S I S
Ì
iÀÊ>««À>V
ÊÃÊÌÊÕÃiÊÌ
iÊ?=OP function as follows: OAHA?P?KQJP$&%=OPkp]hAta_qpekjo(Arajp?h]oo (?=OP$Patp@]p]=OJR=N?D=N$I=T%%Patp@]p] (OQI$@qn]pekj%=O@qn]pekj[Pkp]h (OQI$?LQ%=O?LQ[Pkp]h (OQI$Na]`o%=ONa]`o[Pkp]h (OQI$Snepao%=OSnepao[Pkp]h BNKIPn]_a[P]^ha CNKQL>UArajp?h]oo(?=OP$Patp@]p]=OJR=N?D=N$I=T%% KN@AN>UNa]`o[Pkp]h@AO? The costly queries identified by this approach are a better indication of load than the costly queries with single execution identified by Profiler. For example, a query that requires xäÊÀi>`ÃÊ}
ÌÊLiÊiÝiVÕÌi`Ê£]äääÊÌiðÊ/
iʵÕiÀÞÊÌÃivÊ>ÞÊLiÊVÃ`iÀi`ÊV
i>«ÊiÕ}
]ÊLÕÌÊ Ì
iÊÌÌ>ÊÕLiÀÊvÊÀi>`ÃÊ«iÀvÀi`ÊLÞÊÌ
iʵÕiÀÞÊÌÕÀÃÊÕÌÊÌÊLiÊxä]äääÊrÊxäÊÊ£]äää®]ÊÜ
V
Ê V>ÌÊLiÊVÃ`iÀi`ÊV
i>«°Ê"«Ìâ}ÊÌ
ÃʵÕiÀÞÊÌÊÀi`ÕViÊÌ
iÊÀi>`ÃÊLÞÊiÛiÊ£äÊvÀÊ`Û` Õ>ÊiÝiVÕÌÊÀi`ÕViÃÊÌ
iÊÌÌ>ÊÕLiÀÊvÊÀi>`ÃÊLÞÊ£ä]äääÊrÊ£äÊÊ£]äää®]ÊÜ
V
ÊV>ÊLiÊÀiÊ LiivV>ÊÌ
>Ê«Ìâ}Ê>ÊÃ}iʵÕiÀÞÊÜÌ
Êx]äääÊÀi>`ð The problem with this approach is that most queries will have a varying set of criteria in the SDANA clause or that procedure calls will have different values and numbers of parameters passed in. That makes the simple grouping by Patp@]p] impossible. You can take care of this problem with a number of approaches. One of the better ones is outlined on the Microsoft iÛi«iÀÃÊ iÌÜÀÊ>ÌÊdppl6++io`j*ie_nkokbp*_ki+aj)qo+he^n]nu+]]-314,,$OMH*4,%*]olt. Ì
Õ}
ÊÌÊÜ>ÃÊÜÀÌÌiÊÀ}>ÞÊvÀÊ-+Ê-iÀÛiÀÊÓäää]ÊÌÊÜÊÜÀÊviÊÜÌ
Ê-+Ê-iÀÛiÀÊÓään° Getting the same information out of the ouo*`i[ata_[mqanu[op]po view simply requires a µÕiÀÞÊ>}>ÃÌÊÌ
iÊ 6\ OAHA?Poo*oqi[ata_qpekj[_kqjp (p*PATP (oo*oqi[pkp]h[ah]loa`[peia (oo*oqi[pkp]h[skngan[peia (oo*oqi[pkp]h[hkce_]h[na]`o (oo*oqi[pkp]h[hkce_]h[snepao BNKI$OAHA?Po*lh]j[d]j`ha (OQI$o*ata_qpekj[_kqjp%oqi[ata_qpekj[_kqjp (OQI$o*pkp]h[ah]loa`[peia%oqi[pkp]h[ah]loa`[peia (OQI$o*pkp]h[skngan[peia%oqi[pkp]h[skngan[peia (OQI$o*pkp]h[hkce_]h[na]`o%oqi[pkp]h[hkce_]h[na]`o (OQI$o*pkp]h[hkce_]h[snepao%oqi[pkp]h[hkce_]h[snepao BNKIouo*`i[ata_[mqanu[op]poo CNKQL>Uo*lh]j[d]j`ha %=Ooo ?NKOO=LLHUouo*`i[ata_[omh[patp$oo*lh]j[d]j`ha%p KN@AN>Uoqi[pkp]h[hkce_]h[na]`o@AO? This is so much easier than all the work required to gather trace data that it makes you wonder why you would ever use trace data. The main reason is precision. The ouo*`i[ata_[ mqanu[op]po view is a running aggregate for the time that a given plan has been in memory.
81
82
CHAPTER 3 N SQ L QU ER Y P ER FOR MA NC E A NA L YS IS
ÊÌÀ>Vi]ÊÊÌ
iÊÌ
iÀÊ
>`]ÊÃÊ>Ê
ÃÌÀV>ÊÌÀ>VÊvÀÊÜ
>ÌiÛiÀÊÌiÊvÀ>iÊÞÕÊÀ>ÊÌʰÊ9ÕÊV>Ê even add traces together within a database and have a list of data that you can generate totals in a more precise manner rather than simply relying on a given moment in time. But under stand that a lot of troubleshooting of performance problems is focused on that moment in time when the query is running slowly. That’s when ouo*`i[ata_[mqanu[op]po becomes irre placeably useful.
Identifying Slow-Running Queries Because a user’s experience is highly influenced by the response time of their requests, you should regularly monitor the execution time of incoming SQL queries and find out the ÀiëÃiÊÌiÊvÊÃÜÀÕ}ʵÕiÀiðÊvÊÌ
iÊÀiëÃiÊÌiÊÀÊ`ÕÀ>Ì®ÊvÊÃÜÀÕ}Ê queries becomes unacceptable, then you should analyze the cause of performance degrada Ì°Ê ÌÊiÛiÀÞÊÃÜ«iÀvÀ}ʵÕiÀÞÊÃÊV>ÕÃi`ÊLÞÊÀiÃÕÀViÊÃÃÕiÃ]ÊÌ
Õ}
°Ê"Ì
iÀÊVViÀÃÊ such as blocking can also lead to slow query performance. Blocking is covered in detail in
>«ÌiÀÊ£Ó° /Ê`ÃVÛiÀÊÌ
iÊÃÜÀÕ}Ê-+ʵÕiÀiÃ]Ê}ÀÕ«Ê>ÊÌÀ>ViÊÕÌ«ÕÌÊÊÌ
iÊ@qn]pekj column. This willÊÃÀÌÊÌ
iÊÌÀ>ViÊÕÌ«ÕÌ]Ê>ÃÊÃ
ÜÊÊ}ÕÀiÊΣӰ
Figure 3-12. Trace output sorted on the @qn]pekj column ÀÊ>ÊÃÜÀÕ}ÊÃÞÃÌi]ÊÞÕÊÃ
Õ`ÊÌiÊÌ
iÊ`ÕÀ>ÌÊvÊÃÜÀÕ}ʵÕiÀiÃÊLivÀiÊ >`Ê>vÌiÀÊÌ
iÊ«Ìâ>ÌÊ«ÀViÃðÊvÌiÀÊÞÕÊ>««ÞÊ«Ìâ>ÌÊÌiV
µÕiÃ]ÊÞÕÊÃ
Õ`ÊÌ
iÊ work out the overall effect on the system. It is possible that your optimization steps may have adversely affected other queries, making them slower.
C H A P T E R 3 N S Q L Q U E R Y P E R F O R M A N C E A N A LY S I S
Execution Plans Once you have identified a costly query, you need to find out why it is so costly. You can iden tify the costly query from SQL Profiler or ouo*`i[ata_[mqanu[op]po, rerun it in Management -ÌÕ`]Ê>`ÊÊ>ÌÊÌ
iÊiÝiVÕÌÊ«>ÊÕÃi`ÊLÞÊÌ
iʵÕiÀÞÊ«ÌâiÀ°ÊÊiÝiVÕÌÊ«>ÊÃ
ÜÃÊ Ì
iÊ«ÀViÃÃ}ÊÃÌÀ>Ìi}ÞÊVÕ`}ÊÕÌ«iÊÌiÀi`>ÌiÊÃÌi«Ã®ÊÕÃi`ÊLÞÊÌ
iʵÕiÀÞÊ«ÌâiÀÊÌÊ execute a query. To create an execution plan, the query optimizer evaluates various permutations of `iÝiÃÊ>`ÊÊÃÌÀ>Ìi}iÃ°Ê iV>ÕÃiÊvÊÌ
iÊ«ÃÃLÌÞÊvÊ>Ê>À}iÊÕLiÀÊvÊ«ÌiÌ>Ê«>Ã]ÊÌ
ÃÊ «Ìâ>ÌÊ«ÀViÃÃÊ>ÞÊÌ>iÊ>Ê}ÊÌiÊÌÊ}iiÀ>ÌiÊÌ
iÊÃÌÊVÃÌivviVÌÛiÊiÝiVÕÌÊ«>°Ê To prevent the overoptimization of an execution plan, the optimization process is broken into multiple phases. Each phase is a set of transformation rules that evaluate various permuta ÌÃÊvÊ`iÝiÃÊ>`ÊÊÃÌÀ>Ìi}ið vÌiÀÊ}}ÊÌ
ÀÕ}
Ê>Ê«
>Ãi]ÊÌ
iʵÕiÀÞÊ«ÌâiÀÊiÝ>iÃÊÌ
iÊiÃÌ>Ìi`ÊVÃÌÊvÊÌ
iÊ resulting plan. If the query optimizer determines that the plan is cheap enough, it will use the «>ÊÜÌ
ÕÌÊ}}ÊÌ
ÀÕ}
ÊÌ
iÊÀi>}Ê«Ìâ>ÌÊ«
>ÃiðÊÜiÛiÀ]ÊvÊÌ
iÊ«>ÊÃÊÌÊ cheap enough, the optimizer will go through the next optimization phase. I will cover execu ÌÊ«>Ê}iiÀ>ÌÊÊÀiÊ`i«Ì
ÊÊ
>«ÌiÀʰ SQL Server displays a query execution plan in various forms and from two different types. The most commonly used forms in SQL Server 2008 are the graphical execution plan >`ÊÌ
iÊ8ÊiÝiVÕÌÊ«>°ÊVÌÕ>Þ]ÊÌ
iÊ}À>«
V>ÊiÝiVÕÌÊ«>ÊÃÊëÞÊ>ÊXML execu tion plan parsed for the screen. The two types of execution plan are the estimated plan and the actual plan. The estimated plan includes the results coming from the query optimizer, and the actual plan is the plan used by the query engine. The beauty of the estimated plan is that it doesn’t require the query to be executed. These plan types can differ, but most of the time they will be the same. The graphical execution plan uses icons to represent the pro cessing strategy of a query. To obtain a graphical estimated execution plan, select Query ¢ ë>ÞÊ ÃÌ>Ìi`Ê ÝiVÕÌÊ*>°ÊÊ8ÊiÝiVÕÌÊ«>ÊVÌ>ÃÊÌ
iÊÃ>iÊ`>Ì>Ê>Û>>LiÊ through the graphical plan but in a more immediately accessible format. Further, with the XQuery capabilities of SQL Server 2008, XML execution plans can be queried as if they were Ì>LiÃ°Ê XML execution plan is produced by the OAPODKSLH=J[TIH and OAPOP=PEOPE?OTIH ÃÌ>ÌiiÌðÊ9ÕÊV>Ê>ÃÊÀ}
ÌVVÊ>Ê}À>«
V>ÊiÝiVÕÌÊ«>Ê>` select Showplan XML. You can obtain the estimated XML execution plan for the costliest query identified previ ously using the OAPODKSLH=J[TIHÊV>`Ê>ÃÊvÜÃÊoap[odkslh]j*omhÊÊÌ
iÊ`Ü>`®\ OAPODKSLH=J[TIHKJ CK OAHA?Pokd*=__kqjpJqi^an( ok`*HejaPkp]h( ok`*Kn`anMpu( ok`*QjepLne_a( l*J]ia BNKIO]hao*O]haoKn`anDa]`anokd FKEJO]hao*O]haoKn`an@ap]ehok` KJokd*O]haoKn`anE@9ok`*O]haoKn`anE@ FKEJLnk`q_pekj*Lnk`q_pl KJok`*Lnk`q_pE@9l*Lnk`q_pE@
83
84
CHAPTER 3 N SQ L QU ER Y P ER FOR MA NC E A NA L YS IS
SDANAok`*HejaPkp]h:-,,,7 CK OAPODKSLH=J[TIHKBB CK ,Õ}ÊÌ
ÃʵÕiÀÞÊÀiÃÕÌÃÊÊ>ÊÊÌÊ>ÊiÝiVÕÌÊ«>]ÊÌÊ>ÊiÝiVÕÌÊ«>ÊÀÊ>ÞÊ `>Ì>°Ê V}ÊÌ
iÊÊÜÊ«iÊ>ÊiÝiVÕÌÊ«>°ÊÌ
Õ}
ÊÌ
iÊ«>ÊÜÊLiÊ`ë>Þi`Ê>ÃÊ>Ê }À>«
V>Ê«>]ÊÀ}
ÌVV}ÊÌ
iÊ«>Ê>`ÊÃiiVÌ}Ê-
ÜÊ ÝiVÕÌÊ*>Ê8ÊÜÊ`ë>ÞÊÌ
iÊ 8Ê`>Ì>°Ê}ÕÀiÊΣÎÊÃ
ÜÃÊ>Ê«ÀÌÊvÊÌ
iÊ8ÊiÝiVÕÌÊ«>ÊÕÌ«Õ̰
Figure 3-13. XML execution plan output
Analyzing a Query Execution Plan Let’s start with the costly query identified in oap[odkslh]j*omh°Ê «ÞÊÌÊÕÃÊÌ
iÊOAP ODKSLH=J[TIHÊÃÌ>ÌiiÌîÊÌÊ>>}iiÌÊ-ÌÕ`]Ê>`ÊÌÕÀÊÊVÕ`iÊVÌÕ>Ê ÝiVÕÌÊ *>°Ê Ü]ÊÊiÝiVÕÌ}ÊÌ
ÃʵÕiÀÞ]ÊÞÕ½ÊÃiiÊÌ
iÊiÝiVÕÌÊ«>ÊÊ}ÕÀiÊΣ{°
Figure 3-14. Query execution plan
C H A P T E R 3 N S Q L Q U E R Y P E R F O R M A N C E A N A LY S I S
,i>`ÊÌ
iÊiÝiVÕÌÊ«>ÊvÀÊÀ}
ÌÊÌÊivÌÊ>`ÊvÀÊÌ«ÊÌÊLÌÌ°Ê >V
ÊÃÌi«ÊÀi«ÀiÃiÌÃÊ>Ê operation performed to get the final output of the query. Some of the aspects of a query execu tion represented by an execution plan are as follows: Ê
UÊ vÊ>ʵÕiÀÞÊVÃÃÌÃÊvÊ>ÊL>ÌV
ÊvÊÕÌ«iʵÕiÀiÃ]ÊÌ
iÊiÝiVÕÌÊ«>ÊvÀÊi>V
ʵÕiÀÞÊÜÊ be displayed in the order of execution. Each execution plan in the batch will have a Ài>ÌÛiÊiÃÌ>Ìi`ÊVÃÌ]ÊÜÌ
ÊÌ
iÊÌÌ>ÊVÃÌÊvÊÌ
iÊÜ
iÊL>ÌV
ÊLi}Ê£ääÊ«iÀVḭ
Ê
UÊ ÛiÀÞÊVÊÊ>ÊiÝiVÕÌÊ«>ÊÀi«ÀiÃiÌÃÊ>Ê«iÀ>ÌÀ°Ê/
iÞÊÜÊi>V
Ê
>ÛiÊ>ÊÀi>ÌÛiÊ iÃÌ>Ìi`ÊVÃÌ]ÊÜÌ
ÊÌ
iÊÌÌ>ÊVÃÌÊvÊ>ÊÌ
iÊ`iÃÊÊ>ÊiÝiVÕÌÊ«>ÊLi}Ê£ääÊ«iÀ cent.
Ê
UÊ 1ÃÕ>ÞÊ>ÊÃÌ>ÀÌ}Ê«iÀ>ÌÀÊÊ>ÊiÝiVÕÌÊÀi«ÀiÃiÌÃÊ>Ê`>Ì>ÀiÌÀiÛ>ÊiV
>ÃÊ vÀÊ>Ê`>Ì>L>ÃiÊLiVÌÊ>ÊÌ>LiÊÀÊ>Ê`iÝ®°ÊÀÊiÝ>«i]ÊÊÌ
iÊiÝiVÕÌÊ«>ÊÊ }ÕÀiÊΣ{]ÊÌ
iÊÌ
ÀiiÊÃÌ>ÀÌ}Ê«ÌÃÊÀi«ÀiÃiÌÊÀiÌÀiÛ>ÃÊvÀÊÌ
iÊO]haoKn`anDa]`an, O]haoKn`an@ap]eh, and Lnk`q_p tables.
Ê
UÊ >Ì>ÊÀiÌÀiÛ>ÊÜÊÕÃÕ>ÞÊLiÊiÌ
iÀÊ>ÊÌ>LiÊ«iÀ>ÌÊÀÊ>Ê`iÝÊ«iÀ>̰ÊÀÊ iÝ>«i]ÊÊÌ
iÊiÝiVÕÌÊ«>ÊÊ}ÕÀiÊΣ£]Ê>ÊÌ
ÀiiÊ`>Ì>ÊÀiÌÀiÛ>ÊÃÌi«ÃÊ>ÀiÊ`iÝÊ operations.
Ê
UÊ >Ì>ÊÀiÌÀiÛ>ÊÊ> index will be either an index scan or an index seek. For example, Ì
iÊvÀÃÌÊ>`ÊÌ
À`Ê`iÝÊ«iÀ>ÌÃÊÊ}ÕÀiÊΣ{Ê>ÀiÊ`iÝÊÃV>Ã]Ê>`ÊÌ
iÊÃiV`ÊiÊ is an index seek.
Ê
UÊ /
iÊ>}ÊVÛiÌÊvÀÊ>Ê`>Ì>ÀiÌÀiÛ>Ê«iÀ>ÌÊÊ>Ê`iÝÊÃÊWP]^haJ]iaY* WEj`atJ]iaY.
Ê
UÊ >Ì>ÊvÜÃÊvÀÊÀ}
ÌÊÌÊivÌÊLiÌÜiiÊÌÜÊ«iÀ>ÌÀÃÊ>`ÊÃÊ`V>Ìi`ÊLÞÊ>ÊViVÌ}Ê arrow between the two operators.
Ê
UÊ /
iÊÌ
ViÃÃÊvÊ>ÊViVÌ}Ê>ÀÀÜÊLiÌÜiiÊ«iÀ>ÌÀÃÊÀi«ÀiÃiÌÃÊ>Ê}À>«
V>ÊÀi«Ài sentation of the number of rows transferred.
Ê
UÊ /
iÊ} mechanism between two operators in the same column will be either a iÃÌi`Ê«Ê]Ê>Ê
>Ã
Ê>ÌV
Ê]ÊÀÊ>ÊiÀ}iʰÊÀÊiÝ>«i]ÊÊÌ
iÊiÝiVÕÌÊ«>Ê Ã
ÜÊÊ}ÕÀiÊΣ{]ÊÌ
iÀiÊÃÊiÊiÃÌi`Ê«ÊÊ>`ÊiÊ
>Ã
Ê>ÌV
°
Ê
UÊ ,Õ}ÊÌ
iÊÕÃiÊÛiÀÊ>Ê`iÊÊ>ÊiÝiVÕÌÊ«>ÊÃ
ÜÃÊ>Ê««Õ«ÊÜ`ÜÊÜÌ
Ê ÃiÊ`iÌ>Ã]Ê>ÃÊÞÕÊV>ÊÃiiÊÊ}ÕÀiÊΣx° Ê UÊ ÊV«iÌiÊÃiÌÊvÊ`iÌ>ÃÊ>LÕÌÊ>Ê«iÀ>ÌÀÊÃÊ>Û>>LiÊÊÌ
iÊ*À«iÀÌiÃÊÜ`Ü]Ê Ü
V
ÊÞÕÊV>Ê«iÊLÞÊÀ}
ÌVV}ÊÌ
iÊ«iÀ>ÌÀÊ>`ÊÃiiVÌ}Ê*À«iÀÌiðÊ/
ÃÊÃÊ ÛÃLiÊÊ}ÕÀiÊΣȰ Ê UÊ Ê«iÀ>ÌÀÊ`iÌ>ÊÃ
ÜÃÊLÌ
Ê«
ÞÃV>Ê>`Ê}V>Ê«iÀ>ÌÊÌÞ«iÃÊ>ÌÊÌ
iÊÌ«°Ê Physical operations represent those actually used by the storage engine, while the logical operations are the constructs used by the optimizer to build the estimated execution plan. If logical and physical operations are the same, then only the physical operation is shown. It also displays other useful information, such as row VÕÌ]ÊÉ"ÊVÃÌ]Ê *1ÊVÃÌ]Ê>`ÊÃʰ Ê UÊ /
iÊÀ}ÕiÌÊÃiVÌÊÊ>Ê«iÀ>ÌÀÊ`iÌ>Ê««Õ«ÊÜ`ÜÊÃÊiëiV>ÞÊÕÃivÕÊÊ analysis, because it showsÊÌ
iÊvÌiÀÊÀÊÊVÀÌiÀÊÕÃi`ÊLÞÊÌ
iÊ«ÌâiÀ°
85
86
CHAPTER 3 N SQ L QU ER Y P ER FOR MA NC E A NA L YS IS
Figure 3-15. Execution plan node detail
Figure 3-16. Clustered index scan properties
C H A P T E R 3 N S Q L Q U E R Y P E R F O R M A N C E A N A LY S I S
Identifying the Costly Steps in an Execution Plan Your main interest in the execution plan is to find out which steps are relatively costly. These steps are the starting point for your query optimization. You can choose the starting steps by adopting the following techniques: Ê
UÊ >V
Ê`iÊÊ>ÊiÝiVÕÌÊ«>ÊÃ
ÜÃÊÌÃÊÀi>ÌÛiÊVÃÌÊÊÌ
iÊV«iÌiÊiÝiVÕÌÊ«>]Ê ÜÌ
ÊÌ
iÊÌÌ>ÊVÃÌÊvÊÌ
iÊÜ
iÊ«>ÊLi}Ê£ääÊ«iÀVḭÊ/
iÀivÀi]ÊvVÕÃÊ>ÌÌiÌÊÊ Ì
iÊ`iîÊÜÌ
ÊÌ
iÊ
}
iÃÌÊÀi>ÌÛiÊVÃ̰ÊÀÊiÝ>«i]ÊÌ
iÊiÝiVÕÌÊ«>ÊÊ}ÕÀiÊ Î£{Ê
>ÃÊÌÜÊÃÌi«ÃÊÜÌ
ÊÎ{Ê«iÀViÌÊVÃÌÊi>V
°
Ê
UÊ ÊiÝiVÕÌÊ«>Ê>ÞÊLiÊvÀÊ>ÊL>ÌV
ÊvÊÃÌ>ÌiiÌÃ]ÊÃÊÞÕÊ>ÞÊ>ÃÊii`ÊÌÊv`ÊÌ
iÊ ÃÌÊVÃÌÞÊÃÌ>ÌiḭÊÊ}ÕÀiÊΣxÊ>`Ê}ÕÀiÊΣ{]ÊÞÕÊV>ÊÃiiÊ>ÌÊÌ
iÊÌ«ÊvÊÌ
iÊ«>Ê Ì
iÊÌiÝÌʺ+ÕiÀÞÊ£°»ÊÊ>ÊL>ÌV
ÊÃÌÕ>Ì]ÊÌ
iÀiÊÜÊLiÊÕÌ«iÊ«>Ã]Ê>`ÊÌ
iÞÊÜÊLiÊ numbered in the order they occurred within the batch.
Ê
UÊ "LÃiÀÛiÊÌ
iÊÌ
ViÃÃÊvÊÌ
iÊViVÌ}Ê>ÀÀÜÃÊLiÌÜiiÊ`iðÊÊÛiÀÞÊÌ
VÊV necting arrow indicates a large number of rows being transferred between the VÀÀië`}Ê`iðÊ>ÞâiÊÌ
iÊ`iÊÌÊÌ
iÊivÌÊvÊÌ
iÊ>ÀÀÜÊÌÊÕ`iÀÃÌ>`ÊÜ
ÞÊÌÊ ÀiµÕÀiÃÊÃÊ>ÞÊÀÜðÊ
iVÊÌ
iÊ«À«iÀÌiÃÊvÊÌ
iÊ>ÀÀÜÃḚ̂Ê9ÕÊ>ÞÊÃiiÊÌ
>ÌÊÌ
iÊ iÃÌ>Ìi`ÊÀÜÃÊ>`ÊÌ
iÊ>VÌÕ>ÊÀÜÃÊ>ÀiÊ`vviÀḭÊ/
ÃÊV>ÊLiÊV>ÕÃi`ÊLÞÊÕÌv`>ÌiÊ statistics, among other things.
Ê
UÊ ÊvÀÊ
>Ã
ÊÊ«iÀ>ÌðÊÀÊÃ>ÊÀiÃÕÌÊÃiÌÃ]Ê>ÊiÃÌi`Ê«ÊÊÃÊÕÃÕ>ÞÊÌ
iÊ «ÀiviÀÀi`ÊÊÌiV
µÕi°Ê9ÕÊÜÊi>ÀÊÀiÊ>LÕÌÊ
>Ã
ÊÃÊV«>Ài`ÊÌÊiÃÌi`Ê «ÊÃÊ>ÌiÀÊÊÌ
ÃÊV
>«ÌiÀ°
Ê
UÊ ÊvÀÊL>ÀÊÕ«Ê«iÀ>ÌðÊÊL>ÀÊ«iÀ>ÌÊvÀÊ>Ê>À}iÊÀiÃÕÌÊÃiÌÊV>Ê cause a large number of logical reads. I will cover bookmark lookups in more detail in
>«ÌiÀÊȰ
Ê
UÊ /
iÀiÊ>ÞÊLiÊÜ>À}Ã]Ê`V>Ìi`ÊLÞÊ>ÊiÝV>>ÌÊ«ÌÊÊiÊvÊÌ
iÊ«iÀ>ÌÀÃ]Ê which are areas of immediate concern. These can be caused by a variety of issues, VÕ`}Ê>ÊÊÜÌ
ÕÌÊÊVÀÌiÀ>ÊÀÊ>Ê`iÝÊÀÊ>ÊÌ>LiÊÜÌ
ÊÃÃ}ÊÃÌ>ÌÃÌVÃ°Ê Usually resolving the warning situation will help performance.
Ê
UÊ ÊvÀÊÃÌi«ÃÊ«iÀvÀ}Ê>Êsort operation. This indicates that the data was not retrieved in the correct sort order.
Analyzing Index Effectiveness To examineÊ>ÊVÃÌÞÊÃÌi«ÊÊ>ÊiÝiVÕÌÊ«>ÊvÕÀÌ
iÀ]ÊÞÕÊÃ
Õ`Ê>>ÞâiÊÌ
iÊ`>Ì>ÀiÌÀiÛ>Ê mechanism for the relevant table or index. First, you should check whether an index operation is a seek or a scan. Usually, for best performance, you should retrieve as few rows as possible from a table, and an index seek is usually the most efficient way of accessing a small number vÊÀÜðÊÊscan operation usually indicates that a larger number of rows have been accessed. Therefore, it is generally preferable to seek rather than scan. Next, you want to ensure that the indexing mechanism is properly set up. The query optimizer evaluates the available indexes to discover which index will retrieve data from the table in the most efficient way. If a desired index is not available, the optimizer uses the next best index. For best performance, you should always ensure that the best index is used in
87
88
CHAPTER 3 N SQ L QU ER Y P ER FOR MA NC E A NA L YS IS
>Ê`>Ì>ÀiÌÀiÛ>Ê«iÀ>̰Ê9ÕÊV>ÊÕ`}iÊÌ
iÊ`iÝÊivviVÌÛiiÃÃÊÜ
iÌ
iÀÊÌ
iÊLiÃÌÊ`iÝÊÃÊ ÕÃi`ÊÀÊÌ®ÊLÞÊ>>Þâ}ÊÌ
iÊÀ}ÕiÌÊÃiVÌÊvÊ>Ê`iÊ`iÌ>ÊvÀÊÌ
iÊvÜ}\ Ê
UÊ Ê`>Ì>ÀiÌÀiÛ>Ê«iÀ>Ì
Ê
UÊ ÊÊ«iÀ>Ì
i̽ÃÊÊ>ÌÊÌ
iÊ`>Ì>ÀiÌÀiÛ>ÊiV
>ÃÊvÀÊÌ
iÊLnk`q_p table in the previous execution «>Ê}ÕÀiÊΣ{®°Ê}ÕÀiÊΣÇÊÃ
ÜÃÊÌ
iÊ«iÀ>ÌÀÊ«À«iÀÌið
Figure 3-17. Data-retrieval mechanism for the O]haoKn`an@ap]eh table In the operator properties for the O]haoKn`an@ap]eh table, the K^fa_p property specifies the index used, LG[O]haoKn`an@ap]eh. It uses the following naming convention: W@]p]^]oaY* WKsjanY*WP]^haJ]iaY*WEj`atJ]iaY. The OaagLna`e_]pao property specifies the column, or columns, used to seek into the index. The O]haoKn`an@ap]ehÊÌ>LiÊÃÊi`ÊÜÌ
ÊÌ
iÊ O]haoKn`anDa]`an table on the O]haoKn`anE` column. The OAAG works on the fact that the ÊVÀÌiÀ>]ÊO]haoKn`anE`, is the leading edge of the clustered index and primary key, LG[O]haoKn`an@ap]eh. -iÌiÃÊÞÕÊ>ÞÊ
>ÛiÊ>Ê`vviÀiÌÊ`>Ì>ÀiÌÀiÛ>ÊiV
>Ã]Ê>ÃÊÃ
ÜÊÊ}ÕÀiÊΣn°
C H A P T E R 3 N S Q L Q U E R Y P E R F O R M A N C E A N A LY S I S
Figure 3-18. A variation of the data-retrieval mechanism ÊÌ
iÊ«À«iÀÌiÃÊÊ}ÕÀiÊΣn]ÊÌ
iÀiÊÃÊÊpredicate. The lack of predicate means that the entire table, remembering that a clustered index is the table, is being scanned as input to the mergeÊÊ«iÀ>ÌÀÊÀiviÀÊÌÊÌ
iÊi>ÀiÀÊ}ÕÀiÊΣ{®°
Analyzing Join Effectiveness In additionÊÌÊ>>Þâ}ÊÌ
iÊ`iÝiÃÊÕÃi`]ÊÞÕÊÃ
Õ`ÊiÝ>iÊÌ
iÊivviVÌÛiiÃÃÊvÊÊÃÌÀ>Ìi gies decided by the optimizer. SQL ServerÊÕÃiÃÊÌ
ÀiiÊÌÞ«iÃÊvÊÃ\ Ê
UÊ >Ã
ÊÃ
Ê
UÊ iÀ}iÊÃ
Ê
UÊ
iÃÌi`Ê«ÊÃ
In many simple queries affecting a small set of rows, iÃÌi`Ê«ÊÃÊ>ÀiÊv>ÀÊÃÕ«iÀÀÊÌÊ LÌ
Ê
>Ã
Ê>`ÊiÀ}iÊðÊ/
iÊÊÌÞ«iÃÊÌÊLiÊÕÃi`ÊÊ>ʵÕiÀÞÊ>ÀiÊ`iV`i`Ê`Þ>V>ÞÊLÞÊ the optimizer.
89
90
CHAPTER 3 N SQ L QU ER Y P ER FOR MA NC E A NA L YS IS
Hash Join ToÊÕ`iÀÃÌ>`Ê-+Ê-iÀÛiÀ½ÃÊ
>Ã
ÊÊÃÌÀ>Ìi}Þ]ÊVÃ`iÀÊÌ
iÊvÜ}ÊëiʵÕiÀÞÊd]od*omh ÊÌ
iÊ`Ü>`®\ OAHA?Pl*& BNKILnk`q_pekj*Lnk`q_pl FKEJLnk`q_pekj*Lnk`q_pOq^?]packnuol_ KJl*Lnk`q_pOq^?]packnuE@9ol_*Lnk`q_pOq^?]packnuE@ />LiÊÎÈÊÃ
ÜÃÊÌ
iÊÌÜÊÌ>LiýÊ`iÝiÃÊ>`ÊÕLiÀÊvÊÀÜð Table 3-6. Indexes and Number of Rows of the Lnk`q_po and Lnk`q_p?]packnuP]^hao
Table
Indexes
Number of Rows
Lnk`q_p
ÕÃÌiÀi`Ê`iÝÊÊLnk`q_pE@
Óx
Lnk`q_p?]packnu
ÕÃÌiÀi`Ê`iÝÊÊLnk`q_p?]packnuE`
{£
}ÕÀiÊΣÊÃ
ÜÃÊÌ
iÊiÝiVÕÌÊ«>ÊvÀÊÌ
iÊ«ÀiVi`}ʵÕiÀÞ°
Figure 3-19. Execution plan with a hash join 9ÕÊV>ÊÃiiÊÌ
>ÌÊÌ
iÊ«ÌâiÀÊÕÃi`Ê>Ê
>Ã
ÊÊLiÌÜiiÊÌ
iÊÌÜÊÌ>Lið Ê
>Ã
ÊÊÕÃiÃÊÌ
iÊÌÜÊÊ«ÕÌÃÊ>ÃÊ>Êbuild input and a probe input. The build input is shown as the top input in the execution plan, and the probe input is shown as the bottom input. The smaller of the two inputs serves as the build input. /
iÊ
>Ã
ÊÊ«iÀvÀÃÊÌÃÊ«iÀ>ÌÊÊÌÜÊ«
>ÃiÃ\ÊÌ
iÊbuild phase and the probe phase. ÊÌ
iÊÃÌÊVÞÊÕÃi`ÊvÀÊvÊ
>Ã
Ê]ÊÌ
iÊin-memory hash join, the entire build input is scanned or computed, and then a hash table is built in memory. Each row is inserted into a hash bucket depending on the hash value computed for the hash keyÊÌ
iÊÃiÌÊvÊVÕÃÊÊÌ
iÊ iµÕ>ÌÞÊ«Ài`V>Ìi®° This build phase is followed by the probe phase. The entire probe input is scanned or computed one row at a time, and for each probe row, a hash key value is computed. The corre sponding hash bucket is scanned for the hash key value from the probe input, and the matches >ÀiÊ«À`ÕVi`°Ê}ÕÀiÊÎÓäÊÕÃÌÀ>ÌiÃÊÌ
iÊ«ÀViÃÃÊvÊ>ÊiÀÞÊ
>Ã
ʰ
C H A P T E R 3 N S Q L Q U E R Y P E R F O R M A N C E A N A LY S I S
Figure 3-20. Workflow for an in-memory hash join The query optimizerÊÕÃiÃÊ
>Ã
ÊÃÊÌÊ«ÀViÃÃÊ>À}i]ÊÕÃÀÌi`]Ê`iÝi`Ê«ÕÌÃÊivv ciently. Let’s now look at theÊiÝÌÊÌÞ«iÊvÊ\ÊÌ
iÊiÀ}iʰ
Merge Join In the previous case, input from the Lnk`q_p table is larger, and the table is not indexed on Ì
iÊ}ÊVÕÊLnk`q_p?]packnuE@®°Ê1Ã}ÊÌ
iÊvÜ}ÊëiʵÕiÀÞÊianca*omh in the `Ü>`®]ÊÞÕÊV>ÊÃiiÊ`vviÀiÌÊLi
>ÛÀ\ OAHA?Pli*& BNKILnk`q_pekj*Lnk`q_pIk`ahli FKEJLnk`q_pekj*Lnk`q_pIk`ahLnk`q_p@ao_nelpekj?qhpqnalil` KJli*Lnk`q_pIk`ahE@9lil`*Lnk`q_pIk`ahE@ }ÕÀiÊÎÓ£ÊÃ
ÜÃÊÌ
iÊÀiÃÕÌ>ÌÊiÝiVÕÌÊ«>ÊvÀÊÌ
ÃʵÕiÀÞ°
91
92
CHAPTER 3 N SQ L QU ER Y P ER FOR MA NC E A NA L YS IS
Figure 3-21. Execution plan with a merge join ÀÊÌ
ÃʵÕiÀÞ]ÊÌ
iÊ«ÌâiÀÊÕÃi`Ê>ÊiÀ}iÊÊLiÌÜiiÊÌ
iÊÌÜÊÌ>LiðÊÊiÀ}iÊÊ ÀiµÕÀiÃÊLÌ
ÊÊ«ÕÌÃÊÌÊLiÊÃÀÌi`ÊÊÌ
iÊiÀ}iÊVÕÃ]Ê>ÃÊ`ivi`ÊLÞÊÌ
iÊÊVÀÌiÀ°Ê If indexesÊ>ÀiÊ>Û>>LiÊÊLÌ
Ê}ÊVÕÃ]ÊÌ
iÊÌ
iÊÊ«ÕÌÃÊ>ÀiÊÃÀÌi`ÊLÞÊÌ
iÊ`iÝ°Ê -ViÊi>V
ÊÊ«ÕÌÊÃÊÃÀÌi`]ÊÌ
iÊiÀ}iÊÊ}iÌÃÊ>ÊÀÜÊvÀÊi>V
Ê«ÕÌÊ>`ÊV«>ÀiÃÊÌ
iÊ vÀÊiµÕ>ÌÞ°ÊÊ>ÌV
}ÊÀÜÊÃÊ«À`ÕVi`ÊvÊÌ
iÞÊ>ÀiÊiµÕ>°Ê/
ÃÊ«ÀViÃÃÊÃÊÀi«i>Ìi`ÊÕÌÊ>Ê rows are processed. In this case,ÊÌ
iʵÕiÀÞÊ«ÌâiÀÊvÕ`ÊÌ
>ÌÊÌ
iÊÊ«ÕÌÃÊÜiÀiÊLÌ
ÊÃÀÌi`ÊÀÊ`iÝi`®Ê ÊÌ
iÀÊ}ÊVÕðÊÃÊ>ÊÀiÃÕÌ]ÊÌ
iÊiÀ}iÊÊÜ>ÃÊV
ÃiÊ>ÃÊ>Êv>ÃÌiÀÊÊÃÌÀ>Ìi}ÞÊÌ
>Ê Ì
iÊ
>Ã
ʰ
Nested Loop Join The final typeÊvÊʽÊVÛiÀÊ
iÀiÊÃÊÌ
iÊiÃÌi`ʫʰÊÀÊLiÌÌiÀÊ«iÀvÀ>Vi]ÊÞÕÊÃ
Õ`Ê always access a limited number of rows from individual tables. To understand the effect of ÕÃ}Ê>ÊÃ>iÀÊÀiÃÕÌÊÃiÌ]Ê`iVÀi>ÃiÊÌ
iÊÊ«ÕÌÃÊÊÞÕÀʵÕiÀÞÊ>ÃÊvÜÃÊhkkl*omh in the `Ü>`®\ OAHA?Pokd*& BNKIO]hao*O]haoKn`anDa]`anokd FKEJO]hao*O]haoKn`an@ap]ehok` KJokd*O]haoKn`anE@9ok`*O]haoKn`anE@ SDANAokd*O]haoKn`anE@93-4/. }ÕÀiÊÎÓÓÊÃ
ÜÃÊÌ
iÊÀiÃÕÌ>ÌÊiÝiVÕÌÊ«>ÊvÊÌ
iÊiÜʵÕiÀÞ°
Figure 3-22. Execution plan with a nested loop join ÃÊÞÕÊV>ÊÃii]ÊÌ
iÊ«ÌâiÀÊÕÃi`Ê>ÊiÃÌi`Ê«ÊÊLiÌÜiiÊÌ
iÊÌÜÊÌ>Lið ÊiÃÌi`Ê«ÊÊÕÃiÃÊiÊÊ«ÕÌÊ>ÃÊÌ
iÊÕÌiÀÊ«ÕÌÊÌ>LiÊ>`ÊÌ
iÊÌ
iÀÊ>ÃÊÌ
iÊiÀÊ input table. The outer input table is shown as the top input in the execution plan, and the
C H A P T E R 3 N S Q L Q U E R Y P E R F O R M A N C E A N A LY S I S
inner input table is shown as the bottom input table. The outer loop consumes the outer input table row by row. The inner loop, executed for each outer row, searches for matching rows in the inner input table. iÃÌi`Ê«ÊÃÊ>ÀiÊ
}
ÞÊivviVÌÛiÊvÊÌ
iÊÕÌiÀÊ«ÕÌÊÃʵÕÌiÊÃ>Ê>`ÊÌ
iÊiÀÊ«ÕÌÊ ÃÊ>À}iÊLÕÌÊ`iÝi`°ÊÊ>ÞÊëiʵÕiÀiÃÊ>vviVÌ}Ê>ÊÃ>ÊÃiÌÊvÊÀÜÃ]ÊiÃÌi`Ê«ÊÃÊ >ÀiÊv>ÀÊÃÕ«iÀÀÊÌÊLÌ
Ê
>Ã
Ê>`ÊiÀ}iÊðÊÃÊ«iÀ>ÌiÊLÞÊ}>}Êëii`ÊÌ
ÀÕ}
ÊÌ
iÀÊ Ã>VÀvViðÊÊ«Ê is fast because it uses memory to take a small set of data and compare it µÕVÞÊÌÊ>ÊÃiV`ÊÃiÌÊvÊ`>Ì>°ÊÊiÀ}iÊÊÃ>ÀÞÊÕÃiÃÊiÀÞÊ>`Ê>ÊLÌÊvÊpail`^ to do its À`iÀi`ÊV«>ÀÃðÊÊ
>Ã
ÊÊÕÃiÃÊiÀÞÊ>`Êpail`^ to build out the hash tables for the °ÊÌ
Õ}
Ê>Ê«ÊÊÃÊv>ÃÌiÀ]ÊÌÊÜÊVÃÕiÊÀiÊiÀÞÊÌ
>Ê>Ê
>Ã
ÊÀÊiÀ}iÊ>ÃÊÌ
iÊ data sets get larger, which is why SQL Server will use different plans in different situations for different sets of data.
ÛiÊvÀÊÃ>ÊÊ«ÕÌÃ]ÊÃÕV
Ê>ÃÊÊÌ
iÊ«ÀiÛÕÃʵÕiÀÞ]Ê̽ÃÊ«ÀÌ>ÌÊÌÊ
>ÛiÊ>Ê`iÝÊ ÊÌ
iÊ}ÊVÕðÊÃÊÞÕÊÃ>ÜÊÊÌ
iÊ«ÀiVi`}ÊiÝiVÕÌÊ«>]ÊvÀÊ>ÊÃ>ÊÃiÌÊvÊÀÜÃ]Ê `iÝiÃÊÊ}ÊVÕÃÊ>ÜÊÌ
iʵÕiÀÞÊ«ÌâiÀÊÌÊVÃ`iÀÊ>ÊiÃÌi`Ê«ÊÊÃÌÀ>Ìi}Þ°Ê ÊÃÃ}Ê`iÝÊÊÌ
iÊ}ÊVÕÊvÊ>Ê«ÕÌÊÜÊvÀViÊÌ
iʵÕiÀÞÊ«ÌâiÀÊÌÊÕÃiÊ>Ê
>Ã
Ê ÊÃÌi>`° />LiÊÎÇÊÃÕ>ÀâiÃÊÌ
iÊÕÃiÊvÊÌ
iÊÌ
ÀiiÊÊÌÞ«ið Table 3-7. Characteristics of the Three Join Types
Join Type
Index on Joining Columns
Usual Size of Joining Tables
Presorted
Join Clause
>Ã
Inner table: Not indexed Outer table: Optional Optimal condition: Small outer table, large inner table
Þ
No
µÕ
Merge
Both tables: Must "«Ì>ÊV`Ì\Ê ÕÃÌiÀi`ÊÀÊ covering index on both
Large
Yes
µÕ
Nested loop
Inner table: Must Outer table: Preferable
Small
Optional
NNote The outer table is usually the smaller of the two joining tables in the hash and loop joins.
I will cover index types, including clustered andÊVÛiÀ}Ê`iÝiÃ]ÊÊ
>«ÌiÀÊ{°
Actual vs. Estimated Execution Plans There areÊiÃÌ>Ìi`Ê>`Ê>VÌÕ>ÊiÝiVÕÌÊ«>ðÊÌ
Õ}
ÊÌ
iÃiÊÌÜÊÌÞ«iÃÊvÊ«>ÃÊ>ÀiÊ}iiÀ ally, to a degree, interchangeable, sometimes one is clearly preferred over the other. There are iÛiÊÃÌÕ>ÌÃÊÜ
iÀiÊÌ
iÊiÃÌ>Ìi`Ê«>ÃÊÜÊÌÊÜÀÊ>ÌÊ>°Ê Ã`iÀÊÌ
iÊvÜ}ÊÃÌÀi`Ê «ÀVi`ÕÀiÊ_na]pa[l-*omhÊÊÌ
iÊ`Ü>`®\
93
94
CHAPTER 3 N SQ L QU ER Y P ER FOR MA NC E A NA L YS IS
EB$OAHA?PK>FA?P[E@$#l-#% %EOJKPJQHH @NKLLNK?lCK ?NA=PALNK?l=O ?NA=PAP=>HAp-$_-EJP%7 EJOANPEJPKpOAHA?Plnk`q_pe` BNKILnk`q_pekj*Lnk`q_p OAHA?P& BNKIp-7 @NKLP=>HAp-7 CK You may try to use ODKSLH=J[TIH to obtain the estimated XML execution plan for the query >ÃÊvÜÃÊodkslh]j*omhÊÊÌ
iÊ`Ü>`®\ OAPODKSLH=J[TIHKJ CK ATA?l-7 CK OAPODKSLH=J[TIHKBB CK But this fails with the following error: Oanran6Ioc.,4(Harah-2(Op]pa-(Lnk_a`qnal-(Heja0 Ejr]he`k^fa_pj]ia#p-#* Since ODKSLH=J[TIH doesn’t actually execute the query, the query optimizer can’t generate an execution plan for EJOANP and OAHA?PÊÃÌ>ÌiiÌÃÊÊÌ
iÊÌ>LiÊp-®° Instead, you can use OP=PEOPE?OTIH as follows: OAPOP=PEOPE?OTIHKJ CK ATA?l-7 CK OAPOP=PEOPE?OTIHKBB CK Since OP=PEOPE?OTIH executes the query, a different process is used that allows the table ÌÊLiÊVÀi>Ìi`Ê>`Ê>VViÃÃi`ÊÜÌ
ÊÌ
iʵÕiÀÞ°Ê}ÕÀiÊÎÓÎÊÃ
ÜÃÊ«>ÀÌÊvÊÌ
iÊÌiÝÌÕ>ÊiÝiVÕÌÊ plan provided by OP=PEOPE?OTIH.
C H A P T E R 3 N S Q L Q U E R Y P E R F O R M A N C E A N A LY S I S
Figure 3-23. OP=PEOPE?OLNKBEHA output
NTip Remember to switch Query ¢ Show Execution Plan off in Management Studio, or you will see the graphical, rather than textual, execution plan.
Plan Cache One final place to access execution plans is to read them directly from the memory space Ü
iÀiÊÌ
iÞÊ>ÀiÊÃÌÀi`]ÊÌ
iÊ«>ÊV>V
i°Ê Þ>VÊ>>}iiÌÊÛiÜÃÊ>`ÊvÕVÌÃÊ>ÀiÊ«À vided from SQL Server to access this data. To see a listing of execution plans in cache, run the following query: OAHA?Pl*mqanu[lh]j( p*patp BNKIouo*`i[ata_[_]_da`[lh]jon ?NKOO=LLHUouo*`i[ata_[mqanu[lh]j$n*lh]j[d]j`ha%l ?NKOO=LLHUouo*`i[ata_[omh[patp$n*lh]j[d]j`ha%p The query returns a list of XML execution plan links. Opening any of them will show the execution plan. Working further with columns available through the dynamic management views will allow you to search for specific procedures or execution plans.
Query Cost Even though the execution plan for a query provides a detailed processing strategy and the estimated relative costs of the individual steps involved, it doesn’t provide the actual cost of Ì
iʵÕiÀÞÊÊÌiÀÃÊvÊ *1ÊÕÃ>}i]ÊÀi>`ÃÉÜÀÌiÃÊÌÊ`Ã]ÊÀʵÕiÀÞÊ`ÕÀ>̰Ê7
iÊ«Ìâ}Ê>Ê query, you may add an index to reduce the relative cost of a step. This may adversely affect a dependent step in the execution plan, or sometimes it may even modify the execution plan itself. Thus, if you look only at the execution plan, you can’t be sure that your query optimiza tion benefits the query as a whole, as opposed to that one step in the execution plan. You can analyze the overall cost of a query in different ways. 9ÕÊÃ
Õ`ÊÌÀÊÌ
iÊÛiÀ>ÊVÃÌÊvÊ>ʵÕiÀÞÊÜ
iÊ«Ìâ}Ḛ̂ÊÃÊiÝ«>i`Ê«ÀiÛ ously, you can use SQL Profiler to monitor the @qn]pekj, ?LQ, Na]`o, and Snepao information for the query. To reduce the overhead of Profiler on SQL Server, you should set a filter criterion on
95
96
CHAPTER 3 N SQ L QU ER Y P ER FOR MA NC E A NA L YS IS
OLE@ÊvÀÊiÝ>«i]Ê>Ê`iÌviÀÊÃ`iÊ-+Ê-iÀÛiÀÊÌÊ`iÌvÞÊ>Ê`>Ì>L>ÃiÊÕÃiÀ®ÊiµÕ>ÊÌÊÌ
iÊOLE@ vÊÞÕÀÊ>>}iiÌÊ-ÌÕ`ÊÜ`ܰÊÜiÛiÀ]ÊÕÃ}Ê*ÀviÀÊÃÌÊ>``ÃÊ>ÊVÃÃÌiÌÊÛiÀ
i>`Ê on SQL Server in filtering out the events for other database users or OLE@s. I will explain OLE@ in ÀiÊ`i«Ì
ÊÊ
>«ÌiÀÊ£Ó° There are other ways to collect performance data that are more immediate than running Profiler.
Client Statistics
iÌÊÃÌ>ÌÃÌVÃÊV>«ÌÕÀi execution information from the perspective of your machine as a client of the server. This means that any times recorded include the time it takes to transfer data across the network, not merely the time involved on the SQL Server machine itself. To use them, simply click Query ¢ÊVÕ`iÊ iÌÊ-Ì>ÌÃÌVÃ°Ê Ü]Êi>V
ÊÌiÊÞÕÊÀÕÊ>ʵÕiÀÞ]Ê>Ê Ìi`ÊÃiÌÊvÊ`>Ì>ÊÃÊViVÌi`ÊVÕ`}ÊiÝiVÕÌÊÌi]ÊÌ
iÊÕLiÀÊvÊÀÜÃÊ>vviVÌi`]ÊÀÕ` trips to the server, and more. Further, each execution of the query is displayed separately on Ì
iÊ iÌÊ-Ì>ÌÃÌVÃÊÌ>L]Ê>`Ê>ÊVÕÊ>}}Ài}>Ì}ÊÌ
iÊÕÌ«iÊiÝiVÕÌÃÊÃ
ÜÃÊÌ
iÊ>ÛiÀ ages for the data collected. The statistics will also show whether a time or count has changed vÀÊiÊÀÕÊÌÊÌ
iÊiÝÌ]ÊÃ
Ü}ÊÕ«Ê>ÃÊ>ÀÀÜÃ]Ê>ÃÊÃ
ÜÊÊ}ÕÀiÊÎÓ{° For example, consider this query: OAHA?PPKL-,,l*& BNKILnk`q_pekj*Lnk`q_pl The client statistics information for the query should look something like that shown in }ÕÀiÊÎÓ{°
Figure 3-24. Client statistics Ì
Õ}
ÊV>«ÌÕÀ}ÊViÌÊÃÌ>ÌÃÌVÃÊV>ÊLiÊ>ÊÕÃivÕÊÜ>ÞÊÌÊ}>Ì
iÀÊ`>Ì>]Ê̽ÃÊ>ÊÌi`ÊÃiÌÊvÊ data, and there is no way to show how one execution is different from another. You could even run a completely different query, and its data would be mixed in with the others, making the averages useless. If you need to, you can reset the client statistics. Select the Query menu and then theÊ,iÃiÌÊ iÌÊ-Ì>ÌÃÌVÃÊiÕÊÌi°
C H A P T E R 3 N S Q L Q U E R Y P E R F O R M A N C E A N A LY S I S
Execution Time Both @qn]pekj and ?LQ represent the time factor of a query. To obtain detailed information on Ì
iÊ>ÕÌÊvÊÌiÊÊÃiV`îÊÀiµÕÀi`ÊÌÊ«>ÀÃi]ÊV«i]Ê>`ÊiÝiVÕÌiÊ>ʵÕiÀÞ]ÊÕÃiÊOAP OP=PEOPE?OPEIAÊ>ÃÊvÜÃÊpeiaop]po*omhÊÊÌ
iÊ`Ü>`®\ OAPOP=PEOPE?OPEIAKJ CK OAHA?Pokd*=__kqjpJqi^an( ok`*HejaPkp]h( ok`*Kn`anMpu( ok`*QjepLne_a( l*J]ia BNKIO]hao*O]haoKn`anDa]`anokd FKEJO]hao*O]haoKn`an@ap]ehok` KJokd*O]haoKn`anE@9ok`*O]haoKn`anE@ FKEJLnk`q_pekj*Lnk`q_pl KJok`*Lnk`q_pE@9l*Lnk`q_pE@ SDANAok`*HejaPkp]h:-,,,7 CK OAPOP=PEOPE?OPEIAKBB CK The output of OP=PEOPE?OPEIA for the preceding OAHA?P statement is as follows: OMHOanranl]noa]j`_kilehapeia6 ?LQpeia9,io(ah]loa`peia9,io* $/.-,-nks$o%]bba_pa`% OMHOanranAta_qpekjPeiao6 ?LQpeia9-0,io(ah]loa`peia932/io* OMHOanranl]noa]j`_kilehapeia6 ?LQpeia9,io(ah]loa`peia9,io* The ?LQpeia9-0,io part of the execution times represents the ?LQ value provided by the Profiler tool and the Server Trace option. Similarly, the corresponding Ah]loa`peia9 32/io represents the @qn]pekj value provided by the other mechanisms. ÊäÊÃÊ«>ÀÃiÊ>`ÊV«iÊÌiÊÃ}viÃÊÌ
>ÌÊÌ
iÊ«ÌâiÀÊÀiÕÃi`ÊÌ
iÊiÝÃÌ}ÊiÝiVÕ tion plan for this query and therefore didn’t have to spend any time parsing and compiling the query again. If the query is executed for the first time, then the optimizer has to parse the query first for syntax and then compile it to produce the execution plan. This can be easily verified by clearing out the cache using the system call @>??BNAALNK??=?DA and then rerun ning the query:
97
98
CHAPTER 3 N SQ L QU ER Y P ER FOR MA NC E A NA L YS IS
OMHOanranl]noa]j`_kilehapeia6 ?LQpeia9/.io(ah]loa`peia9/5io* $-2.nks$o%]bba_pa`% OMHOanranAta_qpekjPeiao6 ?LQpeia9-43io(ah]loa`peia9255io* OMHOanranl]noa]j`_kilehapeia6 ?LQpeia9,io(ah]loa`peia9,io* /
ÃÊÌi]Ê-+Ê-iÀÛiÀÊëiÌÊÎÓÊÃÊvÊ *1ÊÌiÊ>`Ê>ÊÌÌ>ÊvÊÎÊÃÊ«>ÀÃ}Ê>`ÊV«}Ê the query.
NNote You should not run @>??BNAALNK??=?DA on your production systems unless you are prepared to incur the NOT INSIGNIFICANT COST OF RECOMPILING EVERY QUERY ON THE SYSTEM )N SOME WAYS THIS WILL BE AS COSTLY to your system as a reboot.
STATISTICS IO ÃÊ`ÃVÕÃÃi`ÊÊÌ
iʺ`iÌvÞ}Ê ÃÌÞÊ+ÕiÀiûÊÃiVÌÊi>ÀiÀÊÊÌ
iÊV
>«ÌiÀ]ÊÌ
iÊÕLiÀÊvÊ reads in the Na]`o column is frequently the most significant cost factor among @qn]pekj, ?LQ, Na]`o, and Snepao. The total number of reads performed by a query consists of the sum of the number of reads performed on all tables involved in the query. The reads performed on the individual tables may vary significantly, depending on the size of the result set requested from the individual table and the indexes available. To reduce the total number of reads, it will be useful to find all the tables accessed in the query and their corresponding number of reads. This detailed information helps you concen trate on optimizing data access on the tables with a large number of reads. The number of Ài>`ÃÊ«iÀÊÌ>LiÊ>ÃÊ
i«ÃÊÞÕÊiÛ>Õ>ÌiÊÌ
iÊ«>VÌÊvÊÌ
iÊ«Ìâ>ÌÊÃÌi«Ê«iiÌi`ÊvÀÊ iÊÌ>Li®ÊÊÌ
iÊÌ
iÀÊÌ>LiÃÊÀiviÀÀi`ÊÌÊÊÌ
iʵÕiÀÞ° In a simple query, you determine the individual tables accessed by taking a close look at the query. This becomes increasingly difficult the more complex the query becomes. In the case of a stored procedure, database views, or functions, it becomes more difficult to identify all the tables actually accessed by the optimizer. You can use OP=PEOPE?OEK to get this infor mation, irrespective of query complexity. To turn OP=PEOPE?OEK on, navigate to Query ¢ Query Options ¢Ê`Û>Vi`Ê¢ Set Statistics IO in Management Studio. You may also get this information programmatically as follows ekop]po*omhÊÊÌ
iÊ`Ü>`®\
C H A P T E R 3 N S Q L Q U E R Y P E R F O R M A N C E A N A LY S I S
OAPOP=PEOPE?OEKKJ CK OAHA?Pokd*=__kqjpJqi^an( ok`*HejaPkp]h( ok`*Kn`anMpu( ok`*QjepLne_a( l*J]ia BNKIO]hao*O]haoKn`anDa]`anokd FKEJO]hao*O]haoKn`an@ap]ehok` KJokd*O]haoKn`anE@9ok`*O]haoKn`anE@ FKEJLnk`q_pekj*Lnk`q_pl KJok`*Lnk`q_pE@9l*Lnk`q_pE@ SDANAok`*O]haoKn`anE`93-4127 CK OAPOP=PEOPE?OEKKBB CK If you run this query and look at the execution plan, it consists of three clustered index ÃiiÃÊÜÌ
ÊÌÜÊ«ÊðÊvÊÞÕÊÀiÛiÊÌ
iÊSDANA clause and run the query again, you get a ÃiÌÊvÊÃV>ÃÊ>`ÊÃiÊ
>Ã
ÊðÊ/
>̽ÃÊ>ÊÌiÀiÃÌ}Êv>VÌpLÕÌÊÞÕÊ`½ÌÊÜÊ
ÜÊÌÊ>vviVÌÃÊ the query cost! You can use OAPOP=PEOPE?OEK as shown previously to compare the cost of the µÕiÀÞÊÊÌiÀÃÊvÊ}V>ÊÀi>`îÊLiÌÜiiÊÌ
iÊÌÜÊ«ÀViÃÃ}ÊÃÌÀ>Ìi}iÃÊÕÃi`ÊLÞÊÌ
iÊ«ÌâiÀ° You get following OP=PEOPE?OEKÊÕÌ«ÕÌÊÜ
iÊÌ
iʵÕiÀÞÊÕÃiÃÊÌ
iÊ
>Ã
Ê\ P]^ha#Skngp]^ha#*O_]j_kqjp,(hkce_]hna]`o,(lduoe_]hna]`o,Å P]^ha#O]haoKn`an@ap]eh#*O_]j_kqjp-(hkce_]hna]`o-.0,(lduoe_]hna]`o,Å P]^ha#O]haoKn`anDa]`an#*O_]j_kqjp-(hkce_]hna]`o242(lduoe_]hna]`o,Å P]^ha#Lnk`q_p#*O_]j_kqjp-(hkce_]hna]`o1(lduoe_]hna]`o,Å Now when you add the SDANA clause to appropriately filter the data, the resultant OP=PEOPE?OEK output turns out to be this: P]^ha#Lnk`q_p#*O_]j_kqjp,(hkce_]hna]`o0(lduoe_]hna]`o,Å P]^ha#O]haoKn`an@ap]eh#*O_]j_kqjp-(hkce_]hna]`o/(lduoe_]hna]`o,Å P]^ha#O]haoKn`anDa]`an#*O_]j_kqjp,(hkce_]hna]`o/(lduoe_]hna]`o,Å Logical reads for the O]haoKn`an@ap]ehÊÌ>LiÊ
>ÛiÊLiiÊVÕÌÊvÀÊ£]Ó{äÊÌÊÎÊLiV>ÕÃiÊvÊÌ
iÊ `iÝÊÃiiÊ>`ÊÌ
iʫʰÊÌÊ>ÃÊ
>ýÌÊÃ}vV>ÌÞÊ>vviVÌi`ÊÌ
iÊ`>Ì>ÊÀiÌÀiÛ>ÊVÃÌÊvÊÌ
iÊ Lnk`q_p table. While interpreting the output of OP=PEOPE?OEK, you mostly refer to the number of logical reads. Sometimes you also refer to the scan count, but even if you perform few logical reads per scan, the total number of logical reads provided by OP=PEOPE?OEK can still be high. If the number of logical reads per scan is small for a specific table, then you may not be able to improve the indexing mechanism of the table any further. The number of physical reads and Ài>`>
i>`ÊÀi>`ÃÊÜÊLiÊâiÀÊÜ
iÊÌ
iÊ`>Ì>ÊÃÊÌÊvÕ`ÊÊÌ
iÊiÀÞ]ÊLÕÌÊViÊÌ
iÊ`>Ì>Ê ÃÊ««Õ>Ìi`ÊÊiÀÞ]ÊÌ
iʫ
ÞÃV>ÊÀi>`ÃÊ>`ÊÀi>`>
i>`ÊÀi>`ÃÊÜÊÌi`ÊÌÊLiÊâiÀ°
99
100
CHAPTER 3 N SQ L QU ER Y P ER FOR MA NC E A NA L YS IS
There is another advantage to knowing all the tables used and their corresponding reads for a query. Both the @qn]pekj and ?LQ values may fluctuate significantly when reexecuting the Ã>iʵÕiÀÞÊÜÌ
ÊÊV
>}iÊÊÌ>LiÊÃV
i>ÊVÕ`}Ê`iÝiîÊÀÊ`>Ì>ÊLiV>ÕÃiÊÌ
iÊiÃÃiÌ>Ê services and background applications running on the SQL Server machine usually affect the processing time of the query under observation. ÕÀ}Ê«Ìâ>ÌÊÃÌi«Ã]ÊÞÕÊii`Ê>ÊvÕVÌÕ>Ì}ÊVÃÌÊv}ÕÀiÊ>ÃÊ>ÊÀiviÀiVi°Ê/
iÊÀi>`ÃÊ ÀÊ}V>ÊÀi>`îÊ`½ÌÊÛ>ÀÞÊLiÌÜiiÊÕÌ«iÊiÝiVÕÌÃÊvÊ>ʵÕiÀÞÊÜÌ
Ê>ÊvÝi`ÊÌ>LiÊÃV
i>Ê and data. For example, if you execute the previous OAHA?P statement ten times, you will prob ably get ten different figures for @qn]pekj and ?LQ, but Na]`o will remain the same each time. Therefore, during optimization, you can refer to the number of reads for an individual table to ensure that you really have reduced the data access cost of the table. Even though the number of logical reads can also be obtained from Profiler or the Server Trace option, you get another benefit when using OP=PEOPE?OEK. The number of logical reads for a query shown by Profiler or the Server Trace option increases as you use different OAP ÃÌ>ÌiiÌÃÊiÌi`Ê«ÀiÛÕÃÞ®Ê>}ÊÜÌ
ÊÌ
iʵÕiÀÞ°Ê ÕÌÊÌ
iÊÕLiÀÊvÊ}V>ÊÀi>`ÃÊ shown by OP=PEOPE?OEK doesn’t include the additional pages that are accessed as OAP state ments are used with a query. Thus, OP=PEOPE?OEK provides a consistent figure for the number of logical reads.
Summary In this chapter, you saw that you can use the Profiler tool or SQL tracing to identify the que ÀiÃÊV>ÕÃ}Ê>Ê
}
Ê>ÕÌÊvÊÃÌÀiÃÃÊÊÌ
iÊÃÞÃÌiÊÀiÃÕÀViÃÊÊ>Ê-+ÊÜÀ>`°Ê iVÌ}Ê the trace data can, and should be, automated using system stored procedures. For immediate access to statistics about running queries, use theÊ 6Êouo*`i[ata_[mqanu[op]po. You can further analyze these queries with Management Studio to find the costly steps in the process ing strategy of the query. For better performance, it is important to consider both the index >`ÊÊiV
>ÃÃÊÕÃi`ÊÊ>ÊiÝiVÕÌÊ«>ÊÜ
iÊ>>Þâ}Ê>ʵÕiÀÞ°Ê/
iÊÕLiÀÊvÊ`>Ì>Ê ÀiÌÀiÛ>ÃÊÀÊÀi>`îÊvÀÊÌ
iÊ`Û`Õ>ÊÌ>LiÃÊ«ÀÛ`i`ÊLÞÊOAPOP=PEOPE?OEK helps concentrate on the data access mechanism of the tables with most number of reads. You also should focus ÊÌ
iÊ *1ÊVÃÌÊ>`ÊÛiÀ>ÊÌiÊvÊÌ
iÊÃÌÊVÃÌÞʵÕiÀið Once you identify a costly query and finish the initial analysis, the next step should be to optimize the query for performance. Because indexing is one of the most commonly used «iÀvÀ>ViÌÕ}ÊÌiV
µÕiÃ]ÊÊÌ
iÊiÝÌÊV
>«ÌiÀÊÊÜÊ`ÃVÕÃÃÊÊ`i«Ì
ÊÌ
iÊÛ>ÀÕÃÊ`iÝ ing mechanisms available in SQL Server.
CHAPTER
4
Index Analysis T
he right index on the right column, or columns, is the basis on which query tuning begins. On the other hand, a missing index or an index placed on the wrong column, or columns, can be the basis for all performance problems starting with basic data access, continuing through joins, and ending in filtering clauses. For these reasons, it is extremely important for everyone—not just the DBA—to understand the different indexing techniques that can be used to optimize the database design. In this chapter, I cover the following topics: Ê
UÊ 7
>ÌÊ>Ê`iÝÊÃ
Ê
UÊ /
iÊLiivÌÃÊ>`ÊÛiÀ
i>`ÊvÊ>Ê`iÝ
Ê
UÊ iiÀ>ÊÀiVi`>ÌÃÊvÀÊ`iÝÊ`iÃ}
Ê
UÊ ÕÃÌiÀi`Ê>`ÊVÕÃÌiÀi`Ê`iÝÊLi
>ÛÀÊ>`ÊV«>ÀÃÃ
Ê
UÊ ,iVi`>ÌÃÊvÀÊVÕÃÌiÀi`Ê>`ÊVÕÃÌiÀi`Ê`iÝiÃ
Ê
UÊ `Û>Vi`Ê`iÝ}ÊÌiV
µÕiÃ\ÊVÛiÀ}Ê`iÝiÃ]Ê`iÝÊÌiÀÃiVÌÃ]Ê`iÝÊÃ]Ê filtered indexes, distributed indexes, indexed views, and index compression
Ê
UÊ -«iV>Ê`iÝÊÌÞ«iÃ
Ê
UÊ ``Ì>ÊV
>À>VÌiÀÃÌVÃÊvÊ`iÝiÃ
What Is an Index? One of the best ways to reduce disk I/O and logical reads is to use an index. An index allows -+Ê-iÀÛiÀÊÌÊv`Ê`>Ì>ÊÊ>ÊÌ>LiÊÜÌ
ÕÌÊÃV>}ÊÌ
iÊiÌÀiÊÌ>Li°ÊÊ`iÝÊÊ>Ê`>Ì>L>ÃiÊÃÊ >>}ÕÃÊÌÊ>Ê`iÝÊÊ>ÊL°Ê->Þ]ÊvÀÊiÝ>«i]ÊÌ
>ÌÊÞÕÊÜ>Ìi`ÊÌÊÊÕ«ÊÌ
iʫ
À>ÃiÊtable scanÊÊÌ
ÃÊL°Ê7Ì
ÕÌÊÌ
iÊ`iÝÊ>ÌÊÌ
iÊL>VÊvÊÌ
iÊL]ÊÞÕÊÜÕ`Ê
>ÛiÊÌÊ«iÀÕÃiÊÌ
iÊ iÌÀiÊLÊÌÊv`ÊÌ
iÊÌiÝÌÊÞÕÊii`i`°Ê7Ì
ÊÌ
iÊ`iÝ]ÊÞÕÊÜÊiÝ>VÌÞÊÜ
iÀiÊÌ
iÊvÀ>tion you want is stored.
101
102
CHAPTER 4 N IND EX A NA L YS IS
7
iÊÌÕ}Ê>Ê`>Ì>L>ÃiÊvÀÊ«iÀvÀ>Vi]ÊÞÕÊVÀi>ÌiÊ`iÝiÃÊÊÌ
iÊ`vviÀiÌÊVÕÃÊ ÕÃi`ÊÊ>ʵÕiÀÞÊÌÊ
i«Ê-+Ê-iÀÛiÀÊv`Ê`>Ì>ʵÕVÞ°ÊÀÊiÝ>«i]ÊÌ
iÊvÜ}ʵÕiÀÞÊ>}>ÃÌÊ the Lnk`q_pekj*Lnk`q_p table results in the data shown in Figure 4-1 (the first six of 500+ rows): OAHA?Pl*Lnk`q_pE@( l*WJ]iaY( l*Op]j`]n`?kop( l*WSaecdpY( NKS[JQI>AN$%KRAN$KN@AN>Ul*J]ia@AO?%=ONksJqi^an BNKILnk`q_pekj*Lnk`q_pl7
Figure 4-1. Sample Lnk`q_pekj*Lnk`q_p table If you need to retrieve all the products where Op]j`]n`?kop is greater than 150, without an index the table will have to be scanned, checking the value of Op]j`]n`?kop at each row to determine which rows contain a value greater than 150. An index on the Op]j`]n`?kop column could speed up this process by providing a mechanism that allows a structured search against the data rather than a row-by-row check. You can take two different, and fundamental, approaches for creating this index: Ê
UÊ Like a dictionary: A dictionary is a distinct listing of words in alphabetical order. An `iÝÊV>ÊLiÊÃÌÀi`ÊÊ>ÊÃ>ÀÊv>Ã
°Ê/
iÊ`>Ì>ÊÃÊÀ`iÀi`]Ê>Ì
Õ}
ÊÌÊÜÊÃÌÊ
>ÛiÊ `Õ«V>ÌiðÊ/
iÊvÀÃÌÊÃÝÊÀÜÃ]ÊÀ`iÀi`ÊLÞÊOp]j`]n`?kop instead of by J]ia, would look like the data shown in Figure 4-2. Notice the NksJqi^an column shows the original placement of the row when ordering by J]ia.
Figure 4-2. Product table sorted on Op]j`]n`?kop Ê
Ê -ÊÜÊvÊÞÕÊÜ>Ìi`ÊÌÊv`Ê>ÊÌ
iÊÀÜÃÊÜ
iÀiÊOp]j`]n`?kop is greater than 150, the index would allow you to find them immediately by moving down to the first value greater than 150. An index that orders the data stored based on the index order is known as a clustered index°Ê iV>ÕÃiÊvÊ
ÜÊ-+Ê-iÀÛiÀÊÃÌÀiÃÊ`>Ì>]ÊÌ
ÃÊÃÊiÊvÊÌ
iÊ most important indexes in your database design. I explain this in detail later in the chapter.
C H A P T E R 4 N I N D E X A N A LY S I S
Ê
UÊ Like a book’s index: An ordered list can be created without altering the layout of the table, similar to the way the index of a book is created. Just like the keyword index of a book lists the keywords in a separate section with a page number to refer to the main content of the book, the list of Op]j`]n`?kop values is created as a separate structure and refers to the corresponding row in the Lnk`q_p table through a pointer. For the example, I’ll use NksJqi^anÊ>ÃÊÌ
iÊ«ÌiÀ°Ê/>LiÊ{£ÊÃ
ÜÃÊÌ
iÊÃÌÀÕVÌÕÀiÊvÊÌ
iÊ>Õfacturer index. Table 4-1. Structure of the Manufacturer Index
Ê
StandardCost
RowNumber
,*4121
365
-*05./
375
-*422/
327
-*422/
498
-*422/
474
-+Ê-iÀÛiÀÊV>ÊÃV>ÊÌ
iÊ>Õv>VÌÕÀiÀÊ`iÝÊÌÊv`ÊÀÜÃÊÜ
iÀiÊOp]j`]n`?kop is greater Ì
>Ê£xä°Ê-ViÊÌ
iÊOp]j`]n`?kopÊÛ>ÕiÃÊ>ÀiÊ>ÀÀ>}i`ÊÊ>ÊÃÀÌi`ÊÀ`iÀ]Ê-+Ê-iÀÛiÀÊV>Ê ÃÌ«ÊÃV>}Ê>ÃÊÃÊ>ÃÊÌÊiVÕÌiÀÃÊÌ
iÊÀÜÊÜÌ
Ê>ÊÛ>ÕiÊvÊ£xä°Ê/
ÃÊÌÞ«iÊvÊ`iÝÊÃÊ called a nonclustered index, and I explain it in detail later in the chapter.
ÊiÌ
iÀÊV>Ãi]Ê-+Ê-iÀÛiÀÊÜÊLiÊ>LiÊÌÊv`Ê>ÊÌ
iÊ«À`ÕVÌÃÊÜ
iÀiÊOp]j`]n`?kop is greater than 150 more quickly than without an index under most circumstances. You can create indexes on either a single column (as described previously) or a combi>ÌÊvÊVÕÃÊÊ>ÊÌ>Li°Ê-+Ê-iÀÛiÀÊ>ÕÌ>ÌV>ÞÊVÀi>ÌiÃÊ`iÝiÃÊvÀÊViÀÌ>ÊÌÞ«iÃÊvÊ constraints (for example, LNEI=NUGAU and QJEMQA constraints).
The Benefit of Indexes -+Ê-iÀÛiÀÊ
>ÃÊÌÊLiÊ>LiÊÌÊv`Ê`>Ì>]ÊiÛiÊÜ
iÊÊ`iÝÊÃÊ«ÀiÃiÌÊÊ>ÊÌ>Li°Ê7
iÊÊVÕÃtered index is present to establish a storage order for the data, the storage engine will simply read through the entire table to find what it needs. A table without a clustered index is called a heap table. A heap is just a crude stack of data with a row identifier as a pointer to the storage V>̰Ê/
ÃÊ`>Ì>ÊÃÊÌÊÀ`iÀi`ÊÀÊÃi>ÀV
>LiÊiÝVi«ÌÊLÞÊÜ>}ÊÌ
ÀÕ}
ÊÌ
iÊ`>Ì>]ÊÀÜLÞ row, in a process called a scan°Ê7
iÊ>ÊVÕÃÌiÀi`Ê`iÝÊÃÊ«>Vi`ÊÊ>ÊÌ>Li]ÊÌ
iÊiÞÊÛ>ÕiÃÊvÊ the index establish an order for the data. Further, with a clustered index, the data is stored with Ì
iÊ`iÝÊÃÊÌ
>ÌÊÌ
iÊ`>Ì>ÊÌÃivÊÃÊÜÊÀ`iÀi`°Ê7
iÊ>ÊVÕÃÌiÀi`Ê`iÝÊÃÊ«ÀiÃiÌ]ÊÌ
iÊ«ÌiÀÊ ÊÌ
iÊVÕÃÌiÀi`Ê`iÝÊVÃÃÌÃÊvÊÌ
iÊÛ>ÕiÃÊÌ
>ÌÊ`iviÊÌ
iÊVÕÃÌiÀi`Ê`iݰÊ/
ÃÊÃÊ>ÊL}Ê part of what makes clustered indexes so important. -ViÊ>Ê«>}iÊ
>ÃÊ>ÊÌi`Ê>ÕÌÊvÊë>Vi]ÊÌÊV>ÊÃÌÀiÊ>Ê>À}iÀÊÕLiÀÊvÊÀÜÃÊvÊÌ
iÊ ÀÜÃÊVÌ>Ê>ÊviÜiÀÊÕLiÀÊvÊVÕðÊ/
iÊVÕÃÌiÀi`Ê`iÝÊusually doesn’t contain all Ì
iÊVÕÃÊvÊÌ
iÊÌ>LiÆÊÌÊÕÃÕ>ÞÊVÌ>ÃÊÞÊ>ÊÌi`ÊÕLiÀÊvÊÌ
iÊVÕðÊ/
iÀivÀi]Ê a page will be able to store more rows of a nonclustered index than rows of the table itself, Ü
V
ÊVÌ>ÃÊ>ÊÌ
iÊVÕÃ°Ê ÃiµÕiÌÞ]Ê-+Ê-iÀÛiÀÊÜÊLiÊ>LiÊÌÊÀi>`ÊÀiÊÛ>ÕiÃÊvÀÊ a column from a page representing a nonclustered index on the column than from a page representing the table that contains the column.
103
104
CHAPTER 4 N IND EX A NA L YS IS
Another benefit of the nonclustered index is that, because it is in a separate structure from the data table, it can be put in a different filegroup, with a different I/O path, as explained in
>«ÌiÀÊÓ°Ê/
ÃÊi>ÃÊÌ
>ÌÊ-+Ê-iÀÛiÀÊV>Ê>VViÃÃÊÌ
iÊ`iÝÊ>`ÊÌ>LiÊVVÕÀÀiÌÞ]Ê>}Ê searches even faster. Indexes store their information in a B-tree structure, so the number of reads required ÌÊv`Ê>Ê«>ÀÌVÕ>ÀÊÀÜÊÃÊâi`°Ê/
iÊvÜ}ÊiÝ>«iÊÃ
ÜÃÊÌ
iÊLiivÌÊvÊ>Ê ÌÀiiÊ structure.
Ã`iÀÊ>ÊÃ}iVÕÊÌ>Li with 27 rows in a random order and only 3 rows per leaf «>}i°Ê-Õ««ÃiÊÌ
iÊ>ÞÕÌÊvÊÌ
iÊÀÜÃÊÊÌ
iÊ«>}iÃÊÃÊ>ÃÊÃ
ÜÊÊ}ÕÀiÊ{ΰ
Figure 4-3. Initial layout of 27 rows /ÊÃi>ÀV
ÊÌ
iÊÀÜÊÀÊÀÜîÊvÀÊÌ
iÊVÕÊÛ>ÕiÊvÊx]Ê-+Ê-iÀÛiÀÊ
>ÃÊÌÊÃV>Ê>ÊÌ
iÊÀÜÃÊ and the pages, since even the last row in the last page may have the value 5. Because the number of reads depends on the number of pages accessed, nine read operations have to be «iÀvÀi`ÊÜÌ
ÕÌÊ>Ê`iÝÊÊÌ
iÊVÕ°Ê/
ÃÊVÌiÌÊV>ÊLiÊÀ`iÀi`ÊLÞÊVÀi>Ì}Ê>Ê`iÝÊ on the column, with the resultant layout of the rows and pages shown in Figure 4-4.
Figure 4-4. Ordered layout of 27 rows `iÝ}ÊÌ
iÊVÕÊ>ÀÀ>}iÃÊÌ
iÊVÌiÌÊÊ>ÊÃÀÌi`Êv>Ã
°Ê/
ÃÊ>ÜÃÊ-+Ê-iÀÛiÀÊ to determine the possible value for a row position in the column with respect to the value of >Ì
iÀÊÀÜÊ«ÃÌÊÊÌ
iÊVÕ°ÊÀÊiÝ>«i]ÊÊ}ÕÀiÊ{{]ÊÜ
iÊ-+Ê-iÀÛiÀÊv`ÃÊÌ
iÊ first row with the column value 6, it can be sure that there are no more rows with the column Û>ÕiÊx°Ê/
ÕÃ]ÊÞÊÌÜÊÀi>`Ê«iÀ>ÌÃÊ>ÀiÊÀiµÕÀi`ÊÌÊviÌV
ÊÌ
iÊÀÜÃÊÜÌ
ÊÌ
iÊÛ>ÕiÊxÊÜ
iÊ the content is indexed. However, what happens if you want to search for the column value 25? /
ÃÊÜÊÀiµÕÀiÊiÊÀi>`Ê«iÀ>ÌÃtÊ/
ÃÊ«ÀLiÊÃÊÃÛi`ÊLÞÊ«iiÌ}Ê`iÝiÃÊÕÃ}Ê the B-tree structure. A B-tree consists of a starting node (or page) called a root node with branch nodes (or pages) growing out of it (or linked to it).ÊÊiÞÃÊ>ÀiÊÃÌÀi`ÊÊÌ
iÊi>ÛiÃ°Ê Ì>i`ÊÊi>V
Ê interior node (above the leaf nodes) are pointers to its branch nodes and values representing the smallest value found in the branch node. Keys are kept in sorted order within each node. B-trees use a balanced tree structure for efficient record retrieval—a B-tree is balanced when the leaf nodes are all at the same level from the root node. For example, creating an index on the preceding content will generate the balanced B-tree structure shown in Figure 4-5.
Figure 4-5. B-tree layout of 27 rows
C H A P T E R 4 N I N D E X A N A LY S I S
/
iÊ ÌÀiiÊ>}ÀÌ
minimizes the number of pages to be accessed to locate a desired key, thereby speeding up the data access process. For example, in Figure 4-5, the search for the iÞÊÛ>ÕiÊxÊÃÌ>ÀÌÃÊ>ÌÊÌ
iÊÌ«ÊÀÌÊ`i°Ê-ViÊÌ
iÊiÞÊÛ>ÕiÊÃÊLiÌÜiiÊ£Ê>`Ê£ä]ÊÌ
iÊÃi>ÀV
Ê«Àcess follows the left branch to the next node. As the key value 5 falls between the values 4 and 7, the search process follows the middle branch to the next node with the starting key value vÊ{°Ê/
iÊÃi>ÀV
Ê«ÀViÃÃÊÀiÌÀiÛiÃÊÌ
iÊiÞÊÛ>ÕiÊxÊvÀÊÌ
ÃÊi>vÊ«>}i°ÊvÊÌ
iÊiÞÊÛ>ÕiÊxÊ`iýÌÊ iÝÃÌÊÊÌ
ÃÊ«>}i]ÊÌ
iÊÃi>ÀV
Ê«ÀViÃÃÊÜÊÃÌ«ÊÃViÊ̽ÃÊÌ
iÊi>vÊ«>}i°Ê->ÀÞ]ÊÌ
iÊiÞÊÛ>ÕiÊÓxÊ can also be searched using the same number of reads.
Index Overhead /
iÊ«iÀvÀ>ViÊLiivÌÊvÊ`iÝiÃÊ`iÃÊViÊ>ÌÊ>ÊVÃ̰Ê/>LiÃÊÜÌ
Ê`iÝiÃÊÀiµÕÀiÊÀiÊ storage and memory space for the index pages in addition to the data pages of the table. Data manipulation queries (EJOANP, QL@=PA, and @AHAPAÊÃÌ>ÌiiÌÃ]ÊÀÊÌ
iÊ 1 Ê«>ÀÌÊvÊVÀi>Ìi]ÊÀi>`]Ê Õ«`>Ìi]Ê`iiÌiÊQ ,1 R®ÊV>ÊÌ>iÊ}iÀ]Êand more processing time is required to maintain Ì
iÊ`iÝiÃÊvÊVÃÌ>ÌÞÊV
>}}ÊÌ>LiðÊ/
ÃÊÃÊLiV>ÕÃi]ÊÕiÊ>ÊOAHA?P statement, data manipulation queries modify the data content of a table. If an EJOANP statement adds a row to the table, then it also has to add a row in the index structure. If the index is a clustered index, the overhead is greater still, because the row has to be added to the data pages themselves in the right order, which may require other data rows to be repositioned below the entry position vÊÌ
iÊiÜÊÀܰÊ/
iÊQL@=PA and @AHAPA data manipulation queries change the index pages in a similar manner. 7
iÊ`iÃ}}Ê`iÝiÃ]ÊÞÕ½ÊLiÊ«iÀ>Ì}ÊvÀÊÌÜÊ`vviÀiÌÊ«ÌÃÊvÊÛiÜ\ÊÌ
iÊiÝÃÌing system, already in production, where you need to measure the overall impact of an index, and the tactical approach where all you worry about is the immediate benefits of an index, ÕÃÕ>ÞÊÜ
iÊÌ>ÞÊ`iÃ}}Ê>ÊÃÞÃÌi°Ê7
iÊÞÕÊ
>ÛiÊÌÊ`i>ÊÜÌ
ÊÌ
iÊiÝÃÌ}ÊÃÞÃÌi]Ê you should ensure that the performance benefits of an index outweigh the extra cost in proViÃÃ}ÊÀiÃÕÀViðÊ9ÕÊV>Ê`ÊÌ
ÃÊLÞÊÕÃ}ÊÌ
iÊ*ÀviÀÊÌÊiÝ«>i`ÊÊ
>«ÌiÀÊήÊÌÊ`Ê>Ê ÛiÀ>ÊÜÀ>`Ê«Ìâ>ÌÊiÝ«>i`ÊÊ
>«ÌiÀʣȮ°Ê7
iÊÞÕ½ÀiÊvVÕÃi`ÊiÝVÕÃÛiÞÊ ÊÌ
iÊi`>ÌiÊLiivÌÃÊvÊ>Ê`iÝ]Ê-+Ê-iÀÛiÀÊÃÕ««iÃÊ>ÊÃiÀiÃÊvÊ`Þ>VÊ>>}iiÌÊ views that provide detailed information about the performance of indexes, ouo*`i[`^[ej`at[ klan]pekj]h[op]po or ouo*`i[`^[ej`at[qo]ca[op]po°Ê/
iÊÛiÜÊouo*`i[`^[ej`at[klan]pekj]h op]poÊÃ
ÜÃÊÌ
iÊÜiÛiÊ>VÌÛÌÞ]ÊÃÕV
Ê>ÃÊVÃÊ>`ÊÉ"]ÊÊ>Ê`iÝÊÌ
>ÌÊÃÊÊÕÃi°Ê/
iÊÛiÜÊ ouo*`i[`^[ej`at[qo]ca[op]po returns statistical counts of the various index operations that
>ÛiÊVVÕÀÀi`ÊÌÊ>Ê`iÝÊÛiÀÊÌi°Ê Ì
ÊvÊÌ
iÃiÊÜÊLiÊÕÃi`ÊÀiÊiÝÌiÃÛiÞÊÊ
>«ÌiÀÊ£ÓÊ when I discuss blocking. /ÊÕ`iÀÃÌ>`ÊÌ
iÊÛiÀ
i>`ÊVÃÌÊvÊ>Ê`iÝÊÊ`>Ì>Ê>«Õ>ÌʵÕiÀiÃ]ÊVÃ`iÀÊ the following example. First, create a test table with 10,000 rows (_na]pa[paop*omh in the download): EB$OAHA?PK>FA?P[E@$#p-#% %EOJKPJQHH @NKLP=>HA`^k*p-7 CK ?NA=PAP=>HA`^k*p-$_-EJP(_.EJP(_/?D=N$1,%%7 OAHA?PPKL-,,,, E@AJPEPU$EJP(-(-%=Oj EJPKJqio
105
106
CHAPTER 4 N IND EX A NA L YS IS
BNKII]opan*`^k*Ouo?khqijoo_(I]opan*`^k*Ouo?khqijoo_.7 EJOANPEJPK`^k*p-$_-(_.(_/% OAHA?Pj (j (#?/# BNKIJqio7 @NKLP=>HAJqio7 If you then run an QL@=PA statement, like so: QL@=PA`^k*pOAP_-9-(_.9SDANA_.9-7 the number of logical reads reported by OAPOP=PEOPE?OEK is as follows: P]^ha#p-#*O_]j_kqjp-(hkce_]hna]`o43 After adding an index on column _-, like so: ?NA=PA?HQOPANA@EJ@ATe-KJp-$_-% the resultant number of logical reads for the same QL@=PA statement increases from 87 to 95: P]^ha#p-#*O_]j_kqjp-(hkce_]hna]`o51 Even though it is true that the amount of overhead required to maintain indexes increases forÊ`>Ì>Ê>«Õ>ÌʵÕiÀiÃ]ÊLiÊ>Ü>ÀiÊÌ
>ÌÊ-+Ê-iÀÛiÀÊÕÃÌÊvÀÃÌÊv`Ê>ÊÀÜÊLivÀiÊÌÊV>Ê update or delete it; therefore, indexes can be helpful for QL@=PA and @AHAPA statements with complex SDANAÊV>ÕÃiÃÊ>ÃÊÜi°Ê/
iÊVÀi>Ãi`ÊivvViVÞÊÊÕÃ}ÊÌ
iÊ`iÝÊÌÊV>ÌiÊ>ÊÀÜÊ usually offsets the extra overhead needed to update the indexes, unless the table has a lot of indexes. /ÊÕ`iÀÃÌ>`Ê
ÜÊ>Ê`iÝÊV>ÊLiivÌÊiÛiÊ`>Ì>Ê`vV>ÌʵÕiÀiÃ]Êi̽ÃÊLÕ`ÊÊÌ
iÊ iÝ>«i°Ê Ài>ÌiÊ>Ì
iÀÊ`iÝÊÊÌ>LiÊp-°Ê/
ÃÊÌi]ÊVÀi>ÌiÊÌ
iÊ`iÝÊÊVÕÊ_. referred to in the SDANA clause of the QL@=PA statement: ?NA=PAEJ@ATe.KJp-$_.%7 After adding this new index, run the QL@=PA command again: QL@=PAp-OAP_-9-(_.9-SDANA_.9-7 the total number of logical reads for this QL@=PA statement decreases from 95 to 20 (= 15 + 5): P]^ha#p-#*O_]j_kqjp-(hkce_]hna]`o-1 P]^ha#Skngp]^ha#*O_]j_kqjp-(hkce_]hna]`o1
C H A P T E R 4 N I N D E X A N A LY S I S
NNote A worktable is a temporary table used internally by SQL Server to process the intermediate results of a query. Worktables are created in the pail`^ database and are dropped automatically after query execution. The examples in this section have demonstrated that although having an index adds some overhead cost to action queries, the overall result is a decrease in cost because of the beneficial effect of indexes on searching.
Index Design Recommendations /
i main recommendations for index design are as follows: Ê
UÊ Ý>iÊÌ
iÊSDANA clause and join criteria columns.
Ê
UÊ 1ÃiÊ>ÀÀÜÊ`iÝið
Ê
UÊ Ý>iÊVÕÊÕµÕiiÃð
Ê
UÊ Ý>iÊÌ
iÊVÕÊ`>Ì>ÊÌÞ«i°
Ê
UÊ Ã`iÀÊVÕÊÀ`iÀ°
Ê
UÊ Ã`iÀÊÌ
iÊÌÞ«iÊvÊ`iÝÊVÕÃÌiÀi`ÊÛðÊVÕÃÌiÀi`®° i̽ÃÊVÃ`iÀÊi>V
ÊvÊÌ
iÃiÊÀiVi`>ÌÃÊÊÌÕÀ°
Examine the WHERE Clause and Join Criteria Columns 7
iÊ>ʵÕiÀÞÊÃÊÃÕLÌÌi`ÊÌÊ-+Ê-iÀÛiÀ]ÊÌ
iʵÕiÀÞÊ«ÌâiÀÊÌÀiÃÊÌÊv`ÊÌ
iÊLiÃÌÊ`>Ì>Ê>VViÃÃÊ mechanism for every table referred to in the query. Here is how it does this: 1. /
iÊ«ÌâiÀÊ`iÌviÃÊÌ
iÊVÕÃÊVÕ`i`ÊÊÌ
iÊSDANA clause and the join criteria. 2. /
iÊ«ÌâiÀÊÌ
iÊiÝ>iÃÊ`iÝiÃÊÊÌ
ÃiÊVÕð 3. /
iÊ«ÌâiÀÊ>ÃÃiÃÃiÃÊÌ
iÊÕÃivÕiÃÃÊvÊi>V
Ê`iÝÊLÞÊ`iÌiÀ}ÊÌ
iÊÃiiVÌÛÌÞÊvÊ the clause (that is, how many rows will be returned) from statistics maintained on the index. 4. Finally, the optimizer estimates the least costly method of retrieving the qualifying rows, based on the information gathered in the previous steps.
NNote Chapter 7 covers statistics in more depth.
/ÊÕ`iÀÃÌ>`ÊÌ
iÊÃ}vV>ViÊvÊ>ÊSDANA clause column in a query, let’s consider an iÝ>«i°Êi̽ÃÊÀiÌÕÀÊÌÊÌ
iÊÀ}>ÊV`iÊÃÌ}ÊÌ
>ÌÊ
i«i`ÊÞÕÊÕ`iÀÃÌ>`ÊÜ
>ÌÊ>Ê`iÝÊÃÆÊ the query consisted of a OAHA?P statement without any SDANA clause, as follows:
107
108
CHAPTER 4 N IND EX A NA L YS IS
OAHA?Pl*Lnk`q_pE@( l*WJ]iaY( l*Op]j`]n`?kop( l*WSaecdpY BNKILnk`q_pekj*Lnk`q_pl7 /
iʵÕiÀÞÊ«ÌâiÀÊ«iÀvÀÃÊ>ÊVÕÃÌiÀi`Ê`iÝÊÃV>]ÊÌ
iÊiµÕÛ>iÌÊvÊ>ÊÌ>LiÊÃV>Ê against a heap on a table that has a clustered index, to read the rows as shown in Figure 4-6 ÃÜÌV
ÊÊÌ
iÊ-
ÜÊ ÝiVÕÌÊ*>Ê«ÌÊLÞÊÕÃ}Ê+ÕiÀÞÊ>ÞâiÀ½ÃÊ+ÕiÀÞÊiÕ]Ê>ÃÊÜiÊ>ÃÊ Ì
iÊ-iÌÊ-Ì>ÌÃÌVÃÊ"Ê«ÌÊLÞÊÕÃ}Ê+ÕiÀÞÊ>ÞâiÀ½ÃÊ/ÃÊ¢ Options menu).
Figure 4-6. Execution plan with no SDANA clause /
iÊÕLiÀÊvÊ}V>ÊÀi>`ÃÊÀi«ÀÌi`ÊLÞÊOAPOP=PEOPE?OEK for the OAHA?P statement is as follows: P]^ha#Lnk`q_p#*O_]j_kqjp-(hkce_]hna]`o-1 /ÊÕ`iÀÃÌ>`ÊÌ
iÊivviVÌÊvÊ>ÊSDANA clause column on the query optimizer’s decision, let’s add a SDANA clause to retrieve a single row: OAHA?Pl*Lnk`q_pE@( l*WJ]iaY( l*Op]j`]n`?kop( l*WSaecdpY BNKILnk`q_pekj*Lnk`q_pl SDANAl*Lnk`q_pE@93/47 7Ì
ÊÌ
iÊSDANA clause in place, the query optimizer examines the SDANA clause column Lnk`q_pE@, identifies the availability of index LG[Lnk`q_p[Lnk`q_pE` on column Lnk`q_pE`, assesses a high selectivity (that is, only one row will be returned) for the SDANA clause from the statistics on index LG[Lnk`q_p[Lnk`q_pE`, and decides to use that index on column Lnk`q_pE`, as shown in Figure 4-7.
Figure 4-7. Execution plan with a SDANA clause /
iÊÀiÃÕÌ>ÌÊÕLiÀÊvÊ}V>ÊÀi>`ÃÊÃÊ>ÃÊvÜÃ\ P]^ha#Lnk`q_p#*O_]j_kqjp,(hkce_]hna]`o.
C H A P T E R 4 N I N D E X A N A LY S I S
/
iÊLi
>ÛÀÊvÊÌ
iÊquery optimizer shows that the SDANA clause column helps the optiâiÀÊV
ÃiÊ>Ê«Ì>Ê`iÝ}Ê«iÀ>ÌÊvÀÊ>ʵÕiÀÞ°Ê/
ÃÊÃÊ>ÃÊ>««V>LiÊvÀÊ>ÊVÕÊ ÕÃi`ÊÊÌ
iÊÊVÀÌiÀ>ÊLiÌÜiiÊÌÜÊÌ>LiðÊ/
iÊ«ÌâiÀÊÃÊvÀÊÌ
iÊ`iÝiÃÊÊÌ
iÊSDANA clause column or the join criterion column and, if available, considers using the index to ÀiÌÀiÛiÊÌ
iÊÀÜÃÊvÀÊÌ
iÊÌ>Li°Ê/
iʵÕiÀÞÊ«ÌâiÀÊVÃ`iÀÃÊ`iÝiîÊÊÌ
iÊSDANA clause VÕîÊ>`ÊÌ
iÊÊVÀÌiÀ>ÊVÕîÊÜ
iÊiÝiVÕÌ}Ê>ʵÕiÀÞ°Ê/
iÀivÀi]Ê
>Û}Ê`iÝiÃÊ on the frequently used columns in the SDANAÊV>ÕÃiÊ>`ÊÌ
iÊÊVÀÌiÀ>ÊvÊ>Ê-+ʵÕiÀÞÊ
i«ÃÊ the optimizer avoid scanning a base table. 7
iÊÌ
iÊ>ÕÌÊvÊ`>Ì>ÊÃ`iÊ>ÊÌ>LiÊÃÊÃÊÃ>ÊÌ
>ÌÊÌÊvÌÃÊÌÊ>ÊÃ}iÊ«>}iÊn ®]Ê>Ê table scan may work better than an index seek. If you have a good index in place but you’re still getting a scan, consider this issue.
Use Narrow Indexes You can create indexes on a combination of columns in a table. For the best performance, use as few columns in an index as you can. You should also avoid very wide data type columns in >Ê`iÝ°Ê ÕÃÊÜÌ
string data types (?D=N, R=N?D=N, J?D=N, and JR=N?D=N) sometimes can be quite wide as can binary; unless they are absolutely necessary, minimize the use of wide data type columns with large sizes in an index. A narrow index can accommodate more rows in an 8KB index page than a wide index. /
ÃÊ
>ÃÊÌ
iÊvÜ}ÊivviVÌÃ\ Ê
UÊ ,i`ÕViÃÊÉ"ÊLÞÊ
>Û}ÊÌÊÀi>`ÊviÜiÀÊn Ê«>}iî
Ê
UÊ >iÃÊ`>Ì>L>ÃiÊV>V
}ÊÀiÊivviVÌÛi]ÊLiV>ÕÃiÊ-+Ê-iÀÛiÀÊV>ÊV>V
iÊviÜiÀÊ`iÝÊ pages, consequently reducing the logical reads required for the index pages in the memory
Ê
UÊ ,i`ÕViÃÊÌ
iÊÃÌÀ>}iÊë>ViÊvÀÊÌ
iÊ`>Ì>L>Ãi
/ÊÕ`iÀÃÌ>`Ê
ÜÊ>Ê>ÀÀÜÊ`iÝÊV>ÊÀi`ÕViÊÌ
iÊÕLiÀÊvÊ}V>ÊÀi>`Ã]ÊVÀi>ÌiÊ>ÊÌiÃÌÊ table with 20 rows and an index (j]nnksE@T[p-*omh in the download): EB$OAHA?PK>FA?P[E@$#p-#%%EOJKPJQHH @NKLP=>HA`^k*pCK ?NA=PAP=>HA`^k*p-$_-EJP(_.EJP%7 SEPDJqio =O$OAHA?P-=Oj QJEKJ=HH OAHA?Pj'BNKIJqio SDANAj8., % EJOANPEJPKp$_-(_.% OAHA?Pj(. BNKIJqio ?NA=PAEJ@ATe-KJp-$_-%
109
110
CHAPTER 4 N IND EX A NA L YS IS
-ViÊÌ
iÊ`iÝi`ÊVÕÊÃÊ>ÀÀÜÊÌ
iÊEJP data type is 4 bytes), all the index rows can be accommodated in one 8KB index page. As shown in Figure 4-8, you can confirm this in the dynamic management views associated with indexes (^]oa[ej`t*omh in the download): OAHA?Pe*J]ia (e*pula[`ao_ (o*l]ca[_kqjp (o*na_kn`[_kqjp (o*ej`at[harah BNKIouo*ej`ataoe FKEJouo*`i[`^[ej`at[lduoe_]h[op]po$@>[E@$J#=`rajpqnaSkngo.,,4#%( K>FA?P[E@$J#`^k*P-#%( JQHH(JQHH(#@AP=EHA@#%=Oo KJe*ej`at[e`9o*ej`at[e` SDANAe*K>FA?P[E@9K>FA?P[E@$J#`^k*P-#%
Figure 4-8. Number of pages for a narrow, nonclustered index /
iÊouo*ej`atao system table is stored in each database and contains the basic informaÌÊÊiÛiÀÞÊ`iÝÊÊÌ
iÊ`>Ì>L>Ãi°Ê/
iÊ`Þ>VÊ>>}iiÌÊvÕVÌ]Êouo*`i[`^[ej`at[ lduoe_]h[op]po, contains the more detailed information about the statistics on the index ÞÕ½Êi>ÀÊÀiÊ>LÕÌÊÌ
ÃÊ ÊÊ
>«ÌiÀÊÇ®°Ê/ÊÕ`iÀÃÌ>`ÊÌ
iÊ`Ã>`Û>Ì>}iÊvÊ>ÊÜ`iÊ index key, modify the data type of the indexed column _- from EJP to ?D=N$1,,% (j]nnks[ ]hpan*omh in the download): @NKLEJ@ATp-*e=HPANP=>HAp-=HPAN?KHQIJ_-?D=N$1,,% ?NA=PAEJ@ATe-KJp-$_-% /
iÊÜ`Ì
ÊvÊ>ÊVÕÊÜÌ
ÊÌ
iÊEJP data type is 4 bytes, and the width of a column with the ?D=N$1,,% data type is 500 bytes. Because of the large width of the indexed column, two index pages are required to contain all 20 index rows. You can confirm this in the ouo*ej`atao system table by running ouoej`at[oaha_p*omh again (see Figure 4-9).
Figure 4-9. Number of pages for a wide, nonclustered index A large index key size increases the number of index pages, thereby increasing the amount of memory and disk activities required for the index. It is always recommended that the index key size is as narrow as possible.
C H A P T E R 4 N I N D E X A N A LY S I S
Drop the test table before continuing: @NKLP=>HA`^k*p-
Examine Column Uniqueness
Ài>Ì}Ê> index on columns with a very low range of possible values (such as gender) will not benefit performance, because the query optimizer will not be able to use the index to ivviVÌÛiÞÊ>ÀÀÜÊ`ÜÊÌ
iÊÀÜÃÊÌÊLiÊÀiÌÕÀi`°Ê Ã`iÀÊ>ÊCaj`an column with only two unique values: I and B°Ê7
iÊÞÕÊiÝiVÕÌiÊ>ʵÕiÀÞÊÜÌ
ÊÌ
iÊCaj`an column in the SDANA clause, you end up with a large number of rows from the table (assuming the distribution of I and B is even), resulting in a costly table or clustered index scan. It is always preferable to have columns in the SDANA clause with lots of unique rows (or high selectivity) to limit the number of rows accessed. You should create an index on those column(s) to help the optimizer access a small result set. Furthermore, while creating an index on multiple columns, which is also referred to as a composite index, column order matters. In some cases, using the most selective column first will help filter the index rows more efficiently.
NNote The importance of column order in a composite index is explained later in the chapter in the “Consider Column Order” section.
From this, you can see that it is important to know the selectivity of a column before creating an index on it. You can find this by executing a query like this one; just substitute the table and column name: OAHA?P?KQJP$@EOPEJ?PCaj`an%=O@eopej_p?khR]hqao (?KQJP$Caj`an%=OJqi^anKbNkso ($?=OP$?KQJP$@EOPEJ?PCaj`an%=O@A?EI=H% +?=OP$?KQJP$Caj`an%=O@A?EI=H%%=OOaha_perepu BNKIDqi]jNaokqn_ao*Ailhkuaa /
iÊVÕÊÜÌ
ÊÌ
iÊ
}
iÃÌÊÕLiÀÊvÊÕµÕiÊÛ>ÕiÃÊÀÊÃiiVÌÛÌÞ®ÊV>ÊLiÊÌ
iÊLiÃÌÊV>didate for indexing when referred to in a SDANA clause or a join criterion. /ÊÕ`iÀÃÌ>`Ê
ÜÊÌ
iÊÃiiVÌÛÌÞÊvÊ>Ê`iÝÊiÞÊVÕÊ>vviVÌÃÊÌ
iÊÕÃiÊvÊÌ
iÊ`iÝ]Ê take a look at the Caj`an column in the Dqi]jNaokqn_ao*Ailhkuaa table. If you run the previous query, you’ll see that it contains only 2 distinct values in more than 290 rows, which is a selectivity of .006. A query to look only for a Caj`an of B would look like this: OAHA?P& BNKIDqi]jNaokqn_ao*Ailhkuaa SDANACaj`an9#B# =J@Oe_gHa]raDkqno915 =J@I]nep]hOp]pqo9#I#
111
112
CHAPTER 4 N IND EX A NA L YS IS
/
ÃÊÀiÃÕÌÃÊÊÌ
iÊiÝiVÕÌÊ«>ÊÊ}ÕÀiÊ{£äÊ>`ÊÌ
iÊvÜ}ÊÉ"Ê>`Êi>«Ãi`ÊÌi\ P]^ha#Ailhkuaa#*O_]j_kqjp-(hkce_]hna]`o5 ?LQpeia9-2io(ah]loa`peia9-,/io*
Figure 4-10. Execution plan with no index /
iÊ`>Ì>ÊÃÊÀiÌÕÀi`ÊLÞÊÃV>}ÊÌ
iÊVÕÃÌiÀi`Ê`iÝÊÜ
iÀiÊÌ
iÊ`>Ì>ÊÃÊÃÌÀi`®ÊÌÊv`ÊÌ
iÊ appropriate values where Caj`an9#B#°Ê/
iÊÌ
iÀÊ«iÀ>ÌÀÃÊÜÊLiÊVÛiÀi`ÊÊ
>«ÌiÀʰ®ÊvÊ you were to place an index on the column, like so: ?NA=PAEJ@ATET[Ailhkuaa[PaopKJDqi]jNaokqn_ao*Ailhkuaa$Caj`an% >`ÊÀÕÊÌ
iʵÕiÀÞÊ>}>]ÊÌ
iÊiÝiVÕÌÊ«>ÊÀi>ÃÊÌ
iÊÃ>i°Ê/
iÊ`>Ì>ÊÃÊÕÃÌÊÌÊÃiiVÌÛiÊ enough for the index to be used, let alone be useful. If instead you use a composite index that looks like this: ?NA=PAEJ@ATET[Ailhkuaa[PaopKJ Dqi]jNaokqn_ao*Ailhkuaa$Oe_gHa]raDkqno(Caj`an(I]nep]hOp]pqo% SEPD$@NKL[ATEOPEJC9KJ% and then rerun the query to see the execution plan in Figure 4-11 and the performance results, you get this: P]^ha#Ailhkuaa#*O_]j_kqjp-(hkce_]hna]`o2 ?LQpeia9,io(ah]loa`peia9/.io*
Figure 4-11. Execution plan with a composite index Now you’re doing better than you were with the clustered index scan. A nice clean Ej`at OaagÊ«iÀ>ÌÊÌ>iÃÊ>LÕÌÊ
>vÊÌ
iÊÌiÊÌÊ}>Ì
iÀÊÌ
iÊ`>Ì>°Ê/
iÊÀiÃÌÊÃÊëiÌÊÊÌ
iÊGauHkkgql operation. A GauHkkgql operation is commonly referred to as a bookmark lookup.
NNote You will learn more about bookmark lookups in Chapter 6.
C H A P T E R 4 N I N D E X A N A LY S I S
Although none of the columns in question would probably be selective enough on their own to make a decent index, together they provide enough selectivity for the optimizer to take advantage of the index offered. It is possible to attempt to force the query to use the first test index you created. If you drop the compound index, create the original again, and then modify the query as follows by using a query hint to force the use of the original index: OAHA?P& BNKIDqi]jNaokqn_ao*AilhkuaaSEPD$EJ@AT$ET[Ailhkuaa[Paop%% SDANAOe_gHa]raDkqno915 =J@Caj`an9#B# =J@I]nep]hOp]pqo9#I# then the results and execution plan shown in Figure 4-12, while similar, are not the same: P]^ha#Ailhkuaa#*O_]j_kqjp-(hkce_]hna]`o-0 ?LQpeia9,io(ah]loa`peia9.5io*
Figure 4-12. Execution plan when the index is chosen with a query hint You see the same index seek, but the number of reads has more than doubled, and the estimated costs within the execution plan have changed. Although forcing the optimizer to choose an index is possible, it clearly isn’t always an optimal approach. Another way to force a different behavior Ê-+Ê-iÀÛiÀÊÓäänÊÃÊÌ
iÊBKN?AOAAG query hint. BKN?AOAAG makes it so the optimizer will choose only Ej`atOaag operations. If the query were rewritten like this: OAHA?P& BNKIDqi]jNaokqn_ao*AilhkuaaSEPD$BKN?AOAAG% SDANAOe_gHa]raDkqno915 =J@Caj`an9#B# =J@I]nep]hOp]pqo9#I# which changes the I/O, execution time, and execution plan results yet again (Figure 4-13), you end up with these results: P]^ha#Ailhkuaa#*O_]j_kqjp-(hkce_]hna]`o-3, ?LQpeia9,io(ah]loa`peia9/5io* Ì}ÊÌ
iÊ«ÌÃÊvÊÌ
iÊ«ÌâiÀÊ>`ÊvÀV}ÊLi
>ÛÀÃÊV>ÊÊÃiÊÃÌÕ>ÌÃÊ
i«]Ê but frequently, as shown with the results here, an increase in execution time and the number of reads is not helpful.
113
114
CHAPTER 4 N IND EX A NA L YS IS
Figure 4-13. Forcing a Oaag operation using BKN?AOAAG query hint
NNote To make the best use of your indexes, it is highly recommended that you create the index on a column (or set of columns) with very high selectivity.
Examine the Column Data Type /
iÊdata type of an index matters. For example, an index search on integer keys is very fast because of the small size and easy arithmetic manipulation of the EJPACAN (or EJP) data type. You can also use other variations of integer data types (>ECEJP, OI=HHEJP, and PEJUEJP) for index columns, whereas string data types (?D=N, R=N?D=N, J?D=N, and JR=N?D=N) require a string match operation, which is usually costlier than an integer match operation. -Õ««ÃiÊÞÕÊÜ>ÌÊÌÊVÀi>ÌiÊ>Ê`iÝÊÊiÊVÕÊ>`ÊÞÕÊ
>ÛiÊÌÜÊV>``>ÌiÊVumns—one with an EJPACAN data type and the other with a ?D=N$0% data type. Even though the ÃâiÊvÊLÌ
Ê`>Ì>ÊÌÞ«iÃÊÃÊ{ÊLÞÌiÃÊÊ-+Ê-iÀÛiÀÊÓään]ÊÞÕÊÜÊÃÌÊ«ÀiviÀÊÌ
iÊEJPACAN data type `iݰÊÊ>ÌÊ>ÀÌ
iÌVÊ«iÀ>ÌÃÊ>ÃÊ>ÊiÝ>«i°Ê/
iÊÛ>ÕiÊ£ÊÊÌ
iÊ?D=N$0% data type is actually stored as 1 followed by three spaces, a combination of the following four bytes: ,t/1, ,t.,, ,t.,, and ,t.,°Ê/
iÊ *1Ê`iýÌÊÕ`iÀÃÌ>`Ê
ÜÊÌÊ«iÀvÀÊ>ÀÌ
iÌVÊ«iÀ>ÌÃÊÊ this data, and therefore it converts to an integer data type before the arithmetic operations, whereas the value 1 in an EJPACAN data type is saved as ,t,,,,,,,-°Ê/
iÊ *1ÊV>Êi>ÃÞÊ«iÀform arithmetic operations on this data. Of course, most of the time, you won’t have the simple choice between identically sized data types, allowing you to choose the more optimal type. Keep this information in mind when designing and building your indexes.
Consider Column Order An index key is sorted on the first column of the index and then subsorted on the next column ÜÌ
Êi>V
ÊÛ>ÕiÊvÊÌ
iÊ«ÀiÛÕÃÊVÕ°Ê/
iÊvÀÃÌÊVÕÊÊ>ÊV«Õ`Ê`iÝÊÃÊvÀiµÕiÌÞÊ referred to as the leading edgeÊvÊÌ
iÊ`iݰÊÀÊiÝ>«i]ÊVÃ`iÀÊ/>LiÊ{Ó°
C H A P T E R 4 N I N D E X A N A LY S I S
Table 4-2. Sample Table
c1
c2
1
1
2
1
3
1
1
2
2
2
3
2
If a composite index is created on the columns (_-, _.), then the index will be ordered as Ã
ÜÊÊ/>LiÊ{ΰ Table 4-3. Composite Index on Columns $_-(_.%
c1
c2
1
1
1
2
2
1
2
2
3
1
3
2
ÃÊÃ
ÜÊÊ/>LiÊ{Î]ÊÌ
iÊ`>Ì>ÊÃÊÃÀÌi`ÊÊÌ
iÊvÀÃÌÊVÕÊ_-) in the composite index. 7Ì
Êi>V
ÊÛ>ÕiÊvÊÌ
iÊvÀÃÌÊVÕ]ÊÌ
iÊ`>Ì>ÊÃÊvÕÀÌ
iÀÊÃÀÌi`ÊÊÌ
iÊÃiV`ÊVÕÊ_.). /
iÀivÀi]ÊÌ
iÊVÕÊÀ`iÀÊÊ>ÊV«ÃÌiÊ`iÝÊÃÊ>Ê«ÀÌ>ÌÊv>VÌÀÊÊÌ
iÊivviVÌÛiness of the index. You can see this by considering the following: Ê
UÊ ÕÊÕµÕiiÃÃ
Ê
UÊ ÕÊÜ`Ì
Ê
UÊ ÕÊ`>Ì>ÊÌÞ«i For example, suppose most of your queries on table p- are similar to the following:
OAHA?P&BNKIp-SDANA_.9-. OAHA?P&BNKIp-SDANA_.9-.=J@_-9-An index on (_., _-) will benefit both the queries. But an index on (_-, _.) will not be appropriate, because it will sort the data initially on column _-, whereas the first OAHA?P statement needs the data to be sorted on column _.. /ÊÕ`iÀÃÌ>`ÊÌ
iÊ«ÀÌ>ViÊvÊVÕÊÀ`iÀ}ÊÊ>Ê`iÝ]ÊVÃ`iÀÊÌ
iÊvÜ}Ê example. In the Lnk`q_pekj*Lnk`q_p table, there is a column for Op]j`]n`?kop and another for HeopLne_a°Ê Ài>ÌiÊ>ÊÌi«À>ÀÞÊ`iÝÊÊÌ
iÊÌ>LiÊiÊÌ
Ã\ ?NA=PAEJ@ATET[PaopKJLanokj*=``naoo$?epu(Lkop]h?k`a%
115
116
CHAPTER 4 N IND EX A NA L YS IS
A simple OAHA?P statement run against the table that will use this new index will look something like this: OAHA?P& BNKILanokj*=``naoo=O] SDANA?epu9#S]nnejcpkj# /
iÊÉ"Ê>`ÊiÝiVÕÌÊÌiÊvÀÊÌ
iʵÕiÀÞÊÃÊ>ÃÊvÜÃ\ P]^ha#=``naoo#*O_]j_kqjp-(hkce_]hna]`o-44 ?LQpeia9,io(ah]loa`peia9-23io* And the execution plan in Figure 4-14 shows the use of the index.
Figure 4-14. Execution plan for query against leading edge of index -]ÊÌ
ÃʵÕiÀÞÊÃÊÌ>}Ê>`Û>Ì>}iÊvÊÌ
iÊi>`}Êi`}iÊvÊÌ
i index to perform a Oaag operation to retrieve the data. If, instead of querying using the leading edge, you use another column in the index like the following query: OAHA?P& BNKILanokj*=``naoo=O] SDANA]*Lkop]h?k`a9#S=/3>D# then the results are as follows: P]^ha#=``naoo#*O_]j_kqjp-(hkce_]hna]`o-3/ And the execution plan is clearly different, as you can see in Figure 4-15.
Figure 4-15. Execution plan for query against inner columns
C H A P T E R 4 N I N D E X A N A LY S I S
/
iÊÀi>`ÃÊvÀÊÌ
iÊÃiV`ʵÕiÀÞÊ>ÀiÊÃ}
ÌÞÊÜiÀÊÌ
>ÊÌ
iÊvÀÃÌ]ÊLÕÌÊÜ
iÊÞÕÊÌ>iÊÌÊ account that the first query returned 86 rows worth of data and the second query returned only 31, you begin to see the difference between the Ej`atOaag operation in Figure 4-14 and the Ej`atO_]j operation in Figure 4-15. Also note that because it had to perform a scan, the optimizer marked the column as possibly missing an index. Finally, to see the order of the index really shine, change the query to this: OAHA?P]*=``naooE@ (]*?epu (]*Lkop]h?k`a BNKILanokj*=``naoo=O] SDANA]*?epu9#S]nnejcpkj# =J@]*Lkop]h?k`a9#S=/3>D# Executing this query will return the same 31 rows as the previous query, resulting in the following: P]^ha#=``naoo#*O_]j_kqjp-(hkce_]hna]`o. with the execution plan visible in Figure 4-16.
Figure 4-16. Execution plan using both columns /
iÊÀ>`V>ÊV
>}iÃÊÊÉ"Ê>`ÊiÝiVÕÌÊ«>ÊÀi«ÀiÃiÌÊÌ
iÊÀi>ÊÕÃiÊvÊ>ÊV«Õ`Ê `iÝ]ÊÌ
iÊVÛiÀ}Ê`iݰÊ/
ÃÊÃÊVÛiÀi`ÊÊ`iÌ>ÊÊÌ
iÊÃiVÌʺ ÛiÀ}Ê`iÝiûÊ>ÌiÀÊÊ the chapter. 7
i finished, drop the index: @NKLEJ@ATLanokj*=``naoo*ET[Paop
Consider the Type of Index InÊ-+Ê-iÀÛiÀ]ÊÞÕ have two main index types: clustered and nonclustered. Both types have a ÌÀiiÊÃÌÀÕVÌÕÀi°Ê/
iÊ>Ê`vviÀiViÊLiÌÜiiÊÌ
iÊÌÜÊÌÞ«iÃÊÃÊÌ
>ÌÊÌ
iÊi>vÊ«>}iÃÊÊ>ÊVÕÃtered index are the data pages of the table and are therefore in the same order as the data to Ü
V
ÊÌ
iÞʫ̰Ê/
ÃÊi>ÃÊÌ
>ÌÊÌ
iÊVÕÃÌiÀi`Ê`iÝÊÃÊÌ
iÊÌ>Li°ÊÃÊÞÕÊ«ÀVii`]ÊÞÕÊÜÊÃiiÊ that the difference at the leaf level between the two index types becomes very important when determining the type of index to use.
Clustered Indexes /
i leaf pages of a clustered index and the data pages of the table the index is on are one and the same. Because of this, table rows are physically sorted on the clustered index column, and since there can be only one physical order of the table data, a table can have only one clustered index.
117
118
CHAPTER 4 N IND EX A NA L YS IS
NTip When you create a primary key constraint, SQL Server automatically creates it as a unique clustered index on the primary key if one does not already exist and if it is not explicitly specified that the index should be a unique nonclustered index. This is not a requirement; it’s just default behavior. You can change it prior to creating the table.
Heap Tables As mentioned earlier in the chapter, a table with no clustered index is called a heap table°Ê/
iÊ data rows of a heap table are not stored in any particular order or linked to the adjacent pages ÊÌ
iÊÌ>Li°Ê/
ÃÊÕÀ}>âi`ÊÃÌÀÕVÌÕÀiÊvÊÌ
iÊ
i>«ÊÌ>LiÊÕÃÕ>ÞÊVÀi>ÃiÃÊÌ
iÊÛiÀ
i>`ÊvÊ accessing a large heap table when compared to accessing a large nonheap table (a table with a clustered index).
Relationship with Nonclustered Indexes /
iÀiÊà an interesting relationship between a clustered index and the nonclustered indexes in -+Ê-iÀÛiÀ°ÊÊ`iÝÊÀÜÊvÊ>ÊVÕÃÌiÀi`Ê`iÝÊVÌ>ÃÊ>Ê«ÌiÀÊÌÊÌ
iÊVÀÀië`}Ê`>Ì>Ê ÀÜÊvÊÌ
iÊÌ>Li°Ê/
ÃÊ«ÌiÀÊÃÊV>i`Ê>Êrow locator°Ê/
iÊÛ>ÕiÊvÊÌ
iÊÀÜÊV>ÌÀÊ`i«i`ÃÊÊ whether the data pages are stored in a heap or are clustered. For a nonclustered index, the row locator is a pointer to the NE@ for the data row in a heap. For a table with a clustered index, the row locator is the clustered index key value. ÀÊiÝ>«i]ÊÃ>ÞÊÞÕÊ
>ÛiÊ>Ê
i>«ÊÌ>LiÊÜÌ
ÊÊVÕÃÌiÀi`Ê`iÝ]Ê>ÃÊÃ
ÜÊÊ/>LiÊ{{° Table 4-4. Data Page for a Sample Table
RowID (Not a Real Column)
c1
c2
c3
1
A1
A2
A3
2
B1
B2
B3
A nonclustered index on column _- in a heap will cause the row locator for the index rows to contain a pointer to the corresponding data row in the database table, as shown in />LiÊ{x° Table 4-5. Nonclustered Index Page with No Clustered Index
c1
Row Locator
A1
Pointer to NE@ = 1
B1
Pointer to NE@ = 2
On creating a clustered index on column _., the row locator values of the nonclustered `iÝÊÀÜÃÊ>ÀiÊV
>}i`°Ê/
iÊiÜÊÛ>ÕiÊvÊÌ
iÊÀÜÊV>ÌÀÊÜÊVÌ>ÊÌ
iÊVÕÃÌiÀi`Ê`iÝÊiÞÊ Û>Õi]Ê>ÃÊÃ
ÜÊÊ/>LiÊ{Ȱ
C H A P T E R 4 N I N D E X A N A LY S I S
Table 4-6. Nonclustered Index Page with a Clustered Index on c2
c1
Row Locator
A1
A2
B1
B2
/ÊÛiÀvÞÊÌ
ÃÊ`i«i`iVÞÊLiÌÜiiÊ>ÊVÕÃÌiÀi`Ê>`Ê>ÊVÕÃÌiÀi`Ê`iÝ]Êi̽ÃÊVÃ`iÀÊ>Ê iÝ>«i°ÊÊÌ
iÊ`ÛiÌÕÀi7ÀÃÓäänÊ`>Ì>L>Ãi]ÊÌ
iÊÌ>LiÊ`^k*@]p]^]oaHkc contains no clustered index, just a nonclustered primary key. If a query is run against it like the following: OAHA?P`h*@]p]^]oaHkcE` (`h*LkopPeia BNKI`^k*@]p]^]oaHkc=O`h SDANA@]p]^]oaHkcE`9--1 then the execution will look like Figure 4-17.
Figure 4-17. Execution plan against a heap As you can see, the index was used in a Oaag operation. But because the data is stored separately from the nonclustered index, an additional operation, the NE@Hkkgql operation, is required in order to retrieve the data, which is then joined back to the information from the Ej`atOaag operation through a Jaopa`HkklÊ«iÀ>̰Ê/
ÃÊÃÊ>ÊV>ÃÃVÊiÝ>«iÊvÊÜ
>ÌÊÃÊ known as a bookmark lookup]ÊÜ
V
ÊÃÊiÝ«>i`ÊÊÀiÊ`iÌ>ÊÊÌ
iʺ iv}ÊÌ
iÊ >ÀÊ Õ«»ÊÃiV̰ÊÊÃ>ÀʵÕiÀÞÊÀÕÊ>}>ÃÌÊ>ÊÌ>LiÊÜÌ
Ê>ÊVÕÃÌiÀi`Ê`iÝÊÊ«>ViÊÜÊÊ like this: OAHA?P`*@al]npiajpE@ (`*Ik`ebea`@]pa BNKIDqi]jNaokqn_ao*@al]npiajp=O` SDANA`*@al]npiajpE@9-, Figure 4-18 shows this execution plan returned.
. Figure 4-18. Execution plan with a clustered index
119
120
CHAPTER 4 N IND EX A NA L YS IS
Although the primary key is used in the same way as the previous query, this time it’s against a clustered index. As you now know, this means the data is stored with the index, so the additional column doesn’t require a lookup operation to get the data. Everything is returned by the simple clustered Ej`atOaag operation. /Ê>Û}>ÌiÊvÀÊ>ÊVÕÃÌiÀi`Ê`iÝÊÀÜÊÌÊ>Ê`>Ì>ÊÀÜ]ÊÌ
ÃÊÀi>ÌÃ
«ÊLiÌÜiiÊÌ
iÊ two index types requires an additional indirection for navigating the B-tree structure of the VÕÃÌiÀi`Ê`iݰÊ7Ì
ÕÌÊÌ
iÊVÕÃÌiÀi`Ê`iÝ]ÊÌ
iÊÀÜÊV>ÌÀÊvÊÌ
iÊVÕÃÌiÀi`Ê`iÝÊÜÕ`Ê be able to navigate directly from the nonclustered index row to the data row in the base table. /
iÊ«ÀiÃiViÊvÊÌ
iÊVÕÃÌiÀi`Ê`iÝÊV>ÕÃiÃÊÌ
iÊ>Û}>ÌÊvÀÊÌ
iÊVÕÃÌiÀi`Ê`iÝÊÀÜÊÌÊ the data row to go through the B-tree structure of the clustered index, since the new row locator value points to the clustered index key. On the other hand, consider inserting an intermediate row in the clustered index key order or expanding the content of an intermediate row. For example, imagine a clustered index table containing four rows per page, with clustered index column values of 1, 2, 4, and 5. Adding a new row in the table with the clustered index value 3 will require space in the page between values 2 and 4. If enough space is not available in that position, a page split will occur on the data page (or clustered index leaf page). Even though the data page split will cause reloV>ÌÊvÊÌ
iÊ`>Ì>ÊÀÜÃ]ÊÌ
iÊVÕÃÌiÀi`Ê`iÝÊÀÜÊV>ÌÀÊÛ>ÕiÃÊii`ÊÌÊLiÊÕ«`>Ìi`°Ê/
iÃiÊ row locators continue to point to the same logical key values of the clustered index key, even though the data rows have physically moved to a different location. In the case of a data page ëÌ]ÊÌ
iÊÀÜÊV>ÌÀÃÊvÊÌ
iÊVÕÃÌiÀi`Ê`iÝiÃÊii`ÊÌÊLiÊÕ«`>Ìi`°Ê/
ÃÊÃÊ>Ê«ÀÌ>ÌÊ point, since tables often have a large number of nonclustered indexes.
NNote Page splits and their effect on performance are explained in more detail in Chapter 8.
Clustered Index Recommendations /
iÊÀi>ÌÃ
« between a clustered index and a nonclustered index imposes some considerations on the clustered index, which are explained in the sections that follow.
Create the Clustered Index First -Vi all nonclustered indexes hold clustered index keys within their index rows, the order of nonclustered and clustered index creation is very important. For example, if the nonclustered indexes are built before the clustered index is created, then the nonclustered index row locator will contain a pointer to the corresponding NE@ÊvÊÌ
iÊÌ>Li°Ê Ài>Ì}ÊÌ
iÊVÕÃÌiÀi`Ê`iÝÊ>ÌiÀÊ will modify all the nonclustered indexes to contain clustered index keys as the new row locator Û>Õi°Ê/
ÃÊivviVÌÛiÞÊÀiLÕ`ÃÊ>ÊÌ
iÊVÕÃÌiÀi`Ê`iÝið For the best performance, I recommend that you create the clustered index before you VÀi>ÌiÊ>ÞÊVÕÃÌiÀi`Ê`iݰÊ/
ÃÊ>ÜÃÊÌ
iÊVÕÃÌiÀi`Ê`iÝiÃÊÌÊ
>ÛiÊÌ
iÀÊÀÜÊV>ÌÀÊ ÃiÌÊÌÊÌ
iÊVÕÃÌiÀi`Ê`iÝÊiÞÃÊ>ÌÊÌ
iÊÌiÊvÊVÀi>̰Ê/
ÃÊ`iÃÊÌÊ
>ÛiÊ>ÞÊivviVÌÊÊÌ
iÊv>Ê performance, but rebuilding the indexes may be quite a large job.
C H A P T E R 4 N I N D E X A N A LY S I S
Keep Indexes Narrow -Vi all nonclustered indexes hold the clustered keys as their row locator, for the best performance keep the overall byte size of the clustered index as small as possible. If you create a wide clustered index, say ?D=N$1,,%]ÊÌ
ÃÊÜÊ>``ÊxääÊLÞÌiÃÊÌÊiÛiÀÞÊVÕÃÌiÀi`Ê`iݰÊ/
ÕÃ]Ê keep the number of columns in the clustered index to a minimum, and carefully consider the byte size of each column to be included in the clustered index. A column of the EJPACAN data type usually makes a good candidate for a clustered index, whereas a string data type column will be a less-than-optimal choice. /ÊÕ`iÀÃÌ>`ÊÌ
iÊeffect of a wide clustered index on a nonclustered index, consider this iÝ>«i°Ê Ài>ÌiÊ>ÊÃ>ÊÌiÃÌÊÌ>LiÊÜÌ
Ê>ÊVÕÃÌiÀi`Ê`iÝÊ>`Ê>ÊVÕÃÌiÀi`Ê`iÝÊ_hqop[ jkj_hqop*omh in the download): EB$OAHA?PK>FA?P[E@$#p-#%%EOJKPJQHH @NKLP=>HAp-7 CK ?NA=PAP=>HAp-$_-EJP(_.EJP%7 SEPDJqio =O$OAHA?P-=Oj QJEKJ=HH OAHA?Pj'BNKIJqio SDANAj8., % EJOANPEJPKp-$_-(_.% OAHA?Pj (j'BNKIJqio ?NA=PA?HQOPANA@EJ@ATe_hKJp-$_.%7 ?NA=PAJKJ?HQOPANA@EJ@ATej_hKJp-$_-%7 -ViÊÌ
iÊÌ>LiÊ
>ÃÊ>ÊVÕÃÌiÀi`Ê`iÝ]ÊÌ
iÊÀÜÊV>ÌÀÊvÊÌ
iÊVÕÃÌiÀi`Ê`iÝÊVÌ>ÃÊ Ì
iÊVÕÃÌiÀi`Ê`iÝÊiÞÊÛ>Õi°Ê/
iÀivÀi\
7`Ì
ÊvÊÌ
iÊVÕÃÌiÀi`Ê`iÝÊÀÜÊrÊ7`Ì
ÊvÊÌ
iÊVÕÃÌiÀi`Ê`iÝÊVÕʳÊ7`Ì
Ê vÊÌ
iÊVÕÃÌiÀi`Ê`iÝÊVÕÊrÊÃâiÊvÊ /Ê`>Ì>ÊÌÞ«iʳÊ-âiÊvÊ /Ê`>Ì>ÊÌÞ«i = 4 bytes + 4 bytes = 8 bytes 7Ì
ÊÌ
ÃÊÃ>ÊÃâiÊvÊ>ÊVÕÃÌiÀi`Ê`iÝÊÀÜ]Ê>ÊÌ
iÊÀÜÃÊV>ÊLiÊÃÌÀi`ÊÊiÊ`iÝÊ page. You can confirm this by querying against the index statistics (^]oa[ej`at*omh), as shown in Figure 4-19: OAHA?Pe*J]ia (e*pula[`ao_ (o*l]ca[_kqjp (o*na_kn`[_kqjp (o*ej`at[harah
121
122
CHAPTER 4 N IND EX A NA L YS IS
BNKIouo*ej`ataoe FKEJouo*`i[`^[ej`at[lduoe_]h[op]po$@>[E@$J#=`rajpqnaSkngo.,,4#%( K>FA?P[E@$J#`^k*p-#%( JQHH(JQHH(#@AP=EHA@#%=Oo KJe*ej`at[e`9o*ej`at[e` SDANAe*K>FA?P[E@9K>FA?P[E@$J#`^k*p-#%
Figure 4-19. Number of index pages for a narrow index /ÊÕ`iÀÃÌ>`ÊÌ
iÊivviVÌÊvÊ>ÊÜ`iÊVÕÃÌiÀi`Ê`iÝÊÊ>ÊVÕÃÌiÀi`Ê`iÝ]Ê`vÞ the data type of the clustered indexed column _. from EJP to ?D=N$1,,%: @NKLEJ@ATp-*e_h =HPANP=>HAp-=HPAN?KHQIJ_.?D=N$1,,% ?NA=PA?HQOPANA@EJ@ATe_hKJp-$_.% ,Õ}Êouoej`at[oaha_p.*omh again returns the result in Figure 4-20.
Figure 4-20. Number of index pages for a wide index You can see that a wide clustered index increases the width of the nonclustered index row size. Because of the large width of the nonclustered index row, one 8KB index page can’t accommodate all the index rows. Instead, two index pages will be required to store all 20 index rows. In the case of a large table, an unreasonable expansion in the size of the nonclustered indexes because of a large clustered index key size can significantly increase the number of pages of the nonclustered indexes. /
iÀivÀi]Ê>Ê>À}iÊVÕÃÌiÀi`Ê`iÝÊiÞÊÃâiÊÌÊÞÊ>vviVÌÃÊÌÃÊÜÊÜ`Ì
ÊLÕÌÊ>ÃÊÜ`iÃÊ >ÊVÕÃÌiÀi`Ê`iÝiÃÊÊÌ
iÊÌ>Li°Ê/
ÃÊVÀi>ÃiÃÊÌ
iÊÕLiÀÊvÊ`iÝÊ«>}iÃÊvÀÊ>ÊÌ
iÊ indexes on the table, increasing the logical reads and disk I/Os required for the indexes.
Rebuild the Clustered Index in a Single Step Because of the dependency of nonclustered indexes on the clustered index, rebuilding the clustered index as separate @NKLEJ@AT and ?NA=PAEJ@AT statements causes all the nonclusÌiÀi`Ê`iÝiÃÊÌÊLiÊÀiLÕÌÊÌÜVi°Ê/Ê>Û`ÊÌ
Ã]ÊÕÃiÊÌ
iÊ@NKL[ATEOPEJC clause of the ?NA=PA EJ@ATÊÃÌ>ÌiiÌÊÌÊÀiLÕ`ÊÌ
iÊVÕÃÌiÀi`Ê`iÝÊÊ>ÊÃ}iÊ>ÌVÊÃÌi«°Ê->ÀÞ]ÊÞÕÊV>Ê>ÃÊ use the @NKL[ATEOPEJC clause with a nonclustered index.
C H A P T E R 4 N I N D E X A N A LY S I S
When to Use a Clustered Index In certain situations, using a clustered index is very helpful. I discuss these in the sections that follow. Retrieving a Range of Data -ViÊÌ
iÊi>vÊ«>}ià of a clustered index and the data pages of the table are the same, the order of the clustered index column not only orders the rows of the clustered index but also physically orders the data rows. If the physical order of the data rows matches the order of data requested by a query, then the disk head can read all the rows sequentially, without much disk head movement. For example, if a query requests all the employee records belonging to the database group and the corresponding Ailhkuaao table has a clustered index on the Cnkql column, then all the relevant Ailhkuaa rows will be physically arranged together ÊÌ
iÊ`ðÊ/
ÃÊ>ÜÃÊÌ
iÊ`ÃÊ
i>`ÊÌÊÛiÊÌÊÌ
iÊ«ÃÌÊvÊÌ
iÊvÀÃÌÊÀÜÊÊÌ
iÊ`ÃÊ>`Ê then electronically read all the data sequentially with minimal physical movement of the disk head. On the other hand, if the rows are not sorted on the disk in the correct physical order, the disk head has to move randomly from one location to another to fetch all the relevant ÀÜðÊ-ViÊ«
ÞÃV>ÊÛiiÌÊvÊÌ
iÊ`ÃÊ
i>`ÊVÃÌÌÕÌiÃÊ>Ê>ÀÊ«ÀÌÊvÊÌ
iÊVÃÌÊvÊ>Ê disk operation, sorting the rows in the proper physical order on the disk (using a clustered index) optimizes the I/O cost. "iʺÀ>}i»ÊvÊ`>Ì>ÊÌ
>ÌÊÃÊ>VViÃÃi`ÊvÀiµÕiÌÞÊÊÀi>Ì>ÊÃÞÃÌiÃÊÃÊÌ
iÊvÀi}ÊiÞÊ vÀÊ>Ì
iÀÊÌ>Li°Ê/
ÃÊ`>Ì>]Ê`i«i`}ÊÊÌ
iÊ>VViÃÃÊiV
>ÃÃÊvÊÌ
iÊ>««V>Ì]ÊÃÊ>Ê great candidate for inclusion in the clustered index. Retrieving Presorted Data
ÕÃÌiÀi`Ê`iÝià are particularly efficient when the data retrieval needs to be sorted. If you create a clustered index on the column or columns that you may need to sort by, then the rows will be physically stored in that order, eliminating the overhead of sorting the data after it is retrieved. i̽ÃÊÃiiÊÌ
ÃÊÊ>VÌ°Ê Ài>ÌiÊ>ÊÌiÃÌÊÌ>LiÊ>ÃÊvÜÃÊ_na]pa[oknp*omh in the download): EB$OAHA?PK>FA?P[E@$#k`#% %EOJKPJQHH @NKLP=>HA`^k*k`7 CK OAHA?P& EJPK`^k*k` BNKILqn_d]oejc*Lqn_d]oaKn`an@ap]eh=Olk` /
iÊiÜÊÌ>LiÊk` is created with data only. It doesn’t have any indexes. You can verify the indexes on the table by executing the following, which returns nothing: ol[dahlej`at`^k*k`
123
124
CHAPTER 4 N IND EX A NA L YS IS
/ÊÕ`iÀÃÌ>`ÊÌ
iÊÕÃiÊvÊ>ÊVÕÃÌiÀi`Ê`iÝ]ÊviÌV
Ê>Ê>À}iÊÀ>}iÊvÊÀÜÃÊÀ`iÀi`ÊÊ>Ê certain column: OAHA?P& BNKI`^k*k` SDANAk`*Lnk`q_pE@>APSAAJ1,,=J@1-, KN@AN>Uk`*Lnk`q_pE@ You can obtain the cost of executing this query (without any indexes) from the OP=PEOPE?O EK output: P]^ha#k`#*O_]j_kqjp-(hkce_]hna]`o35 /Ê«ÀÛiÊÌ
iÊ«iÀvÀ>ViÊvÊÌ
ÃʵÕiÀÞ]ÊÞÕÊÃ
Õ`ÊVÀi>ÌiÊ>Ê`iÝÊÊÌ
iÊSDANA clause VÕ°Ê/
ÃʵÕiÀÞÊÀiµÕÀiÃÊLÌ
Ê>ÊÀ>}iÊvÊÀÜÃÊ>`Ê>ÊÃÀÌi`ÊÕÌ«Õ̰Ê/
iÊÀiÃÕÌÊÃiÌÊÀiµÕÀiiÌÊvÊÌ
ÃʵÕiÀÞÊiiÌÃÊÌ
iÊÀiVi`>ÌÃÊvÀÊ>ÊVÕÃÌiÀi`Ê`iݰÊ/
iÀivÀi]ÊVÀi>ÌiÊ>Ê clustered index as follows, and reexamine the cost of the query: ?NA=PA?HQOPANA@EJ@ATe-KJk`$Lnk`q_pE@% 7
iÊÞÕÊÀÕÊÌ
iʵÕiÀÞÊ>}>]ÊÌ
iÊÀiÃÕÌ>ÌÊVÃÌÊvÊÌ
iʵÕiÀÞÊÜÌ
Ê>ÊVÕÃÌiÀi`Ê`iÝ®ÊÃÊ>ÃÊ follows: P]^ha#k`#*O_]j_kqjp-(hkce_]hna]`o4
Ài>Ì}ÊÌ
iÊVÕÃÌiÀi`Ê`iÝÊÀi`ÕVi`ÊÌ
iÊÕLiÀÊvÊ}V>ÊÀi>`ÃÊ>`ÊÌ
iÀivÀiÊÃ
Õ`Ê contribute to the query performance improvement. On the other hand, if you create a nonclustered index (instead of a clustered index) on Ì
iÊV>``>ÌiÊVÕ]ÊÌ
iÊÌ
iʵÕiÀÞÊ«iÀvÀ>ViÊ>ÞÊLiÊ>vviVÌi`Ê>`ÛiÀÃiÞ°Êi̽ÃÊÛiÀvÞÊÌ
iÊ effect of a nonclustered index in this case: @NKLEJ@ATk`*e?NA=PAJKJ?HQOPANA@EJ@ATe-kj`^k*k`$Lnk`q_pE@% /
iÊÀiÃÕÌ>ÌÊVÃÌÊvÊÌ
iʵÕiÀÞÊÜÌ
Ê>ÊVÕÃÌiÀi`Ê`iÝ®ÊÃÊ>ÃÊvÜÃ\ P]^ha#k`#*O_]j_kqjp-(hkce_]hna]`o43 /
iÊVÕÃÌiÀi`Ê`iÝÊÃ}vV>ÌÞÊVÀi>ÃiÃÊÌ
iÊÕLiÀÊvÊ}V>ÊÀi>`Ã]Ê>vviVÌ}ÊÌ
iÊ query performance accordingly. Drop the test table when you’re done: @NKLP=>HA`^k*k`
NNote For a query that retrieves a large range of rows and/or an ordered output, a clustered index is usually a better choice than a nonclustered index.
C H A P T E R 4 N I N D E X A N A LY S I S
When Not to Use a Clustered Index In certain situations, you are better off not using a clustered index. I discuss these in the sections that follow. Frequently Updatable Columns If the clustered index columns are frequently updated, this will cause the row locator of all the nonclustered indexes to be updated accordingly, significantly increasing the cost of the ÀiiÛ>ÌÊ>VÌʵÕiÀiðÊ/
ÃÊ>ÃÊ>vviVÌÃÊ`>Ì>L>ÃiÊVVÕÀÀiVÞÊLÞÊblocking all other queries referring to the same part of the table and the nonclustered indexes during that period. /
iÀivÀi]Ê>Û`ÊVÀi>Ì}Ê>ÊVÕÃÌiÀi`Ê`iÝÊÊVÕÃÊÌ
>ÌÊ>ÀiÊ
}
ÞÊÕ«`>Ì>Li°
NNote Chapter 12 covers blocking in more depth.
/ÊÕ`iÀÃÌ>`Ê
ÜÊÌ
iÊVÃÌÊvÊ>ÊQL@=PA statement that modifies only a clustered key column is increased by the presence of nonclustered indexes on the table, consider the folÜ}ÊiÝ>«i°Ê/
iÊO]hao*Ola_e]hKbbanLnk`q_p table has a composite clustered index on the primary key, which is also the foreign key from two different tables; this is a classic manyto-many join. In this example, I update one of the two columns using the following statement (note the use of the transaction to keep the test data intact): >ACEJPN=J OAPOP=PEOPE?OEKKJ7 QL@=PAo]hao*Ola_e]hKbbanLnk`q_p OAPLnk`q_pE@9/01 SDANAOla_e]hKbbanE@9=J@lnk`q_pe`93.,7 OAPOP=PEOPE?OEKKBB7 NKHH>=?GPN=J /
i OP=PEOPE?OEK output shows the reads necessary: P]^ha#Lnk`q_p#*O_]j_kqjp,(hkce_]hna]`o. P]^ha#O]haoKn`an@ap]eh#*O_]j_kqjp-(hkce_]hna]`o-.0, P]^ha#Ola_e]hKbbanLnk`q_p#*O_]j_kqjp,(hkce_]hna]`o-, If you added a nonclustered index to the table, you would see the reads increase: ?NA=PAJKJ?HQOPANA@EJ@ATetPaopKJo]hao*Ola_e]hKbbanLnk`q_p$Ik`ebea`@]pa% 7
iÊÞÕÊÀÕÊÌ
iÊÃ>iʵÕiÀÞÊ>}>]ÊÌ
iÊÕÌ«ÕÌÊvÊOP=PEOPE?OEK changes for the Ola_e]hKbbanLnk`q_p table: P]^ha#Lnk`q_p#*O_]j_kqjp,(hkce_]hna]`o. P]^ha#O]haoKn`an@ap]eh#*O_]j_kqjp-(hkce_]hna]`o-.0, P]^ha#Ola_e]hKbbanLnk`q_p#*O_]j_kqjp,(hkce_]hna]`o-5
125
126
CHAPTER 4 N IND EX A NA L YS IS
As you can see, the number of reads caused by the update of the clustered index is increased with the addition of the nonclustered index. Be sure to drop the index: @NKLEJ@ATO]hao*Ola_e]hKbbanLnk`q_p*etPaop
Wide Keys -ViÊ>ÊVÕÃÌiÀi`Ê`iÝià hold the clustered keys as their row locator, for performance reasons you should avoid creating a clustered index on a very wide column (or columns) or on too many columns. As explained in the preceding section, a clustered index must be as narrow as possible. Too Many Concurrent Inserts in Sequential Order If you want to add many new rows concurrently, then it may be better for performance to distribute them across the data pages of the table. However, if you add all the rows in the same order as that imposed by the clustered index, then all the inserts will be attempted on the last «>}iÊvÊÌ
iÊÌ>Li°Ê/
ÃÊ>ÞÊV>ÕÃiÊ>Ê
Õ}iʺ
ÌÊë̻ÊÊÌ
iÊVÀÀië`}ÊÃiVÌÀÊvÊÌ
iÊ`Ã°Ê /Ê>Û`ÊÌ
ÃÊ`ÃÊ
ÌÊëÌ]ÊÞÕÊÃ
Õ`ÊÌÊ>ÀÀ>}iÊÌ
iÊ`>Ì>ÊÀÜÃÊÊÌ
iÊÃ>iÊÀ`iÀÊ>ÃÊÌ
iÀÊ «
ÞÃV>ÊV>ÌðÊ/
iÊÃiÀÌÃÊV>ÊLiÊÀ>`âi`ÊÌ
ÀÕ}
ÕÌÊÌ
iÊÌ>LiÊLÞÊVÀi>Ì}Ê>ÊVÕÃÌiÀi`Ê index on another column that doesn’t arrange the rows in the same order as that of the new ÀÜðÊ/
ÃÊÃÊ>ÊÃÃÕiÊÞÊÜÌ
Ê>Ê>À}iÊÕLiÀÊvÊÃÕÌ>iÕÃÊÃiÀÌð /
iÀiÊÃÊ>ÊV>Ûi>ÌÊÌÊÌ
ÃÊÀiVi`>̰ÊÜ}ÊÃiÀÌÃÊÊÌ
iÊLÌÌÊvÊÌ
iÊÌ>LiÊ«Àivents page splits on the intermediate pages that are required to accommodate the new rows in those pages. If the number of concurrent inserts is low, then ordering the data rows (using a clustered index) in the order of the new rows will prevent intermediate page splits. However, if the disk hot spot becomes a performance bottleneck, then new rows can be accommodated in intermediate pages without causing page splits by reducing the fill factor of the table. In addiÌ]ÊÌ
iʺ
̻ʫ>}iÃÊÜÊLi in memory, which also benefits performance.
NNote Chapter 8 covers the fill factor in depth.
Nonclustered Indexes A nonclustered index does not affect the order of the data in the table pages, because the leaf pages of a nonclustered index and the data pages of the table are separate. A pointer (the row locator) is required to navigate from an index row to the data row. As you learned in the i>ÀiÀʺ ÕÃÌiÀi`Ê`iÝiûÊÃiVÌ]ÊÌ
iÊÃÌÀÕVÌÕÀiÊvÊÌ
iÊÀÜÊV>ÌÀÊ`i«i`ÃÊÊÜ
iÌ
iÀÊÌ
iÊ data pages are stored in a heap or a clustered index. For a heap, the row locator is a pointer to the NE@ for the data row; for a table with a clustered index, the row locator is the clustered index key.
C H A P T E R 4 N I N D E X A N A LY S I S
Nonclustered Index Maintenance /
iÊÀÜÊV>ÌÀÊÛ>Õi of the nonclustered indexes continues to have the same clustered index value, even when the clustered index rows are physically relocated. /Ê«ÌâiÊÌ
ÃÊ>Ìi>ViÊVÃÌ]Ê-+Ê-iÀÛiÀÊ>``ÃÊ>Ê«ÌiÀÊÌÊÌ
iÊ`Ê`>Ì>Ê«>}iÊÌÊ point to the new data page after a page split, instead of updating the row locator of all the relevant nonclustered indexes. Although this reduces the maintenance cost of the nonclustered indexes, it increases the navigation cost from the nonclustered index row to the data ÀÜ]ÊÃViÊ>ÊiÝÌÀ>ÊÊÃÊ>``i`ÊLiÌÜiiÊÌ
iÊ`Ê`>Ì>Ê«>}iÊ>`ÊÌ
iÊiÜÊ`>Ì>Ê«>}i°Ê/
iÀivÀi]Ê having a clustered index as the row locator decreases this overhead associated with the nonclustered index.
Defining the Bookmark Lookup 7
iÊ>ʵÕiÀÞ requests columns that are not part of the nonclustered index chosen by the «ÌâiÀ]Ê>ÊÕ«ÊÃÊÀiµÕÀi`°Ê/
ÃÊ>ÞÊLiÊ>ÊiÞÊÕ«ÊÜ
iÊ}}Ê>}>ÃÌÊ>ÊVÕÃÌiÀi`Ê`iÝÊ or an NE@ÊÕ«ÊÜ
iÊ«iÀvÀi`Ê>}>ÃÌÊ>Ê
i>«°Ê/
iÊVÊÌiÀÊvÀÊÌ
iÃiÊÕ«ÃÊViÃÊ from the old definition name, bookmark lookup°Ê/
iÊÕ«ÊviÌV
iÃÊÌ
iÊVÀÀië`}Ê`>Ì>Ê row from the table by following the row locator value from the index row, requiring a logical read on the data page besides the logical read on the index page. However, if all the columns required by the query are available in the index itself, then access to the data page is not ÀiµÕÀi`°Ê/
ÃÊÃÊÜÊ>ÃÊ>Êcovering index. /
iÃiÊL>ÀÊÕ«ÃÊ>ÀiÊÌ
iÊÀi>ÃÊÌ
>ÌÊ>À}iÊÀiÃÕÌÊÃiÌÃÊ>ÀiÊLiÌÌiÀÊÃiÀÛi`ÊÜÌ
Ê>Ê clustered index. A clustered index doesn’t require a bookmark lookup, since the leaf pages and data pages for a clustered index are the same.
NNote Chapter 6 covers bookmark lookups in more detail.
Nonclustered Index Recommendations -ViÊ>ÊÌ>Li can have only one clustered index, you can use the flexibility of multiple nonclustered indexes to help improve performance. I explain the factors that decide the use of a nonclustered index in the following sections.
When to Use a Nonclustered Index A nonclustered index is most useful when all you want to do is retrieve a small number of rows from a large table. As the number of rows to be retrieved increases, the overhead cost of the L>ÀÊÕ«ÊÀÃiÃÊ«À«ÀÌ>ÌiÞ°Ê/ÊÀiÌÀiÛiÊ>ÊÃ>ÊÕLiÀÊvÊÀÜÃÊvÀÊ>ÊÌ>Li]ÊÌ
iÊ indexed column should have a very high selectivity.
127
128
CHAPTER 4 N IND EX A NA L YS IS
Furthermore, there will be indexing requirements that won’t be suitable for a clustered index, as explained inÊÌ
iʺ ÕÃÌiÀi`Ê`iÝiûÊÃiVÌ\ Ê
UÊ ÀiµÕiÌÞÊÕ«`>Ì>LiÊVÕÃ
Ê
UÊ 7`iÊiÞÃ
In these cases, you can use a nonclustered index, since, unlike a clustered index, it doesn’t affect other indexes in the table. A nonclustered index on a frequently updatable column isn’t >ÃÊVÃÌÞÊ>ÃÊ
>Û}Ê>ÊVÕÃÌiÀi`Ê`iÝÊÊÌ
>ÌÊVÕ°Ê/
iÊQL@=PA operation on a nonclustered index is limited to the base table and the nonclustered index. It doesn’t affect any other nonVÕÃÌiÀi`Ê`iÝiÃÊÊÌ
iÊÌ>Li°Ê->ÀÞ]Ê>ÊVÕÃÌiÀi`Ê`iÝÊÊ>ÊÜ`iÊVÕÊÀÊÃiÌÊvÊ columns) doesn’t increase the size of any other index, unlike that with a clustered index. However, remain cautious, even while creating a nonclustered index on a highly updatable column or a wide column (or set of columns), since this can increase the cost of action queries, as explained earlier in the chapter.
NTip A nonclustered index can also help resolve blocking and deadlock issues. I cover this in more depth in Chapters 12 and 13.
When Not to Use a Nonclustered Index Nonclustered indexesÊ>ÀiÊÌÊÃÕÌ>LiÊvÀʵÕiÀiÃÊÌ
>ÌÊÀiÌÀiÛiÊ>Ê>À}iÊÕLiÀÊvÊÀÜðÊ-ÕV
Ê queries are better served with a clustered index, which doesn’t require a separate bookmark Õ«ÊÌÊÀiÌÀiÛiÊ>Ê`>Ì>ÊÀܰÊ-ViÊ>ÊL>ÀÊÕ«ÊÀiµÕÀiÃÊ>Ê>``Ì>Ê}V>ÊÀi>`ÊÊ the data page besides the logical read on the nonclustered index page, the cost of a query using >ÊVÕÃÌiÀi`Ê`iÝÊVÀi>ÃiÃÊÃ}vV>ÌÞÊvÀÊ>Ê>À}iÊÕLiÀÊvÊÀÜðÊ/
iÊ-+Ê-iÀÛiÀʵÕiÀÞÊ optimizer takes this cost into effect and accordingly discards the nonclustered index when retrieving a large result set. If your requirement is to retrieve a large result set from a table, then having a nonclustered index on the filter criterion (or the join criterion) column will not be useful unless you use a special type of nonclustered index called a covering index. I describe this index type in detail later in the chapter.
Clustered vs. Nonclustered Indexes /
iÊ> considerations in choosing between a clustered and a nonclustered index are as follows: Ê
UÊ
ÕLiÀÊvÊÀÜÃÊÌÊLiÊÀiÌÀiÛi`
Ê
UÊ >Ì>À`iÀ}ÊÀiµÕÀiiÌ
Ê
UÊ `iÝÊiÞÊÜ`Ì
C H A P T E R 4 N I N D E X A N A LY S I S
Ê
UÊ ÕÊÕ«`>ÌiÊvÀiµÕiVÞ
Ê
UÊ >ÀÊVÃÌ
Ê
UÊ ÞÊ`ÃÊ
ÌÊëÌÃ
Benefits of a Clustered Index over a Nonclustered Index 7
iÊ`iV`}ÊÕ«Ê> type of index on a table with no indexes, the clustered index is usually the preferred choice. Because the index page and the data pages are the same, the clustered index doesn’t have to jump from the index row to the base row as required in the case of a nonclustered index. /ÊÕ`iÀÃÌ>`Ê
ÜÊ>ÊVÕÃÌiÀi` index can outperform a nonclustered index in most circumstances, even in retrieving small number of rows, create a test table with a high selectivity for one column (_hqopan[^aja*omh in the download): EB$OAHA?PK>FA?P[E@$#p-#%%EOJKPJQHH @NKLP=>HApCK ?NA=PAP=>HAp-$_-EJP(_.EJP%7 OAHA?PPKL-,,,, E@AJPEPU$EJP(-(-%=Oj EJPKJqio BNKII]opan*`^k*Ouo?khqijoo_(I]opan*`^k*Ouo?khqijoo_.7 EJOANPEJPKp$_-(_.% OAHA?Pj(. BNKIJqio @NKLP=>HAJqio7 /
iÊvÜ}ÊOAHA?P statement fetches only 1 out of 10,000 rows from the table: OAHA?P_(_. BNKIpSDANA_-9with the graphical execution plan shown in Figure 4-21 and the output of OAPOP=PEOPE?OEK as follows: P]^ha#p-#*O_]j_kqjp-(hkce_]hna]`o..
Figure 4-21. Execution plan with no index
129
130
CHAPTER 4 N IND EX A NA L YS IS
Ã`iÀ}ÊÌ
iÊÃ>ÊÃâiÊvÊÌ
iÊÀiÃÕÌÊÃiÌÊÀiÌÀiÛi`ÊLÞÊÌ
iÊ«ÀiVi`}ÊOAHA?P statement, a nonclustered column on _- can be a good choice: ?NA=PAJKJ?HQOPANA@EJ@ATej_hKJp-$_-% You can run the same OAHA?PÊV>`Ê>}>°Ê-ViÊÀiÌÀiÛ}Ê>ÊÃ>ÊÕLiÀÊvÊÀÜÃÊ through a nonclustered index is more economical than a table scan, the optimizer used the nonclustered index on column _-]Ê>ÃÊÃ
ÜÊÊ}ÕÀiÊ{ÓÓ°Ê/
iÊÕLiÀÊvÊ}V>ÊÀi>`ÃÊ reported by OP=PEOPE?OEK is as follows: P]^ha#p-#*O_]j_kqjp-(hkce_]hna]`o/
Figure 4-22. Execution plan with a nonclustered index Even though retrieving a small result set using a column with high selectivity is a good pointer toward creating a nonclustered index on the column, a clustered index on the same VÕÊV>ÊLiÊiµÕ>ÞÊLiivV>ÊÀÊiÛiÊLiÌÌiÀ°Ê/ÊiÛ>Õ>ÌiÊ
ÜÊÌ
iÊVÕÃÌiÀi`Ê`iÝÊV>ÊLiÊ more beneficial than the nonclustered index, create a clustered index on the same column: ?NA=PA?HQOPANA@EJ@ATe_hKJp-$_-% ,ÕÊÌ
iÊÃ>iÊOAHA?P command again. From the resultant execution plan (see Figure 4-22) of the preceding OAHA?P statement, you can see that the optimizer used the clustered index ÃÌi>`ÊvÊÌ
iÊVÕÃÌiÀi`Ê`iÝ®ÊiÛiÊvÀÊ>ÊÃ>ÊÀiÃÕÌÊÃḭÊ/
iÊÕLiÀÊvÊ}V>ÊÀi>`ÃÊvÀÊ the OAHA?P statement decreased from three to two (Figure 4-23): P]^ha#p-#*O_]j_kqjp-(hkce_]hna]`o.
Figure 4-23. Execution plan with a clustered index
NNote Even though a clustered index can outperform a nonclustered index in many instances of data retrieval, a table can have only one clustered index. Therefore, reserve the clustered index for a situation in which it can be of the greatest benefit.
C H A P T E R 4 N I N D E X A N A LY S I S
Benefits of a Nonclustered Index over a Clustered Index As you learned in the previous section, a nonclustered index is preferred over a clustered index in the following situations: Ê
UÊ 7
iÊÌ
iÊ`iÝÊiÞÊÃâiÊÃÊ>À}i°
Ê
UÊ /Ê>Û`ÊÌ
iÊÛiÀ
i>`ÊVÃÌÊ>ÃÃV>Ìi`ÊÜÌ
Ê>ÊVÕÃÌiÀi`Ê`iÝÊÃViÊÀiLÕ`}ÊÌ
iÊ clustered index rebuilds all the nonclustered indexes of the table.
Ê
UÊ /ÊÀiÃÛi blocking by having a database reader work on the pages of a nonclustered index, while a database writer modifies other columns (not included in the nonclustered index) in the data page. In this case, the writer working on the data page won’t block a reader that can get all the required column values from the nonclustered index ÜÌ
ÕÌÊ
ÌÌ}ÊÌ
iÊL>ÃiÊÌ>Li°Ê½ÊiÝ«>ÊÌ
ÃÊÊ`iÌ>ÊÊ
>«ÌiÀÊ£Ó°
Ê
UÊ 7
iÊ>ÊÌ
iÊVÕÃÊvÀÊ>ÊÌ>Li®ÊÀiviÀÀi`ÊÌÊLÞÊ>ʵÕiÀÞÊV>ÊLiÊÃ>viÞÊ>VVdated in the nonclustered index itself, as explained in this section.
As already established, the data-retrieval performance when using a nonclustered index is generally poorer than that when using a clustered index, because of the cost associated in jumping from the nonclustered index rows to the data rows in the base table. In cases where the jump to the data rows is not required, the performance of a nonclustered index should be ÕÃÌÊ>ÃÊ}`Ê>ÃpÀÊiÛiÊLiÌÌiÀÊÌ
>p>ÊVÕÃÌiÀi`Ê`iݰÊ/
ÃÊÃÊ«ÃÃLiÊvÊÌ
iÊVÕÃÌiÀi`Ê index key includes all the columns required from the table. /ÊÕ`iÀÃÌ>`ÊÌ
iÊÃÌÕ>ÌÊÜ
iÀiÊ>ÊVÕÃÌiÀi`Ê`iÝÊV>ÊÕÌ«iÀvÀÊ>ÊVÕÃÌiÀi`Ê index, consider the following example. Assume for our purposes that you need to examine the VÀi`ÌÊV>À`ÃÊÌ
>ÌÊ>ÀiÊiÝ«À}ÊLiÌÜiiÊÌ
iÊÌ
ÃÊvÊÕiÊ>`Ê-i«ÌiLiÀÊvÊÓään°Ê9ÕÊ>ÞÊ have a query that returns a large number of rows and looks like this: OAHA?P__*?na`ep?]n`E@ (__*?]n`Jqi^an (__*AtlIkjpd (__*AtlUa]n BNKIO]hao*?na`ep?]n`__ SDANA__*AtlIkjpd>APSAAJ2=J@5 =J@__*AtlUa]n9.,,4 KN@AN>U__*AtlIkjpd /
iÊvÜ}Ê>ÀiÊÌ
iÊÉ"ÊÀiÃÕÌÃ]Ê>`Ê}ÕÀiÊ{Ó{ÊÃ
ÜÃÊÌ
iÊiÝiVÕÌÊ«>\ P]^ha#?na`ep?]n`#*O_]j_kqjp-(hkce_]hna]`o-45
Figure 4-24. Execution plan scanning the clustered index /
iÊVÕÃÌiÀi`Ê`iÝÊÃÊÊÌ
iÊ«À>ÀÞÊiÞ]Ê>`Ê>Ì
Õ}
ÊÃÌÊ>VViÃÃÊ>}>ÃÌÊÌ
iÊÌ>LiÊ>ÞÊ be through that key, making the index useful, the cluster in this instance is just not performing
131
132
CHAPTER 4 N IND EX A NA L YS IS
in the way you need. Although you could expand the definition of the index to include all the other columns in the query, they’re not really needed to make the cluster function, and they would interfere with the operation of the primary key. In this instance, creating a different index is in order: ?NA=PAJKJ?HQOPANA@EJ@ATetPaopKJO]hao*?na`ep?]n`$AtlIkjpd(AtlUa]n(?]n`Jqi^an% Now when the query is run again, this is the result: P]^ha#?na`ep?]n`#*O_]j_kqjp-(hkce_]hna]`o/, Figure 4-25 shows the corresponding execution plan.
Figure 4-25. Execution plan with a nonclustered index In this case, the OAHA?P statement doesn’t include any column that requires a jump from the nonclustered index page to the data page of the table, which is what usually makes a nonVÕÃÌiÀi`Ê`iÝÊVÃÌiÀÊÌ
>Ê>ÊVÕÃÌiÀi`Ê`iÝÊvÀÊ>Ê>À}iÊÀiÃÕÌÊÃiÌÊ>`ÉÀÊÃÀÌi`ÊÕÌ«Õ̰Ê/
ÃÊ kind of nonclustered index is called a covering index.
i>ÊÕ«ÊÌ
iÊ`iÝÊ>vÌiÀ the testing is done: @NKLEJ@ATO]hao*?na`ep?]n`*etPaop
Advanced Indexing Techniques A few of the more advanced indexing techniques that you can also consider are as follows: Ê
UÊ Covering indexes\Ê/
iÃi were introduced in the preceding section.
Ê
UÊ Index intersections\Ê1Ãi multiple nonclustered indexes to satisfy all the column requirements (from a table) for a query.
Ê
UÊ Index joins\Ê1Ãi the index intersection and covering index techniques to avoid hitting the base table.
Ê
UÊ Filtered indexes\Ê/ÊLi able to index fields with odd data distributions or sparse columns, a filter can be applied to an index so that it indexes only some data.
Ê
UÊ Indexed views\Ê/
à materializes the output of a view on disk. I cover these topics in more detail in the following sections.
Covering Indexes A covering index is a nonclusteredÊ`iÝÊLÕÌÊÕ«Ê>ÊÌ
iÊVÕÃÊÀiµÕÀi`ÊÌÊÃ>ÌÃvÞÊ>Ê-+Ê query without going to the base table. If a query encounters an index and does not need to refer to the underlying data table at all, then the index can be considered a covering index.
C H A P T E R 4 N I N D E X A N A LY S I S
For example, in the following OAHA?P statement, irrespective of where the columns are referred, all the columns (Op]paLnkrej_aE` and Lkop]h?k`a) should be included in the nonclustered index to cover the query fully: OAHA?P]*Lkop]h?k`a BNKILanokj*=``naoo=O] SDANA]*Op]paLnkrej_aE@90. /
iÊ>ÊÌ
iÊÀiµÕÀi`Ê`>Ì>ÊvÀÊÌ
iʵÕiÀÞÊV>ÊLiÊLÌ>i`ÊvÀÊÌ
iÊVÕÃÌiÀi`Ê`iÝÊ «>}i]ÊÜÌ
ÕÌÊ>VViÃÃ}ÊÌ
iÊ`>Ì>Ê«>}i°Ê/
ÃÊ
i«ÃÊ-+Ê-iÀÛiÀÊÃ>ÛiÊ}V>Ê>`Ê«
ÞÃV>ÊÀi>`ðÊvÊ you run the query, you’ll get the following I/O and execution time as well as the execution plan in Figure 4-26: P]^ha#=``naoo#*O_]j_kqjp-(hkce_]hna]`o-4 ?LQpeia9-1io(ah]loa`peia9/.io*
Figure 4-26. Query without a covering index Here you have a classic bookmark lookup with the GauHkkgql operator pulling the Lkop]h?k`a data from the clustered index and joining it with the Ej`atOaag operator against the ET[=``naoo[Op]paLnkrej_aE` index. Although you can re-create the index with both key columns, another way to make an index a covering index is to use the new EJ?HQ@AÊ«iÀ>ÌÀ°Ê/
ÃÊÃÌÀiÃÊ`>Ì>ÊÜÌ
ÊÌ
iÊ`iÝÊÜÌ
ÕÌÊV
>}}ÊÌ
iÊÃÌÀÕVÌÕÀiÊvÊÌ
iÊ`iÝÊÌÃiv°Ê1ÃiÊÌ
iÊvÜ}ÊÌÊÀiVÀi>ÌiÊÌ
iÊ`iÝ\ ?NA=PAJKJ?HQOPANA@EJ@ATWET[=``naoo[Op]paLnkrej_aE@YKJ WLanokjY*W=``naooY$WOp]paLnkrej_aE@Y=O?% EJ?HQ@A$Lkop]h?k`a% SEPD$ @NKL[ATEOPEJC9KJ% If you rerun the query, the execution plan (Figure 4-27), I/O, and execution time change: P]^ha#=``naoo#*O_]j_kqjp-(hkce_]hna]`o. ?LQpeia9,io(ah]loa`peia9,io*
Figure 4-27. Query with a covering index
133
134
CHAPTER 4 N IND EX A NA L YS IS
/
iÊÀi>`ÃÊ
>ÛiÊ`À««i`ÊvÀÊ£nÊÌÊÓ]Ê>`ÊÌ
iÊiÝiVÕÌÊ«>ÊÃÊÕÃÌÊ>LÕÌÊ>ÃÊëiÊ>ÃÊ̽ÃÊ possible to be; it’s a single Ej`atOaag operation against the new and improved index, which is now covering. A covering index is a useful technique for reducing the number of logical reads of a query. Adding columns using the EJ?HQ@A statement makes this functionality easier to achieve without adding to the number of columns in an index or the size of the index key since the included columns are stored only at the leaf level of the index. /
iÊEJ?HQ@A is best used in the following cases: Ê
UÊ 9ÕÊ`½ÌÊÜ>ÌÊÌÊVÀi>ÃiÊÌ
iÊÃâiÊvÊÌ
iÊ`iÝÊiÞÃ]ÊLÕÌÊÞÕÊÃÌÊÜ>ÌÊÌÊ>iÊÌ
iÊ index a covering index.
Ê
UÊ 9Õ½ÀiÊ`iÝ}Ê>Ê`>Ì>ÊÌÞ«iÊÌ
>ÌÊV>½ÌÊLiÊ`iÝi`ÊiÝVi«ÌÊÌiÝÌ]ÊÌiÝÌ]Ê>`Ê>}iî°
Ê
UÊ 9Õ½ÛiÊ>Ài>`ÞÊiÝVii`i`ÊÌ
iÊ>ÝÕÊÕLiÀÊvÊiÞÊVÕÃÊvÀÊ>Ê`iÝÊ>Ì
Õ}
Ê this is a problem best avoided).
A Pseudoclustered Index /
i covering index physically organizes the data of all the indexed columns in a sequential À`iÀ°Ê/
ÕÃ]ÊvÀÊ>Ê`ÃÊÉ"Ê«iÀëiVÌÛi]Ê>ÊVÛiÀ}Ê`iÝÊÌ
>ÌÊ`iýÌÊÕÃiÊVÕ`i`ÊVÕÃÊ becomes a clustered index for all queries satisfied completely by the columns in the covering index. If the result set of a query requires a sorted output, then the covering index can be used to physically maintain the column data in the same order as required by the result set— it can then be used in the same way as a clustered index for sorted output. As shown in the previous example, covering indexes can give better performance than clustered indexes for µÕiÀiÃÊÀiµÕiÃÌ}Ê>ÊÀ>}iÊvÊÀÜÃÊ>`ÉÀÊÃÀÌi`ÊÕÌ«Õ̰Ê/
iÊVÕ`i`ÊVÕÃÊ>ÀiÊÌÊ«>ÀÌÊ of the key and therefore wouldn’t offer the same benefits for ordering as the key columns of the index.
Recommendations /ÊÌ>iÊ>`Û>Ì>}iÊvÊVÛiÀ}Ê`iÝiÃ]ÊLiÊV>ÀivÕÊÜÌ
ÊÌ
iÊVÕÊÃÌÊÊOAHA?P statements. 1ÃiÊ>ÃÊviÜÊVÕÃÊ>ÃÊ«ÃÃLiÊÌÊii«ÊÌ
iÊ`iÝÊiÞÊÃâiÊÃ>ÊvÀÊÌ
iÊVÛiÀ}Ê`iÝiÃ°Ê Add columns using the EJ?HQ@AÊÃÌ>ÌiiÌÊÊ«>ViÃÊÜ
iÀiÊÌÊ>iÃÊÃiÃi°Ê-ViÊ>ÊVÛiÀ}Ê index includes all columns used in a query, it has a tendency to be very wide, increasing the maintenance cost of the covering indexes. You must balance the maintenance cost with the performance gain that the covering index brings. If the number of bytes from all the columns in the index is small compared to the number of bytes in a single data row of that table and you are certain the query taking advantage of the covered index will be executed frequently, then it may be beneficial to use a covering index.
NTip Covering indexes can also help resolve blocking and deadlocks, as you will see in Chapters 12 and 13.
C H A P T E R 4 N I N D E X A N A LY S I S
ivÀiÊLÕ`}Ê>ÊÌÊvÊVÛiÀ}Ê`iÝiÃ]ÊVÃ`iÀÊ
ÜÊ-+Ê-iÀÛiÀÊV>ÊivviVÌÛiÞÊ>`Ê automatically create covering indexes for queries on the fly using index intersection.
Index Intersections If a table has ÕÌ«iÊ`iÝiÃ]ÊÌ
iÊ-+Ê-iÀÛiÀÊV>ÊÕÃiÊÕÌ«iÊ`iÝiÃÊÌÊiÝiVÕÌiÊ>ʵÕiÀÞ°Ê -+Ê-iÀÛiÀÊV>ÊÌ>iÊ>`Û>Ì>}i of multiple indexes, selecting small subsets of data based on each index and then performing an intersection of the two subsets (that is, returning only Ì
ÃiÊÀÜÃÊÌ
>ÌÊiiÌÊ>ÊÌ
iÊVÀÌiÀ>®°Ê-+Ê-iÀÛiÀÊV>ÊiÝ«ÌÊÕÌ«iÊ`iÝiÃÊÊ>ÊÌ>LiÊ>`Ê then employ a join algorithm to obtain the index intersection between the two subsets. In the following OAHA?P statement, for the SDANA clause columns the table has a nonclustered index on the O]haoLanokjE@ column, but it has no index on the Kn`an@]pa column: OAHA?Pokd*& BNKIO]hao*O]haoKn`anDa]`an=Ookd SDANAokd*O]haoLanokjE@9.32 =J@okd*Kn`an@]pa>APSAAJ#0+-+.,,.#]j`#3+-+.,,.# Figure 4-28 shows the execution plan for this query.
Figure 4-28. Execution plan with no index on the Kn`an@]pa column As you can see, the optimizer didn’t use the nonclustered index on the O]haoLanokjE@ VÕ°Ê-ViÊÌ
iÊÛ>ÕiÊvÊÌ
iÊKn`an@]pa column is also required, the optimizer chose the clustered index to fetch the value of all the referred columns. /Ê«ÀÛiÊÌ
iÊ«iÀvÀ>ViÊvÊÌ
iʵÕiÀÞ]ÊÌ
iÊKn`an@]pa column can be added to the nonclustered index on the O]haoLanokjE` column or defined as an include column on the same index. But in this real-world scenario, you may have to consider the following while modifying an existing index: Ê
UÊ ÌÊ>ÞÊÌÊLiÊ«iÀÃÃLiÊÌÊ`vÞÊ>ÊiÝÃÌ}Ê`iÝÊvÀÊÛ>ÀÕÃÊÀi>Ãð
Ê
UÊ /
iÊiÝÃÌ}ÊVÕÃÌiÀi`Ê`iÝÊiÞÊ>ÞÊLiÊ>Ài>`ÞʵÕÌiÊÜ`i°
Ê
UÊ /
iÊVÃÌÊvÊÌ
iʵÕiÀiÃÊÕÃ}ÊÌ
iÊiÝÃÌ}Ê`iÝÊÜÊLiÊ>vviVÌi`ÊLÞÊÌ
iÊ`vV>̰ In such cases, you can create a new nonclustered index on the Kn`an@]pa column:
?NA=PAJKJ?HQOPANA@EJ@ATET[PaopKJO]hao*O]haoKn`anDa]`an$Kn`an@]pa% ,ÕÊÞÕÀÊOAHA?P command again. Figure 4-29 shows the resultant execution plan of the OAHA?P statement.
135
136
CHAPTER 4 N IND EX A NA L YS IS
Figure 4-29. Execution plan with an index on the Kn`an@]pa column ÃÊÞÕÊV>ÊÃii]Ê-+Ê-iÀÛiÀÊiÝ«Ìi`ÊLÌ
ÊÌ
iÊVÕÃÌiÀi`Ê`iÝiÃÊ>ÃÊ`iÝÊÃiiÃÊÀ>Ì
iÀÊ than scans) and then employed an intersection algorithm to obtain the index intersection of the two subsets. It then did a GauHkkgql from the resulting data to retrieve the rest of the data not included in the indexes. /Ê«ÀÛiÊÌ
iÊ«iÀvÀ>ViÊvÊ>ʵÕiÀÞ]Ê-+Ê-iÀÛiÀÊV>ÊÕÃiÊÕÌ«iÊ`iÝiÃÊÊ>ÊÌ>Li°Ê /
iÀivÀi]ÊÃÌi>`ÊvÊVÀi>Ì}ÊÜ`iÊ`iÝÊiÞÃ]ÊVÃ`iÀÊVÀi>Ì}ÊÕÌ«iÊ>ÀÀÜÊ`iÝiÃ°Ê -+Ê-iÀÛiÀÊÜÊLiÊ>LiÊÌÊÕÃiÊÌ
iÊÌ}iÌ
iÀÊÜ
iÀiÊÀiµÕÀi`]Ê>`ÊÜ
iÊÌÊÀiµÕÀi`]ʵÕiÀiÃÊ LiivÌÊvÀÊ>ÀÀÜÊ`iÝiðÊ7
iÊVÀi>Ì}Ê>ÊVÛiÀ}Ê`iÝ]Ê`iÌiÀiÊÜ
iÌ
iÀÊÌ
iÊÜ`Ì
ÊvÊ the index will be acceptable and whether using include columns will get the job done. If not, then identify the existing nonclustered indexes that include most of the columns required by the covering index. You may already have two existing nonclustered indexes that jointly serve all the columns required by the covering index. If it is possible, rearrange the column order of the existing nonclustered indexes appropriately, allowing the optimizer to consider an index intersection between the two nonclustered indexes. At times, it is possible that you may have to create a separate nonclustered index for the following reasons: Ê
UÊ ,iÀ`iÀ}ÊÌ
iÊVÕÃÊÊiÊvÊÌ
iÊiÝÃÌ}Ê`iÝiÃÊÃÊÌÊ>Üi`°
Ê
UÊ -iÊvÊÌ
iÊVÕÃÊÀiµÕÀi`ÊLÞÊÌ
iÊVÛiÀ}Ê`iÝÊ>ÞÊÌÊLiÊVÕ`i`ÊÊÌ
iÊiÝÃÌing nonclustered indexes.
Ê
UÊ /
iÊÌÌ>ÊÕLiÀÊvÊVÕÃÊÊÌ
iÊÌÜÊiÝÃÌ}ÊVÕÃÌiÀi`Ê`iÝiÃÊ>ÞÊLiÊÀiÊ than the number of columns required by the covering index.
In such cases, you can create a nonclustered index on the remaining columns. If the combined column order of the new index and an existing nonclustered index meets the ÀiµÕÀiiÌÊvÊÌ
iÊVÛiÀ}Ê`iÝ]ÊÌ
iÊ«ÌâiÀÊÜÊLiÊ>LiÊÌÊÕÃiÊ`iÝÊÌiÀÃiV̰Ê7
iÊ identifying the columns and their order for the new index, try to maximize their benefit by keeping an eye on other queries, too. Drop the index that was created for the tests: @NKLEJ@ATO]hao*O]haoKn`anDa]`an*ET[Paop
C H A P T E R 4 N I N D E X A N A LY S I S
Index Joins /
iÊindex join is a variation of index intersection, where the covering index technique is applied to the index intersection. If no single index covers a query but multiple indexes Ì}iÌ
iÀÊV>ÊVÛiÀÊÌ
iʵÕiÀÞ]Ê-+Ê-iÀÛiÀÊV>ÊÕÃiÊ>Ê`iÝÊÊÌÊÃ>ÌÃvÞÊÌ
iʵÕiÀÞÊvÕÞÊÜÌ
out going to the base table. i̽ÃÊÊ>ÌÊÌ
ÃÊ`iÝ}ÊÌiV
µÕiÊ>ÌÊÜÀ°Ê>iÊ>ÊÃ}
ÌÊ`vV>ÌÊÌÊÌ
iʵÕiÀÞÊvÀÊ Ì
iʺ`iÝÊÌiÀÃiVÌûÊÃiVÌÊiÊÌ
Ã\ OAHA?Pokd*O]haoLanokjE@(okd*Kn`an@]pa BNKIO]hao*O]haoKn`anDa]`an=Ookd SDANAokd*O]haoLanokjE@9.32 =J@okd*Kn`an@]pa>APSAAJ#0+-+.,,.#]j`#3+-+.,,.# /
iÊiÝiVÕÌÊ«>ÊvÀÊÌ
ÃʵÕiÀÞÊÃÊÃ
ÜÊÊ}ÕÀiÊ{Îä]Ê>`ÊÌ
iÊÀi>`ÃÊ>ÀiÊ>ÃÊvÜÃ\ P]^ha#O]haoKn`anDa]`an#*O_]j_kqjp-(hkce_]hna]`o242
Figure 4-30. Execution plan with no index join As shown in Figure 4-30, the optimizer didn’t use the existing nonclustered index on the O]haoLanokjE@ÊVÕ°Ê-ViÊÌ
iʵÕiÀÞÊÀiµÕÀiÃÊÌ
iÊÛ>ÕiÊvÊÌ
iÊKn`an@]pa column also, the optimizer selected the clustered index to retrieve values for all the columns referred to in the query. If an index is created on the Kn`an@]pa column like this: ?NA=PAJKJ?HQOPANA@EJ@ATWET[PaopYKJWO]haoY*WO]haoKn`anDa]`anY$WKn`an@]paY=O?%7 and the query is rerun, then Figure 4-31 shows the result, and you can see the reads here: P]^ha#Skngp]^ha#*O_]j_kqjp,(hkce_]hna]`o, P]^ha#O]haoKn`anDa]`an#*O_]j_kqjp.(hkce_]hna]`o4
Figure 4-31. Execution plan with an index join /
iÊVL>ÌÊvÊÌ
iÊÌÜÊ`iÝiÃÊ>VÌÃÊiÊ>ÊVÛiÀ}Ê`iÝÊÀi`ÕV}ÊÌ
iÊÀi>`ÃÊ>}>ÃÌÊ the table from 686 to 8 because it’s using two Ej`atOaag operations joined together instead of a clustered index scan.
137
138
CHAPTER 4 N IND EX A NA L YS IS
But what if the SDANA clause didn’t result in both indexes being used? Instead, you know that both indexes exist and that a seek against each would work like the previous query, so you choose to use an index hint: OAHA?Pokd*O]haoLanokjE@ (okd*Kn`an@]pa BNKIO]hao*O]haoKn`anDa]`an=OokdSEPD$EJ@AT$ET[Paop( ET[O]haoKn`anDa]`an[O]haoLanokjE`%% SDANAokd*Kn`an@]pa>APSAAJ#0+-+.,,.#=J@#3+-+.,,.#7 /
iÊÀiÃÕÌÃÊvÊÌ
ÃÊiÜʵÕiÀÞÊ>ÀiÊÃ
ÜÊÊ}ÕÀiÊ{ÎÓ]Ê>`ÊÌ
iÊÉ"ÊÃÊ>ÃÊvÜÃ\ P]^ha#Skngp]^ha#*O_]j_kqjp,(hkce_]hna]`o, P]^ha#O]haoKn`anDa]`an#*O_]j_kqjp.(hkce_]hna]`o2.
Figure 4-32. Execution plan with index join through a hint /
iÊÀi>`ÃÊ
>ÛiÊVi>ÀÞÊVÀi>Ãi`]Ê>`ÊÌ
iÊiÃÌ>Ìi`ÊVÃÌÃÊ>}>ÃÌÊÌ
iÊÛiÀÞÊÃ>iÊ`iÝiÃÊ Ì
>ÌÊ
>`ÊLiiÊ«Vi`ÊLÞÊÌ
iʵÕiÀÞÊ«ÌâiÀÊ>ÀiÊÜÊÕV
Ê
}
iÀ°ÊÃÌÊvÊÌ
iÊÌi]ÊÌ
iÊ optimizer makes very good choices when it comes to indexes and execution plans. Although query hints are available to allow you to take control from the optimizer, this control can cause as many problems as it solves. In attempting to force an index join as a performance benefit, instead the forced selection of indexes slowed down the execution of the query.
NNote While generating a query execution plan, the SQL Server optimizer goes through the optimization phases not only to determine the type of index and join strategy to be used but also to evaluate the advanced indexing techniques such as index intersection and index join. Therefore, instead of creating wide covering indexes, consider creating multiple narrow indexes. SQL Server can use them together to serve as a covering index yet use them separately where required.
Filtered Indexes A filtered index is a nonclustered index that uses a filter, basically a SDANA clause, to create a highly selective set of keys against a column or columns that may not have good selectivity otherwise. For example, a column with a large number of jqhh values may be stored as a sparse column to reduce the overhead of those jqhh values. Adding a filtered index to the column will
C H A P T E R 4 N I N D E X A N A LY S I S
allow you to have an index available on the data that is not jqhh°Ê/
iÊLiÃÌÊÜ>ÞÊÌÊÕ`iÀÃÌ>`Ê this is to see it in action. /
iÊO]hao*O]haoKn`anDa]`an table has more than 30,000 rows. Of those rows, 27,000+ have a jqhh value in the Lqn_d]oaKn`anJqi^an column and the O]haoLanokjE` column. If you wanted to get a simple list of purchase order numbers, the query might look like this: OAHA?Pokd*Lqn_d]oaKn`anJqi^an (okd*Kn`an@]pa (okd*Odel@]pa (okd*O]haoLanokjE@ BNKIO]hao*O]haoKn`anDa]`an=Ookd SDANALqn_d]oaKn`anJqi^anHEGA#LK1!# =J@okd*O]haoLanokjE@EOJKPJQHH7 ,Õ}ÊÌ
iʵÕiÀÞÊÀiÃÕÌÃÊ]Ê>ÃÊÞÕÊ}
ÌÊiÝ«iVÌ]Ê>ÊVÕÃÌiÀi`Ê`iÝÊÃV>]Ê>`ÊÌ
iÊvÜing I/O and execution time, as shown in Figure 4-33: P]^ha#O]haoKn`anDa]`an#*O_]j_kqjp-(hkce_]hna]`o242 ?LQpeia9,io(ah]loa`peia92-5io*
Figure 4-33. Execution plan without an index /ÊvÝÊÌ
Ã]ÊÌÊÃÊ«ÃÃLiÊÌÊVÀi>ÌiÊ>Ê`iÝÊ>`ÊVÕ`iÊÃiÊvÊÌ
iÊVÕÃÊvÀÊÌ
iÊ query to make this a covering index: ?NA=PAJKJ?HQOPANA@EJ@ATET[PaopKJO]hao*O]haoKn`anDa]`an$Lqn_d]oaKn`anJqi^an (O]haoLanokjE`% EJ?HQ@A$Kn`an@]pa(Odel@]pa%7 7
iÊÞÕÊÀiÀÕÊÌ
iʵÕiÀÞ]ÊÌ
iÊ«iÀvÀ>ViÊ«ÀÛiiÌÊÃÊv>ÀÞÊÀ>`V>ÊÃiiÊ}ÕÀiÊ{ÎÎÊ and the I/O and time in the following result): P]^ha#O]haoKn`anDa]`an#*O_]j_kqjp-(hkce_]hna]`o1 ?LQpeia9,io(ah]loa`peia9-/5io*
Figure 4-34. Execution plan with a covering index As you can see, the covering index dropped the reads from 686 down to 5 and the time from 619 ms to 139 ms. Normally, this would be enough. Assume for a moment that this query has to be called frequently. Very frequently. Now, every bit of speed you can wring from it will pay dividends. Knowing that so much of the data in the indexed columns is jqhh, you can
139
140
CHAPTER 4 N IND EX A NA L YS IS
adjust the index so that it filters out the jqhh values, which aren’t used by the index anyway, reducing the size of the tree and therefore the amount of searching required: ?NA=PAJKJ?HQOPANA@EJ@ATET[PaopKJ O]hao*O]haoKn`anDa]`an$Lqn_d]oaKn`anJqi^an (O]haoLanokjE`% EJ?HQ@A$Kn`an@]pa(Odel@]pa% SDANALqn_d]oaKn`anJqi^anEOJKPJQHH =J@O]haoLanokjE`EOJKPJQHH SEPD$@NKL[ATEOPEJC9KJ%7 /
iÊv>ÊÀÕÊvÊÌ
iʵÕiÀÞÊÃÊÛÃLiÊÊÌ
iÊvÜ}ÊÀiÃÕÌÊ>`ÊÊ}ÕÀiÊ{Îx\ P]^ha#O]haoKn`anDa]`an#*O_]j_kqjp-(hkce_]hna]`o0 ?LQpeia9,io(ah]loa`peia9/2io*
Figure 4-35. Execution plan with a filtered index Although in terms of sheer numbers reducing the reads from 5 to 4 isn’t much, it is a 20 percent reduction in the I/O cost of the query, and if this query were running hundreds or even thousands of times in a minute, as some queries do, that 20 percent reduction would be a }Ài>ÌÊ«>ÞvvÊ`ii`°Ê/
iÊÀi>Ê«>ÞvvÊÃÊÛÃLiÊÊÌ
iÊiÝiVÕÌÊÌi]ÊÜ
V
Ê`À««i`Ê>}>ÊvÀÊ 139 ms to 36 ms. Filtered indexes pay off in many ways: Ê
UÊ «ÀÛ}ÊÌ
iÊivvViVÞÊvʵÕiÀiÃÊLÞÊÀi`ÕV}ÊÌ
iÊÃâiÊvÊÌ
iÊ`iÝ
Ê
UÊ ,i`ÕV}ÊÃÌÀ>}iÊVÃÌÃÊLÞÊ>}ÊÃ>iÀÊ`iÝiÃ
Ê
UÊ ÕÌÌ}Ê`ÜÊÊÌ
iÊVÃÌÃÊvÊ`iÝÊ>Ìi>ViÊLiV>ÕÃiÊvÊÌ
iÊÀi`ÕVi`ÊÃâi
One of the first places suggested for their use is just like the previous example, eliminating jqhh values from the index. You can also isolate frequently accessed sets of data with a special index so that the queries against that data perform much faster. You can use the SDANA clause to filter data in a fashion similar to creating an indexed view (covered in more detail in the º`iÝi`Ê6iÜûÊÃiVÌ®ÊÜÌ
ÕÌÊÌ
iÊ`>Ì>Ê>Ìi>ViÊ
i>`>V
iÃÊ>ÃÃV>Ìi`ÊÜÌ
Ê`iÝi`Ê views by creating a filtered index that is a covering index, just like the earlier example. ÌiÀi`Ê`iÝiÃÊÀiµÕÀiÊ>ÊëiVvVÊÃiÌÊvÊ -ÊÃiÌÌ}ÃÊÜ
iÊÌ
iÞÊ>ÀiÊ>VViÃÃi`ÊÀÊVÀi>Ìi`\ Ê
UÊ KJ: =JOE[JQHHO, =JOE[L=@@EJC, =JOE[S=NJEJCO, =NEPD=>KNP, ?KJ?=P[JQHH[UEAH@O[JQHH, MQKPA@[E@AJPEBEAN
Ê
UÊ KBB: JQIANE?[NKQJ@=>KNP 7
iÊV«iÌi`]Ê`À«ÊÌ
i testing index:
@NKLEJ@ATO]hao*O]haoKn`anDa]`an*ET[Paop7
C H A P T E R 4 N I N D E X A N A LY S I S
Indexed Views A database viewÊÊ-+Ê-iÀÛiÀÊÃÊiÊ>ÊÛÀÌÕ>ÊÌ>LiÊÌ
>ÌÊÀi«ÀiÃiÌÃÊÌ
iÊÕÌ«ÕÌÊvÊ>ÊOAHA?P statement. You create a view using the ?NA=PAREAS statement, and you can query it exactly like a table. In general, a view doesn’t store any data—only the OAHA?P statement associated with it. Every time a view is queried, it further queries the underlying tables by executing its associated OAHA?P statement. A database view can be materialized on the disk by creating a unique clustered index on Ì
iÊÛiܰÊ-ÕV
Ê>ÊÛiÜÊÃÊÀiviÀÀi`ÊÌÊ>ÃÊ>Êindexed view or a materialized view. After a unique clustered index is created on the view, the view’s result set is materialized immediately and persisted in physical storage in the database, saving the overhead of performing costly operations during query execution. After the view is materialized, multiple nonclustered indexes can be created on the indexed view.
Benefit You can use an indexed view to increase the performance of a query in the following ways: Ê
UÊ }}Ài}>ÌÃÊV>ÊLiÊ«ÀiV«ÕÌi`Ê>`ÊÃÌÀi`ÊÊÌ
iÊ`iÝi`ÊÛiÜÊÌÊâiÊ expensive computations during query execution.
Ê
UÊ />LiÃÊV>ÊLiÊ«Àii`]Ê>`ÊÌ
iÊÀiÃÕÌ}Ê`>Ì>ÊÃiÌÊV>ÊLiÊ>ÌiÀ>âi`°
Ê
UÊ L>ÌÃÊvÊÃÊÀÊ>}}Ài}>ÌÃÊV>ÊLiÊ>ÌiÀ>âi`°
Overhead Indexed ÛiÜÃÊV>Ê«À`ÕViÊ>ÀÊÛiÀ
i>`ÊÊ>Ê"/*Ê`>Ì>L>Ãi°Ê-iÊvÊÌ
iÊÛiÀ
i>`ÃÊvÊ indexed views are as follows: Ê
UÊ ÞÊV
>}iÊÊÌ
iÊL>ÃiÊÌ>LiîÊ
>ÃÊÌÊLiÊÀiviVÌi`ÊÊÌ
iÊ`iÝi`ÊÛiÜÊLÞÊiÝiVÕÌ}ÊÌ
iÊ view’s OAHA?P statement.
Ê
UÊ ÞÊV
>}iÃÊÌÊ>ÊL>ÃiÊÌ>LiÊÊÜ
V
Ê>Ê`iÝi`ÊÛiÜÊÃÊ`ivi`Ê>ÞÊÌ>ÌiÊiÊÀÊ ÀiÊV
>}iÃÊÊÌ
iÊVÕÃÌiÀi`Ê`iÝiÃÊvÊÌ
iÊ`iÝi`ÊÛiܰÊ/
iÊVÕÃÌiÀi`Ê`iÝÊÜÊ also have to be changed if the clustering key is updated.
Ê
UÊ /
iÊ`iÝi`ÊÛiÜÊ>``ÃÊÌÊÌ
iÊ}}Ê>Ìi>ViÊÛiÀ
i>`ÊvÊÌ
iÊ`>Ì>L>Ãi°
Ê
UÊ ``Ì>ÊÃÌÀ>}iÊÃÊÀiµÕÀi`ÊÊÌ
iÊ`>Ì>L>Ãi° /
iÊrestrictions on creating an indexed view include the following:
Ê
UÊ /
iÊvÀÃÌÊ`iÝÊÊÌ
iÊÛiÜÊÕÃÌÊLiÊ>ÊÕµÕiÊVÕÃÌiÀi`Ê`iݰ
Ê
UÊ
Ê
UÊ /
iÊÛiÜÊ`ivÌÊÕÃÌÊLiÊdeterministic—that is, it is able to return only one possible result for a given query. (A list of deterministic and nondeterministic functions is «ÀÛ`i`ÊÊ-+Ê-iÀÛiÀÊ ÃÊ"i°®
Ê
UÊ /
iÊ`iÝi`ÊÛiÜÊÕÃÌÊÀiviÀiViÊÞÊL>ÃiÊÌ>LiÃÊÊÌ
iÊÃ>iÊ`>Ì>L>Ãi]ÊÌÊÌ
iÀÊÛiÜð
Ê
UÊ /
iÊ`iÝi`ÊÛiÜÊ>ÞÊVÌ>Êv>ÌÊVÕðÊÜiÛiÀ]ÊÃÕV
ÊVÕÃÊV>ÌÊLiÊ included in the clustered index key.
VÕÃÌiÀi`Ê`iÝiÃÊÊ>Ê`iÝi`ÊÛiÜÊV>ÊLiÊVÀi>Ìi`ÊÞÊ>vÌiÀÊÌ
iÊÕµÕiÊ clustered index is created.
141
142
CHAPTER 4 N IND EX A NA L YS IS
Ê
UÊ /
iÊ`iÝi`ÊÛiÜÊÕÃÌÊLiÊ>ÊÃV
i>ÊLÕ`ÊÌÊÌ
iÊÌ>LiÃÊÀiviÀÀi`ÊÌÊÊÌ
iÊÛiÜÊÌÊ prevent modifications of the table schema.
Ê
UÊ /
iÀiÊ>ÀiÊÃiÛiÀ>ÊÀiÃÌÀVÌÃÊÊÌ
iÊÃÞÌ>ÝÊvÊÌ
iÊÛiÜÊ`iv̰ÊÊÃÌÊvÊÌ
iÊÃÞÌ>ÝÊ Ì>ÌÃÊÊÌ
iÊÛiÜÊ`ivÌÊÃÊ«ÀÛ`i`ÊÊ-+Ê-iÀÛiÀÊ ÃÊ"i°®
Ê
UÊ /
iÊÃÌÊvÊOAP options that must be fixed are as follows: Ê UÊ KJ: =NEPD=>KNP, ?KJ?=P[JQHH[UEAH@O[JQHH, MQKPA@[E@AJPEBEAN, =JOE[JQHHO, =JOE[ L=@@EJC, and =JOE[S=NJEJC Ê UÊ KBB: JQIANE?[NKQJ@=>KNP
Usage Scenarios ,i«ÀÌ}ÊÃÞÃÌiÃÊLiivÌÊÌ
iÊÃÌÊvÀÊ`iÝi`ÊÛiÜðÊ"/*ÊÃÞÃÌiÃÊÜÌ
ÊvÀiµÕiÌÊÜÀÌiÃÊ may not be able to take advantage of the indexed views because of the increased maintenance VÃÌÊ>ÃÃV>Ìi`ÊÜÌ
ÊÕ«`>Ì}ÊLÌ
ÊÌ
iÊÛiÜÊ>`ÊÌ
iÊÕ`iÀÞ}ÊL>ÃiÊÌ>LiðÊ/
iÊiÌÊ«iÀvÀmance improvement provided by an indexed view is the difference between the total query execution savings offered by the view and the cost of storing and maintaining the view. An indexed view need not be referenced in the query for the query optimizer to use it `ÕÀ}ʵÕiÀÞÊiÝiVÕ̰Ê/
ÃÊ>ÜÃÊiÝÃÌ}Ê>««V>ÌÃÊÌÊLiivÌÊvÀÊÌ
iÊiÜÞÊVÀi>Ìi`Ê `iÝi`ÊÛiÜÃÊÜÌ
ÕÌÊV
>}}ÊÌ
ÃiÊ>««V>ÌðÊ/
iʵÕiÀÞÊ«ÌâiÀÊVÃ`iÀÃÊ`iÝi`Ê views only for queries with nontrivial cost. i̽ÃÊÃiiÊ
ÜÊ`iÝi`ÊÛiÜÃÊÜÀÊÜÌ
ÊÌ
iÊvÜ}ÊiÝ>«i°Ê Ã`iÀÊÌ
iÊÌ
ÀiiʵÕiÀiÃÊ ÊÃÌ}ÃÊ{£ÊÌ
ÀÕ}
Ê{ΰ Listing 4-1. Mqanu-*omh OAHA?Pl*WJ]iaY=OLnk`q_pJ]ia (OQI$lk`*Kn`anMpu%=OKn`anMpu (OQI$lk`*Na_aera`Mpu%=ONa_aera`Mpu (OQI$lk`*Nafa_pa`Mpu%=ONafa_pa`Mpu BNKILqn_d]oejc*Lqn_d]oaKn`an@ap]eh=Olk` FKEJLnk`q_pekj*Lnk`q_p=Ol KJl*Lnk`q_pE@9lk`*Lnk`q_pE@ CNKQL>Ul*WJ]iaY
Listing 4-2. Mqanu.*omh OAHA?Pl*WJ]iaY=OLnk`q_pJ]ia (OQI$lk`*Kn`anMpu%=OKn`anMpu (OQI$lk`*Na_aera`Mpu%=ONa_aera`Mpu (OQI$lk`*Nafa_pa`Mpu%=ONafa_pa`Mpu BNKILqn_d]oejc*Lqn_d]oaKn`an@ap]eh=Olk` FKEJLnk`q_pekj*Lnk`q_p=Ol KJl*Lnk`q_pE@9lk`*Lnk`q_pE@ CNKQL>Ul*WJ]iaY D=REJC$OQI$lk`*Nafa_pa`Mpu%+OQI$lk`*Na_aera`Mpu%%:*,4
C H A P T E R 4 N I N D E X A N A LY S I S
Listing 4-3. Mqanu/*omh OAHA?Pl*WJ]iaY=OLnk`q_pJ]ia (OQI$lk`*Kn`anMpu%=OKn`anMpu (OQI$lk`*Na_aera`Mpu%=ONa_aera`Mpu (OQI$lk`*Nafa_pa`Mpu%=ONafa_pa`Mpu BNKILqn_d]oejc*Lqn_d]oaKn`an@ap]eh=Olk` FKEJLnk`q_pekj*Lnk`q_p=Ol KJl*Lnk`q_pE@9lk`*Lnk`q_pE@ SDANAl*WJ]iaYHEGA#?d]ej!# CNKQL>Ul*WJ]iaY All the three queries use the aggregation function OQI on columns of the Lqn_d]oaKn`an@ap]ehÊÌ>Li°Ê/
iÀivÀi]ÊÞÕÊV>ÊVÀi>ÌiÊ>Ê`iÝi`ÊÛiÜÊÌÊ«ÀiV«ÕÌiÊÌ
iÃiÊ aggregations and minimize the cost of these complex computations during query execution. ÃÌ}ÃÊ{{ÊÌ
ÀÕ}
Ê{ÈÊÃ
ÜÊÌ
iÊÕLiÀÊvÊ}V>ÊÀi>`ÃÊ«iÀvÀi`ÊLÞÊÌ
iÃiʵÕiÀiÃÊÌÊ access the appropriate tables. Listing 4-4. Logical Reads by MqanuP]^ha#Skngp]^ha#*O_]j_kqjp,(hkce_]hna]`o, P]^ha#Lnk`q_p#*O_]j_kqjp-(hkce_]hna]`o1 P]^ha#Lqn_d]oaKn`an@ap]eh#*O_]j_kqjp-(hkce_]hna]`o22 ?LQpeia9-1io(ah]loa`peia9-,,io*
Listing 4-5. Logical Reads by Mqanu. P]^ha#Skngp]^ha#*O_]j_kqjp,(hkce_]hna]`o, P]^ha#Lnk`q_p#*O_]j_kqjp-(hkce_]hna]`o1 P]^ha#Lqn_d]oaKn`an@ap]eh#*O_]j_kqjp-(hkce_]hna]`o22 ?LQpeia9-2io(ah]loa`peia9-,io*
Listing 4-6. Logical Reads by Mqanu/ P]^ha#Lqn_d]oaKn`an@ap]eh#*O_]j_kqjp1(hkce_]hna]`o450 P]^ha#Lnk`q_p#*O_]j_kqjp-(hkce_]hna]`o. ?LQpeia9,io(ah]loa`peia902io*
Ài>ÌiÊ>Ê`iÝi`ÊÛiÜÊÌÊ«ÀiV«ÕÌiÊÌ
iÊVÃÌÞÊV«ÕÌ>ÌÃÊ>`ÊÊÌ
iÊÌ>LiÃÊ (k`Reas*omh in the download): EBATEOPO$OAHA?P& BNKIouo*reaso SDANAk^fa_p[e`9K>FA?P[E@$J#WLqn_d]oejcY*WEj`ata`ReasY#%% @NKLREASWLqn_d]oejcY*WEj`ata`ReasY CK ?NA=PAREASLqn_d]oejc*Ej`ata`Reas SEPDO?DAI=>EJ@EJC
143
144
CHAPTER 4 N IND EX A NA L YS IS
=OOAHA?Plk`*Lnk`q_pE@ (OQI$lk`*Kn`anMpu%=OKn`anMpu (OQI$lk`*Na_aera`Mpu%=ONa_aera`Mpu (OQI$lk`*Nafa_pa`Mpu%=ONafa_pa`Mpu (?KQJP[>EC$&%=OW?kqjpY BNKILqn_d]oejc*Lqn_d]oaKn`an@ap]eh=Olk` CNKQL>Ulk`*Lnk`q_pE@ CK ?NA=PAQJEMQA?HQOPANA@EJ@ATerKJLqn_d]oejc*Ej`ata`Reas$Lnk`q_pE`%7 CK
iÀÌ>ÊVÃÌÀÕVÌÃÊÃÕV
Ê>ÃÊ=RC are disallowed. (For the complete list of disallowed conÃÌÀÕVÌÃ]ÊÀiviÀÊÌÊ-+Ê-iÀÛiÀÊ ÃÊ"i°®ÊvÊ>}}Ài}>ÌiÃÊ>ÀiÊVÕ`i`ÊÊÌ
iÊÛiÜ]Ê>ÃÊÊÌ
ÃÊi]Ê you must include ?KQJP[>EC by default. /
iÊ`iÝi`ÊÛiÜÊ>ÌiÀ>âiÃÊÌ
iÊÕÌ«ÕÌÊvÊÌ
iÊ>}}Ài}>ÌiÊvÕVÌÃÊÊÌ
iÊ`ðÊ/
ÃÊ eliminates the need for computing the aggregate functions during the execution of a query interested in the aggregate outputs. For example, Mqanu/*omh requests the sum of Na_aera`Mpu and Nafa_pa`Mpu for certain products from the Lqn_d]oaKn`an@ap]eh table. Because these values are materialized in the indexed view for every product in the Lqn_d]oaKn`an@ap]eh table, you can fetch these preaggregated values using the following OAHA?P statement on the indexed view: OAHA?PLnk`q_pE@( Na_aera`Mpu( Nafa_pa`Mpu BNKILqn_d]oejc*Ej`ata`Reas As shown in the execution plan in Figure 4-36, the OAHA?P statement retrieves the values directly from the indexed view without accessing the base table (Lqn_d]oaKn`an@ap]eh).
Figure 4-36. Execution plan with an indexed view /
iÊ`iÝi`ÊÛiÜÊLiivÌÃÊÌÊÞÊÌ
iʵÕiÀiÃÊL>Ãi`ÊÊÌ
iÊÛiÜÊ`ÀiVÌÞÊLÕÌÊ>ÃÊÌ
iÀÊ queries that may be interested in the materialized data. For example, with the indexed view in place, the three queries on Lqn_d]oaKn`an@ap]eh benefit without being rewritten (see the execution plan in Figure 4-37 for the execution plan from the first query), and the number of }V>ÊÀi>`ÃÊ`iVÀi>ÃiÃ]Ê>ÃÊÃ
ÜÊÊÃÌ}ÃÊ{ÇÊÌ
ÀÕ}
Ê{° Listing 4-7. Logical Reads by Mqanu- with an Indexed View P]^ha#Lnk`q_p#*O_]j_kqjp-(hkce_]hna]`o-/ P]^ha#Ej`ata`Reas#*O_]j_kqjp-(hkce_]hna]`o0 ?LQpeia9,io(ah]loa`peia904io*
C H A P T E R 4 N I N D E X A N A LY S I S
Listing 4-8. Logical Reads by Mqanu. with an Indexed View P]^ha#Lnk`q_p#*O_]j_kqjp-(hkce_]hna]`o-/ P]^ha#Ej`ata`Reas#*O_]j_kqjp-(hkce_]hna]`o0 ?LQpeia9,io(ah]loa`peia9,io*
Listing 4-9. Logical Reads by Mqanu/ with an Indexed View P]^ha#Ej`ata`Reas#*O_]j_kqjp,(hkce_]hna]`o-, P]^ha#Lnk`q_p#*O_]j_kqjp-(hkce_]hna]`o. ?LQpeia9,io(ah]loa`peia9-2io*
Figure 4-37. Execution plan with the indexed view automatically used Even though the queries are not modified to refer to the new indexed view, the optimizer ÃÌÊÕÃiÃÊÌ
iÊ`iÝi`ÊÛiÜÊÌÊ«ÀÛiÊ«iÀvÀ>Vi°Ê/
ÕÃ]ÊiÛiÊiÝÃÌ}ʵÕiÀiÃÊÊÌ
iÊ`>Ì>base application can benefit from new indexed views without any modifications to the queries.
Index Compression Data and `iÝÊV«ÀiÃÃÊÜ>ÃÊÌÀ`ÕVi`ÊÊ-+Ê-iÀÛiÀÊÓäänÊ>Û>>LiÊÊÌ
iÊ ÌiÀ«ÀÃiÊ and Developer Editions). Compressing an index means getting more key information onto >ÊÃ}iÊ«>}i°Ê/
ÃÊV>Êi>`ÊÌÊÃiÀÕÃÊ«iÀvÀ>ViÊ«ÀÛiiÌÃÊLiV>ÕÃiÊviÜiÀÊ«>}iÃÊ >`ÊviÜiÀÊ`iÝÊiÛiÃÊ>ÀiÊii`i`ÊÌÊÃÌÀiÊÌ
iÊ`iݰÊ/
iÀiÊÜÊLiÊÛiÀ
i>`ÊÊÌ
iÊ *1Ê>`Ê memory as the key values in the index are compressed and decompressed, so this may not be a solution for all indexes. By default, an index will be not be compressed. You have to explicitly call for the index ÌÊLiÊV«ÀiÃÃi`ÊÜ
iÊÞÕÊVÀi>ÌiÊÌ
iÊ`iݰÊ/
iÀiÊ>ÀiÊÌÜÊÌÞ«iÃÊvÊV«ÀiÃÃ\ÊÀÜÊ>`Ê page-level compression. Nonleaf pages in an index receive no compression under the page ÌÞ«i°Ê/ÊÃiiÊÌÊÊ>VÌ]ÊVÃ`iÀÊÌ
iÊvÜ}Ê`iÝÊ`½ÌÊÀÕÊÌ
ÃÊV`iÊÃViÊÌ
iÊ`iÝÊ already exists): ?NA=PAJKJ?HQOPANA@EJ@ATWET[PaopYKJ WLanokjY*W=``naooY$W?epuY=O?(WLkop]h?k`aY=O?% /
ÃÊ`iÝÊÜ>ÃÊVÀi>Ìi`Êi>ÀiÀÊÊÌ
iÊV
>«ÌiÀ°ÊvÊÞÕÊÜiÀiÊÌÊÀiVÀi>ÌiÊÌÊ>ÃÊ`ivi`Ê
iÀi ?NA=PAJKJ?HQOPANA@EJ@ATET[?kil[Paop KJLanokj*=``naoo$W?epuY(WLkop]h?k`aY% SEPD$@=P=[?KILNAOOEKJ9NKS%7
145
146
CHAPTER 4 N IND EX A NA L YS IS
this creates a row type of compression on an index with the same two columns as the first test index ET[Paop.
Ài>ÌiÊiÊÀiÊ`iÝ\ ?NA=PAJKJ?HQOPANA@EJ@ATET[?kil[L]ca[Paop KJLanokj*=``naoo$W?epuY(WLkop]h?k`aY% SEPD$@=P=[?KILNAOOEKJ9L=CA%7 /ÊiÝ>iÊÌ
iÊ`iÝiÃÊLi}ÊÃÌÀi`]Ê`vÞ the original query against ouo*`i[`^[ej`at[ lduoe_]h[op]po to add another column, _kilnaooa`[l]ca[_kqjp: OAHA?Pe*J]ia (e*pula[`ao_ (o*l]ca[_kqjp (o*na_kn`[_kqjp (o*ej`at[harah (_kilnaooa`[l]ca[_kqjp BNKIouo*ej`ataoe FKEJouo*`i[`^[ej`at[lduoe_]h[op]po$@>[E@$J#=`rajpqnaSkngo.,,4#%( K>FA?P[E@$J#Lanokj*=``naoo#%(JQHH( JQHH(#@AP=EHA@#%=Oo KJe*ej`at[e`9o*ej`at[e` SDANAe*K>FA?P[E@9K>FA?P[E@$J#Lanokj*=``naoo#% ,Õ}ÊÌ
iʵÕiÀÞ]ÊÞÕÊ}iÌÊÌ
iÊÀiÃÕÌÃÊÊ}ÕÀiÊ{În°
Figure 4-38. ouo*`i[`^[ej`at[lduoe_]h[op]po output about compressed indexes For this index, you can see that the page compression was able move the index from 99 «>}iÃÊ`ÜÊÌÊÓÇ]ÊvÊÜ
V
ÊÓÈÊÜiÀiÊV«ÀiÃÃi`°Ê/
iÊÀÜÊÌÞ«iÊV«ÀiÃÃÊÊÌ
ÃÊÃÌ>ViÊ didn’t make much of a difference; in fact, the overhead associated with compression increased the number of pages it took to define the index. /ÊÃiiÊÌ
iÊV«ÀiÃÃÊÊ>VÌ]ÊÀÕÊÌ
iÊvÜ}ʵÕiÀÞ\
C H A P T E R 4 N I N D E X A N A LY S I S
OAHA?P]*?epu(]*Lkop]h?k`a BNKILanokj*=``naoo=O] SDANA]*?epu9#Jaspkj# =J@]*Lkop]h?k`a9#R.I-J3# /
iÊ«ÌâiÀÊV
Ãi]ÊÊÞÊÃÞÃÌi]ÊÌÊÕÃiÊÌ
iÊET[?kil[L]ca[Paop index. Even if I forced it to use the ET[Paop index, thusly, OAHA?P]*?epu(]*Lkop]h?k`a BNKILanokj*=``naoo=O]SEPD$EJ@AT9ET[Paop% SDANA]*?epu9#Jaspkj# =J@]*Lkop]h?k`a9#R.I-J3# Ì
iÊ«iÀvÀ>ViÊÜ>ÃÊ`iÌV>°Ê-Ê>Ì
Õ}
ÊiÊ`iÝÊÃÊÌ>}ÊÕ«ÊÀ>`V>ÞÊiÃÃÊÀÊÊ approximately one quarter as many pages, it’s done at no cost in performance.
«ÀiÃÃÊ
>ÃÊ>ÊÃiÀiÃÊvÊ«>VÌÃÊÊÌ
iÀÊ«ÀViÃÃiÃÊÜÌ
Ê-+Ê-iÀÛiÀ]ÊÃÊvÕÀÌ
iÀÊ understanding of the possible impacts as well as the possible benefits should be explored thoroughly prior to implementation.
i>ÊÕ«ÊÌ
iÊ`iÝiÃÊ>vÌiÀ you finish testing: @NKLEJ@ATLanokj*=``naoo*ET[Paop7 @NKLEJ@ATLanokj*=``naoo*ET[?kil[Paop7 @NKLEJ@ATLanokj*=``naooET[?kil[L]ca[Paop7
Special Index Types As special data typesÊ>`ÊÃÌÀ>}iÊiV
>ÃÃÊ>ÀiÊÌÀ`ÕVi`ÊÌÊ-+Ê-iÀÛiÀÊLÞÊVÀÃvÌ]Ê methods for indexing these special storage types are also developed. Explaining all the details possible for each of these special index types is outside the scope of the book. In the following sections, I introduce the basic concepts of each index type in order to facilitate the possibility of their use in tuning your queries.
Full-Text You can store largeÊ>ÕÌÃÊvÊÌiÝÌÊÊ-+Ê-iÀÛiÀÊLÞÊÕÃ}ÊÌ
iÊI=T value in the R=N?D=N, JR=N?D=N, ?D=N, and J?D=N fields. A normal clustered or nonclustered index against these large fields would be unsupportable because a single value can far exceed the page size within an `iݰÊ-]Ê>Ê`vviÀiÌÊiV
>ÃÊvÊ`iÝ}ÊÌiÝÌÊÃÊÌÊÕÃiÊÌ
iÊÕ/iÝÌÊ }i]ÊÜ
V
ÊÕÃÌÊLiÊ running to work with full-text indexes. You can also build a full-text index on R=N>EJ=NU data. 9ÕÊii`ÊÌÊ
>ÛiÊiÊVÕÊÊÌ
iÊÌ>LiÊÌ
>ÌÊÃÊÕµÕi°Ê/
iÊLiÃÌÊV>``>ÌiÃÊvÀÊ«iÀvÀmance are integers: EJP or >ECEJP°Ê/
ÃÊVÕÊÃÊÌ
iÊÕÃi`Ê>}ÊÜÌ
ÊÌ
iÊÜÀ`ÊÌÊ`iÌvÞÊ Ü
V
ÊÀÜÊÜÌ
ÊÌ
iÊÌ>LiÊÌÊLi}ÃÊÌ]Ê>ÃÊÜiÊ>ÃÊÌÃÊV>ÌÊÜÌ
ÊÌ
iÊvi`°Ê-+Ê-iÀÛiÀÊ allows for incremental changes, either change tracking or time based, to the full-text indexes as well as complete rebuilds. For more details, check out Pro Full-Text Search in SQL Server 2008ÊLÞÊ>ÀÞÊ ÌÌiÀÊ>`Ê V
>iÊ iÃÊ«ÀiÃÃ]ÊÓään®°
147
148
CHAPTER 4 N IND EX A NA L YS IS
Spatial Introduced inÊ-+Ê-iÀÛiÀÊÓäänÊÃÊÌ
iÊ>LÌÞÊÌÊÃÌÀiÊë>Ì>Ê`>Ì>°Ê/
ÃÊ`>Ì>ÊV>ÊLiÊiÌ
iÀÊ>Ê geometry type or the very complex geographical type, literally identifying a point on the earth. /ÊÃ>ÞÊÌ
iÊi>ÃÌ]Ê`iÝ}ÊÌ
ÃÊÌÞ«iÊvÊ`>Ì>ÊÃÊV«V>Ìi`°Ê-+Ê-iÀÛiÀÊÃÌÀiÃÊÌ
iÃiÊ`iÝiÃÊÊ a flat B-tree, similar to regular indexes, except that it is also a hierarchy of four grids linked together. Each of the grids can be given a density of low, medium, or high, outlining how big i>V
Ê}À`ÊðÊÀÊÌ
iÀiÊÌÊLi}ÃÊÌÊ}iÌÊÌÀÕÞÊV«V>Ìi`°Ê-ÕvvViÊÌÊÃ>Þ]ÊÌ
iÀiÊ>ÀiÊiV
>ÃÃÊ to support indexing of the spatial data types so that different types of queries, such as finding when one object is within the boundaries or near another object, can benefit from performance increases inherent in indexing. A spatial index can be created only against a column of type geometry or geography. It has to be on a base table, it must have no indexed views, and the table must have a primary key. You can create up to 249 spatial indexes on any given column on a table. Different indexes >ÀiÊÕÃi`ÊÌÊ`iviÊ`vviÀiÌÊÌÞ«iÃÊvÊ`iÝÊLi
>ÛÀ°ÊÀiÊvÀ>ÌÊÃÊ>Û>>LiÊÊÌ
iÊLÊ Beginning Spatial with SQL Server 2008 by Alastair Aitchison (Apress, 2009).
XML Introduced asÊ>Ê`>Ì>ÊÌÞ«iÊÊ-+Ê-iÀÛiÀÊÓääx]Ê8ÊV>ÊLiÊÃÌÀi`ÊÌÊ>ÃÊÌiÝÌÊLÕÌÊ>ÃÊÜivÀi`Ê 8Ê`>Ì>ÊÜÌ
Ê-+Ê-iÀÛiÀ°Ê/
ÃÊ`>Ì>ÊV>ÊLiʵÕiÀiÃÊÕÃ}ÊÌ
iÊ8+ÕiÀÞÊ>}Õ>}iÊ>ÃÊÃÕ««ÀÌi`Ê LÞÊ-+Ê-iÀÛiÀ°Ê/Êi
>ViÊÌ
iÊ«iÀvÀ>ViÊV>«>LÌiÃ]Ê>ÊëiV>ÊÃiÌÊvÊ`iÝiÃÊ
>ÃÊLiiÊ `ivi`°ÊÊ8ÊVÕÊV>Ê
>ÛiÊiÊ«À>ÀÞÊ>`ÊÃiÛiÀ>ÊÃiV`>ÀÞÊ`iÝiðÊ/
iÊ«À>ÀÞÊ 8ÊÃ
Ài`ÃÊÌ
iÊ«À«iÀÌiÃ]Ê>ÌÌÀLÕÌiÃ]Ê>`ÊiiiÌÃÊvÊÌ
iÊ8Ê`>Ì>Ê>`ÊÃÌÀiÃÊÌÊ>ÃÊ>ÊÌiÀ>ÊÌ>Li°Ê/
iÀiÊÕÃÌÊLiÊ>Ê«À>ÀÞÊiÞÊÊÌ
iÊÌ>Li]Ê>`ÊÌ
>ÌÊ«À>ÀÞÊiÞÊÕÃÌÊLiÊVÕÃÌiÀi`Ê ÊÀ`iÀÊÌÊVÀi>ÌiÊ>Ê8Ê`iݰÊvÌiÀÊÌ
iÊ8Ê`iÝÊÃÊVÀi>Ìi`]ÊÌ
iÊÃiV`>ÀÞÊ`iÝiÃÊV>ÊLiÊ VÀi>Ìi`°Ê/
iÃiÊ`iÝiÃÊ
>ÛiÊÌÞ«iÃÊL]pd, R]hqa, and Lnklanpu, depending on how you query the 8°ÊÀÊÀi details, check out Pro SQL Server 2008 XMLÊLÞÊV
>iÊ iÃÊ«ÀiÃÃ]ÊÓään®°
Additional Characteristics of Indexes Other index properties can affect performance, positively and negatively. A few of these behaviors are explored here.
Different Column Sort Order -+Ê-iÀÛiÀÊÃÕ««ÀÌÃÊVÀi>Ì}Ê>ÊV«ÃÌiÊ`iÝÊÜÌ
Ê>Ê`vviÀiÌÊÃÀÌÊÀ`iÀÊvÀÊÌ
iÊ`vviÀiÌÊVÕÃÊvÊÌ
iÊ`iݰÊ-Õ««ÃiÊÞÕÊÜ>ÌÊ>Ê`iÝÊÜÌ
ÊÌ
iÊvÀÃÌÊVÕÊÃÀÌi`ÊÊ>ÃVi`}ÊÀ`iÀÊ and the second column sorted in descending order. You could achieve this as follows: ?NA=PAJKJ?HQOPANA@EJ@ATe-KJp-$_-=O?(_.@AO?%
Index on Computed Columns You can create an index on a computed column, as long as the expression defined for the computed column meets certain restrictions, such as that it references columns only from the table containing the computed column and it is deterministic.
C H A P T E R 4 N I N D E X A N A LY S I S
Index on BIT Data Type Columns -+Ê-iÀÛiÀÊ>ÜÃ you to create an index on columns with the >EPÊ`>Ì>ÊÌÞ«i°Ê/
iÊ>LÌÞÊÌÊ create an index on a >EP data type column by itself is not a big advantage since such a column can have only two unique values. As mentioned previously, columns with such low selectivity (number of unique values) are not usually good candidates for indexing. However, this feature comes into its own when you consider covering indexes. Because covering indexes require including all the columns in the index, the ability to add the >EP data type column to an index allows covering indexes to include such a column, if required.
CREATE INDEX Statement Processed As a Query /
iÊ?NA=PAEJ@AT operationÊÃÊÌi}À>Ìi`ÊÌÊÌ
iʵÕiÀÞÊ«ÀViÃÃÀ°Ê/
iÊ«ÌâiÀÊV>ÊÕÃiÊ existing index(es) to reduce scan cost and sort while creating an index. />i]ÊvÀÊiÝ>«i]ÊÌ
iÊLanokj*=``naoo table. A nonclustered index exists on a number of columns: =``naooHeja-, =``naooHeja., ?epu, Op]paLnkrej_aE`, and Lkop]h?k`a. If you needed to run queries against the ?epu column with the existing index, you’ll get a scan of that index. Now create a new index like this: ?NA=PAEJ@ATET[PaopKJLanokj*=``naoo$?epu% You can see in Figure 4-39 that, instead of scanning the table, the optimizer chose to scan the index in order to create the new index because the column needed for the new index was contained within the other nonclustered index.
Figure 4-39. Execution plan for ?NA=PAEJ@AT
Parallel Index Creation -+Ê-iÀÛiÀÊÃÕ««ÀÌÃ parallel plans for a ?NA=PAEJ@ATÊÃÌ>ÌiiÌ]Ê>ÃÊÃÕ««ÀÌi`ÊÊÌ
iÀÊ-+Ê queries. On a multiprocessor machine, index creation won’t be restricted to a single processor but will benefit from the multiple processors. You can control the number of processors to be used in a ?NA=PAEJ@AT statement with the i]t`acnaakbl]n]hhaheoi configuration paramiÌiÀÊvÊ-+Ê-iÀÛiÀ°Ê/
iÊ`iv>ÕÌÊÛ>ÕiÊvÀÊÌ
ÃÊ«>À>iÌiÀÊÃÊä]Ê>ÃÊÞÕÊV>ÊÃiiÊLÞÊiÝiVÕÌ}ÊÌ
iÊ ol[_kjbecqna stored procedure: ATA?ol[_kjbecqna#i]t`acnaakbl]n]hhaheoi# /
iÊ`iv>ÕÌÊÛ>ÕiÊvÊäÊi>ÃÊÌ
>ÌÊ-+Ê-iÀÛiÀÊV>ÊÕÃiÊ>ÊÌ
iÊ>Û>>LiÊ *1ÃÊÊÌ
iÊÃÞÃÌiÊ vÀÊÌ
iÊ«>À>iÊiÝiVÕÌÊvÊ>Ê/-+ÊÃÌ>ÌiḭÊ"Ê>ÊÃÞÃÌiÊÜÌ
ÊvÕÀÊ«ÀViÃÃÀÃ]ÊÌ
iÊ>Ýmum degree of parallelism can be set to 2 by executing ol[_kjbecqna: ATA?ol[_kjbecqna#i]t`acnaakbl]n]hhaheoi#(. NA?KJBECQNASEPDKRANNE@A
149
150
CHAPTER 4 N IND EX A NA L YS IS
/
ÃÊ>ÜÃÊ-+Ê-iÀÛiÀÊÌÊÕÃiÊÕ«ÊÌÊÌÜÊ *1ÃÊvÀÊÌ
iÊ«>À>iÊiÝiVÕÌÊvÊ>Ê/-+ÊÃÌ>ÌiḭÊ/
ÃÊVv}ÕÀ>ÌÊÃiÌÌ}ÊÌ>iÃÊivviVÌÊi`>ÌiÞ]ÊÜÌ
ÕÌÊ>ÊÃiÀÛiÀÊÀiÃÌ>À̰ /
iÊquery hint I=T@KL can be used for the ?NA=PAEJ@AT statement. Also, be aware that the parallel ?NA=PAEJ@ATÊvi>ÌÕÀiÊÃÊ>Û>>LiÊÞÊÊ-+Ê-iÀÛiÀÊÓääxÊ>`ÊÓäänÊ ÌiÀ«ÀÃiÊ `Ìð
Online Index Creation /
iÊ`iv>ÕÌÊVÀi>ÌÊvÊ>Ê`iÝÊÃÊ`iÊ>ÃÊ>ÊvviÊ«iÀ>̰Ê/
ÃÊi>ÃÊÌ
>ÌÊiÝVÕÃÛiÊ locks are placed on the table, restricting user access while the index is created. It is possible ÌÊVÀi>ÌiÊÌ
iÊ`iÝiÃÊ>ÃÊ>ÊiÊ«iÀ>̰Ê/
ÃÊ>ÜÃÊÕÃiÀÃÊÌÊVÌÕiÊÌÊ>VViÃÃÊÌ
iÊ`>Ì>Ê Ü
iÊÌ
iÊ`iÝÊÃÊLi}ÊVÀi>Ìi`°Ê/
ÃÊViÃÊ>ÌÊÌ
iÊVÃÌÊvÊVÀi>Ã}ÊÌ
iÊ>ÕÌÊvÊÌiÊ>`Ê ÀiÃÕÀViÃÊÌÊÌ>iÃÊÌÊVÀi>ÌiÊÌ
iÊ`iݰÊ"iÊ`iÝÊ«iÀ>ÌÃÊ>ÀiÊ>Û>>LiÊÞÊÊ-+Ê-iÀÛiÀÊ 2005 and 2008 Enterprise Editions.
Considering the Database Engine Tuning Advisor A simple approach to indexing is to use the >Ì>L>ÃiÊ }iÊ/Õ}Ê`ÛÃÀÊÌÊ«ÀÛ`i`ÊLÞÊ -+Ê-iÀÛiÀ°Ê/
ÃÊÌÊÃÊ>ÊÕÃ>}iL>Ãi`ÊÌÊÌ
>ÌÊÃÊ>ÌÊ>Ê«>ÀÌVÕ>ÀÊÜÀ>`Ê>`ÊÜÀÃÊÜÌ
Ê the query optimizer to determine the costs associated with various index combinations. Based on the tool’s analysis, you can add or drop indexes as appropriate.
NNote I will cover the Database Engine Tuning Advisor tool in more depth in Chapter 5.
Summary In this chapter, you learned that indexing is an effective method for reducing the number of logical reads and disk I/O for a query. Although an index may add overhead to action queries, even action queries such as QL@=PA and @AHAPA can benefit from an index. /Ê`iV`iÊÌ
i index key columns for a particular query, evaluate the SDANA clause and the join criteria of the query. Factors such as column selectivity, width, data type, and column À`iÀÊ>ÀiÊ«ÀÌ>ÌÊÊ`iV`}ÊÌ
iÊVÕÃÊÊ>Ê`iÝÊiÞ°Ê-ViÊ>Ê`iÝÊÃÊ>ÞÊÕÃivÕÊ in retrieving a small number of rows, the selectivity of an indexed column should be very high. It is important to note that nonclustered indexes contain the value of a clustered index key as their row locator, because this behavior greatly influences the selection of an index type. ÀÊLiÌÌiÀÊ«iÀvÀ>Vi]ÊÌÀÞÊÌÊVÛiÀÊ>ʵÕiÀÞÊvÕÞÊÕÃ}Ê>ÊVÛiÀ}Ê`iݰÊ-ViÊ-+Ê-iÀÛiÀÊ can benefit from multiple indexes, use the index intersection and index join techniques, and VÃ`iÀÊ
>Û}ÊÕÌ«iÊ>ÀÀÜÊ`iÝiÃÊÃÌi>`ÊvÊiÊÛiÀÞÊÜ`iÊ`iݰÊ7
iÊÜÀ}ÊÜÌ
Ê special data types, apply the indexes that work with those special data types in order to help performance. ÊÌ
iÊiÝÌÊV
>«ÌiÀ]ÊÞÕÊÜÊi>ÀÊÀiÊ>LÕÌÊÌ
iÊ >Ì>L>ÃiÊ }iÊ/Õ}Ê`ÛÃÀ]ÊÌ
iÊ -+Ê-iÀÛiÀ«ÀÛ`i`ÊÌÊÌ
>ÌÊV>Ê
i«ÊÞÕÊ`iÌiÀiÊÌ
iÊVÀÀiVÌÊ`iÝiÃÊÊ>Ê`>Ì>L>ÃiÊvÀÊ>Ê }ÛiÊ-+ÊÜÀ>`°
CHAPTER
5
Database Engine Tuning Advisor S
QL Server’s performance largely depends upon having proper indexes on the database tables. However, as the workload and data change over time, the existing indexes may not be entirely appropriate, and new indexes may be required. The task of deciding upon the correct indexes is complicated by the fact that an index change that benefits one set of queries may be detrimental to another set of queries. To help you through this process, SQL Server provides a tool called the Database Engine Tuning Advisor. This tool helps identify an optimal set of indexes and statistics for a given workload without requiring an expert understanding of the database schema, workload, or SQL Server internals. It can also recommend tuning options for a small set of problem queries. In addition to the tool’s benefits, I cover its limitations in this chapter, because it is a tool that can cause more harm than good if used incorrectly. In this chapter, I cover the following topics: Ê
UÊ ÜÊÌ
iÊ >Ì>L>ÃiÊ }iÊ/Õ}Ê`ÛÃÀÊÜÀÃ
Ê
UÊ ÜÊÌÊÕÃiÊÌ
iÊ >Ì>L>ÃiÊ }iÊ/Õ}Ê`ÛÃÀÊÊ>ÊÃiÌÊvÊ«ÀLi>ÌVʵÕiÀiÃÊvÀÊ index recommendations, including how to define traces
Ê
UÊ /
iÊÌ>ÌÃÊvÊÌ
iÊ >Ì>L>ÃiÊ }iÊ/Õ}Ê`ÛÃÀ
Database Engine Tuning Advisor Mechanisms You can run the Database Engine Tuning Advisor directly by selecting Microsoft SQL Server 2008 ¢ Performance Tools ¢ Database Engine Tuning Advisor. You can also run it from the command prompt (`p]*ata), from SQL Profiler (Tools ¢ Database Engine Tuning Advisor), from a query in Management Studio (highlight the required query, and select Query ¢ Analyze Query in Database Engine Tuning Advisor), or from Management Studio (select Tools ¢ Database Engine Tuning Advisor). Once the tool is opened and you’re connected to a server, you should see a window like the one in Figure 5-1. I’ll run through the options to define and run an analysis in this section and then follow up in the next session with some detailed examples.
151
152
C H A P T E R 5 N D A T A B A S E E N G I N E T U N I N G A D V I S O R
Figure 5-1. Selecting the server and database in the Database Engine Tuning Advisor The Database Engine Tuning Advisor is already connected to a server. From here, you begin to outline the workload and the objects you want to tune. Creating a session name is necessary to label the session for documentation purposes. Then you need to pick a workload, either a file or a table, and browse to the appropriate location. The workload is defined depending on how you launched the Database Engine Tuning Advisor. If you launched it from a query window, you would see a Query radio button, and the File and Table radio buttons would be disabled. You also have to define the Database for Workload Analysis setting and finally select a database to tune.
NTip The Database Engine Tuning Advisor recommends indexed views only for platforms that support them. SQL Server 2008 Enterprise Edition does, but Standard Edition doesn’t.
When you select a database, you can also select individual tables to be tuned by clicking the drop-down box on the right side of the screen; you’ll see a list of tables like those shown in Figure 5-2.
C H A P T E R 5 N D A T A B A S E E N G I N E T U N I N G A D V I S O R
Figure 5-2. Clicking the boxes defines individual tables for tuning in the Database Engine Tuning Advisor. Once you define the workload, you need to select the Tuning Options tab, which is shown in Figure 5-3.
Figure 5-3. Defining options in the Database Engine Tuning Advisor You define the length of time you want the Database Engine Tuning Advisor to run by selecting Limit Tuning Time and then defining a date and time for the tuning to stop. The longer the Database Engine Tuning Advisor runs, the better recommendations it should make. You pick the type of physical design structures to be considered for creation by the Database Engine Tuning Advisor, and you can also set the partitioning strategy so that the tuning advisor knows whether it should consider partitioning the tables and indexes as part of the analysis. Just remember, partitioning isn’t necessarily a desirable outcome if your data and structures don’t warrant it. Finally, you can define the physical design structures that you want
153
154
C H A P T E R 5 N D A T A B A S E E N G I N E T U N I N G A D V I S O R
left alone within the database. Changing these options will narrow or widen the choices that the Database Engine Tuning Advisor can make to improve performance. You can click the Advanced Options button to see even more options, as shown in Figure 5-4.
Figure 5-4. Advanced Tuning Options dialog box This dialog box allows you to limit the space of the recommendations and the number of columns that can be included in an index. Finally, you can define whether the new indexes or changes in indexes are done as an online or offline index operation. Once you’ve appropriately defined all of these settings, you can start the Database Engine Tuning Advisor by clicking the Start Analysis button. The sessions created are kept in the io`^ database for any server instance that you run the Database Engine Tuning Advisor against. It displays details about what is being analyzed and the progress made, which you can see in Figure 5-5.
Figure 5-5. Tuning progress
C H A P T E R 5 N D A T A B A S E E N G I N E T U N I N G A D V I S O R
You’ll see more detailed examples of the progress displayed in the example analysis in the next session. After the analysis completes, you’ll get a list of recommendations (visible in Figure 5-6), and a number of reports become available. Table 5-1 describes the reports. Table 5-1. Database Engine Tuning Advisor Reports
Report Name
Report Description
Column Access
Lists the columns and tables referenced in the workload.
Database Access
Lists each database referenced in the workload and percentage of workload statements for each database.
Event Frequency
Lists all events in the workload ordered by frequency of occurrence.
Index Detail (Current)
Defines indexes and their properties referenced by the workload.
Index Detail (Recommended)
Is the same as the Index Detail (Current) report but shows the information about the indexes recommended by the Database Engine Tuning Advisor.
Index Usage (Current)
Lists the indexes and the percentage of their use referenced by the workload.
Index Usage (Recommended)
Is the same as the Index Usage (Current) report but from the recommended indexes.
Statement Cost
Lists the performance improvements for each statement if the recommendations are implemented.
Statement Cost Range
Breaks down the costs improvements by percentiles to show how much benefit you can achieve for any given set of changes.
Statement Detail
Lists the statements in the workload, their cost, and the reduced cost if the recommendations are implemented.
Statement-to-Index Relationship
Lists the indexes referenced by individual statements. Current and recommended versions of the report are available.
Table Access
Lists the tables referenced by the workload.
View-to-Table Relationship
Lists the tables referenced by materialized views.
Workload Analysis
Gives details about the workload, including the number of statements, the number of statements whose cost is decreased, and the number where the cost remains the same.
Database Engine Tuning Advisor Examples The best way to learn how to use the Database Engine Tuning Advisor is to use it. It’s not a terribly difficult tool to master, so I recommend opening it and getting started.
Tuning a Query You can use the Database Engine Tuning Advisor to recommend indexes for a complete database by using a workload that fairly represents all SQL activities. You can also use it to recommend indexes for a set of problematic queries.
155
156
C H A P T E R 5 N D A T A B A S E E N G I N E T U N I N G A D V I S O R
To learn how you can use the Database Engine Tuning Advisor to get index recommendations on a set of problematic queries, say you have a simple query that is called rather frequently. Because of the frequency, you want a quick turnaround for some tuning. This is the query: OAHA?Pokd*@qa@]pa (okd*?qopkianE@ BNKIO]hao*O]haoKn`anDa]`an=Ookd SDANA@qa@]pa>APSAAJ#-+-+-53,#=J@#-+-+-53-# =J@Op]pqo:.,, To analyze the query, right-click it in the query window, and select Analyze Query in the Database Engine Tuning Advisor. The advisor opens with a window where you can change the session name to something meaningful. In this case, I chose Report Query Round 1 – 10/5/2008. The database and tables don’t need to be edited. The first tab, General, will look like Figure 5-6 when you’re done.
Figure 5-6. Query tuning general settings Because this query is important and tuning it is extremely critical to the business, I’m going to change some settings on the Tuning Options tab. For the purposes of the example, I’m going to let the Database Engine Tuning Advisor run for 15 minutes, but a more realistic approach here would be to let it run for an hour. I’m going to select the Include Filtered Indexes check box so that if a filtered index will help, it can be considered. I’m also going to switch the Partitioning Strategy to Employ setting from No Partitioning to Full Partitioning. Finally, I’m going to allow the Database Engine Tuning Advisor to come up with structural changes if it can find any that will help by switching from Keep All Existing PDS to Do Not Keep Any Existing PDS. Once completed, the Tuning Options tab will look like Figure 5-7.
C H A P T E R 5 N D A T A B A S E E N G I N E T U N I N G A D V I S O R
Figure 5-7. Tuning Options tab adjusted After starting the analysis, the progress screen should appear. Although the settings were for 15 minutes of evaluations, it took only about a minute for it to evaluate this query. The initial recommendations were not good. As you can see in Figure 5-8, the Database Engine Tuning Advisor has recommended dropping a huge swath of indexes in the database. This is not the type of recommendation that you want when running the tool.
Figure 5-8. Query tuning initial recommendations
157
158
C H A P T E R 5 N D A T A B A S E E N G I N E T U N I N G A D V I S O R
This is because the Database Engine Tuning Advisor assumes that the load being tested is the full load of the database. If there are indexes not being used, then they should be removed. This is a best practice and one that should be implemented on any database. However, in this case, this is a single query, not a full load of the system. To see whether the advisor can come up with a meaningful recommendation, you must start a new session. This time, I’ll adjust the options so that the Database Engine Tuning Advisor will not be able to drop any of the existing structure. This is set on the Tuning Options tab (shown earlier in Figure 5-7). There I’ll change the Physical Design Structure (PDS) to Keep in Database setting from Do Not Keep Any Existing PDS to Keep All Existing PDS. I’ll keep the running time the same because the evaluation worked well within the time frame. Running the Database Engine Tuning Advisor again, it finishes in less than a minute and displays the recommendations shown in Figure 5-9.
Figure 5-9. Query tuning recommendations The first time through, the Database Engine Tuning Advisor suggested dropping most of the indexes on the tables being tested and a bunch of the related tables. This time it suggests creating a covering index on the two columns referenced in the query. As outlined in Chapter 4, a covering index is one of the most performant methods of indexing. The Database Engine Tuning Advisor was able to recognize that creating an index with all the columns referenced by the query, a covering index, would perform best. Once you’ve received a recommendation, you should take a look at the proposed T-SQL command. Finally, you’ll want to apply the recommendation. Select Actions ¢ Evaluate Recommendations. This opens a new Database Engine Tuning Advisor session and allows you to evaluate whether the recommendations will work. It looks just like a regular evaluation report. Next select Actions ¢ Apply Recommendation. This opens a dialog box that allows you to apply the recommendation immediately or schedule the application (see Figure 5-10).
C H A P T E R 5 N D A T A B A S E E N G I N E T U N I N G A D V I S O R
Figure 5-10. Apply Recommendations dialog box If you click the OK button, the Database Engine Tuning Advisor will apply the index to the database where you’ve been testing queries (see Figure 5-11).
Figure 5-11. A successful tuning session applied After you generate recommendations, you may want to, instead of applying them on the spot, save the T-SQL statements to a file and accumulate a series of changes for release to your production environment during scheduled deployment windows. Remember that applying indexes to tables, especially large tables, can cause a performance impact to processes actively running on the system while the index is being created. Although getting index suggestions one at a time is nice, it would be better to be able to get large swaths of the database checked all at once. That’s where tuning a trace workload comes in.
Tuning a Trace Workload Capturing a trace from the real-world queries that are running against a production server is a way to feed meaningful data to the Database Engine Tuning Advisor. (Capturing traces was covered in Chapter 3.) The easiest way to define a trace for use in the Database Engine Tuning Advisor is to implement the trace using the Tuning template. Start the trace on the system you need to tune. I generated three different loads by running queries in a loop from the omhlo*ata command prompt. This is the PowerShell command prompt with the SQL Server configuration settings. It gets installed with SQL Server.
159
160
C H A P T E R 5 N D A T A B A S E E N G I N E T U N I N G A D V I S O R
These are the scripts run (mqanuhk]`*lo-): r]h9,7sdeha$ r]h)hp1,%w r]h''7Ejrkga)Omh_i` )OanranUkqnOanranJ]ia)@]p]^]oa=`rajpqnaSkngo.,,4 )EjlqpBehal]pdXmqanuhk]`*omhy r]h9,7sdeha$ r]h)hp.,,,,%w r]h''7Ejrkga)omh_i` )OanranUkqnOanranJ]ia)@]p]^]oa=`rajpqnaSkngo.,,4 )MqanuATA?`^k*qolCapAilhkuaaI]j]cano<>qoejaooAjpepuE@9 r]hy r]h9,7sdeha$ r]h)hp.,,,,%w r]h''7Ejrkga)omh_i` )OanranUkqnOanranJ]ia)@]p]^]oa=`rajpqnaSkngo.,,4 )MqanuATA?`^k*qolCapI]j]canAilhkuaao<>qoejaooAjpepuE@9 r]hy The mqanuhk]`*omh file is also available in the download file. It’s just a collection of queries with various parameters to provide a simulated load.
NNote For more information on PowerShell, check out Pro Windows PowerShell by Hristo Deshev (Apress, 2008).
Once you’ve created the trace file, open the Database Engine Tuning Advisor. It defaults to a file type, so you’ll only have to browse to the trace file location. Set the appropriate tuning options, and start the analysis. This time, it will take more than a minute to run (see Figure 5-12).
Figure 5-12. Database tuning engine in progress As you can see, simply passing any number to the qolCapAilhkuaaI]j]cano procedure, and others, can generate instances where no tables were referenced. The processing runs for about 15 minutes on my machine. Then it generates output, shown in Figure 5-13.
C H A P T E R 5 N D A T A B A S E E N G I N E T U N I N G A D V I S O R
Figure 5-13. No recommendations After running all the queries through the Database Engine Tuning Advisor, the best recommendation it could currently come up with was to drop a couple of indexed views. That doesn’t mean there aren’t other possible improvements; it just means that the advisor is not always able to recognize all the possible improvements.
Database Engine Tuning Advisor Limitations The Database Engine Tuning Advisor recommendations are based on the input workload. If the input workload is not a true representation of the actual workload, then the recommended indexes may sometimes have a negative effect on some queries that are missing in the workload. But most important, as you saw in the second example of this chapter, the Database Engine Tuning Advisor may not recognize possible tuning opportunities. It has a sophisticated testing engine, but in some scenarios, its capabilities are limited. For a production server, you should ensure that the SQL trace includes a complete representation of the database workload. For most database applications, capturing a trace for a complete day usually includes most of the queries executed on the database. A few of the other considerations/limitations with the Database Engine Tuning Advisor are as follows:
161
162
CHAPTER 5 N D ATA B A S E ENG INE TU NING A DVIS OR
Ê
UÊ Trace input using the OMH6>]p_d?kilhapa` event: As mentioned earlier, the SQL trace input to the Database Engine Tuning Advisor must include the OMH6>]p_d?kilhapa` event; otherwise, the wizard won’t be able to identify the queries in the workload.
Ê
UÊ Query distribution in the workload: In a workload, a query may be executed multiple times with the same parameter value. Even a small performance improvement to the most common query can make a bigger contribution to the performance of the overall workload, compared to a large improvement in performance of a query that is executed only once.
Ê
UÊ Index hints: Index hints in a SQL query can prevent the Database Engine Tuning Advisor from choosing a better execution plan. The wizard includes all index hints used in a SQL query as part of its recommendations. Because these indexes may not be optimal for the table, remove all index hints from queries before submitting the workload to the wizard, bearing in mind that you need to add them back in to see whether they do actually improve performance.
Summary As you learned in this chapter, the Database Engine Tuning Advisor is a useful tool for analyzing the effectiveness of existing indexes and recommending new indexes for a SQL workload. As the SQL workload changes over time, you can use this tool to determine which existing indexes are no longer in use and which new indexes are required to improve performance. It can be a good idea to run the wizard occasionally just to check that your existing indexes really are the best fit for your current workload. It also provides many useful reports for analyzing the SQL workload and the effectiveness of its own recommendations. Just remember that the limitations of the tool prevent it from spotting all tuning opportunities. If your database is in bad shape, this tool can give you a quick leg up. If you’re already monitoring and tuning your queries regularly, you may see no benefit from the recommendations of the Database Engine Tuning Advisor. Frequently, you will rely on nonclustered indexes to improve the performance of a SQL workload. This assumes that you’ve already assigned a clustered index to your tables. Because the performance of a nonclustered index is highly dependent on the cost of the bookmark lookup associated with the nonclustered index, you will see in the next chapter how to analyze and resolve a bookmark lookup.
CHAPTER
6
Bookmark Lookup Analysis T
o maximize the benefit from nonclustered indexes, you must minimize the cost of the data retrieval as much as possible. A major overhead associated with nonclustered indexes is the cost of excessive key lookups, commonly known as bookmark lookups, which are a mechanism to navigate from a nonclustered index row to the corresponding data row in the clustered index or the base table. Therefore, it makes sense to look at the cause of bookmark lookups and to evaluate how to avoid this cost. In this chapter, I cover the following topics: Ê
UÊ /
iÊ«ÕÀ«ÃiÊvÊL>ÀÊÕ«Ã
Ê
UÊ À>ÜL>VÃÊvÊÕÃ}ÊL>ÀÊÕ«Ã
Ê
UÊ >ÞÃÃÊvÊÌ
iÊV>ÕÃiÊvÊL>ÀÊÕ«Ã
Ê
UÊ /iV
µÕiÃÊÌÊÀiÃÛiÊL>ÀÊÕ«Ã
Purpose of Bookmark Lookups 7
iÊ>Ê-+ʵÕiÀÞÊÀiµÕiÃÌà a small number of rows, the optimizer can use the nonclustered index, if available, on the column(s) in the SDANA or FKEJÊV>ÕÃiÊÌÊÀiÌÀiÛiÊÌ
iÊ`>Ì>°ÊvÊÌ
iʵÕiÀÞÊ refers to columns that are not part of the nonclustered index used to retrieve the data, then >Û}>ÌÊÃÊÀiµÕÀi`ÊvÀÊÌ
iÊ`iÝÊÀÜÊÌÊÌ
iÊVÀÀië`}Ê`>Ì>ÊÀÜÊÊÌ
iÊÌ>LiÊÌÊ>VViÃÃÊ these columns. For example, in the following OAHA?P statement, if the nonclustered index used by the «ÌâiÀÊ`iýÌÊVÕ`iÊ>ÊÌ
iÊVÕÃ]Ê>Û}>ÌÊÜÊLiÊÀiµÕÀi`ÊvÀÊ>ÊVÕÃÌiÀi`Ê index row to the data row in the base table to retrieve the value of those columns: OAHA?Pl*WJ]iaY (=RC$ok`*HejaPkp]h% BNKIO]hao*O]haoKn`an@ap]eh=Ook` FKEJLnk`q_pekj*Lnk`q_pl KJok`*Lnk`q_pE@9l*Lnk`q_pE@ SDANAok`*Lnk`q_pE@9332 CNKQL>Uok`*?]nneanPn]_gejcJqi^an (l*WJ]iaY D=REJCI=T$Kn`anMpu%:KN@AN>UIEJ$ok`*HejaPkp]h% 163
164
CHAPTER 6 N BO OK MA R K L OOK U P A NA L YS IS
The O]haoKn`an@ap]eh table has a nonclustered index on the Lnk`q_pE@ column. The optimizer can use the index to filter the rows from the table. The table has a clustered index on O]haoKn`anE@ and O]haoKn`an@ap]ehE@, so they would be included in the nonclustered `iÝ°Ê ÕÌÊÃViÊÌ
iÞ½ÀiÊÌÊÀiviÀiVi`ÊÊÌ
iʵÕiÀÞ]ÊÌ
iÞÊܽÌÊ
i«ÊÌ
iʵÕiÀÞÊ>ÌÊ>°Ê/
iÊ other columns (HejaPkp]h, ?]nneanPn]_gejcJqi^an, Kn`anMpu, and HejaPkp]h) referred to by Ì
iʵÕiÀÞÊ>ÀiÊÌÊ>Û>>LiÊÊÌ
iÊVÕÃÌiÀi`Ê`iݰÊ/ÊviÌV
ÊÌ
iÊÛ>ÕiÃÊvÀÊÌ
ÃiÊVÕÃ]Ê navigation from the nonclustered index row to the corresponding data row through the clusÌiÀi`Ê`iÝÊÃÊÀiµÕÀi`]Ê>`ÊÌ
ÃÊ«iÀ>ÌÊÃÊ>ÊL>ÀÊÕ«°Ê9ÕÊV>ÊÃiiÊÌ
ÃÊÊ>VÌÊ in Figure 6-1.
Figure 6-1. Bookmark lookup in a complicated execution plan To better understand how a nonclustered index can cause a bookmark lookup, consider the following OAHA?PÊÃÌ>ÌiiÌ]ÊÜ
V
ÊÀiµÕiÃÌÃÊÞÊ>ÊviÜÊÀÜÃÊvÀÊÌ
iÊO]haoKn`an@ap]eh table by using a filter criterion on column Lnk`q_pE@: OAHA?P& BNKIO]hao*O]haoKn`an@ap]eh=Ook` SDANAok`*Lnk`q_pE@9332 The optimizer evaluates the SDANA clause and finds that the column Lnk`q_pE@ included in the SDANA clause has a nonclustered index on it that filters the number of rows down. Since ÞÊ>ÊviÜÊÀÜÃ]ÊÓÓÎ]Ê>ÀiÊÀiµÕiÃÌi`]ÊÀiÌÀiÛ}ÊÌ
iÊ`>Ì>ÊÌ
ÀÕ}
ÊÌ
iÊVÕÃÌiÀi`Ê`iÝÊÜÊLiÊ cheaper than scanning the clustered index (containing more than 120,000 rows) to identify the matching rows. The nonclustered index on column Lnk`q_pE@ will help identify the match}ÊÀÜÃʵÕVÞ°Ê/
iÊVÕÃÌiÀi`Ê`iÝÊVÕ`iÃÊVÕÊLnk`q_pE@ and the clustered index columns O]haoKn`anE@ and O]haoKn`an@ap]ehE@; all the other columns are not included. Therefore, as you may have guessed, to retrieve the rest of the columns while using the nonclustered `iÝ]ÊÞÕÊÀiµÕÀiÊ>ÊL>ÀÊÕ«° This is shown in the following metrics and in the execution plan in Figure 6-2 (you can turn on OP=PEOPE?OEK using the Query ¢ Query Options menu). Look for the GauHkkgql $?hqopana`% operator. That is the bookmark lookup in action. P]^ha#O]haoKn`an@ap]eh#*O_]j_kqjp-(hkce_]hna]`o3,5 ?LQpeia9,io(ah]loa`peia9/./io*
C H A P T E R 6 N B O O K M A R K LO O K U P A N A LY S I S
Figure 6-2. Execution plan with a bookmark lookup
Drawbacks of Bookmark Lookups A bookmark lookupÊÀiµÕÀiÃÊ`>Ì>Ê«>}iÊ>VViÃÃÊÊ>``ÌÊÌÊ`iÝÊ«>}iÊ>VViÃðÊVViÃÃ}Ê ÌÜÊÃiÌÃÊvÊ«>}iÃÊVÀi>ÃiÃÊÌ
iÊÕLiÀÊvÊ}V>ÊÀi>`ÃÊvÀÊÌ
iʵÕiÀÞ°Ê``Ì>Þ]ÊvÊÌ
iÊ «>}iÃÊ>ÀiÊÌÊ>Û>>LiÊÊiÀÞ]Ê>ÊL>ÀÊÕ«ÊÜÊ«ÀL>LÞÊÀiµÕÀiÊ>ÊÀ>`ÊÀÊ ÃiµÕiÌ>®ÊÉ"Ê«iÀ>ÌÊÊÌ
iÊ`ÃÊÌÊÕ«ÊvÀÊÌ
iÊ`iÝÊ«>}iÊÌÊÌ
iÊ`>Ì>Ê«>}iÊ>ÃÊ ÜiÊ>ÃÊÀiµÕÀ}ÊÌ
iÊiViÃÃ>ÀÞÊ *1Ê«ÜiÀÊÌÊ>ÀÃ
>ÊÌ
ÃÊ`>Ì>Ê>`Ê«iÀvÀÊÌ
iÊiViÃÃ>ÀÞÊ operations. This is because, for a large table, the index page and the corresponding data page usually won’t be close to each other on the disk. /
iÊVÀi>Ãi`Ê}V>ÊÀi>`ÃÊ>`ÊVÃÌÞÊ«
ÞÃV>ÊÀi>`ÃÊvÊÀiµÕÀi`®Ê>iÊÌ
iÊ`>Ì>ÀiÌÀiÛ>Ê «iÀ>ÌÊvÊÌ
iÊL>ÀÊիʵÕÌiÊVÃÌÞ°Ê/
ÃÊVÃÌÊv>VÌÀÊÃÊÌ
iÊÀi>ÃÊÌ
>ÌÊVÕÃÌiÀi`Ê `iÝiÃÊ>ÀiÊLiÌÌiÀÊÃÕÌi`ÊvÀʵÕiÀiÃÊÌ
>ÌÊÀiÌÕÀÊ>ÊÃ>ÊÃiÌÊvÊÀÜÃÊvÀÊÌ
iÊÌ>Li°ÊÃÊÌ
iÊnumLiÀÊvÊÀÜÃÊÀiÌÀiÛi`ÊLÞÊ>ʵÕiÀÞÊVÀi>ÃiÃ]ÊÌ
iÊÛiÀ
i>`ÊVÃÌÊvÊ>ÊL>ÀÊÕ«ÊLiViÃÊ unacceptable. To understand how a bookmark lookup makes a nonclustered index ineffective as the ÕLiÀÊvÊÀÜÃÊÀiÌÀiÛi`ÊVÀi>ÃiÃ]Êi̽ÃÊÊ>ÌÊ>Ê`vviÀiÌÊiÝ>«i°Ê/
iʵÕiÀÞÊÌ
>ÌÊ«À`ÕVi`Ê the execution plan in Figure 6-2 returned just a few rows from the O]haoKn`an@ap]eh table. i>Û}ÊÌ
iʵÕiÀÞÊÌ
iÊÃ>iÊLÕÌÊV
>}}ÊÌ
iÊ«>À>iÌiÀÊÌÊ>Ê`vviÀiÌÊÛ>ÕiÊÜ]ÊvÊVÕÀÃi]Ê change the number of rows returned. If you change the parameter value to look like this: OAHA?P& BNKIO]hao*O]haoKn`an@ap]eh=Ook` SDANAok`*Lnk`q_pE@935/ Ì
iÊÀÕ}ÊÌ
iʵÕiÀÞÊÀiÌÕÀÃÊÀiÊÌ
>ÊÇääÊÀÜÃ]ÊÜÌ
Ê`vviÀiÌÊ«iÀvÀ>ViÊiÌÀVÃÊ>`Ê>Ê completely different execution plan (Figure 6-3): P]^ha#O]haoKn`an@ap]eh#*O_]j_kqjp-(hkce_]hna]`o-.0, ?LQpeia9-1io(ah]loa`peia9//.io*
Figure 6-3. Execution plan with more rows
165
166
CHAPTER 6 N BO OK MA R K L OOK U P A NA L YS IS
To determine how costly it will be to use the nonclustered index, consider the number of }V>ÊÀi>`ÃÊ£]Ó{ä®Ê«iÀvÀi`ÊLÞÊÌ
iʵÕiÀÞÊ`ÕÀ}ÊÌ
iÊÌ>LiÊÃV>°ÊvÊÞÕÊforce the optimizer to use the nonclustered index by using an index hint, like this: OAHA?P& BNKIO]hao*O]haoKn`an@ap]eh=Ook`SEPD$EJ@AT$ET[O]haoKn`an@ap]eh[Lnk`q_pE@%% SDANAok`*Lnk`q_pE@935/ then the number of logical reads increases from 9 to 1,004: P]^ha#O]haoKn`an@ap]eh#*O_]j_kqjp-(hkce_]hna]`o.-3/ ?LQpeia9,io(ah]loa`peia9//2io* Figure 6-4 shows the corresponding execution plan.
Figure 6-4. Execution plan for fetching more rows with an index hint To benefit fromÊVÕÃÌiÀi`Ê`iÝiÃ]ʵÕiÀiÃÊÃ
Õ`ÊÀiµÕiÃÌÊ>ÊÀi>ÌÛiÞÊÃ>ÊÕLiÀÊvÊ ÀÜðʫ«V>ÌÊ`iÃ}Ê«>ÞÃÊ>Ê«ÀÌ>ÌÊÀiÊvÀÊÌ
iÊÀiµÕÀiiÌÃÊÌ
>ÌÊ
>`iÊ>À}iÊÀiÃÕÌÊ sets. For example, search engines on the Web mostly return a limited number of articles at a Ìi]ÊiÛiÊvÊÌ
iÊÃi>ÀV
ÊVÀÌiÀÊÀiÌÕÀÃÊÌ
ÕÃ>`ÃÊvÊ>ÌV
}Ê>ÀÌViðÊvÊÌ
iʵÕiÀiÃÊÀiµÕiÃÌÊ a large number of rows, then the increased overhead cost of a bookmark lookup makes the VÕÃÌiÀi`Ê`iÝÊÕÃÕÌ>LiÆÊÃÕLÃiµÕiÌÞ]ÊÞÕÊ
>ÛiÊÌÊVÃ`iÀÊÌ
iÊ«ÃÃLÌiÃÊvÊ>Û`ing the bookmark lookup operation.
Analyzing the Cause of a Bookmark Lookup Since a bookmarkÊÕ«ÊV>ÊLiÊ>ÊVÃÌÞÊ«iÀ>Ì]ÊÞÕÊÃ
Õ`Ê>>ÞâiÊÜ
>ÌÊV>ÕÃiÃÊ>ʵÕiÀÞÊ «>ÊÌÊV
ÃiÊ>ÊiÞÊÕ«ÊÃÌi«ÊÊ>ÊiÝiVÕÌÊ«>°Ê9ÕÊ>ÞÊv`ÊÌ
>ÌÊÞÕÊ>ÀiÊ>LiÊÌÊ>Û`Ê the bookmark lookup by including the missing columns in the nonclustered index key or as EJ?HQ@A columns at the index page level and thereby avoid the cost overhead associated with the bookmark lookup. To learn how to identify the columns not included in the nonclustered index, consider the vÜ}ʵÕiÀÞ]ÊÜ
V
Ê«ÕÃÊvÀ>ÌÊvÀÊÌ
iÊDqi]jNaokqn_ao*Ailhkuaa table based on J]pekj]hE@Jqi^an:
C H A P T E R 6 N B O O K M A R K LO O K U P A N A LY S I S
OAHA?PJ]pekj]hE@Jqi^an (Fk^Pepha (Dena@]pa BNKIDqi]jNaokqn_ao*Ailhkuaa=Oa SDANAa*J]pekj]hE@Jqi^an9#25/-242-/# This produces the following performance metrics and execution plan (see Figure 6-5): P]^ha#Ailhkuaa#*O_]j_kqjp,(hkce_]hna]`o0 ?LQpeia9,io(ah]loa`peia9-43io*
Figure 6-5. Execution plan with a bookmark lookup As shown in the execution plan, you have a key lookup. The OAHA?P statement refers to columns J]pekj]hE@Jqi^an, Fk^Pepha, and Dena@]pa. The nonclustered index on column J]pekj]hE@Jqi^an doesn’t provide values for columns Fk^Pepha and Dena@]pa, so a bookmark Õ«Ê«iÀ>ÌÊÜ>ÃÊÀiµÕÀi`ÊÌÊÀiÌÀiÛiÊÌ
ÃiÊVÕÃÊvÀÊÌ
iÊL>ÃiÊÌ>Li°ÊÜiÛiÀ]ÊÊÌ
iÊ Ài>ÊÜÀ`]ÊÌÊÕÃÕ>ÞÊܽÌÊLiÊÌ
ÃÊi>ÃÞÊÌÊ`iÌvÞÊ>ÊÌ
iÊVÕÃÊÕÃi`ÊLÞÊ>ʵÕiÀÞ°Ê,iiLiÀÊ that a bookmark lookup operation will be caused if all the columns referred to in any part of Ì
iʵÕiÀÞÊÌÊÕÃÌÊÌ
iÊÃiiVÌÊÃÌ®Ê>Ài½ÌÊVÕ`i`ÊÊÌ
iÊVÕÃÌiÀi`Ê`iÝÊÕÃi`° ÊÌ
iÊV>ÃiÊvÊ>ÊV«iÝʵÕiÀÞÊL>Ãi`ÊÊÛiÜÃÊ>`ÊÕÃiÀ`ivi`ÊvÕVÌÃ]ÊÌÊ>ÞÊLiÊ ÌÊ`vvVÕÌÊÌÊv`Ê>ÊÌ
iÊVÕÃÊÀiviÀÀi`ÊÌÊLÞÊÌ
iʵÕiÀÞ°ÊÃÊ>ÊÀiÃÕÌ]ÊÞÕÊii`Ê>ÊÃÌ>`>À`Ê mechanism to find the columns returned by the bookmark lookup that are not included in the nonclustered index. If you look at the tool tip on the GauHkkgql$?hqopana`% operation, you can see the output list for the operation. This shows you the columns being output by the bookmark lookup. ÜiÛiÀ]ÊvÊÌ
iÊÃÌÊÃÊÛiÀÞÊ}]ÊÌÊÜÊLiʵÕÌiÊ`vvVÕÌÊÌÊÀi>`]Ê>`ÊÞÕÊV>½ÌÊV«ÞÊ`>Ì>Ê vÀÊÌ
iÊÌÊÌ«°Ê-]ÊÌÊ}iÌÊÌ
iÊÃÌÊvÊÕÌ«ÕÌÊVÕÃʵÕVÞÊ>`Êi>ÃÞÊ>`ÊLiÊ>LiÊÌÊV«ÞÊ them, right-click the operator, which in this case is GauHkkgql$?hqopana`%. Then select the *À«iÀÌiÃÊiÕÊÌi°Ê-VÀÊ`ÜÊÌÊÌ
iÊ"ÕÌ«ÕÌÊÃÌÊ«À«iÀÌÞÊÊÌ
iÊ*À«iÀÌiÃÊÜ`ÜÊÌ
>ÌÊ opens (Figure 6-6). This property has a plus sign, which allows you to expand the column list, and has plus signs next to each column, which allows you to expand the properties of the column.
167
168
CHAPTER 6 N BO OK MA R K L OOK U P A NA L YS IS
Figure 6-6. Key lookup Properties window /Ê}iÌÊÌ
iÊÃÌÊvÊVÕÃÊ`ÀiVÌÞÊvÀÊÌ
iÊ*À«iÀÌiÃÊÜ`Ü]ÊVVÊÌ
iÊi«ÃÃÊÊÌ
iÊ right side of the Output List property. This opens the output list in a text window from which ÞÕÊV>ÊV«ÞÊÌ
iÊ`>Ì>ÊvÀÊÕÃiÊÜ
iÊ`vÞ}ÊÞÕÀÊ`iÝÊ}ÕÀiÊÈÇ®°
Figure 6-7. The required columns that were not available in the nonclustered index
C H A P T E R 6 N B O O K M A R K LO O K U P A N A LY S I S
Resolving Bookmark Lookups Since the relative cost of a bookmark lookup can be very high, you should, wherever possible, try to get rid of bookmark lookup operations. In the preceding section, you needed to obtain the values of columns Fk^Pepha and Dena@]pa without navigating from the index row to the `>Ì>ÊÀܰÊ9ÕÊV>Ê`ÊÌ
ÃÊÊÌ
ÀiiÊ`vviÀiÌÊÜ>ÞÃ]Ê>ÃÊiÝ«>i`ÊÊÌ
iÊvÜ}ÊÃiVÌð
Using a Clustered Index For a clustered index, the leaf page of the index is the same as the data page of the table. Therefore, when reading the values of the clustered index key columns, the database engine can also read the values of other columns without any navigation from the index row. In the previous example, if you convert the nonclustered index to a clustered index for a particular row, SQL Server can retrieve values of all the columns from the same page. Simply saying that you want to convert the nonclustered index to a clustered index is easy ÌÊ`°ÊÜiÛiÀ]ÊÊÌ
ÃÊV>Ãi]Ê>`ÊÊÃÌÊV>ÃiÃÊÞÕ½ÀiÊiÞÊÌÊiVÕÌiÀ]ÊÌÊýÌÊ«ÃÃLiÊÌÊ do so, since the table already has a clustered index in place. The clustered index on this table >ÃÊ
>««iÃÊÌÊLiÊÌ
iÊ«À>ÀÞÊiÞ°Ê9ÕÊÜÕ`Ê
>ÛiÊÌÊ`À«Ê>ÊvÀi}ÊiÞÊVÃÌÀ>ÌÃ]Ê`À«Ê and re-create the primary key as a nonclustered index, and then re-create the index against J]pekj]hE@Jqi^an. Not only do you need to take into account the work involved, but you may ÃiÀÕÃÞÊ>vviVÌÊÌ
iÀʵÕiÀiÃÊÌ
>ÌÊ>ÀiÊ`i«i`iÌÊÊÌ
iÊiÝÃÌ}ÊVÕÃÌiÀi`Ê`iݰ
NNote Remember that a table can have only one clustered index.
Using a Covering Index Ê
>«ÌiÀÊ{]ÊÞÕÊi>Ài`ÊÌ
>ÌÊ>ÊVÛiÀ}Ê`iÝÊÃÊiÊ>Ê«ÃiÕ`VÕÃÌiÀi`Ê`iÝÊvÀÊÌ
iʵÕiÀiÃ]Ê since it can return results without recourse to the table data. So, you can also use a covering index to avoid a bookmark lookup. To understand how you can use a covering index to avoid a bookmark lookup, examine Ì
iʵÕiÀÞÊ>}>ÃÌÊÌ
iÊDqi]jNaokqn_ao*Ailhkuaa table again: OAHA?PJ]pekj]hE@Jqi^an (Fk^Pepha (Dena@]pa BNKIDqi]jNaokqn_ao*Ailhkuaa=Oa SDANAa*J]pekj]hE@Jqi^an9#25/-242-/# /Ê>Û`ÊÌ
ÃÊL>À]ÊÞÕÊV>Ê>``ÊÌ
iÊVÕÃÊÀiviÀÀi`ÊÌÊÊÌ
iʵÕiÀÞ]ÊFk^Pepha and Dena@]pa, directly to the nonclustered index key. This will make the nonclustered index a coviÀ}Ê`iÝÊvÀÊÌ
ÃʵÕiÀÞÊLiV>ÕÃiÊ>ÊVÕÃÊV>ÊLiÊÀiÌÀiÛi`ÊvÀÊÌ
iÊ`iÝÊÜÌ
ÕÌÊ
>Û}Ê to go to the base table or clustered index.
169
170
CHAPTER 6 N BO OK MA R K L OOK U P A NA L YS IS
?NA=PAQJEMQAJKJ?HQOPANA@EJ@ATW=G[Ailhkuaa[J]pekj]hE@Jqi^anYKJ WDqi]jNaokqn_aoY*WAilhkuaaY $ J]pekj]hE@Jqi^an=O?(Fk^Pepha=O?(Dena@]pa=O? %SEPD`nkl[ateopejc ÜÊÜ
iÊÌ
iʵÕiÀÞÊ}iÌÃÊÀÕ]ÊÞÕ½ÊÃiiÊÌ
iÊvÜ}ÊiÌÀVÃÊ>`Ê>Ê`vviÀiÌÊiÝiVÕÌÊ plan (Figure 6-8): P]^ha#Ailhkuaa#*O_]j_kqjp-(hkce_]hna]`o. ?LQpeia9,io(ah]loa`peia9,io*
Figure 6-8. Execution plan with a covering index There are a couple of caveats to creating a covering index by changing the key, however. If you add too many columns to a nonclustered index, it becomes too wide, and the index >Ìi>ViÊVÃÌÊ>ÃÃV>Ìi`ÊÜÌ
ÊÌ
iÊ>VÌʵÕiÀiÃÊV>ÊVÀi>Ãi]Ê>ÃÊ`ÃVÕÃÃi`ÊÊ
>«ÌiÀÊ{°Ê Therefore, evaluate closely the number of columns (for size and data type) to be added to the nonclustered index key. If the total width of the additional columns is not too large (best determined through testing and measuring the resultant index size), then those columns can be added in the nonclustered index key to be used as a covering index. Also, if you add columns to Ì
iÊ`iÝÊiÞ]Ê`i«i`}ÊÊÌ
iÊ`iÝ]ÊvÊVÕÀÃi]ÊÞÕÊ>ÞÊLiÊ>vviVÌ}ÊÌ
iÀʵÕiÀiÃÊÊ>Êi}>tive fashion. They may have expected to see the index key columns in a particular order or may not refer to some of the columns in the key, causing the index to not be used by the optimizer. Another way to arrive at the covering index, without reshaping the index by adding key columns, is to use the EJ?HQ@AÊVÕðÊ
>}iÊÌ
iÊ`iÝÊÌÊÊiÊÌ
Ã\ ?NA=PAQJEMQAJKJ?HQOPANA@EJ@ATW=G[Ailhkuaa[J]pekj]hE@Jqi^anY KJWDqi]jNaokqn_aoY*WAilhkuaaY $ J]pekj]hE@Jqi^an=O? %EJ?HQ@A$Fk^Pepha(Dena@]pa% SEPD`nkl[ateopejc ÜÊÜ
iÊÌ
iʵÕiÀÞÊÃÊÀÕ]ÊÞÕÊ}iÌÊÌ
iÊvÜ}ÊiÌÀVÃÊ>`ÊiÝiVÕÌÊ«>Ê}ÕÀiÊÈ®\ P]^ha#Ailhkuaa#*O_]j_kqjp,(hkce_]hna]`o. ?LQpeia9,io(ah]loa`peia9,io*
Figure 6-9. Execution plan with EJ?HQ@A columns
C H A P T E R 6 N B O O K M A R K LO O K U P A N A LY S I S
The index is still covering, exactly as it was in the execution plan displayed in Figure 6-8. Because the data is stored at the leaf level of the index, when the index is used to retrieve the key values, the rest of the columns in the EJ?HQ@A statement are available for use, almost like they were part of the key. Another way to get a covering index is to take advantage of the structures within SQL -iÀÛiÀ°ÊvÊÌ
iÊ«ÀiÛÕÃʵÕiÀÞÊÜiÀiÊ`vi`ÊÃ}
ÌÞÊÌÊÀiÌÀiÛiÊ>Ê`vviÀiÌÊÃiÌÊvÊ`>Ì>ÊÃÌi>`ÊvÊ a particular J]pekj]hE@Jqi^an and its associated Fk^Pepha and Dena@]pa]ÊÌ
ÃÊÌiÊÌ
iʵÕiÀÞÊ would retrieve the J]pekj]hE@Jqi^an as an alternate key and the >qoejaooAjpepuE@, the primary key for the table, over a range of values: OAHA?PJ]pekj]hE@Jqi^an (>qoejaooAjpepuE@ BNKIDqi]jNaokqn_ao*Ailhkuaa=Oa SDANAa*J]pekj]hE@Jqi^an>APSAAJ#25/-242-/# =J@#3,,,,,,,,,# The original index on the table doesn’t reference the >qoejaooAjpepuE@ column in any way: ?NA=PAQJEMQAJKJ?HQOPANA@EJ@ATW=G[Ailhkuaa[J]pekj]hE@Jqi^anY KJWDqi]jNaokqn_aoY*WAilhkuaaY $ WJ]pekj]hE@Jqi^anY=O? %SEPD`nkl[ateopejc 7
iÊÌ
iʵÕiÀÞÊÃÊÀÕÊ>}>ÃÌÊÌ
iÊÌ>Li]ÊÞÕÊV>ÊÃiiÊÌ
iÊÀiÃÕÌÃÊÃ
ÜÊÊ}ÕÀiÊÈ£ä°
Figure 6-10. Unexpected covering index ÜÊ``ÊÌ
iÊ«ÌâiÀÊ>ÀÀÛiÊ>ÌÊ>ÊVÛiÀ}Ê`iÝÊvÀÊÌ
ÃʵÕiÀÞÊL>Ãi`ÊÊÌ
iÊ`iÝÊ«Àvided? It’s aware that on a table with a clustered index, the clustered index key, in this case the >qoejaooAjpepuE@ column, is stored as a pointer to the data with the nonclustered index. That i>ÃÊÌ
>ÌÊ>ÞʵÕiÀÞÊÌ
>ÌÊVÀ«À>ÌiÃÊ>ÊVÕÃÌiÀi`Ê`iÝÊ>`Ê>ÊÃiÌÊvÊVÕÃÊvÀÊ>ÊVÕÃÌiÀi`Ê`iÝÊ>ÃÊ«>ÀÌÊvÊÌ
iÊvÌiÀ}ÊiV
>ÃÃÊvÊÌ
iʵÕiÀÞ]ÊÌ
iÊSDANA clause, or the join criteria can take advantage of the covering index. To see how these three different indexes are reflected in storage, you can look at the statistics of the indexes themselves using @>??ODKS[OP=PEOPE?O°Ê7
iÊÞÕÊÀÕÊÌ
iÊvÜ}ʵÕiÀÞÊ against the index, you can see the output in Figure 6-11: @>??ODKS[OP=PEOPE?O$#Dqi]jNaokqn_ao*Ailhkuaa#(=G[Ailhkuaa[J]pekj]hE@Jqi^an%
Figure 6-11. @>??ODKS[OP=PEOPE?O output for original index
171
172
CHAPTER 6 N BO OK MA R K L OOK U P A NA L YS IS
As you can see, the J]pekj]hE@Jqi^an is listed first, but the primary key for the table is included as part of the index, so a second row that includes the >qoejaooAjpepuE@ column is there. It makes the average length of the key about 22 bytes. This is how indexes that refer to the primary key values as well as the index key values can function as covering indexes. If you run the same @>??ODKS[OP=PEOPE?O on the first alternate index you tried, with all three columns included in the key, like so: ?NA=PAQJEMQAJKJ?HQOPANA@EJ@ATW=G[Ailhkuaa[J]pekj]hE@Jqi^anY KJWDqi]jNaokqn_aoY*WAilhkuaaY $ J]pekj]hE@Jqi^an=O?(Fk^Pepha=O?(Dena@]pa=O? %SEPD`nkl[ateopejc you will see a different set of statistics (Figure 6-12).
Figure 6-12. @>??ODKS[OP=PEOPE?O output for a wide key covering index 9ÕÊÜÊÃiiÊÌ
iÊVÕÃÊ>``i`ÊÕ«]Ê>ÊÌ
ÀiiÊvÊÌ
iÊ`iÝÊiÞÊVÕÃ]Ê>`Êv>ÞÊÌ
iÊ«À>ÀÞÊiÞÊ>``i`ʰÊÃÌi>`ÊvÊ>ÊÜ`Ì
ÊvÊÓÓÊLÞÌiÃ]Ê̽ÃÊ}ÀÜÊÌÊÇ{°Ê/
>ÌÊÀiviVÌÃÊÌ
iÊ>``ÌÊvÊ the Fk^Pepha column, a R=N?D=N$1,% as well as the 16-byte-wide datetime field. Finally, looking at the statistics for the second alternate index: ?NA=PAQJEMQAJKJ?HQOPANA@EJ@ATW=G[Ailhkuaa[J]pekj]hE@Jqi^anY KJWDqi]jNaokqn_aoY*WAilhkuaaY $ J]pekj]hE@Jqi^an=O? %EJ?HQ@A$Fk^Pepha(Dena@]pa% SEPD`nkl[ateopejc With the included columns you’ll see the output in Figure 6-13.
Figure 6-13. @>??ODKS[OP=PEOPE?O output for a covering index using EJ?HQ@A Now the key width is back to the original size because the columns in the EJ?HQ@A statement are stored not with the key but at the leaf level of the index. There is more interesting information to be gleaned from the data stored about statistics, LÕÌʽÊVÛiÀÊÌ
>ÌÊÊ
>«ÌiÀÊǰ
C H A P T E R 6 N B O O K M A R K LO O K U P A N A LY S I S
Using an Index Join If the coveringÊ`iÝÊLiViÃÊÛiÀÞÊÜ`i]ÊÌ
iÊÞÕÊ}
ÌÊVÃ`iÀÊ>Ê`iÝÊÊÌiV
µÕi°Ê ÃÊiÝ«>i`ÊÊ
>«ÌiÀÊ{]ÊÌ
iÊ`iÝÊÊÌiV
µÕiÊÕÃiÃÊ>Ê`iÝ intersection between two ÀÊÀiÊ`iÝiÃÊÌÊVÛiÀÊ>ʵÕiÀÞÊvÕÞ°Ê-ViÊÌ
iÊ`iÝÊÊÌiV
µÕiÊÀiµÕÀiÃÊ>VViÃÃÊÌÊÀiÊ Ì
>ÊiÊ`iÝ]ÊÌÊ
>ÃÊÌÊ«iÀvÀÊ}V>ÊÀi>`ÃÊÊ>ÊÌ
iÊ`iÝiÃÊÕÃi`ÊÊÌ
iÊ`iÝÊ°Ê ÃiµÕiÌÞ]ÊÌÊÀiµÕÀiÃÊ>Ê
}
iÀÊÕLiÀÊvÊ}V>ÊÀi>`ÃÊÌ
>ÊÌ
iÊVÛiÀ}Ê`iÝ°Ê ÕÌÊÃViÊÌ
iÊ ÕÌ«iÊ>ÀÀÜÊ`iÝiÃÊÕÃi`ÊvÀÊÌ
iÊ`iÝÊÊV>ÊÃiÀÛiÊÀiʵÕiÀiÃÊÌ
>Ê>ÊÜ`iÊVÛiÀ}Ê `iÝÊ>ÃÊiÝ«>i`ÊÊ
>«ÌiÀÊ{®]ÊÞÕÊV>ÊViÀÌ>ÞÊVÃ`iÀÊÌ
iÊ`iÝÊÊ>ÃÊ>ÊÌiV
µÕiÊÌÊ avoid bookmark lookups. To better understand how an index join can be used to avoid bookmark lookups, run the vÜ}ʵÕiÀÞÊ>}>ÃÌÊÌ
iÊLqn_d]oaKn`anDa]`an table in order to retrieve a Lqn_d]oaKn`anE@ for a particular vendor on a particular date: OAHA?Plkd*Lqn_d]oaKn`anE@ (lkd*Raj`knE@ (lkd*Kn`an@]pa BNKILqn_d]oejc*Lqn_d]oaKn`anDa]`an=Olkd SDANARaj`knE@9-2/2 =J@lkd*Kn`an@]pa9#-.+1+.,,/# 7
iÊÀÕ]ÊÌ
ÃʵÕiÀÞÊÀiÃÕÌÃÊÊ>ÊGauHkkgqlÊ«iÀ>ÌÊ}ÕÀiÊÈ£{®Ê>`ÊÌ
iÊvÜ}ÊÉ"\ P]^ha#Lqn_d]oaKn`anDa]`an#*O_]j_kqjp-(hkce_]hna]`o-, ?LQpeia9,io(ah]loa`peia902io*
Figure 6-14. A GauHkkgql operation The bookmark lookup is caused since all the columns referred to by the OAHA?P statement and SDANA clause are not included in the nonclustered index on column Raj`knE@°Ê1Ã}ÊÌ
iÊ VÕÃÌiÀi`Ê`iÝÊÃÊÃÌÊLiÌÌiÀÊÌ
>ÊÌÊÕÃ}ÊÌ]ÊÃViÊÌ
>ÌÊÜÕ`ÊÀiµÕÀiÊ>ÊÃV>ÊÊÌ
iÊÌ>LiÊ (in this case, a clustered index scan) with a larger number of logical reads. To avoid the bookmark lookup, you can consider a covering index on the column Kn`an@]pa as explained in the previous section. But in addition to the covering index solution, ÞÕÊV>ÊVÃ`iÀÊ>Ê`iÝʰÊÃÊÞÕÊi>Ài`]Ê>Ê`iÝÊÊÀiµÕÀiÃÊ>ÀÀÜiÀÊ`iÝiÃÊÌ
>Ê the covering index and thereby provides the following two benefits: Ê
UÊ ÕÌ«iÊ>ÀÀÜÊ`iÝiÃÊV>ÊÃiÀÛiÊ>Ê>À}iÀÊÕLiÀÊvʵÕiÀiÃÊÌ
>ÊÌ
iÊÜ`iÊVÛiÀ}Ê index.
Ê
UÊ
>ÀÀÜÊ`iÝiÃÊÀiµÕÀiÊiÃÃÊ>Ìi>ViÊÛiÀ
i>`ÊÌ
>ÊÌ
iÊÜ`iÊVÛiÀ}Ê`iݰ
173
174
CHAPTER 6 N BO OK MA R K L OOK U P A NA L YS IS
To avoid the bookmark lookup using an index join, create a narrow nonclustered index on column Kn`an@]pa that is not included in the existing nonclustered index: ?NA=PAJKJ?HQOPANA@EJ@ATEt[PAOPKJLqn_d]oejc*Lqn_d]oaKn`anDa]`an$Kn`an`]pa% If you run the OAHA?P statement again, the following output and the execution plan shown in Figure 6-15 are returned: P]^ha#Lqn_d]oaKn`anDa]`an#*O_]j_kqjp.(hkce_]hna]`o0 ?LQpeia9,io(ah]loa`peia9/1io*
Figure 6-15. Execution plan without a bookmark lookup From the preceding execution plan, you can see that the optimizer used the nonclustered index, ET[Lqn_d]oaKn`an[Raj`knE@, on column Raj`knE@ and the new nonclustered index, Et[PAOP, on column Kn`anE@ÊÌÊÃiÀÛiÊÌ
iʵÕiÀÞÊvÕÞÊÜÌ
ÕÌÊ
ÌÌ}ÊÌ
iÊL>ÃiÊÌ>Li°Ê/
ÃÊ`iÝÊ Ê«iÀ>ÌÊ>Û`i`ÊÌ
iÊL>ÀÊÕ«Ê>`ÊVÃiµÕiÌÞÊ`iVÀi>Ãi`ÊÌ
iÊÕLiÀÊvÊ}cal reads from 10 to 4. It is true that a covering index on columns Raj`knE@ and Kn`anE@ (_-, _.) could reduce the number of logical reads further. But it may not always be possible to use covering indexes, since they can be wide and have their associated overhead. In such cases, an index join can be a good alternative.
Summary As demonstrated in this chapter, the bookmark lookup step associated with a nonclustered index makes data retrieval through a nonclustered index very costly. The SQL Server optimizer takes this into account when generating an execution plan, and if it finds the overhead cost of using a nonclustered index to be very high, it discards the index and performs a table scan (or a clustered index scan if the table contains a clustered index). Therefore, to improve the effectiveness of a nonclustered index, it makes sense to analyze the cause of a bookmark lookup and consider whether you can avoid it completely by adding fields to the index key or to the EJ?HQ@A column (or index join) and creating a covering index. 1«ÊÌÊÌ
ÃÊ«Ì]ÊÞÕÊ
>ÛiÊVViÌÀ>Ìi`ÊÊ`iÝ}ÊÌiV
µÕiÃÊ>`Ê«ÀiÃÕi`ÊÌ
>ÌÊÌ
iÊ -+Ê-iÀÛiÀÊ«ÌâiÀÊÜÕ`ÊLiÊ>LiÊÌÊ`iÌiÀiÊÌ
iÊivviVÌÛiiÃÃÊvÊ>Ê`iÝÊvÀÊ>ʵÕiÀÞ°ÊÊ the next chapter, you will see the importance of statistics in helping the optimizer determine the effectiveness of an index.
CHAPTER
7
Statistics Analysis B
y now, you should have a good understanding of the importance of indexes. It is equally important for the optimizer to have the necessary statistics on the data distribution so that it can choose indexes effectively. In SQL Server, this information is maintained in the form of statistics on the index key. In this chapter, you’ll learn the importance of statistics in query optimization. Specifically, I cover the following topics: Ê
UÊ /
iÊÀiÊvÊÃÌ>ÌÃÌVÃÊʵÕiÀÞÊ«Ìâ>Ì
Ê
UÊ /
iÊ«ÀÌ>ViÊvÊÃÌ>ÌÃÌVÃÊÊVÕÃÊÜÌ
Ê`iÝiÃ
Ê
UÊ /
iÊ«ÀÌ>ViÊvÊÃÌ>ÌÃÌVÃÊÊ`iÝi`ÊVÕÃÊÕÃi`ÊÊÊ>`ÊvÌiÀÊVÀÌiÀ>
Ê
UÊ >ÞÃÃÊvÊÃ}iVÕÊ>`ÊÕÌVÕÊÃÌ>ÌÃÌVÃ]ÊVÕ`}ÊÌ
iÊV«ÕÌ>ÌÊvÊ selectivity of a column for indexing
Ê
UÊ -Ì>ÌÃÌVÃÊ>Ìi>Vi
Ê
UÊ vviVÌÛiÊiÛ>Õ>ÌÊvÊÃÌ>ÌÃÌVÃÊÕÃi`ÊÊ>ʵÕiÀÞÊiÝiVÕÌ
The Role of Statistics in Query Optimization SQL Server’s query optimizer is aÊVÃÌL>Ãi`Ê«ÌâiÀÆÊÌÊ`iV`iÃÊÊÌ
iÊLiÃÌÊ`>Ì>Ê>VViÃÃÊ iV
>ÃÊ>`ÊÊÃÌÀ>Ìi}ÞÊLÞÊ`iÌvÞ}ÊÌ
iÊÃiiVÌÛÌÞ]Ê
ÜÊÕµÕiÊÌ
iÊ`>Ì>ÊÃ]Ê>`ÊÜ
V
Ê columns are used in filtering the data (meaning via the SDANA or FKEJ clause). Statistics exist with an index, but they also exist on columns without an index that are used as part of a predi V>Ìi°ÊÃÊÞÕÊi>Ài`ÊÊ
>«ÌiÀÊ{]ÊÞÕÊÃ
Õ`ÊÕÃiÊ>ÊVÕÃÌiÀi`Ê`iÝÊÌÊÀiÌÀiÛiÊ>ÊÃ>Ê result set, whereas for data in related sets, a clustered index works better. With a large result set, going to the clustered index or table directly is usually more beneficial. 1«Ì`>ÌiÊvÀ>ÌÊÊ`>Ì>Ê`ÃÌÀLÕÌÊÊÌ
iÊVÕÃÊÀiviÀiVi`Ê>ÃÊpredicates helps the optimizer determine the query strategy to use. In SQL Server, this information is maintained in the form of statistics, which are essential for the VÃÌL>Ãi`Ê«ÌâiÀÊÌÊVÀi>ÌiÊ >ÊivviVÌÛiʵÕiÀÞÊiÝiVÕÌÊ«>°Ê/
ÀÕ}
ÊÌ
iÊÃÌ>ÌÃÌVÃ]ÊÌ
iÊ«ÌâiÀÊV>Ê>iÊÀi>Ã>LÞÊ
175
176
CHAPTER 7 N ST A TIS TIC S A NA L YS IS
accurate estimates about how long it will take to return a result set or an intermediate result ÃiÌÊ>`ÊÌ
iÀivÀiÊ`iÌiÀiÊÌ
iÊÃÌÊivviVÌÛiÊ«iÀ>ÌÃÊÌÊÕÃi°ÊÃÊ}Ê>ÃÊÞÕÊiÃÕÀiÊÌ
>ÌÊÌ
iÊ default statistical settings for the database are set, the optimizer will be able to do its best to `iÌiÀiÊivviVÌÛiÊ«ÀViÃÃ}ÊÃÌÀ>Ìi}iÃÊ`Þ>V>Þ°ÊÃ]Ê>ÃÊ>ÊÃ>viÌÞÊi>ÃÕÀiÊÜ
iÊÌÀÕLi shooting performance, you should ensure that the automatic statistics maintenance routine ÃÊ`}ÊÌÃÊLÊ>ÃÊ`iÃÀi`°Ê7
iÀiÊiViÃÃ>ÀÞ]ÊÞÕÊ>ÞÊiÛiÊ
>ÛiÊÌÊÌ>iÊ>Õ>ÊVÌÀÊÛiÀÊ the creation and/or maintenance of statistics. (I cover this in the “Manual Maintenance” sec Ì]Ê>`ÊÊVÛiÀÊÌ
iÊ«ÀiVÃiÊ>ÌÕÀiÊvÊÌ
iÊvÕVÌÃÊ>`ÊÃ
>«iÊvÊÃÌ>ÌÃÌVÃÊÊÌ
iʺ>Þâ}Ê Statistics” section.) In the following section, I show you why statistics are important to indexed columns and nonindexed columns functioning as predicates.
Statistics on an Indexed Column /
iÊÕÃivÕiÃÃÊvÊ>Ê`iÝÊÃÊvÕÞÊ`i«i`iÌÊÊÌ
iÊÃÌ>ÌÃÌVÃÊvÊÌ
iÊ`iÝi`ÊVÕÃÆÊÜÌ
ÕÌÊ ÃÌ>ÌÃÌVÃ]Ê-+Ê-iÀÛiÀ½ÃÊVÃÌL>Ãi`ʵÕiÀÞÊ«ÌâiÀÊV>½ÌÊ`iV`iÊÕ«ÊÌ
iÊÃÌÊivviVÌÛiÊÜ>ÞÊvÊ ÕÃ}Ê>Ê`iݰÊ/ÊiiÌÊÌ
ÃÊÀiµÕÀiiÌ]Ê-+Ê-iÀÛiÀÊ>ÕÌ>ÌV>ÞÊcreates the statistics of an index key whenever the index is created. It isn’t possible to turn this feature off. ÃÊ`>Ì>ÊV
>}iÃ]ÊÌ
iÊ`>Ì>ÀiÌÀiÛ>ÊiV
>ÃÊÀiµÕÀi`ÊÌÊii«ÊÌ
iÊVÃÌÊvÊ>ʵÕiÀÞÊÜÊ may also change. For example, if a table has only one matching row for a certain column value, then it makes sense to retrieve the matching rows from the table by going through the nonclustered index on the column. But if the data in the table changes so that a large number of rows are added with the same column value, then using the nonclustered index no longer >iÃÊÃiÃi°Ê/ÊLiÊ>LiÊÌÊ
>ÛiÊ-+Ê-iÀÛiÀÊ`iV`iÊÌ
ÃÊV
>}iÊÊ«ÀViÃÃ}ÊÃÌÀ>Ìi}ÞÊ>ÃÊÌ
iÊ `>Ì>ÊV
>}iÃÊÛiÀÊÌi]ÊÌÊÃÊÛÌ>ÊÌÊ
>ÛiÊÕ«Ì`>ÌiÊÃÌ>ÌÃÌVð SQL Server can keep the statistics on an index updated as the contents of the indexed column are modified. By default, this feature is turned on and is configurable through the Properties ¢ Options ¢ÊÕÌÊ1«`>ÌiÊ-Ì>ÌÃÌVà setting of a database. Updating statistics VÃÕiÃÊiÝÌÀ>Ê *1ÊVÞViðÊ/Ê«ÌâiÊÌ
iÊÕ«`>ÌiÊ«ÀViÃÃ]Ê-+Ê-iÀÛiÀÊÕÃiÃÊ>ÊivvViÌÊ>} rithm to decide when to execute the update statistics procedure, based on factors such as the number of modifications and the size of the table: Ê
UÊ 7
iÊ>ÊÌ>LiÊÜÌ
ÊÊÀÜÃÊ}iÌÃÊ>ÊÀÜ
Ê
UÊ 7
iÊ>ÊÌ>LiÊ
>ÃÊviÜiÀÊÌ
>ÊxääÊÀÜÃÊ>`ÊÃÊVÀi>Ãi`ÊLÞÊxääÊÀÊÀiÊÀÜÃ
Ê
UÊ 7
iÊ>ÊÌ>LiÊ
>ÃÊÀiÊÌ
>ÊxääÊÀÜÃÊ>`ÊÃÊVÀi>Ãi`ÊLÞÊxääÊÀÜÃʳÊÓäÊ«iÀViÌÊvÊÌ
iÊ number of rows
/
ÃÊLÕÌÊÌi}iViÊii«ÃÊÌ
iÊ *1ÊÕÌâ>ÌÊLÞÊi>V
Ê«ÀViÃÃÊÛiÀÞÊܰÊ̽ÃÊ>Ãʫà ÃLiÊÌÊÕ«`>ÌiÊÌ
iÊÃÌ>ÌÃÌVÃÊ>ÃÞV
ÀÕÃÞ°Ê/
ÃÊi>ÃÊÜ
iÊ>ʵÕiÀÞÊÜÕ`ÊÀ>ÞÊV>ÕÃiÊ statistics to be updated, instead that query proceeds with the old statistics, and the statistics >ÀiÊÕ«`>Ìi`Êvvi°Ê/
ÃÊV>Êëii`ÊÕ«ÊÌ
iÊÀiëÃiÊÌiÊvÊÃiʵÕiÀiÃ]ÊÃÕV
Ê>ÃÊÜ
iÊÌ
iÊ database is large or when you have a short timeout period. You can manually disable (or enable) the auto update statistics and the auto update sta tistics asynchronously features by using the =HPAN@=P=>=OA command. By default, the auto update statistics feature is enabled, and it is strongly recommended that you keep it enabled. /
iÊ>ÕÌÊÕ«`>ÌiÊÃÌ>ÌÃÌVÃÊ>ÃÞV
ÀÕÃÞÊvi>ÌÕÀiÊÃÊ`Ã>Li`ÊLÞÊ`iv>Õ̰Ê/ÕÀÊÌ
ÃÊvi>ÌÕÀiÊÊ only if you’ve determined it will help with timeouts on your database.
C H A P T E R 7 N S T A T I S T I C S A N A L Y S I S
NNote I explain =HPAN@=P=>=OA later in this chapter in the “Manual Maintenance” section.
Benefits of Updated Statistics /
iÊLiivÌÃÊvÊ«iÀvÀ}Ê>Ê>ÕÌÊÕ«`>Ìi usually outweigh its cost on the system resources. /ÊÀiÊ`ÀiVÌÞÊVÌÀÊÌ
iÊLi
>ÛÀÊvÊÌ
iÊ`>Ì>]ÊÃÌi>`ÊvÊÕÃ}ÊÌ
iÊÌ>LiÃÊÊ`ÛiÌÕÀi Works, for this set of examples you will create one manually. Specifically, create a test table (_na]pa[p-*omh in the download) with only three rows and a nonclustered index: EB$OAHA?PK>FA?P[E@$#p-#% %EOJKPJQHH @NKLP=>HA`^k*p-7 CK ?NA=PAP=>HA`^k*p-$_-EJP(_.EJPE@AJPEPU%7 OAHA?PPKL-1,, E@AJPEPU$EJP(-(-%=Oj EJPKJqio BNKII]opan*`^k*Ouo?khqijoo_(I]opan*`^k*Ouo?khqijoo_.7 EJOANPEJPK`^k*p-$_-% OAHA?Pj BNKIJqio @NKLP=>HAJqio ?NA=PAJKJ?HQOPANA@EJ@ATe-KJ`^k*p-$_-%7 If you execute a OAHA?P statement with a very selective filter criterion on the indexed col umn to retrieve only one row, as shown in the following line of code, then the optimizer uses a VÕÃÌiÀi`Ê`iÝÊÃii]Ê>ÃÊÃ
ÜÊÊÌ
iÊiÝiVÕÌÊ«>ÊÊ}ÕÀiÊÇ£\ OAHA?P&BNKIp-SDANA_-9.))Napneara-nks
Figure 7-1. Execution plan for a very small result set
177
178
C H A P T E R 7 N S T A T I S T I C S A N A L Y S I S
/ÊÕ`iÀÃÌ>`ÊÌ
iÊivviVÌÊvÊÃ>Ê`>Ì>Ê`vV>ÌÃÊÊ>ÊÃÌ>ÌÃÌVÃÊÕ«`>Ìi]ÊVÀi>ÌiÊ>ÊÌÀ>ViÊ using Profiler. In the trace, add the event =qpkOp]po, which captures statistics update and cre ate events, and add OMH6>]p_d?kilhapa` with a filter on the Patp@]p]ÊVÕ°Ê/
iÊvÌiÀÊÃ
Õ`Ê look like JkpHegaOAP!ÊÜ
iÊÞÕÊ>ÀiÊ`i°Ê``ÊÞÊiÊÀÜÊÌÊÌ
iÊÌ>Li\ EJOANPEJPKp-$_-%R=HQAO$.% When you reexecute the preceding OAHA?P statement, you get the same execution plan as Ã
ÜÊÊ}ÕÀiÊÇ£°Ê}ÕÀiÊÇÓÊÃ
ÜÃÊÌ
iÊÌÀ>ViÊiÛiÌÃÊ}iiÀ>Ìi`ÊLÞÊÌ
iʵÕiÀÞ°
Figure 7-2. Trace output on the addition of a small number of rows /
iÊÌÀ>ViÊÕÌ«ÕÌÊ`iýÌÊVÌ>Ê>ÞÊ-+Ê>VÌÛÌÞÊÀi«ÀiÃiÌ}Ê>ÊÃÌ>ÌÃÌVÃÊÕ«`>ÌiÊLiV>ÕÃiÊ Ì
iÊÕLiÀÊvÊV
>}iÃÊviÊLiÜÊÌ
iÊÌ
ÀiÃ
`ÊÜ
iÀiÊ>ÞÊÌ>LiÊÌ
>ÌÊ
>ÃÊÀiÊÌ
>ÊxääÊÀÜÃÊ ÕÃÌÊ
>ÛiÊ>ÊVÀi>ÃiÊvÊxääÊÀÜÃÊ«ÕÃÊÓäÊ«iÀViÌÊvÊÌ
iÊÕLiÀÊvÊÀÜð /ÊÕ`iÀÃÌ>`ÊÌ
iÊivviVÌÊvÊ>À}iÊ`>Ì>Ê`vV>ÌÊÊÃÌ>ÌÃÌVÃÊÕ«`>Ìi]Ê>``Ê£]xääÊÀÜÃÊÌÊ the table (]``[nkso*omh in the download): OAHA?PPKL-1,, E@AJPEPU$EJP(-(-%=Oj EJPKJqio BNKII]opan*`^k*Ouo?khqijoo_(I]opan*`^k*Ouo?khqijoo_.7 EJOANPEJPK`^k*p-$ _%OAHA?P. BNKIJqio7 @NKLP=>HAJqio7 Now, if you reexecute the OAHA?P statement, like so: OAHA?P&BNKI`^k*p-SDANA_-9.7 >Ê>À}iÊÀiÃÕÌÊÃiÌÊ£]xäÓÊÀÜÃÊÕÌÊvÊÎ]ää£ÊÀÜîÊÜÊLiÊÀiÌÀiÛi`°Ê-ViÊ>Ê>À}iÊÀiÃÕÌÊÃiÌÊÃÊ requested, scanning the base table directly is preferable to going through the nonclustered `iÝÊÌÊÌ
iÊL>ÃiÊÌ>LiÊ£]xäÓÊÌiðÊVViÃÃ}ÊÌ
iÊL>ÃiÊÌ>LiÊ`ÀiVÌÞÊÜÊ«ÀiÛiÌÊÌ
iÊÛiÀ
i>`Ê VÃÌÊvÊL>ÀÊÕ«ÃÊ>ÃÃV>Ìi`ÊÜÌ
ÊÌ
iÊVÕÃÌiÀi`Ê`iݰÊ/
ÃÊÃÊÀi«ÀiÃiÌi`ÊÊÌ
iÊ ÀiÃÕÌ>ÌÊiÝiVÕÌÊ«>ÊÃiiÊ}ÕÀiÊÇή°
Figure 7-3. Execution plan for a large result set }ÕÀiÊÇ{ÊÃ
ÜÃÊÌ
iÊÀiÃÕÌ>ÌÊ*ÀviÀÊÌÀ>ViÊÕÌ«Õ̰
C H A P T E R 7 N S T A T I S T I C S A N A L Y S I S
Figure 7-4. Trace output on the addition of a large number of rows /
iÊ*ÀviÀÊÌÀ>ViÊÕÌ«ÕÌÊVÕ`ià an =qpkOp]po event since the threshold was exceeded LÞÊÌ
iÊ>À}iÃV>iÊÕ«`>ÌiÊÌ
ÃÊÌi°Ê/
iÃiÊ-+Ê>VÌÛÌiÃÊVÃÕiÊÃiÊiÝÌÀ>Ê *1ÊVÞViÃ°Ê ÜiÛiÀ]ÊLÞÊ`}ÊÌ
Ã]ÊÌ
iÊ«ÌâiÀÊ`iÌiÀiÃÊ>ÊLiÌÌiÀÊ`>Ì>«ÀViÃÃ}ÊÃÌÀ>Ìi}ÞÊ>`Êii«ÃÊ the overall cost of the query low.
Drawbacks of Outdated Statistics ÃÊiÝ«>i`ÊÊÌ
iÊ«ÀiVi`}ÊÃiVÌ]ÊÌ
i auto update statistics feature allows the optimizer to decide on an efficient processing strategy for a query as the data changes. If the statistics become outdated, however, then the processing strategies decided on by the optimizer may not be applicable for the current data set and thereby will degrade performance. /ÊÕ`iÀÃÌ>`ÊÌ
iÊ`iÌÀiÌ>ÊivviVÌÊvÊ
>Û}ÊÕÌ`>Ìi`ÊÃÌ>ÌÃÌVÃ]ÊvÜÊÌ
iÃiÊÃÌi«Ã\ 1. ,iVÀi>ÌiÊÌ
iÊ«ÀiVi`}ÊÌiÃÌÊÌ>LiÊÜÌ
Ê£]xääÊÀÜÃÊÞÊ>`ÊÌ
iÊVÀÀië`}ÊVÕà tered index. 2. *ÀiÛiÌÊ-+Ê-iÀÛiÀÊvÀÊÕ«`>Ì}ÊÃÌ>ÌÃÌVÃÊ>ÕÌ>ÌV>ÞÊ>ÃÊÌ
iÊ`>Ì>ÊV
>}iðÊ/Ê`Ê so, disable the auto update statistics feature by executing the following SQL statement: =HPAN@=P=>=OA=`rajpqnaSkngo.,,4OAP=QPK[QL@=PA[OP=PEOPE?OKBB 3. ``Ê£]xääÊÀÜÃÊÌÊÌ
iÊÌ>LiÊ>ÃÊLivÀi° Now, reexecute the OAHA?P statement to understand the effect of the outdated statistics on Ì
iʵÕiÀÞÊ«ÌâiÀ°Ê/
iʵÕiÀÞÊÃÊÀi«i>Ìi`Ê
iÀiÊvÀÊV>ÀÌÞ\ OAHA?P&BNKI`^k*p-SDANA_-9. }ÕÀiÊÇxÊ>`Ê}ÕÀiÊÇÈÊÃ
ÜÊÌ
iÊÀiÃÕÌ>ÌÊiÝiVÕÌÊ«>Ê>`ÊÌ
iÊ*ÀviÀÊÌÀ>ViÊÕÌ«ÕÌÊ for this query, respectively.
Figure 7-5. Execution plan with =QPK[QL@=PA[OP=PEOPE?OKBB
Figure 7-6. Trace output with =QPK[QL@=PA[OP=PEOPE?OKBB
179
180
CHAPTER 7 N ST A TIS TIC S A NA L YS IS
With the auto update statistics feature switched off, the query optimizer has selected a different execution plan from the one it selected with this feature on. Based on the outdated statistics, which have only one row for the filter criterion (_-ÊrÊÓ®]ÊÌ
iÊ«ÌâiÀÊ`iV`i`ÊÌÊÕÃiÊ >ÊVÕÃÌiÀi`Ê`iÝÊÃii°Ê/
iÊ«ÌâiÀÊVÕ`½ÌÊ>iÊÌÃÊ`iVÃÊL>Ãi`ÊÊÌ
iÊVÕÀÀiÌÊ`>Ì>Ê distribution in the column. For performance reasons, it would have been better to hit the base Ì>LiÊ`ÀiVÌÞÊÃÌi>`ÊvÊ}}ÊÌ
ÀÕ}
ÊÌ
iÊVÕÃÌiÀi`Ê`iÝ]ÊÃViÊ>Ê>À}iÊÀiÃÕÌÊÃiÌÊ£]xä£Ê ÀÜÃÊÕÌÊvÊÎ]äääÊÀÜîÊÃÊÀiµÕiÃÌi`° You can see that turning off the auto update statistics feature has a negative effect on per vÀ>ViÊLÞÊV«>À}ÊÌ
iÊVÃÌÊvÊÌ
ÃʵÕiÀÞÊÜÌ
Ê>`ÊÜÌ
ÕÌÊÕ«`>Ìi`ÊÃÌ>ÌÃÌVðÊ/>LiÊÇ£Ê shows the difference in the cost of this query. Table 7-1. Cost of the Query with and Without Updated Statistics
Statistics Update Status
Figure
Cost (SQL:Batch Completed Event) CPU (ms)
Number of Reads
Updated
}ÕÀiÊÇ{
ä
Î{
Not updated
}ÕÀiÊÇÈ
£x
£xä
/
iÊÕLiÀÊvÊ}V>ÊÀi>`ÃÊ>`ÊÌ
iÊ *1ÊÕÌâ>ÌÊÃÊÃ}vV>ÌÞÊ
}
iÀÊÜ
iÊÌ
iÊÃÌ> ÌÃÌVÃÊ>ÀiÊÕÌv`>ÌiÊiÛiÊÌ
Õ}
ÊÌ
iÊ`>Ì>ÊÀiÌÕÀi`ÊÃÊi>ÀÞÊ`iÌV>Ê>`ÊÌ
iʵÕiÀÞÊÜ>ÃÊ «ÀiVÃiÞÊÌ
iÊÃ>i°Ê/
iÀivÀi]ÊÌÊÃÊÀiVi`i`ÊÌ
>ÌÊÞÕÊii«ÊÌ
iÊ>ÕÌÊÕ«`>ÌiÊÃÌ>ÌÃÌVÃÊ vi>ÌÕÀiʰÊ/
iÊLiivÌÃÊvÊii«}ÊÃÌ>ÌÃÌVÃÊÕ«`>Ìi`ÊÕÌÜi}
ÊÌ
iÊVÃÌÃÊvÊ«iÀvÀ}ÊÌ
iÊ update. Before you leave this section, turn =QPK[QL@=PA[OP=PEOPE?O back on (although you can also use ol[]qpkop]po): =HPAN@=P=>=OA=`rajpqnaSkngo.,,4OAP=QPK[QL@=PA[OP=PEOPE?OKJ
Statistics on a Nonindexed Column SometimesÊÞÕÊ>ÞÊ
>ÛiÊVÕÃÊÊÊÀÊvÌiÀÊVÀÌiÀ>ÊÜÌ
ÕÌÊ>ÞÊ`iÝ°Ê ÛiÊvÀÊÃÕV
Ê nonindexed columns, the query optimizer is more likely to make the best choice if it knows the data distribution (or statistics) of those columns. In addition to statistics on indexes, SQL Server can build statistics on columns with no `iÝiðÊ/
iÊvÀ>ÌÊÊ`>Ì>Ê`ÃÌÀLÕÌ]ÊÀÊÌ
iÊi
`ÊvÊ>Ê«>ÀÌVÕ>ÀÊÛ>ÕiÊVVÕÀÀ}Ê in a nonindexed column, can help the query optimizer determine an optimal processing strat i}Þ°Ê/
ÃÊLiivÌÃÊÌ
iʵÕiÀÞÊ«ÌâiÀÊiÛiÊvÊÌÊV>½ÌÊÕÃiÊ>Ê`iÝÊÌÊ>VÌÕ>ÞÊV>ÌiÊÌ
iÊÛ>ÕiÃ°Ê SQL Server automatically builds statistics on nonindexed columns if it deems this informa tion valuable in creating a better plan, usually when the columns are used in a predicate. By default, this feature is turned on, and it’s configurable through the Properties ¢ Options ¢ ÕÌÊ Ài>ÌiÊ-Ì>ÌÃÌVÃÊÃiÌÌ}ÊvÊ>Ê`>Ì>L>Ãi°Ê9ÕÊV>ÊÛiÀÀ`iÊÌ
ÃÊÃiÌÌ}Ê«À}À>>ÌV>ÞÊLÞÊ using the =HPAN@=P=>=OA command. However, for better performance, it is strongly recom mended that you keep this feature on. In general, you should not disable the automatic creation of statistics on nonindexed col umns. One of the scenarios in which you may consider disabling this feature is while executing
C H A P T E R 7 N S T A T I S T I C S A N A LY S I S
a series of ad hoc SQL activities that you will not execute again. In such a case, you must decide whether you want to pay the cost of automatic statistics creation to get a better plan in this one case and affect the performance of other SQL Server activities. It is worthwhile noting that SQL Server eventually removes statistics when it realizes that they have not been used for a while. So, in general, you should keep this feature on and not be concerned about it.
Benefits of Statistics on a Nonindexed Column /ÊÕ`iÀÃÌ>`ÊÌ
iÊLiivÌÊvÊ
>Û}ÊÃÌ>ÌÃÌVÃÊÊ>ÊVÕÊÜÌ
ÊÊ`iÝ]ÊVÀi>ÌiÊÌÜÊÌiÃÌÊÌ>LiÃÊ with disproportionate data distributions, as shown in the following code (_na]pa[p-[p.*omh ÊÌ
iÊ`Ü>`®°Ê Ì
ÊÌ>LiÃÊVÌ>Ê£ä]ää£ÊÀÜðÊ/>LiÊp- contains only one row for a value of the second column (p-[_.®ÊiµÕ>ÊÌÊ£]Ê>`ÊÌ
iÊÀi>}Ê£ä]äääÊÀÜÃÊVÌ>ÊÌ
ÃÊVÕÊ Û>ÕiÊ>ÃÊÓ°Ê/>LiÊp. contains exactly the opposite data distribution. ))?na]pabenopp]^hasepd-,,,-nkso EB$OAHA?PK>FA?P[E@$#`^k*p-#%%EOJKPJQHH @NKLP=>HA`^k*p-7 CK ?NA=PAP=>HA`^k*p-$p-[_-EJPE@AJPEPU(p-[_.EJP%7 EJOANPEJPK`^k*p-$p-[_.%R=HQAO$-%7 OAHA?PPKL--5,.2 E@AJPEPU$EJP(-(-%=Oj EJPKJqio BNKII]opan*`^k*Ouo?khqijoo_(I]opan*`^k*Ouo?khqijoo_.7 EJOANPEJPK`^k*p-$ p-[_. %OAHA?P. BNKIJqio CK ?NA=PA?HQOPANA@EJ@ATe-KJ`^k*p-$p-[_-% ))?na]paoa_kj`p]^hasepd-,,,-nkso( ))^qpkllkoepa`]p]`eopne^qpekj EB$OAHA?PK>FA?P[E@$#`^k*p.#%%EOJKPJQHH @NKLP=>HA`^k*p.7 CK ?NA=PAP=>HA`^k*p.$p.[_-EJPE@AJPEPU(p.[_.EJP%7 EJOANPEJPK`^k*p.$p.[_.%R=HQAO$.%7 EJOANPEJPK`^k*p.$ p.[_. %OAHA?PBNKIJqio7 @NKLP=>HAJqio7 CK ?NA=PA?HQOPANA@EJ@ATe-KJ`^k*p.$p.[_-%7
181
182
C H A P T E R 7 N S T A T I S T I C S A N A L Y S I S
/>LiÊÇÓÊÕÃÌÀ>ÌiÃÊ
ÜÊÌ
iÊÌ>LiÃÊÜʰ Table 7-2. Sample Tables
Table t1
Table t2
Column
t1_c1
t1_c2
t2_c1
t2_c2
Nks-
£
£
£
Ó
Nks.
Ó
Ó
Ó
£
NksJ
N
Ó
N
£
Nks-,,,-
£äää£
Ó
£äää£
£
/ÊÕ`iÀÃÌ>`ÊÌ
iÊimportance of statistics on a nonindexed column, use the default set ting for the auto create statistics feature. By default, this feature is on. You can verify this using the @=P=>=OALNKLANPUAT function (although you can also query the ouo*`]p]^]oao view): OAHA?P@=P=>=OALNKLANPUAT$#=`rajpqnaSkngo.,,4#(#Eo=qpk?na]paOp]peope_o#%
NNote You can find a detailed description of configuring the auto create statistics feature later in this chapter.
Use the following OAHA?P statement (jkjej`ata`[oaha_p*omh in the download) to access a large result set from table p- and a small result set from table p.°Ê/>LiÊp-Ê
>ÃÊ£ä]äääÊÀÜÃÊvÀÊ the column value of p-[_.ÊrÊÓ]Ê>`ÊÌ>LiÊp.Ê
>ÃÊ£ÊÀÜÊvÀÊp.[_.ÊrÊÓ°Ê ÌiÊÌ
>ÌÊÌ
iÃiÊVÕÃÊ ÕÃi`ÊÊÌ
iÊÊ>`ÊvÌiÀÊVÀÌiÀ>Ê
>ÛiÊÊ`iÝÊÊiÌ
iÀÊÌ>Li° OAHA?Pp-*p-[_. (p.*p.[_. BNKI`^k*pFKEJ`^k*p. KJp-*p-[_.9p.*p.[_. SDANAp-*p-[_.9.7 }ÕÀiÊÇÇÊÃ
ÜÃÊÌ
iÊiÝiVÕÌÊ«>ÊvÀÊÌ
ÃʵÕiÀÞ°
Figure 7-7. Execution plan with =QPK[?NA=PA[OP=PEOPE?OKJ
C H A P T E R 7 N S T A T I S T I C S A N A L Y S I S
}ÕÀiÊÇnÊÃ
ÜÃÊÌ
iÊ*ÀviÀÊÌÀ>ViÊÕÌ«ÕÌÊÜÌ
Ê>ÊV«iÌi`ÊiÛiÌÃÊ>`ÊÌ
iÊ=qpkOp]po event for this query. You can use this to evaluate some of the added costs for a given query.
NTip To keep the Profiler trace output compact, I used a prefilter on Patp@]p]JkphegaOAP!. This filters the OAP statements used to show the graphical execution plan.
/
iÊ*ÀviÀÊÌÀ>ViÊÕÌ«ÕÌÊÃ
ÜÊÊ}ÕÀiÊÇnÊVÕ`iÃÊÌÜÊ=qpkOp]po events creating sta tistics on the nonindexed columns referred to in the FKEJ and SDANA clauses, p.[_. and p-[_.. /
ÃÊ>VÌÛÌÞÊVÃÕiÃÊ>ÊviÜÊiÝÌÀ>Ê *1ÊVÞViÃÊÃViÊiÊVÕ`ÊLiÊ`iÌiVÌi`®]ÊLÕÌÊLÞÊVÃÕ }ÊÌ
iÃiÊiÝÌÀ>Ê *1ÊVÞViÃ]ÊÌ
iÊ«ÌâiÀÊ`iV`iÃÊÕ«Ê>ÊLiÌÌiÀÊ«ÀViÃÃ}ÊÃÌÀ>Ìi}ÞÊvÀÊii«}Ê the overall cost of the query low.
Figure 7-8. Trace output with =QPK[?NA=PA[OP=PEOPE?OKJ /ÊÛiÀvÞÊÌ
iÊÃÌ>ÌÃÌVÃÊ>ÕÌ>ÌV>ÞÊVÀi>Ìi`ÊLÞÊ-+Ê-iÀÛiÀÊÊÌ
iÊ`iÝi`ÊVÕÃÊvÊ each table, run this OAHA?P statement against the ouo*op]po table: OAHA?P& BNKIouo*op]po SDANAk^fa_p[e`9K>FA?P[E@$#p-#% }ÕÀiÊÇÊÃ
ÜÃÊÌ
i automatic statistics created for table p-.
Figure 7-9. Automatic statistics for table p/ÊÛiÀvÞÊ
ÜÊ>Ê`vviÀiÌÊÀiÃÕÌÊÃiÌÊÃâiÊvÀÊÌ
iÊÌÜÊÌ>LiÃÊvÕiViÃÊÌ
iÊ`iVÃÊvÊÌ
iÊ query optimizer, modify the filter criteria of jkjej`ata`[oaha_p*omh to access an opposite result set size from the two tables (small from p- and large from p.). Instead of filtering on p-*p-[_.ÊrÊÓ]ÊV
>}iÊÌÊÌÊvÌiÀÊÊ£\ OAHA?Pp-*p-[_. (p.*p.[_. BNKI`^k*pFKEJ`^k*p. KJp-*p-[_.9p.*p.[_. SDANAp-*p-[_.9-
183
184
C H A P T E R 7 N S T A T I S T I C S A N A L Y S I S
}ÕÀiÊÇ£äÊÃ
ÜÃÊÌ
iÊÀiÃÕÌ>ÌÊiÝiVÕÌÊ«>]Ê>`Ê}ÕÀiÊÇ££ÊÃ
ÜÃÊÌ
iÊ*ÀviÀÊÌÀ>ViÊ output of this query.
Figure 7-10. Execution plan for a different result set
Figure 7-11. Trace output for a different result set /
iÊÀiÃÕÌ>ÌÊ*ÀviÀÊÌÀ>ViÊÕÌ«ÕÌÊ`iýÌÊ«iÀvÀÊ>ÞÊ>``Ì>Ê-+Ê>VÌÛÌiÃÊÌÊ> >}iÊÃÌ>ÌÃÌVðÊ/
iÊÃÌ>ÌÃÌVÃÊÊÌ
iÊ`iÝi`ÊVÕÃÊp-*p-[_. and p.*p.[_.) had already been created during the previous execution of the query. For effective cost optimization, in each case the query optimizer selected different pro cessing strategies, depending upon the statistics on the nonindexed columns (p-*p-[_. and p.*p.[_.). You can see this from the last two execution plans. In the first, table p- is the outer Ì>LiÊvÀÊÌ
iÊiÃÌi`Ê«Ê]ÊÜ
iÀi>ÃÊÊÌ
iÊ>ÌiÃÌÊi]ÊÌ>LiÊp. is the outer table. By having statistics on the nonindexed columns (p-*p-[_. and p.*p.[_.), the query optimizer can create >ÊVÃÌivviVÌÛiÊ«>ÊÃÕÌ>LiÊvÀÊi>V
ÊV>Ãi° ÊiÛiÊLiÌÌiÀÊÃÕÌÊÜÕ`ÊLiÊÌÊ
>ÛiÊ>Ê`iÝÊÊÌ
iÊVÕ°Ê/
ÃÊÜÕ`ÊÌÊÞÊ create the statistics on the column but also allow fast data retrieval through an Ej`atOaag operation, while retrieving a small result set. However, in the case of a database application with queries referring to nonindexed columns in the SDANA clause, keeping the auto create statistics feature on still allows the optimizer to determine the best processing strategy for the existing data distribution in the column. If you need to know which column or columns might be covered by a given statistic, you need to look into the ouo*op]po[_khqijo system table. You can query it in the same way as you did the ouo*op]po table: OAHA?P& BNKIouo*op]po[_khqijo SDANAk^fa_p[e`9K>FA?P[E@$#p-#% /
ÃÊÜÊÃ
ÜÊÌ
iÊVÕÊÀÊVÕÃÊLi}ÊÀiviÀiVi`ÊLÞÊÌ
iÊ>ÕÌ>ÌV>ÞÊVÀi>Ìi`Ê ÃÌ>ÌÃÌVðÊ/
ÃÊÜÊ
i«ÊÞÕÊvÊÞÕÊ`iV`iÊÞÕÊii`ÊÌÊVÀi>ÌiÊ>Ê`iÝÊÌÊÀi«>ViÊÌ
iÊÃÌ>ÌÃÌVÃ]Ê because you will need to know which columns to create the statistics on.
C H A P T E R 7 N S T A T I S T I C S A N A L Y S I S
Drawback of Missing Statistics on a Nonindexed Column /ÊÕ`iÀÃÌ>`ÊÌ
iÊ`iÌÀiÌ>ÊivviVÌÊvÊÌÊ
>Û}ÊÃÌ>ÌÃÌVÃÊÊ`iÝi`ÊVÕÃ]Ê`À«Ê the statistics automatically created by SQL Server and prevent SQL Server from automatically creating statistics on columns with no index by following these steps: 1. Drop the automatic statistics created on column p-*p-[_. through the Manage Statistics dialog box as shown in the section “Benefits of Statistics on a Nonindexed
Õ]»ÊÀÊÕÃiÊÌ
i following SQL command: @NKLOP=PEOPE?OWp-Y*Op]peope_oJ]ia 2. Similarly, drop the corresponding statistics on column p.*p.[_.. 3. Ã>LiÊÌ
iÊ>ÕÌÊVÀi>ÌiÊÃÌ>ÌÃÌVÃÊvi>ÌÕÀiÊLÞÊ`iÃiiVÌ}ÊÌ
iÊÕÌÊ Ài>ÌiÊ-Ì>ÌÃÌVÃÊV
iVÊ box for the corresponding database or by executing the following SQL command: =HPAN@=P=>=OA=`rajpqnaSkngo.,,4OAP=QPK[?NA=PA[OP=PEOPE?OKBB Now reexecute the OAHA?P statement jkjej`ata`[oaha_p*omh: OAHA?Pp-*p-[_. (p.*p.[_. BNKI`^k*pFKEJ`^k*p. KJp-*p-[_.9p.*p.[_. SDANAp-*p-[_.9.7 }ÕÀiÊÇ£ÓÊ>`Ê}ÕÀiÊÇ£ÎÊÃ
ÜÊÌ
iÊÀiÃÕÌ>ÌÊiÝiVÕÌÊ«>Ê>`Ê*ÀviÀÊÌÀ>ViÊÕÌ«ÕÌ]Ê respectively.
Figure 7-12. Execution plan with =QPK[?NA=PA[OP=PEOPE?OKBB
Figure 7-13. Trace output with =QPK[?NA=PA[OP=PEOPE?OKBB
185
186
C H A P T E R 7 N S T A T I S T I C S A N A L Y S I S
With the auto create statistics feature off, the query optimizer selected a different execu tion plan compared to the one it selected with the auto create statistics feature on. On not finding statistics on the relevant columns, the optimizer chose the first table (p-) in the BNKI V>ÕÃiÊ>ÃÊÌ
iÊÕÌiÀÊÌ>LiÊvÊÌ
iÊiÃÌi`Ê«ÊÊ«iÀ>̰Ê/
iÊ«ÌâiÀÊVÕ`½ÌÊ>iÊÌÃÊ decision based on the actual data distribution in the column. Not only that, but the optimizer and the query engine determined that this query passed the threshold for parallelism, making this a parallel execution (those are the little arrows on the operators, marking them as parallel ÀiÊÊ«>À>iÊiÝiVÕÌÊ«>ÃÊÊ
>«ÌiÀÊ®°ÊÀÊiÝ>«i]ÊvÊÞÕÊ`vÞÊÌ
iʵÕiÀÞÊÌÊÀiviÀ ence table p. as the first table in the BNKI clause: OAHA?Pp-*p-[_. (p.*p.[_. BNKI`^k*p. FKEJ`^k*pKJp-*p-[_.9p.*p.[_. SDANAp-*p-[_.9.7 then the optimizer selects table p.Ê>ÃÊÌ
iÊÕÌiÀÊÌ>LiÊvÊÌ
iÊiÃÌi`Ê«ÊÊ«iÀ>Ì°Ê }ÕÀiÊÇ£{ÊÃ
ÜÃÊÌ
iÊiÝiVÕÌÊ«>°
Figure 7-14. Execution plan with =QPK[?NA=PA[OP=PEOPE?OKBB (a variation) You can see that turning off the auto create statistics feature has a negative effect on per formance by comparing the cost of this query with and without statistics on a nonindexed VÕ°Ê/>LiÊÇÎÊÃ
ÜÃÊÌ
iÊ`vviÀiViÊÊÌ
iÊVÃÌÊvÊÌ
ÃʵÕiÀÞ° Table 7-3. Cost Comparison of a Query with and Without Statistics on a Nonindexed Column
Statistics on Nonindexed Column
Figure
Cost (SQL:Batch Completed Event) Duration (ms)
Number of Reads
With statistics
}ÕÀiÊÇ££
£x
{n
Without statistics
}ÕÀiÊÇ£Î
ÎäÎ
Óä]ΣÎ
/
iÊÕLiÀÊvÊ}V>ÊÀi>`ÃÊ>`ÊÌ
iÊ *1ÊÕÌâ>ÌÊ>ÀiÊÛiÀÞÊ
}
ÊÜÌ
ÊÊÃÌ>ÌÃÌVÃÊÊÌ
iÊ `iÝi`ÊVÕðÊ7Ì
ÕÌÊÌ
iÃiÊÃÌ>ÌÃÌVÃ]ÊÌ
iÊ«ÌâiÀÊV>½ÌÊVÀi>ÌiÊ>ÊVÃÌivviVÌÛiÊ«>° ʵÕiÀÞÊiÝiVÕÌÊ«>Ê
}
}
ÌÃÊÌ
iÊÃÃ}ÊÃÌ>ÌÃÌVÃÊLÞÊ«>V}Ê>ÊiÝV>>ÌÊ«ÌÊ on the operator that would have used the statistics. You can see this in the clustered index ÃV>Ê«iÀ>ÌÀÃÊÊÌ
iÊ«ÀiÛÕÃÊiÝiVÕÌÊ«>Ê}ÕÀiÊÇ£{®]Ê>ÃÊÜiÊ>ÃÊÊÌ
iÊ`iÌ>i`Ê`iÃVÀ« ÌÊÊÌ
iÊ7>À}ÃÊÃiVÌÊvÀÊ>Ê`iÊÊ>Ê}À>«
V>ÊiÝiVÕÌÊ«>]Ê>ÃÊÃ
ÜÊÊ}ÕÀiÊÇ£xÊ for table p..
C H A P T E R 7 N S T A T I S T I C S A N A L Y S I S
Figure 7-15. Missing statistics indication in a graphical plan /
iÊ8ÊiÝiVÕÌÊ«>Ê«ÀÛ`iÃÊÌ
iÊÃÃ}ÊÃÌ>ÌÃÌVÃÊvÀ>ÌÊÕ`iÀÊÌ
iÊS]njejco VÕ]Ê>ÃÊÃ
ÜÊÊ}ÕÀiÊǣȰÊ,iiLiÀÊÌ
>ÌÊÞÕÊV>ÊLÌ>ÊÌ
iÊ8ÊiÝiVÕÌÊ«>ÊLÞÊ À}
ÌVV}ÊÜÌ
ÊÌ
iÊ}À>«
V>ÊiÝiVÕÌÊ«>Ê>`ÊÃiiVÌ}Ê-
ÜÊ ÝiVÕÌÊ*>Ê8ÊÀÊ by using OAPOP=PEOPE?OTIH in the query window.
Figure 7-16. Missing statistics indication in an XML plan
NNote In a database application, there is always the possibility of queries using columns with no indexes. Therefore, for performance reasons, leaving the auto create statistics feature of SQL Server databases on is recommended.
Analyzing Statistics Statistics are collections of informationÊÃÌÀi`Ê>ÃÊ
ÃÌ}À>ðÊÊhistogram is a statistical con ÃÌÀÕVÌÊÌ
>ÌÊÃ
ÜÃÊ
ÜÊvÌiÊ`>Ì>Êv>ÃÊÌÊÛ>ÀÞ}ÊV>Ìi}ÀiðÊ/
iÊ
ÃÌ}À>ÊÃÌÀi`ÊLÞÊ-+Ê Server consists of a sampling of data distribution for a column or an index key (or the first VÕÊvÊ>ÊÕÌVÕÊ`iÝÊiÞ®ÊvÊÕ«ÊÌÊÓääÊÀÜðÊ/
iÊvÀ>ÌÊÊÌ
iÊrange of index
187
188
CHAPTER 7 N ST A TIS TIC S A NA L YS IS
key values between two consecutive samples is called a step°Ê/
iÃi steps consist of varying size ÌiÀÛ>ÃÊLiÌÜiiÊÌ
iÊÓääÊÛ>ÕiÃÊÃÌÀi`°ÊÊÃÌi«Ê«ÀÛ`iÃÊÌ
iÊvÜ}ÊvÀ>Ì\ Ê
UÊ /
i top value of a given step (N=JCA[DE[GAU).
Ê
UÊ /
i number of rows equal to N=JCA[DE[GAU (AM[NKSO).
Ê
UÊ /
i range of rows between the previous top value and the current top value, without counting either of these samples (N=JCA[NKSO).
Ê
UÊ /
iÊÕLiÀ of distinct rows in the range (@EOPEJ?P[N=JCA[NKSO). If all values in the range are unique, then N=JCA[NKSO equals @EOPEJ?P[N=JCA[NKSO.
Ê
UÊ /
iÊ>ÛiÀ>}iÊÕLiÀ of rows equal to a key value within a range (=RC[N=JCA[NKSO).
/
iÊÛ>Õi of AM[NKSO for an index key value (N=JCA[DE[GAU) helps the optimizer decide how (and whether) to use the index when the indexed column is referred to in a SDANA clause. Because the optimizer can perform a OAAG or O?=J operation to retrieve rows from a table, the optimizer can decide which operation to perform based on the number of matching rows (AM[NKSO) for the index key value. /ÊÕ`iÀÃÌ>`Ê
ÜÊÌ
iÊ«ÌâiÀ½ÃÊ`>Ì>ÀiÌÀiÛ>ÊÃÌÀ>Ìi}ÞÊ`i«i`ÃÊÊÌ
iÊÕLiÀÊvÊ matching rows, create a test table (_na]pa[p/*omh in the download) with different data distri butions on an indexed column: EB$OAHA?PK>FA?P[E@$#`^k*p-#% %EOJKPJQHH @NKLP=>HA`^k*p-7 CK ?NA=PAP=>HA`^k*p-$_-EJP(_.EJPE@AJPEPU%7 EJOANPEJPK`^k*p-$_-% R=HQAO$-%7 OAHA?PPKL--5,.2 E@AJPEPU$EJP(-(-%=Oj EJPKJqio7 EJOANPEJPK`^k*p-$_-% OAHA?P. BNKIJqio7 ?NA=PAJKJ?HQOPANA@EJ@ATe-KJ`^k*p-$_-%7 When the preceding nonclustered index is created, SQL Server automatically creates statistics on the index key. You can obtain statistics for this nonclustered index key (e-) by executing the @>??ODKS[OP=PEOPE?O command: @>??ODKS[OP=PEOPE?O$p-(e-% }ÕÀiÊÇ£ÇÊÃ
ÜÃÊÌ
iÊÃÌ>ÌÃÌVÃÊÕÌ«Õ̰
C H A P T E R 7 N S T A T I S T I C S A N A L Y S I S
Figure 7-17. Statistics on index eÜ]ÊÌÊÕ`iÀÃÌ>`Ê
ÜÊivviVÌÛiÞÊÌ
iÊ«ÌâiÀÊ`iV`iÃÊÕ«Ê`vviÀiÌÊ`>Ì>ÀiÌÀiÛ>Ê strategies based on statistics, execute the following two queries requesting different numbers of rows: OAHA?P&BNKI`^k*p-SDANA_-9-))Napneara-nks7 OAHA?P&BNKI`^k*p-SDANA_-9.))Napneara-,,,,nkso7 }ÕÀiÊÇ£nÊÃ
ÜÃÊiÝiVÕÌÊ«>ÃÊvÊÌ
iÃiʵÕiÀið
Figure 7-18. Execution plans of small and large result set queries From the statistics, the optimizer can find the number of rows affected by the preceding two queries. Understanding that there is only one row to be retrieved for the first query, the optimizer chose an Ej`atOaag operation, followed by the necessary NE@Hkkgql to retrieve the data not stored with the clustered index. For the second query, the optimizer knows that a >À}iÊÕLiÀÊvÊÀÜÃÊ£ä]äääÊÀÜîÊÜÊLiÊ>vviVÌi`Ê>`ÊÌ
iÀivÀiÊ>Û`i`ÊÌ
iÊ`iÝÊÌÊ>ÌÌi«ÌÊ ÌÊ«ÀÛiÊ«iÀvÀ>Vi°Ê
>«ÌiÀÊ{ÊiÝ«>ÃÊ`iÝ}ÊÃÌÀ>Ìi}iÃÊÊ`iÌ>°®
189
190
C H A P T E R 7 N S T A T I S T I C S A N A L Y S I S
Besides the information on steps, other useful information in the statistics includes the following: Ê
UÊ /
iÊÌiÊÃÌ>ÌÃÌVÃÊÜiÀiÊ>ÃÌÊÕ«`>Ìi`
Ê
UÊ /
iÊÕLiÀÊvÊÀÜÃÊÊÌ
iÊÌ>Li
Ê
UÊ /
iÊ>ÛiÀ>}iÊ`iÝÊiÞÊi}Ì
Ê
UÊ /
iÊÕLiÀÊvÊÀÜÃÊÃ>«i`ÊvÀÊÌ
iÊ
ÃÌ}À>
Ê
UÊ iÃÌiÃÊvÀÊVL>ÌÃÊvÊVÕÃ
Information on the time of the last update can help you decide whether you should man Õ>ÞÊÕ«`>ÌiÊÌ
iÊÃÌ>ÌÃÌVðÊ/
iÊaverage key length represents the average size of the data in the index key column(s). It helps you understand the width of the index key, which is an important i>ÃÕÀiÊÊ`iÌiÀ}ÊÌ
iÊivviVÌÛiiÃÃÊvÊÌ
iÊ`iݰÊÃÊiÝ«>i`ÊÊ
>«ÌiÀÊ{]Ê>ÊÜ`iÊ`iÝÊ is usually costly to maintain and requires more disk space and memory pages.
Density When creating an execution plan, the query optimizer analyzes the statistics of the columns used in the filter and FKEJÊV>ÕÃiðÊÊfilter criterion with high selectivity limits the number vÊÀÜÃÊvÀÊ>ÊÌ>LiÊÌÊ>ÊÃ>ÊÀiÃÕÌÊÃiÌÊ>`Ê
i«ÃÊÌ
iÊ«ÌâiÀÊii«ÊÌ
iʵÕiÀÞÊVÃÌÊܰÊÊ column with a unique index will have a very high selectivity, since it can limit the number of matching rows to one. On the other hand, a filter criterion with low selectivity will return a large result set from Ì
iÊÌ>Li°ÊÊvÌiÀÊVÀÌiÀÊÜÌ
ÊÛiÀÞ low selectivity makes a nonclustered index on the column ineffective. Navigating through a nonclustered index to the base table for a large result set is usually costlier than scanning the base table (or clustered index) directly because of the cost overhead of bookmark lookups associated with the nonclustered index. You can observe this Li
>ÛÀÊÊÌ
iÊiÝiVÕÌÊ«>ÊÊ}ÕÀiÊÇ£n° -Ì>ÌÃÌVÃÊÌÀ>VÊÌ
iÊÃiiVÌÛÌÞÊvÊ>ÊVÕÊÊÌ
iÊvÀÊvÊ>Ê`iÃÌÞÊÀ>̰ÊÊVÕÊÜÌ
high selectivity (or uniqueness) willÊ
>ÛiÊÜÊ`iÃÌÞ°ÊÊVÕÊÜÌ
ÊÜÊ`iÃÌÞÊÌ
>ÌÊÃ]Ê
}
Ê selectivity) is suitable for a nonclustered index, because it helps the optimizer retrieve a small ÕLiÀÊvÊÀÜÃÊÛiÀÞÊv>Ã̰Ê/
ÃÊÃÊ>ÃÊÌ
iÊ«ÀV«>ÊÊÜ
V
ÊvÌiÀi` indexes operate since the filter’s goal is to increase the selectivity, or density, of the index. Density can be expressed as follows:
iÃÌÞÊrÊ£ÊÉÊ ÕLiÀÊvÊ`ÃÌVÌÊÛ>ÕiÃÊvÀÊ>ÊVÕ iÃÌÞÊÜÊ>Ü>ÞÃÊViÊÕÌÊ>ÃÊ>ÊÕLiÀÊÃiÜ
iÀiÊLiÌÜiiÊäÊ>`Ê£°Ê/
iÊÜiÀÊÌ
iÊV umn density, the more suitable it is for use in a nonclustered index. You can perform your own calculations to determine the density of columns within your own indexes and statistics. For example, to calculate the density of column _- from the test table built by _na]pa[p/*omh, use Ì
iÊvÜ}ÊÀiÃÕÌÃÊÊ}ÕÀiÊÇ£®\ OAHA?P-*,+?KQJP$@EOPEJ?P_-%BNKIp-
Figure 7-19. Results of density calculation for column _-
C H A P T E R 7 N S T A T I S T I C S A N A L Y S I S
You can see this as actual data in the =hh`ajoepu column in the output from @>??ODKS[ OP=PEOPE?O°Ê/
ÃÊ
}
`iÃÌÞÊÛ>ÕiÊvÀÊÌ
iÊVÕÊ>iÃÊÌÊ>ÊiÃÃÊÃÕÌ>LiÊV>``>ÌiÊvÀÊ>Ê index, even a filtered index. However, the statistics of the index key values maintained in the steps help the query optimizer use the index for the predicate _-9-, as shown in the previous execution plan.
Statistics on a Multicolumn Index In the case of an index with one column, statistics consist of a histogram and a density value for that column. Statistics for a composite index with multiple columns consist of one his togram for the first column only and multipleÊ`iÃÌÞÊÛ>ÕiðÊ/
ÃÊÃÊiÊÀi>ÃÊÜ
ÞÊ̽ÃÊ wise to put the more selective column, the one with the lowest density, first when building a V«Õ`Ê`iÝÊÀÊV«Õ`ÊÃÌ>ÌÃÌVðÊ/
iÊ`iÃÌÞÊÛ>ÕiÃÊVÕ`iÊÌ
iÊ`iÃÌÞÊvÀÊÌ
iÊvÀÃÌÊ column and for each prefix combination of the index key columns. Multiple density values help the optimizer find the selectivity of the composite index when multiple columns are referred to by predicates in the SDANA and FKEJÊV>ÕÃiðÊÌ
Õ}
ÊÌ
iÊvÀÃÌÊVÕÊV>Ê
i«Ê determine the histogram, the final density of the column itself would be the same regardless of column order. /ÊLiÌÌiÀÊÕ`iÀÃÌ>`ÊÌ
iÊ`iÃÌÞÊÛ>ÕiÃÊ>Ì>i`ÊvÀÊ>ÊÕÌVÕÊ`iÝ]ÊÞÕÊV>Ê modify the nonclustered index used earlier to include two columns: ?NA=PAJKJ?HQOPANA@EJ@ATe-KJ`^k*p-$_-(_.%SEPD@NKL[ATEOPEJC }ÕÀiÊÇÓäÊÃ
ÜÃÊÌ
iÊÀiÃÕÌ>ÌÊÃÌ>ÌÃÌVÃÊ«ÀÛ`i`ÊLÞÊ@>??ODKS[OP=PEOPE?O.
Figure 7-20. Statistics on the multicolumn index eÃÊÞÕÊV>ÊÃii]ÊÌ
iÀiÊ>ÀiÊÌÜÊ`iÃÌÞÊÛ>ÕiÃÊÕ`iÀÊÌ
iÊ=hh`ajoepu column: Ê
UÊ /
iÊ`iÃÌÞÊvÊÌ
iÊvÀÃÌÊVÕ
Ê
UÊ /
iÊ`iÃÌÞÊvÊÌ
iÊvÀÃÌʳÊÃiV`®ÊVÕÃ
For a multicolumn index with three columns, the statistics for the index would also con Ì>ÊÌ
iÊ`iÃÌÞÊÛ>ÕiÊvÊÌ
iÊvÀÃÌʳÊÃiV`ʳÊÌ
À`®ÊVÕðÊ/
iÊÃÌ>ÌÃÌVÃÊܽÌÊVÌ>Ê>Ê `iÃÌÞÊÛ>ÕiÊvÀÊ>ÞÊÌ
iÀÊVL>ÌÊvÊVÕðÊ/
iÀivÀi]ÊÌ
ÃÊ`iÝÊe-) won’t be very useful for filtering rows only on the second column (_.), because the density value of the sec ond column (_.) alone isn’t maintained in the statistics. You can computeÊÌ
iÊÃiV`Ê`iÃÌÞÊÛ>ÕiÊä°£äÓÈ®ÊÃ
ÜÊÊ}ÕÀiÊÇ£ÊÌ
ÀÕ}
Ê Ì
iÊvÜ}ÊÃÌi«Ã°Ê/
ÃÊÃÊÌ
iÊÕLiÀÊvÊ`ÃÌVÌÊÛ>ÕiÃÊvÀ a column combination of (_-, _.): OAHA?P-*,+?KQJP$&% BNKI$OAHA?P@EOPEJ?P_-(_.BNKI`^k*p-%@eopej_pNkso
191
192
C H A P T E R 7 N S T A T I S T I C S A N A L Y S I S
Statistics on a Filtered Index /
iÊ«ÕÀ«ÃiÊvÊ>Êfiltered index is to change the data that makes up the index and therefore change the density and histogram to make the index more performant. Instead of a test table, Ì
ÃÊiÝ>«iÊÜÊÕÃiÊÌ
iÊ`ÛiÌÕÀi7ÀÃÓäänÊ`>Ì>L>Ãi°Ê Ài>ÌiÊ>Ê`iÝÊÊÌ
iÊO]hao* Lqn_d]oaKn`anDa]`an table on the Lqn_d]oaKn`anJqi^an column: ?NA=PAEJ@ATET[PaopKJO]hao*O]haoKn`anDa]`an$Lqn_d]oaKn`anJqi^an% }ÕÀiÊÇÓ£ÊÃ
ÜÃÊÌ
iÊ
i>`iÀÊ>`ÊÌ
iÊ`iÃÌÞÊvÊÌ
iÊÕÌ«ÕÌÊvÀÊ@>??ODKS[OP=PEOPE?O run against this new index: @>??ODKS[OP=PEOPE?O$#O]hao*O]haoKn`anDa]`an#(ET[Paop%
Figure 7-21. Statistics header of an unfiltered index vÊÌ
iÊÃ>iÊ`iÝÊÃÊÀiVÀi>Ìi`ÊÌÊ`i>ÊÜÌ
ÊÛ>ÕiÃÊvÊÌ
iÊVÕÊÌ
>ÌÊ>ÀiÊÌÊjqhh, it would look something like this: ?NA=PAEJ@ATET[PaopKJO]hao*O]haoKn`anDa]`an$Lqn_d]oaKn`anJqi^an% SDANALqn_d]oaKn`anJqi^anEOJKPJQHH SEPD@NKL[ATEOPEJC `ÊÜ]ÊÊ}ÕÀiÊÇÓÓ]ÊÌ>iÊ>ÊÊ>ÌÊÌ
iÊÃÌ>ÌÃÌVÃÊvÀ>̰
Figure 7-22. Statistics header for a filtered index First you can see that the number of rows that compose the statistics have radically dropped in the filtered index because there is a filter in place. Notice also that the average key i}Ì
Ê
>ÃÊVÀi>Ãi`ÊÃViÊÞÕ½ÀiÊÊ}iÀÊ`i>}ÊÜÌ
ÊâiÀi}Ì
ÊÃÌÀ}ðÊÊvÌiÀÊiÝ«ÀiÃÃÊ has been defined rather than the JQHHÊÛ>ÕiÊÛÃLiÊÊ}ÕÀiÊÇÓ£°Ê ÕÌÊÌ
iÊÕvÌiÀi`ÊÀÜÃÊvÊ both sets of data are the same. /
iÊ`iÃÌÞÊi>ÃÕÀiiÌÃÊ>ÀiÊÛiÀÞÊÌiÀiÃÌ}°Ê ÌViÊÌ
>ÌÊÌ
iÊ`iÃÌÞÊÃÊVÃiÊÌÊÌ
iÊ same for both values, but the filtered density is slightly lower, meaning fewer unique values. /
ÃÊÃÊLiV>ÕÃiÊÌ
iÊvÌiÀi`Ê`>Ì>]ÊÜ
iÊ>À}>ÞÊiÃÃÊÃiiVÌÛi]ÊÃÊ>VÌÕ>ÞÊÀiÊ>VVÕÀ>Ìi]Ê i>Ì}Ê>ÊÌ
iÊi«ÌÞÊÛ>ÕiÃÊÌ
>ÌÊܽÌÊVÌÀLÕÌiÊÌÊ>ÊÃi>ÀV
°Ê`ÊÌ
iÊ`iÃÌÞÊvÊÌ
iÊ second value, which represents the clustered index pointer, is identical with the value of the density of the Lqn_d]oaKn`anJqi^an alone because each represents the same amount of unique `>Ì>°Ê/
iÊ`iÃÌÞÊvÊÌ
i additional clustered index in the previous column is a much smaller
C H A P T E R 7 N S T A T I S T I C S A N A LY S I S
number because of all the unique values of O]haoKn`anE` that are not included in the filtered data because of the elimination of the jqhh values. "iÊÌ
iÀÊ«ÌÊ«iÊÌÊÞÕÊÃÊÌÊVÀi>ÌiÊvÌiÀi`ÊÃÌ>ÌÃÌVðÊ/
ÃÊ>ÜÃÊÞÕÊÌÊVÀi>ÌiÊiÛiÊ ÀiÊviÌÕi`Ê
ÃÌ}À>ÃÊÊ«>ÀÌÌi`ÊÌ>LiðÊ/
ÃÊÃÊiViÃÃ>ÀÞÊLiV>ÕÃiÊÃÌ>ÌÃÌVÃÊ>ÀiÊ not automatically created on partitioned tables and you can’t create your own using ?NA=PA OP=PEOPE?O. You can create filtered indexes by partition and get statistics or create filtered sta tistics specifically by partition. Before going on, clean the indexes created, if any: @NKLEJ@ATO]hao*O]haoKn`anDa]`an*ET[Paop7
Statistics Maintenance SQL Server allows a user to manually override the maintenance of statistics in an individual `>Ì>L>Ãi°Ê/
iÊvÕÀÊ>Êconfigurations controlling automatic statistics maintenance behavior of SQL Server are as follows: Ê
UÊ
iÜÊÃÌ>ÌÃÌVÃ on columns with no index (auto create statistics)
Ê
UÊ 1«`>Ì} existing statistics (auto update statistics)
Ê
UÊ /
iÊ`i}ÀiiÊvÊsampling used to collect statistics
Ê
UÊ ÃÞV
ÀÕÃ updating of existing statistics (auto update statistics async)
You can control the preceding configurations at the levels of a database (all indexes and ÃÌ>ÌÃÌVÃÊÊ>ÊÌ>LiîÊÀÊÊ>ÊV>ÃiLÞV>ÃiÊL>ÃÃÊÊ`Û`Õ>Ê`iÝiÃÊÀÊÃÌ>ÌÃÌVðÊ/
iÊ>ÕÌÊ create statistics setting is applicable for nonindexed columns only, because SQL Server always VÀi>ÌiÃÊÃÌ>ÌÃÌVÃÊvÀÊ>Ê`iÝÊiÞÊÜ
iÊÌ
iÊ`iÝÊÃÊVÀi>Ìi`°Ê/
iÊ>ÕÌÊÕ«`>ÌiÊÃÌ>ÌÃÌVÃÊÃiÌÌ}]Ê and the asynchronous version, is applicable for statistics on both indexes and SDANA clause col umns with no index.
Automatic Maintenance By default, SQL Server automatically takes care of statistics. Both the auto create statistics and >ÕÌÊÕ«`>ÌiÊÃÌ>ÌÃÌVÃÊÃiÌÌ}ÃÊ>ÀiÊÊLÞÊ`iv>Õ̰Ê/
iÃiÊÌÜÊvi>ÌÕÀiÃÊÌ}iÌ
iÀÊ>ÀiÊÀiviÀÀi`ÊÌÊ as autostats°ÊÃÊiÝ«>i`Ê«ÀiÛÕÃÞ]ÊÌÊÃÊÕÃÕ>ÞÊLiÌÌiÀÊÌÊii«ÊÌ
iÃiÊÃiÌÌ}ÃʰÊ/
i auto update statistics async setting is off by default.
Auto Create Statistics /
iÊauto create statistics feature automatically creates statistics on nonindexed columns when referred to in the SDANA clause of a query. For example, when this OAHA?P statement is run against the O]hao*O]haoKn`anDa]`an table on a column with no index, statistics for the column are created: OAHA?P& BNKIO]hao*O]haoKn`anDa]`an=Ookd SDANAOdelPk=``naooE@9.525.
193
194
C H A P T E R 7 N S T A T I S T I C S A N A L Y S I S
/
iÊÌ
iÊ>ÕÌÊVÀi>ÌiÊÃÌ>ÌÃÌVÃÊvi>ÌÕÀiÊ>iÊÃÕÀiÊÌÊÃÊÌÕÀi`ÊL>VÊÊvÊÞÕÊ
>ÛiÊÌÕÀi`ÊÌÊ off) automatically creates statistics on column OdelPk=``naooE@. You can see this in the Profiler ÌÀ>ViÊÕÌ«ÕÌÊÊ}ÕÀiÊÇÓΰ
Figure 7-23. Trace output with =QPK[?NA=PA[OP=PEOPE?OKJ
Auto Update Statistics /
i auto update statistics feature automatically updates existing statistics on the indexes and columns of a permanent table when the table is referred to in a query, provided the statistics
>ÛiÊLiiÊ>Ài`Ê>ÃÊÕÌv`>Ìi°Ê/
iÊÌÞ«iÃÊvÊV
>}iÃÊ>ÀiÊ>VÌÊÃÌ>ÌiiÌÃ]ÊÃÕV
Ê>ÃÊEJOANP, QL@=PA, and @AHAPA°Ê/
iÊÌ
ÀiÃ
`ÊvÀÊÌ
iÊÕLiÀÊvÊV
>}iÃÊ`i«i`ÃÊÊÌ
iÊÕLiÀÊvÊÀÜÃÊ ÊÌ
iÊÌ>Li]Ê>ÃÊÃ
ÜÊÊ/>LiÊÇ{° Table 7-4. Update Statistics Threshold for Number of Changes
Number of Rows
Threshold for Number of Changes
ä
Ê£ÊÃiÀÌ
Êxää
ÊxääÊV
>}iÃ
Êxää
xääʳÊÓä¯ÊvÊV>À`>ÌÞÊV
>}iÃ
In SQL Server, cardinality is counted as the number of rows in the table. Using an internal threshold reduces the frequency of the automatic update of statistics, and using column changes rather than rows changes further reduces the frequency. For exam ple, consider the following table (_na]pa[p0*omh in the download): EB$OAHA?PK>FA?P[E@$#`^k*p-#%%EOJKPJQHH @NKLP=>HA`^k*p-7 ?NA=PAP=>HA`^k*p-$_-EJP%7 ?NA=PAEJ@ATet-KJ`^k*p-$_-%7 EJOANPEJPK`^k*p-$ _%R=HQAO$,%7 vÌiÀÊÌ
iÊVÕÃÌiÀi`Ê`iÝÊÃÊVÀi>Ìi`]Ê>ÊÃ}iÊÀÜÊÃÊ>``i`ÊÌÊÌ
iÊÌ>Li°Ê/
ÃÊÕÌ`>ÌiÃÊ the existing statistics on the nonclustered index. If the following OAHA?P statement is executed with a reference to the indexed column in the SDANA clause, like so: OAHA?P&BNKI`^k*p-SDANA_-9, then the auto update statistics feature automatically updates statistics on the nonclustered `iÝ]Ê>ÃÊÃ
ÜÊÊÌ
iÊ*ÀviÀÊÌÀ>ViÊÕÌ«ÕÌÊÊ}ÕÀiÊÇÓ{°
C H A P T E R 7 N S T A T I S T I C S A N A L Y S I S
Figure 7-24. Trace output with =QPK[QL@=PA[OP=PEOPE?OKJ "ViÊÌ
iÊÃÌ>ÌÃÌVÃÊ>ÀiÊÕ«`>Ìi`]ÊÌ
iÊV
>}iÌÀ>V}ÊiV
>ÃÃÊvÀÊÌ
iÊVÀÀië`}Ê Ì>LiÃÊ>ÀiÊÃiÌÊÌÊä°Ê/
ÃÊÜ>Þ]Ê-+Ê-iÀÛiÀÊii«ÃÊÌÀ>VÊvÊÌ
iÊÕLiÀÊvÊV
>}iÃÊÌÊÌ
iÊÌ>LiÃÊ>`Ê manages the frequency of automatic updates of statistics.
Auto Update Statistics Asynchronously If auto update statistics asynchronously is set to on, the basic behavior of statistics in SQL -iÀÛiÀÊýÌÊV
>}i`ÊÀ>`V>Þ°Ê7
iÊ>ÊÃiÌÊvÊÃÌ>ÌÃÌVÃÊÃÊ>Ài`Ê>ÃÊÕÌv`>ÌiÊ>`Ê>ʵÕiÀÞÊÃÊ then run against those statistics, the statistics update does not interrupt the query, as normally happens. Instead, the query finishes execution using the older set of statistics. Once the query V«iÌiÃ]ÊÌ
iÊÃÌ>ÌÃÌVÃÊ>ÀiÊÕ«`>Ìi`°Ê/
iÊÀi>ÃÊÌ
ÃÊ>ÞÊLiÊ>ÌÌÀ>VÌÛiÊÃÊÌ
>ÌÊÜ
iÊÃÌ>ÌÃÌVÃÊ are updated, query plans in the procedure cache are removed, and the query being run must be recompiled. So, rather than make a query wait for both the update of the statistics and >ÊÀiV«iÊvÊÌ
iÊ«ÀVi`ÕÀi]ÊÌ
iʵÕiÀÞÊV«iÌiÃÊÌÃÊÀÕ°Ê/
iÊiÝÌÊÌiÊÌ
iÊÃ>iʵÕiÀÞÊÃÊ called, it will have updated statistics waiting for it, and it will have to recompile only. Ì
Õ}
ÊÌ
ÃÊvÕVÌ>ÌÞÊ`iÃÊ>iÊÀiV«iÃÊÃiÜ
>ÌÊv>ÃÌiÀ]ÊÌÊV>Ê>ÃÊV>ÕÃiÊ queries that could benefit from updated statistics and a new execution plan to work with the `ÊiÝiVÕÌÊ«>°Ê >ÀivÕÊÌiÃÌ}ÊÃÊÀiµÕÀi`ÊLivÀiÊÌÕÀ}ÊÌ
ÃÊvÕVÌ>ÌÞÊÊÌÊiÃÕÀiÊÌÊ doesn’t cause more harm than good.
NNote If you are attempting to update statistics asynchronously, you must also have =QPK[QL@=PA[ OP=PEOPE?O set to KJ.
Manual Maintenance /
i following are situations in which you need to interfere with the automatic maintenance of statistics: Ê
UÊ When experimenting with statistics: Just a friendly suggestion: please spare your pro duction servers from experiments such as the ones you are doing in this book.
Ê
UÊ After upgrading from a previous version to SQL Server 2008: Since the statistics main Ìi>ViÊvÊ-+Ê-iÀÛiÀÊÓäänÊ
>ÃÊLiiÊÕ«}À>`i`]ÊÞÕÊÃ
Õ`Ê>Õ>ÞÊÕ«`>ÌiÊÌ
iÊ statistics of the complete database immediately after the upgrade instead of waiting for SQL Server to update it over time with the help of automatic statistics.
Ê
UÊ While executing a series of ad hoc SQL activities that you won’t execute again: In such cases, you must decide whether you want to pay the cost of automatic statistics main tenance to get a better plan in that one case and affect the performance of other SQL -iÀÛiÀÊ>VÌÛÌiðÊ-]ÊÊ}iiÀ>]ÊÞÕÊ`½ÌÊii`ÊÌÊLiÊVViÀi`ÊÜÌ
ÊÃÕV
ÊiÌiÀð
195
196
CHAPTER 7 N ST A TIS TIC S A NA L YS IS
Ê
UÊ When you come upon an issue with the automatic statistics maintenance and the only workaround for the time being is to keep the automatic statistics maintenance feature off\Ê ÛiÊÊÌ
iÃiÊV>ÃiÃÊÞÕÊV>ÊÌÕÀÊÌ
iÊvi>ÌÕÀiÊvvÊvÀÊÌ
iÊëiVvVÊ`>Ì>L>ÃiÊÌ>LiÊÌ
>ÌÊ faces the problem instead of disabling it for the complete database.
Ê
UÊ While analyzing the performance of a query, you realize that the statistics are missing for a few of the database objects referred to by the query\Ê/
ÃÊV>ÊLiÊiÛ>Õ>Ìi`ÊvÀÊÌ
iÊ }À>«
V>Ê>`Ê8ÊiÝiVÕÌÊ«>Ã]Ê>ÃÊiÝ«>i`Êi>ÀiÀÊÊÌ
iÊV
>«ÌiÀ°
Ê
UÊ While analyzing the effectiveness of statistics, you realize that they are inaccurate\Ê/
ÃÊ can be determined when poor execution plans are being created from what should be good sets of indexes.
SQL Server allows a user to control many of its automatic statistics maintenance features. You can enable (or disable) the automatic statistics creation and update features by using the auto create statistics and auto update statistics settings, respectively, and then you can get your hands dirty.
Manage Statistics Settings You can control the auto create statisticsÊÃiÌÌ}Ê>ÌÊ>Ê`>Ì>L>ÃiÊiÛi°Ê/Ê`Ã>LiÊÌ
ÃÊÃiÌÌ}]ÊÕÃiÊ the =HPAN@=P=>=OA command: =HPAN@=P=>=OA=`rajpqnaSkngo.,,4OAP=QPK[?NA=PA[OP=PEOPE?OKBB You can control the auto update statistics setting at different levels of a database, includ }Ê>Ê`iÝiÃÊ>`ÊÃÌ>ÌÃÌVÃÊÊ>ÊÌ>Li]ÊÀÊ>ÌÊÌ
iÊ`Û`Õ>Ê`iÝÊÀÊÃÌ>ÌÃÌVÃÊiÛi°Ê/Ê`Ã>LiÊ auto update statistics at the database level, use the =HPAN@=P=>=OA command: =HPAN@=P=>=OA=`rajpqnaSkngo.,,4OAP=QPK[QL@=PA[OP=PEOPE?OKBB Disabling this setting at the database level overrides individual settings at lower levels. ÕÌÊÕ«`>ÌiÊÃÌ>ÌÃÌVÃ asynchronously requires that the auto update statistics be on first. /
iÊÞÕÊV> enable the asynchronous update: =HPAN@=P=>=OA=`rajpqnaSkngo.,,4OAP=QPK[QL@=PA[OP=PEOPE?O[=OUJ?KJ /ÊVv}ÕÀi auto update statistics for all indexes and statistics on a table in the current database, use the ol[]qpkop]po system stored procedure: QOA=`rajpqnaSkngo.,,4 ATA?ol[]qpkop]po#Dqi]jNaokqn_ao*@al]npiajp#(#KBB# You can also use the same stored procedure to configure this setting for individual indexes ÀÊÃÌ>ÌÃÌVðÊ/Ê`Ã>LiÊÌ
ÃÊÃiÌÌ}ÊvÀÊÌ
iÊ=G[@al]npiajp[J]ia index on =`rajpqnaSkngo.,,4* Dqi]jNaokqn_ao*@al]npiajp, execute the following statements: QOA=`rajpqnaSkngo.,,4 ATA?ol[]qpkop]poÐDqi]jNaokqn_ao*@al]npiajpÑ(#KBB#(=G[@al]npiajp[J]ia
C H A P T E R 7 N S T A T I S T I C S A N A LY S I S
You can also use the QL@=PAOP=PEOPE?O command’s SEPDJKNA?KILQPA option to disable Ì
ÃÊÃiÌÌ}ÊvÀÊ>ÊÀÊ`Û`Õ>Ê`iÝiÃÊ>`ÊÃÌ>ÌÃÌVÃÊÊ>ÊÌ>LiÊÊÌ
iÊVÕÀÀiÌÊ`>Ì>L>Ãi°Ê/
i ol[_na]paop]po stored procedure also has the JKNA?KILQPAʫ̰Ê/
iÊJKNA?KILQPA option will not disable automatic update of statistics directly, but it will prevent them, which is almost the same. Û`Ê`Ã>L}ÊÌ
iÊ>ÕÌ>ÌVÊÃÌ>ÌÃÌVÃÊvi>ÌÕÀiÃ]ÊÕiÃÃÊÞÕÊ
>ÛiÊVvÀi`ÊÌ
ÀÕ}
ÊÌiÃÌ ing that this brings a performance benefit. If the automatic statistics features are disabled, then you should manually identify and create missing statistics on the columns that are not `iÝi`Ê>`ÊÌ
iÊii«ÊÌ
iÊiÝÃÌ}ÊÃÌ>ÌÃÌVÃÊÕ«Ì`>Ìi° Reset the automatic maintenance of the index so that it is on where it has been turned off: ATA?ol[]qpkop]po#Dqi]jNaokqn_ao*@al]npiajp#(#KJ# ATA?ol[]qpkop]poÐDqi]jNaokqn_ao*@al]npiajpÑ(#KJ#(=G[@al]npiajp[J]ia
Generate Statistics / create statistics manually, use one of the following options: Ê
UÊ ?NA=PAOP=PEOPE?O: You can use this option to create statistics on single or multiple columns of a table or an indexed view. Unlike the ?NA=PAEJ@AT command, ?NA=PA OP=PEOPE?O uses sampling by default.
Ê
UÊ ol[_na]paop]po: Use this storedÊ«ÀVi`ÕÀiÊÌÊVÀi>ÌiÊÃ}iVÕÊÃÌ>ÌÃÌVÃÊvÀÊ>Ê eligibleÊVÕÃÊvÀÊ>ÊÕÃiÀÊÌ>LiÃÊÊÌ
iÊVÕÀÀiÌÊ`>Ì>L>Ãi°Ê/
ÃÊVÕ`iÃÊ>ÊVÕÃÊ iÝVi«ÌÊV«ÕÌi`ÊVÕÃÆÊVÕÃÊÜÌ
ÊÌ
iÊJPATP, PATP, CAKIAPNU, CAKCN=LDU, or EI=CA `>Ì>ÊÌÞ«iÆÊë>ÀÃiÊVÕÃÆÊ>`ÊVÕÃÊÌ
>ÌÊ>Ài>`ÞÊ
>ÛiÊÃÌ>ÌÃÌVÃÊÀÊ>ÀiÊÌ
iÊvÀÃÌÊ column of an index. Similarly, to update statistics manually, use one of the following options:
Ê
UÊ QL@=PAOP=PEOPE?O: You can use this option to update the statistics of individual or all index keys and nonindexed columns of a table or an indexed view.
Ê
UÊ ol[ql`]paop]po: Use this stored procedure to update statistics of all user tables in the current database.
You may find that allowing the automatic updating of statistics is not quite adequate for your system. Scheduling QL@=PAOP=PEOPE?OÊvÀÊÌ
iÊ`>Ì>L>ÃiÊ`ÕÀ}Êvv
ÕÀÃÊÃÊ>Ê>VVi«Ì>LiÊ way to deal with this issue. QL@=PAOP=PEOPE?O is the preferred mechanism because it offers a greater degree of flexibility and control. It’s possible, because of the types of data inserted, that the sampling method for gathering the statistics, used because it’s faster, may not gather the appropriate data. In these cases, you can force a BQHHO?=J so that all the data is used to update Ì
iÊÃÌ>ÌÃÌVÃÊÕÃÌÊiÊÜ
>ÌÊ
>««iÃÊÜ
iÊÌ
iÊÃÌ>ÌÃÌVÃÊ>ÀiÊÌ>ÞÊVÀi>Ìi`°Ê/
ÃÊV>ÊLiÊ>ÊÛiÀÞÊ costly operation, so it’s best to be very selective about which indexes receive this treatment and when it is run.
NNote In general, you should always use the default settings for automatic statistics. Consider modifying these settings only after identifying that the default settings appear to detract from performance.
197
198
C H A P T E R 7 N S T A T I S T I C S A N A L Y S I S
Statistics Maintenance Status You can verify the current settings for the autostats feature using the following: Ê
UÊ @=P=>=OALNKLANPUAT
Ê
UÊ ol[]qpkop]po
Status of Auto Create Statistics You can verify the current setting for auto create statistics by running a query against the ouo* `]p]^]oao system table: OAHA?Peo[]qpk[_na]pa[op]po[kj BNKIouo*`]p]^]oao SDANAWj]iaY9#=`rajpqnaSkngo.,,4# ÊÀiÌÕÀÊÛ>ÕiÊvÊ£Êi>ÃÊi>Li`]Ê>`Ê>ÊÛ>ÕiÊvÊäÊi>ÃÊ`Ã>Li`° You can also verify the status of this feature using the ol[]qpkop]po system stored proce dure, as shown in the following code. Supplying any table name to the stored procedure will provide the configuration value of auto create statistics for the current database under the Kqplqp section of the global statistics settings: QOA=`rajpqnaSkngo.,,4 ATA?ol[]qpkop]poÑDqi]jNaokqn_ao*@al]npiajpÑ }ÕÀiÊÇÓxÊÃ
ÜÃÊ>ÊiÝViÀ«ÌÊvÊÌ
iÊ«ÀiVi`}Êol[]qpkop]po statement’s output.
Figure 7-25. ol[]qpkop]po output ÊÀiÌÕÀÊÛ>ÕiÊvÊKJ means enabled, and a value of KBB means disabled. /
ÃÊÃÌÀi`Ê«ÀVi`ÕÀiÊÃÊÀiÊÕÃivÕÊÜ
iÊÛiÀvÞ}ÊÌ
iÊÃÌ>ÌÕÃÊvÊ>ÕÌÊÕ«`>ÌiÊÃÌ>ÌÃÌVÃ]Ê>ÃÊ explained later in this chapter.
Status of Auto Update Statistics You can verify the current setting for auto update statistics, and auto update statistics asyn chronously, in a similar manner to auto create statistics. Here’s how to do it using the function @=P=>=OALNKLANPUAT: OAHA?P@=P=>=OALNKLANPUAT$#=`rajpqnaSkngo.,,4#(#Eo=qpkQl`]paOp]peope_o#% Here’s how to do it using ol[]qpkop]po: QOA=`rajpqnaSkngo.,,4 ATA?ol[]qpkop]po#O]hao*O]haoKn`an@ap]eh#
C H A P T E R 7 N S T A T I S T I C S A N A LY S I S
Analyzing the Effectiveness of Statistics for a Query For performance reasons, it is extremely important to maintain proper statistics on your data L>ÃiÊLiVÌðÊÃÃÕiÃÊÜÌ
ÊÃÌ>ÌÃÌVÃÊ>ÀiÊÕV°ÊÜiÛiÀ]ÊÞÕÊÃÌÊii`ÊÌÊii«ÊÞÕÀÊiÞiÃÊ open to the possibility of problems with statistics while analyzing the performance of a query. If an issue with statistics does arise, then it can really take you for a ride. In fact, checking that Ì
iÊÃÌ>ÌÃÌVÃÊ>ÀiÊÕ«Ì`>ÌiÊ>ÌÊÌ
iÊLi}}ÊvÊ>ʵÕiÀÞÌÕ}ÊÃiÃÃÊi>ÌiÃÊ>Êi>ÃÞÊ fixed problem. In this section, you’ll see what you can do should you find statistics to be miss }ÊÀÊÕÌv`>Ìi° While analyzing an execution plan for a query, look for the following points to ensure a VÃÌivviVÌÛiÊ«ÀViÃÃ}ÊÃÌÀ>Ìi}Þ\ Ê
UÊ `iÝiÃÊ>ÀiÊ>Û>>LiÊÊÌ
iÊVÕÃÊÀiviÀÀi`ÊÌÊÊÌ
iÊvÌiÀÊ>`ÊÊVÀÌiÀ>°
Ê
UÊ ÊÌ
iÊV>ÃiÊvÊ>ÊÃÃ}Ê`iÝ]Êstatistics should be available on the columns with no index. It is preferable to have the index itself.
Ê
UÊ -ViÊÕÌ`>Ìi`ÊÃÌ>ÌÃÌVÃÊ>ÀiÊvÊÊÕÃiÊ>`ÊV>ÊiÛiÊLiÊÃi>`}]ÊÌÊÃÊ«ÀÌ>ÌÊÌ
>ÌÊ Ì
iÊiÃÌ>ÌiÃÊÕÃi`ÊLÞÊÌ
iÊ«ÌâiÀÊvÀÊÌ
iÊÃÌ>ÌÃÌVÃÊ>ÀiÊÕ«Ì`>Ìi°
9ÕÊ>>Þâi`ÊÌ
iÊÕÃiÊvÊ>Ê«À«iÀÊ`iÝÊÊ
>«ÌiÀÊ{°ÊÊÌ
ÃÊÃiVÌ]ÊÞÕÊÜÊ>>ÞâiÊÌ
iÊ effectiveness of statistics for a query.
Resolving a Missing Statistics Issue /ÊÃiiÊ
ÜÊÌÊ`iÌvÞÊ>`Êresolve a missing statistics issue, consider the following example. /ÊÀiÊ`ÀiVÌÞÊVÌÀÊÌ
iÊ`>Ì>]ʽÊÕÃiÊ>ÊÌiÃÌÊÌ>LiÊÃÌi>`ÊvÊiÊvÊÌ
iÊ`ÛiÌÕÀi 7ÀÃÓäänÊÌ>LiðÊÀÃÌ]Ê`Ã>LiÊLÌ
Ê>ÕÌÊVÀi>ÌiÊÃÌ>ÌÃÌVÃÊ>`Ê>ÕÌÊÕ«`>ÌiÊÃÌ>ÌÃÌVÃÊÕÃ}ÊÌ
iÊ =HPAN@=P=>=OA command: =HPAN@=P=>=OA=`rajpqnaSkngo.,,4OAP=QPK[?NA=PA[OP=PEOPE?OKBB7 =HPAN@=P=>=OA=`rajpqnaSkngo.,,4OAP=QPK[QL@=PA[OP=PEOPE?OKBB7
Ài>ÌiÊ>ÊÌiÃÌÊÌ>LiÊÜÌ
Ê>Ê>À}iÊÕLiÀÊvÊÀÜÃÊ>`Ê>ÊVÕÃÌiÀi`Ê`iÝÊ_na]pa[p2*omh in the download): EBATEOPO$OAHA?P& BNKIouo*k^fa_po SDANAk^fa_p[e`9K>FA?P[E@$J#W`^kY*Wp-Y#% =J@pulaEJ$J#Q#%% @NKLP=>HAW`^kY*Wp-Y CK ?NA=PAP=>HA`^k*p-$_-EJP(_.EJP(_/?D=N$1,%%7 EJOANPEJPK`^k*p-$_-(_.(_/% R=HQAO$1-(-(#_/#%7 EJOANPEJPK`^k*p-$_-(_.(_/% R=HQAO$1.(-(#_/#%7 ?NA=PAJKJ?HQOPANA@EJ@ATe-KJ`^k*p-$_-(_.%7
199
200
C H A P T E R 7 N S T A T I S T I C S A N A L Y S I S
OAHA?PPKL-,,,, E@AJPEPU$EJP(-(-%=Oj EJPKJqio BNKII]opan*`^k*Ouo?khqijoo_(I]opan*`^k*Ouo?khqijoo_.7 EJOANPEJPK`^k*p-$_-(_.(_/% OAHA?Pj!1, (j (#_/# BNKIJqio7 @NKLP=>HAJqio7 Since the index is created on (_-, _.), the statistics on the index contain a histogram for the first column, _-, and density values for the prefixed column combinations (_- and _-ʳÊ_.). /
iÀiÊ>ÀiÊÊ
ÃÌ}À>ÃÊÀÊ`iÃÌÞÊÛ>ÕiÃÊvÀÊVÕÊ_.. /ÊÕ`iÀÃÌ>`Ê
ÜÊÌÊ`iÌvÞÊÃÃ}ÊÃÌ>ÌÃÌVÃÊÊ>ÊVÕÊÜÌ
ÊÊ`iÝ]ÊiÝiVÕÌiÊÌ
iÊ following OAHA?P statement. Since the auto create statistics feature is off, the optimizer won’t be able to find the data distribution for the column _. used in the SDANA clause. You can see this in the execution plan: OAHA?P&BNKI`^k*p-SDANAp-*_.9-7 vÊÞÕÊÀ}
ÌVVÊÌ
iÊiÝiVÕÌÊ«>]ÊÞÕÊV>ÊÌ>iÊ>ÊÊ>ÌÊÌ
iÊ8Ê`>Ì>ÊLi
`Ḛ̂ÊÃÊ Ã
ÜÊÊ}ÕÀiÊÇÓÈ]ÊÌ
iÊ8ÊiÝiVÕÌÊ«>Ê`V>ÌiÃÊÃÃ}ÊÃÌ>ÌÃÌVÃÊvÀÊ>Ê«>ÀÌVÕ>ÀÊ execution step under its S]njejcoÊiiḭÊ/
ÃÊÃ
ÜÃÊÌ
>ÌÊÌ
iÊÃÌ>ÌÃÌVÃÊÊVÕÊp-*_. are missing.
Figure 7-26. Missing statistics indication in an XML plan /
iÊvÀ>ÌÊÊÃÃ}ÊÃÌ>ÌÃÌVÃÊÃÊ>ÃÊ«ÀÛ`i`ÊLÞÊÌ
iÊ}À>«
V>ÊiÝiVÕÌÊ«>]Ê>ÃÊ Ã
ÜÊÊ}ÕÀiÊÇÓǰ
Figure 7-27. Missing statistics indication in a graphical plan /
iÊ}À>«
V>ÊiÝiVÕÌÊ«>ÊVÌ>ÃÊ>Ê`iÊÜÌ
ÊÌ
iÊÞiÜÊiÝV>>Ìʫ̰Ê/
ÃÊ `V>ÌiÃÊÃiÊ«ÀLiÊÜÌ
ÊÌ
iÊ`>Ì>ÀiÌÀiÛ>ÊiV
>ÃÊÕÃÕ>ÞÊÃÃ}ÊÃÌ>ÌÃÌVî°Ê9ÕÊ can obtain a detailed description of the error by moving your mouse over the corresponding `iÊvÊÌ
iÊiÝiVÕÌÊ«>ÊÌÊÀiÌÀiÛiÊÌ
iÊÌÊÌ«]Ê>ÃÊÃ
ÜÊÊ}ÕÀiÊÇÓn°
C H A P T E R 7 N S T A T I S T I C S A N A L Y S I S
Figure 7-28. Tool tip of a graphical plan’s node }ÕÀiÊÇÓnÊÃ
ÜÃÊÌ
>ÌÊÌ
iÊÃÌ>ÌÃÌVÃÊvÀÊÌ
iÊVÕÊ>ÀiÊÃÃ}°Ê/
ÃÊ>ÞÊ«ÀiÛiÌÊÌ
iÊ optimizer from selectingÊÌ
iÊLiÃÌÊ«ÀViÃÃ}ÊÃÌÀ>Ìi}Þ°Ê/
iÊVÕÀÀiÌÊVÃÌÊvÊÌ
ÃʵÕiÀÞÊ>ÃÊÃ
ÜÊ by OAPOP=PEOPE?OEK and OAPOP=PEOPE?OPEIA is as follows: P]^ha#p-#*O_]j_kqjp-(hkce_]hna]`o40 OMHOanranAta_qpekjPeiao6 ?LQpeia9,io(ah]loa`peia900io* /ÊÀiÃÛiÊÌ
ÃÊÃÃ}ÊÃÌ>ÌÃÌVÃÊÃÃÕi]ÊÞÕÊV>ÊVÀi>ÌiÊÌ
iÊÃÌ>ÌÃÌVÃÊÊVÕÊp-*_. by using the ?NA=PAOP=PEOPE?O statement: ?NA=PAOP=PEOPE?Oo-KJp-$_.%7 Before rerunning the procedure, be sure to clean out the procedure cache because this query will benefit from simple parameterization: @>??BNAALNK??=?DA$%7 }ÕÀiÊÇÓÊÃ
ÜÃÊÌ
iÊÀiÃÕÌ>ÌÊiÝiVÕÌÊ«>ÊÜÌ
ÊÃÌ>ÌÃÌVÃÊVÀi>Ìi`ÊÊVÕÊ_.. P]^ha#p-#*O_]j_kqjp-(hkce_]hna]`o0/ OMHOanranAta_qpekjPeiao6 ?LQpeia9,io(ah]loa`peia9-1io*
201
202
C H A P T E R 7 N S T A T I S T I C S A N A L Y S I S
Figure 7-29. Execution plan with statistics in place /
iʵÕiÀÞÊoptimizer uses statistics on a noninitial column in a composite index to deter mine whether scanning the leaf level of the composite index to obtain the bookmarks will be a more efficient processing strategy than scanning the whole table. In this case, creating sta tistics on column _. allows the optimizer to determine that instead of scanning the base table, it will be less costly to scan the composite index on (_-, _.) and bookmark lookup to the base Ì>LiÊvÀÊÌ
iÊviÜÊ>ÌV
}ÊÀÜÃ°Ê ÃiµÕiÌÞ]ÊÌ
iÊÕLiÀÊvÊ}V>ÊÀi>`ÃÊ
>Ã decreased vÀÊnÇÊÌÊ{Ó]ÊLÕÌÊÌ
iÊi>«Ãi`ÊÌiÊ
>ÃÊ`iVÀi>Ãi`ÊÞÊÃ}
ÌÞ°
Resolving an Outdated Statistics Issue Sometimes outdated or incorrect statistics can be more damaging than missing statistics. Based on old statistics or a partial scan of changed data, the optimizer may decide upon a particular indexing strategy, which may be highly inappropriate for the current data distribu tion. Unfortunately, the execution plans don’t show the same glaring warnings for outdated or incorrect statistics as they do for missing statistics. /Ê`iÌvÞÊÕÌ`>Ìi`ÊÃÌ>ÌÃÌVÃ]ÊÞÕÊÃ
Õ`ÊiÝ>iÊ
ÜÊVÃiÊÌ
iÊ«ÌâiÀ½ÃÊiÃÌ>ÌÊ of the number of rows affected is to the actual number of rows affected. /
iÊvÜ}ÊiÝ>«iÊÃ
ÜÃÊÞÕÊ
ÜÊÌÊ`iÌvÞÊ>`ÊÀiÃÛiÊ>ÊÕÌ`>Ìi`ÊÃÌ>ÌÃÌVÃÊÃÃÕi°Ê }ÕÀiÊÇÎäÊÃ
ÜÃÊÌ
iÊÃÌ>ÌÃÌVÃÊÊÌ
iÊVÕÃÌiÀi`Ê`iÝÊiÞÊ column _- provided by @>?? ODKS[OP=PEOPE?O. @>??ODKS[OP=PEOPE?O$p-(e-%7 /
iÃiÊÀiÃÕÌÃÊÃ>ÞÊÌ
>ÌÊÌ
iÊ`iÃÌÞÊÛ>ÕiÊvÀÊVÕÊ_-ÊÃÊä°x°Ê ÜÊVÃ`iÀÊÌ
iÊvÜ}Ê OAHA?P statement: OAHA?P&BNKI`^k*p-SDANA_-91-7 -ViÊÌ
iÊÌÌ>ÊÕLiÀÊvÊÀÜÃÊÊÌ
iÊÌ>LiÊÃÊVÕÀÀiÌÞÊ£ä]ääÓ]ÊÌ
iÊÕLiÀÊvÊ>ÌV
}Ê rows for the filter criteria _-91-ÊV>ÊLiÊiÃÌ>Ìi`ÊÌÊLiÊx]ää£ÊrÊä°xÊÊ£ä]ääÓ®°Ê/
ÃÊiÃÌ>Ìi`Ê ÕLiÀÊvÊÀÜÃÊx]ä䣮ÊÃÊÜ>ÞÊvvÊÌ
iÊ>VÌÕ>ÊÕLiÀÊvÊ>ÌV
}ÊÀÜÃÊvÀÊÌ
ÃÊVÕÊÛ>Õi°Ê /
iÊÌ>LiÊ>VÌÕ>ÞÊVÌ>ÃÊÞÊiÊÀÜÊvÀÊ_-91-.
Figure 7-30. Statistics on index e-
C H A P T E R 7 N S T A T I S T I C S A N A L Y S I S
You can get the information on both the estimated and actual number of rows from the >VÌÕ>ÊiÝiVÕÌÊ«>°ÊÊiÃÌ>Ìi`Ê«>ÊÀiviÀÃÊÌÊ>`ÊÕÃiÃÊÌ
iÊÃÌ>ÌÃÌVÃÊÞ]ÊÌÊÌ
iÊ>VÌÕ>Ê `>Ì>°Ê/
ÃÊi>ÃÊÌÊV>ÊLiÊÜ`ÞÊ`vviÀiÌÊvÀÊÌ
iÊÀi>Ê`>Ì>]Ê>ÃÊÞÕ½ÀiÊÃii}ÊܰÊ/
iÊ>VÌÕ>Ê execution plan, on the other hand, has both the estimated and actual number of rows available.
ÝiVÕÌ}ÊÌ
iʵÕiÀÞÊÀiÃÕÌÃÊÊÌ
ÃÊiÝiVÕÌÊ«>Ê}ÕÀiÊÇΣ®Ê>`Ê«iÀvÀ>Vi\ P]^ha#p-#*O_]j_kqjp-(hkce_]hna]`o40 OMHOanranAta_qpekjPeiao6 ?LQpeia9-2io(ah]loa`peia9.io*
Figure 7-31. Execution plan with outdated statistics /ÊÃiiÊÌ
iÊiÃÌ>Ìi`Ê>`Ê>VÌÕ>ÊÀÜÃ]ÊÞÕÊV>ÊÛiÜÊÌ
iÊÌÊÌ«ÊLÞÊ
ÛiÀ}ÊÛiÀÊÌ
iÊP]^ha O_]jÊ«iÀ>ÌÀÊ}ÕÀiÊÇÎÓ®°
Figure 7-32. Tool tip showing row count discrepancy From the estimated rows value vs. the actual rows value, it’s clear that the optimizer made >ÊVÀÀiVÌÊiÃÌ>ÌÊL>Ãi`ÊÊÕÌv`>ÌiÊÃÌ>ÌÃÌVðÊvÊÌ
iÊ`vviÀiViÊLiÌÜiiÊÌ
iÊiÃÌ>Ìi`Ê ÀÜÃÊ>`Ê>VÌÕ>ÊÀÜÃÊÃÊÀiÊÌ
>Ê>Êv>VÌÀÊvÊ£ä]ÊÌ
iÊ̽ÃʵÕÌiÊ«ÃÃLiÊÌ
>ÌÊÌ
iÊ«ÀViÃÃ}Ê ÃÌÀ>Ìi}ÞÊV
ÃiÊ>ÞÊÌÊLiÊÛiÀÞÊVÃÌÊivviVÌÛiÊvÀÊÌ
iÊVÕÀÀiÌÊ`>Ì>Ê`ÃÌÀLÕ̰ÊÊ>VVÕÀ>ÌiÊ estimation may misguide the optimizer in deciding the processing strategy. /Ê
i«ÊÌ
iÊ«ÌâiÀÊ>iÊ>Ê>VVÕÀ>ÌiÊiÃÌ>Ì]ÊÞÕÊÃ
Õ`ÊÕ«`>ÌiÊÌ
iÊÃÌ>ÌÃÌVÃÊÊ the nonclustered index key on column _-Ê>ÌiÀ>ÌÛiÞ]ÊvÊVÕÀÃi]ÊÞÕÊV>ÊÕÃÌÊi>ÛiÊÌ
iÊ>ÕÌÊ update statistics feature on): QL@=PAOP=PEOPE?Op-e-7
203
204
C H A P T E R 7 N S T A T I S T I C S A N A L Y S I S
If you run the query again, you’ll get the following statistics, and the resultant output is as Ã
ÜÊÊ}ÕÀiÊÇÎÎ\ P]^ha#p-#*O_]j_kqjp-(hkce_]hna]`o/ OMHOanranAta_qpekjPeiao6 ?LQpeia9,io(ah]loa`peia9,io*
Figure 7-33. Actual and estimated number of rows with up-to-date statistics /
iÊ«ÌâiÀÊ>VVÕÀ>ÌiÞÊiÃÌ>Ìi`ÊÌ
iÊÕLiÀÊvÊÀÜÃÊÕÃ}ÊÕ«`>Ìi`ÊÃÌ>ÌÃÌVÃÊ>`ÊVÃi µÕiÌÞÊÜ>ÃÊ>LiÊÌÊViÊÕ«ÊÜÌ
Ê>Ê«>°Ê-ViÊÌ
iÊiÃÌ>Ìi`ÊÕLiÀÊvÊÀÜÃÊÃÊ£]ÊÌÊ>iÃÊÃiÃiÊ to retrieve the row through the nonclustered index on _- instead of scanning the base table. Updated, accurate statistics on the index key column help the optimizer come to a better `iVÃÊÊÌ
iÊ«ÀViÃÃ}ÊÃÌÀ>Ìi}ÞÊ>`ÊÌ
iÀiLÞÊÀi`ÕViÊÌ
iÊÕLiÀÊvÊ}V>ÊÀi>`ÃÊvÀÊn{ÊÌÊ ÎÊ>`ÊÀi`ÕViÊÌ
iÊiÝiVÕÌÊÌiÊvÀÊ£ÈÊÃÊÌÊHäÊÃÊÌ
iÀiÊÃÊ>ÊH{ÊÃÊ>}ÊÌi®° Before continuing, turn the statistics back on for the database: =HPAN@=P=>=OA=`rajpqnaSkngo.,,4OAP=QPK[?NA=PA[OP=PEOPE?OKJ7 =HPAN@=P=>=OA=`rajpqnaSkngo.,,4OAP=QPK[QL@=PA[OP=PEOPE?OKJ7
Recommendations /
ÀÕ}
ÕÌÊÌ
à chapter, I covered various recommendations for statistics. For easy reference, I’ve consolidated and expanded upon these recommendations in the sections that follow.
Backward Compatibility of Statistics -Ì>ÌÃÌV>ÊvÀ>ÌÊÊ-+Ê-iÀÛiÀÊÓäänÊà different from that in previous versions of SQL -iÀÛiÀ°ÊÜiÛiÀ]Ê-+Ê-iÀÛiÀÊÓäänÊÌÀ>ÃviÀÃÊÌ
iÊÃÌ>ÌÃÌVÃÊ`ÕÀ}ÊÕ«}À>`iÊ>`]ÊLÞÊ`iv>ÕÌ]Ê>ÕÌ matically updates these statistics over time. For the best performance, however, manually update the statistics immediately after an upgrade.
Auto Create Statistics /
ÃÊvi>ÌÕÀi should usually be left on. With the default setting, during the creation of an execu tion plan, SQL Server determines whether statistics on a nonindexed column will be useful. If this is deemed beneficial, SQL Server creates statistics on the nonindexed column. However, if you plan to create statistics on nonindexed columns manually, then you have to identify exactly for which nonindexed columns statistics will be beneficial.
C H A P T E R 7 N S T A T I S T I C S A N A L Y S I S
Auto Update Statistics /
ÃÊvi>ÌÕÀiÊÃ
Õ`ÊÕÃÕ>ÞÊLi left on, allowing SQL Server to decide on the appropriate execution plan as the data distribution changes over time. Usually the performance benefit provided by this feature outweighs the cost overhead. You will seldom need to interfere with the automatic maintenance of statistics, and such requirements are usually identified while ÌÀÕLiÃ
Ì}ÊÀÊ>>Þâ}Ê«iÀvÀ>Vi°Ê/ÊiÃÕÀiÊÌ
>ÌÊÞÕÊ>Ài½ÌÊv>V}ÊÃÕÀ«ÀÃiÃÊvÀÊÌ
iÊ automatic statistics features, it’s important to analyze the effectiveness of statistics while diag nosing SQL Server issues. Unfortunately, if you come across an issue with the auto update statistics feature and
>ÛiÊÌÊÌÕÀÊÌÊvv]Ê>iÊÃÕÀiÊÌÊVÀi>ÌiÊ>Ê-+Ê-iÀÛiÀÊLÊÌÊÕ«`>ÌiÊÌ
iÊÃÌ>ÌÃÌVÃÊ>`ÊÃV
i`ÕiÊ ÌÊÌÊÀÕÊ>ÌÊÀi}Õ>ÀÊÌiÀÛ>ðÊÀÊ«iÀvÀ>ViÊÀi>ÃÃ]ÊiÃÕÀiÊÌ
>ÌÊÌ
iÊ-+ÊLÊÃÊÃV
i`Õi`ÊÌÊ ÀÕÊ`ÕÀ}Êvv«i>Ê
ÕÀð 9ÕÊV>ÊVÀi>ÌiÊ>Ê-+Ê-iÀÛiÀÊLÊÌÊÕ«`>ÌiÊÌ
iÊÃÌ>ÌÃÌVÃÊvÀÊ-+Ê-iÀÛiÀÊ>>}iiÌÊ-ÌÕ dio by following these simple steps: 1. Select ServerName ¢Ê-+Ê-iÀÛiÀÊ}iÌÊ¢ÊLÃ]ÊÀ}
ÌVV]Ê>`ÊÃiiVÌÊ iÜÊL° 2. "ÊÌ
iÊiiÀ>Ê«>}iÊvÊÌ
iÊ iÜÊLÊ`>}ÊLÝ]ÊiÌiÀÊÌ
iÊLÊ>iÊ>`ÊÌ
iÀÊ`iÌ>Ã]Ê>ÃÊ Ã
ÜÊÊ}ÕÀiÊÇÎ{°
Figure 7-34. Entering new job information
205
206
C H A P T E R 7 N S T A T I S T I C S A N A L Y S I S
3.
ÃiÊÌ
iÊ-Ìi«ÃÊ«>}i]ÊVVÊ iÜ]Ê>`ÊiÌiÀÊÌ
iÊ-+ÊV>`ÊvÀÊÌ
iÊÕÃiÀÊ`>Ì>L>Ãi]Ê >ÃÊÃ
ÜÊÊ}ÕÀiÊÇÎx°ÊÊÕÃi`Êol[ql`]paop]po here instead of QL@=PAOP=PEOPE?O because it’s a shortcut. I could have run QL@=PAOP=PEOPE?O against each table in the database a different way, especially if I was interested in taking advantage of all the control offered by QL@=PAOP=PEOPE?O: ATA?ol[iobkna]_dp]^ha#QL@=PAOP=PEOPE?O;=HH#
Figure 7-35. Entering the SQL command for the user database 4. Return to the New Job dialog box by clicking the OK button. 5. On the Schedules page of the New Job dialog box, click New Schedule, and enter an >««À«À>ÌiÊÃV
i`ÕiÊÌÊÀÕÊÌ
iÊ-+Ê-iÀÛiÀÊL]Ê>ÃÊÃ
ÜÊÊ}ÕÀiÊÇÎȰ
C H A P T E R 7 N S T A T I S T I C S A N A L Y S I S
Figure 7-36. Scheduling the SQL Server job 6. Return to the New Job dialog box by clicking the OK button. 7. Once you’ve entered all the information, click OK in the New Job dialog box to create Ì
iÊ-+Ê-iÀÛiÀÊL° 8. ÃÕÀiÊÌ
>ÌÊ-+Ê-iÀÛiÀÊ}iÌÊÃÊÀÕ}ÊÃÊÌ
>ÌÊÌ
iÊ-+Ê-iÀÛiÀÊLÊÃÊÀÕÊ>ÕÌ>ÌV>ÞÊ at the set schedule.
Automatic Update Statistics Asynchronously Letting statisticsÊÕ«`>ÌiÊ>ÌÊÌ
iÊLi}}ÊvÊ>ʵÕiÀÞ]ÊÜ
V
ÊÃÊÌ
iÊ`iv>ÕÌÊLi
>ÛÀ]ÊÜÊLiÊÕÃÌÊ fine in most cases. In the very rare circumstances where the statistics update or the execu tion plan recompiles resulting from that update are very expensive (more expensive than the VÃÌÊvÊÕÌv`>ÌiÊÃÌ>ÌÃÌVî]ÊÌ
iÊÞÕÊV>ÊÌÕÀÊÊÌ
iÊ>ÃÞV
ÀÕÃÊÕ«`>ÌiÊvÊÃÌ>ÌÃÌVðÊÕÃÌÊ Õ`iÀÃÌ>`ÊÌ
>ÌÊÌÊ>ÞÊi>ÊÌ
>ÌÊ«ÀVi`ÕÀiÃÊÌ
>ÌÊÜÕ`ÊLiivÌÊvÀÊÀiÊÕ«Ì`>ÌiÊÃÌ>ÌÃ tics will suffer until the next time they are run. Don’t forget—you do need automatic update of statistics enabled in order to enable the asynchronous updates.
207
208
CHAPTER 7 N ST A TIS TIC S A NA L YS IS
Amount of Sampling to Collect Statistics It is generally recommended that you use the default Ã>«}ÊÀ>Ìi°Ê/
ÃÊÀ>ÌiÊÃÊ`iV`i`ÊLÞÊ>Ê ivvViÌÊ>}ÀÌ
ÊL>Ãi`ÊÊÌ
iÊ`>Ì>ÊÃâiÊ>`ÊÕLiÀÊvÊ`vV>ÌðÊÌ
Õ}
ÊÌ
iÊ`iv>ÕÌÊ sampling rate turns out to be best in most cases, if for a particular query you find that the sta tistics are not very accurate, then you can manually update them with BQHHO?=J. vÊÌ
ÃÊÃÊÀiµÕÀi`ÊÀi«i>Ìi`Þ]ÊÌ
iÊÞÕÊV>Ê>``Ê>Ê-+Ê-iÀÛiÀÊLÊÌÊÌ>iÊV>ÀiÊvḚ̂ÊÀÊ «iÀvÀ>ViÊÀi>ÃÃ]ÊiÃÕÀiÊÌ
>ÌÊÌ
iÊ-+ÊLÊÃÊÃV
i`Õi`ÊÌÊÀÕÊ`ÕÀ}Êvv«i>Ê
ÕÀðÊ/Ê identify cases in which the default sampling rate doesn’t turn out to be the best, analyze the statistics effectiveness for costly queries while troubleshooting the database performance. Remember that BQHHO?=J is expensive, so you should run it only on those tables or indexes that you’ve determined will really benefit from it.
Summary ÃÊ`ÃVÕÃÃi`ÊÊÌ
ÃÊV
>«ÌiÀ]Ê-+Ê-iÀÛiÀ½ÃÊVÃÌL>Ãi`Ê«ÌâiÀÊÀiµÕÀiÃÊ>VVÕÀ>ÌiÊÃÌ>ÌÃÌVÃÊÊ VÕÃÊÕÃi`ÊÊvÌiÀÊ>`ÊÊVÀÌiÀ>ÊÌÊ`iÌiÀiÊ>ÊivvViÌÊ«ÀViÃÃ}ÊÃÌÀ>Ìi}Þ°Ê-Ì>ÌÃÌVÃÊ on an index key are always created during the creation of the index, and by default, SQL Server also keeps the statistics on indexed and nonindexed columns updated as the data changes. /
ÃÊi>LiÃÊÌÊÌÊ`iÌiÀiÊÌ
iÊLiÃÌÊ«ÀViÃÃ}ÊÃÌÀ>Ìi}iÃÊ>««V>LiÊÌÊÌ
iÊVÕÀÀiÌÊ`>Ì>Ê distribution.
ÛiÊÌ
Õ}
ÊÞÕÊV>Ê`Ã>LiÊLÌ
ÊÌ
iÊ>ÕÌÊVÀi>ÌiÊÃÌ>ÌÃÌVÃÊ>`Ê>ÕÌ update statistics fea tures, it is recommended that you leave these features on, since their benefit to the optimizer is almost always more than their overhead cost. For a costly query, analyze the statistics to iÃÕÀiÊÌ
>ÌÊÌ
iÊ>ÕÌ>ÌVÊÃÌ>ÌÃÌVÃÊ>Ìi>ViÊÛiÃÊÕ«ÊÌÊÌÃÊ«ÀÃi°Ê/
iÊLiÃÌÊiÜÃÊÃÊÌ
>ÌÊ ÞÕÊV>ÊÀiÃÌÊi>ÃÞÊÜÌ
Ê>ÊÌÌiÊÛ}>Vi]ÊÃViÊ>ÕÌ>ÌVÊÃÌ>ÌÃÌVÃÊ`ÊÌ
iÀÊLÊÜiÊÃÌÊvÊÌ
iÊ Ìi°ÊvÊ>Õ>ÊÃÌ>ÌÃÌVÃÊ>Ìi>ViÊ«ÀVi`ÕÀiÃÊ>ÀiÊÕÃi`]ÊÌ
iÊÞÕÊV>ÊÕÃiÊ-+Ê-iÀÛiÀÊLÃÊ to automate these procedures.
ÛiÊÜÌ
Ê«À«iÀÊ`iÝiÃÊ>`ÊÃÌ>ÌÃÌVÃÊÊ«>Vi]Ê>Ê
i>ÛÞÊvÀ>}iÌi`Ê`>Ì>L>ÃiÊÜÊVÕÀÊ >ÊVÀi>Ãi`Ê`>Ì>ÀiÌÀiÛ>ÊVÃ̰ÊÊÌ
iÊiÝÌÊV
>«ÌiÀ]ÊÞÕÊÜÊÃiiÊ
ÜÊvÀ>}iÌ>ÌÊÊ>Ê index can affect query performance, and you’ll learn how to analyze and resolve fragmentation.
CHAPTER
8
Fragmentation Analysis A
s explained in Chapter 4, index column values are stored in the leaf pages of an index’s B-tree structure. When you create an index (clustered or nonclustered) on a table, the cost of data retrieval is reduced by properly ordering the leaf pages of the index and the rows within the leaf pages. In an OLTP database, data changes continually, causing fragmentation of the indexes. As a result, the number of reads required to return the same number of rows increases over time. In this chapter, I cover the following topics: Ê
UÊ /
iÊV>ÕÃiÃÊvÊ`iÝÊvÀ>}iÌ>Ì]ÊVÕ`}Ê>Ê>>ÞÃÃÊvÊ«>}iÊëÌÃÊV>ÕÃi`ÊLÞÊ EJOANP and QL@=PA statements
Ê
UÊ /
iÊÛiÀ
i>`ÊVÃÌÃÊ>ÃÃV>Ìi`ÊÜÌ
ÊvÀ>}iÌ>Ì
Ê
UÊ ÜÊÌÊ>>ÞâiÊÌ
iÊ>ÕÌÊvÊvÀ>}iÌ>Ì
Ê
UÊ /iV
µÕiÃÊÕÃi`ÊÌÊÀiÃÛiÊvÀ>}iÌ>Ì
Ê
UÊ /
iÊÃ}vV>ViÊvÊÌ
iÊvÊv>VÌÀÊÊ
i«}ÊÌÊVÌÀÊvÀ>}iÌ>Ì
Ê
UÊ ÜÊÌÊ>ÕÌ>ÌiÊÌ
iÊvÀ>}iÌ>ÌÊ>>ÞÃÃÊ«ÀViÃÃ
Causes of Fragmentation Fragmentation occurs when data is modified in a table. When you insert or update data in a table (via EJOANP or QL@=PA), the table’s corresponding clustered indexes and the affected nonclustered indexes are modified. This can cause an index leaf page split if the modification to an index can’t be accommodated in the same page. A new leaf page will then be added that contains part of the original page and maintains the logical order of the rows in the index key. Although the new leaf page maintains the logical order of the data rows in the original page, this new page usually won’t be physically adjacent to the original page on the disk. Or, put a slightly different way, the logical key order of the index doesn’t match the physical order within the file.
209
210
CHAPTER 8 N F RA G MENTA TION A NA L YS IS
ÀÊiÝ>«i]ÊÃÕ««ÃiÊ>Ê`iÝÊ
>ÃÊiÊiÞÊÛ>ÕiÃÊÀÊ`iÝÊÀÜîÊ>`ÊÌ
iÊ>ÛiÀ>}iÊÃâiÊ of the index rows allows a maximum of four index rows in a leaf page. As explained in Chapter 4, the 8KB leaf pages are connected to the previous and next leaf pages to maintain the logical order of the index. Figure 8-1 illustrates the layout of the leaf pages for the index.
Figure 8-1. Leaf pages layout Since the index key values in the leaf pages are always sorted, a new index row with a key value of 25 has to occupy a place between the existing key values 20 and 30. Because the leaf page containing these existing index key values is full with the four index rows, the new index row will cause the corresponding leaf page to split. A new leaf page will be assigned to the index, and part of the first leaf page will be moved to this new leaf page so that the new index key can be inserted in the correct logical order. The links between the index pages will also be updated so that the pages are logically connected in the order of the index. As shown in Figure 8-2, the new leaf page, even though linked to the other pages in the correct logical order, can be physically out of order.
Figure 8-2. Out-of-order leaf pages The pages are grouped together in bigger units called extents, which can contain eight pages. SQL Server uses an extent as a physical unit of allocation on the disk. Ideally, the physical order of the extents containing the leaf pages of an index should be the same as the logical order of the index. This reduces the number of switches required between extents when ÀiÌÀiÛ}Ê>ÊÀ>}iÊvÊ`iÝÊÀÜðÊÜiÛiÀ]Êpage splits can physically disorder the pages within the extents, and they can also physically disorder the extents themselves. For example, suppose the first two leaf pages of the index are in extent 1, and say the third leaf page is in extent 2. If extent 2 contains free space, then the new leaf page allocated to the index because of the page split will be in extent 2, as shown in Figure 8-3.
C H A P T E R 8 N F R A G M E N T A T I O N A N A LY S I S
Figure 8-3. Out-of-order leaf pages distributed across extents With the leaf pages distributed between two extents, ideally you expect to read a range of `iÝÊÀÜÃÊÜÌ
Ê>Ê>ÝÕÊvÊiÊÃÜÌV
ÊLiÌÜiiÊÌ
iÊÌÜÊiÝÌiÌðÊÜiÛiÀ]ÊÌ
iÊ`ÃÀ}>â>ÌÊvÊ«>}iÃÊLiÌÜiiÊÌ
iÊiÝÌiÌÃÊV>ÊV>ÕÃiÊÀiÊÌ
>ÊiÊiÝÌiÌÊÃÜÌV
ÊÜ
iÊÀiÌÀiÛ}Ê>Ê range of index rows. For example, to retrieve a range of index rows between 25 and 90, you will need three extent switches between the two extents, as follows: Ê
UÊ ÀÃÌÊiÝÌiÌÊÃÜÌV
ÊÌÊÀiÌÀiÛiÊÌ
iÊiÞÊÛ>ÕiÊÎäÊ>vÌiÀÊÌ
iÊiÞÊÛ>ÕiÊÓx
Ê
UÊ -iV`ÊiÝÌiÌÊÃÜÌV
ÊÌÊÀiÌÀiÛiÊÌ
iÊiÞÊÛ>ÕiÊxäÊ>vÌiÀÊÌ
iÊiÞÊÛ>ÕiÊ{ä
Ê
UÊ /
À`ÊiÝÌiÌÊÃÜÌV
ÊÌÊÀiÌÀiÛiÊÌ
iÊiÞÊÛ>ÕiÊäÊ>vÌiÀÊÌ
iÊiÞÊÛ>ÕiÊnä
This type of fragmentation is called external fragmentation. External fragmentation is always undesirable. Fragmentation can also happen within an index page. If an EJOANP or QL@=PA operation creates a page split, then free space will be left behind in the original leaf page. Free space can also be caused by a @AHAPA operation. The net effect is to reduce the number of rows included in a leaf page. For example, in Figure 8-3, the page split caused by the EJOANP operation has created an empty space within the first leaf page. This is known as internal fragmentation. For a highly transactional database, it is desirable to deliberately leave some free space ÜÌ
ÊÞÕÀÊi>vÊ«>}iÃÊÃÊÌ
>ÌÊÞÕÊV>Ê>``ÊiÜÊÀÜÃ]ÊÀÊV
>}iÊÌ
iÊÃâiÊvÊiÝÃÌ}ÊÀÜÃ]ÊÜÌ
out causing a page split. In Figure 8-3, the free space within the first leaf page allows an index key value of 26 to be added to the leaf page without causing a page split.
NNote Note that this index fragmentation is different from disk fragmentation. The index fragmentation cannot be fixed simply by running the disk defragmentation tool, because the order of pages within a SQL Server file is understood only by SQL Server, not by the operating system.
SQL Server 2008 exposes the leaf pages and other data through a dynamic management view called ouo*`i[`^[ej`at[lduoe_]h[op]po. It storesÊLÌ
ÊÌ
iÊ`iÝÊÃâiÊ>`ÊÌ
iÊvÀ>}iÌ>tion. I’ll cover it in more detail in the next section. The DMV is much easier to work with than @>??ODKS?KJPEC. Let’s now take a look at the mechanics of fragmentation.
211
212
CHAPTER 8 N F RA G MENTA TION A NA L YS IS
Page Split by an UPDATE Statement To show what happens when a page split is caused by an QL@=PA statement, I’ll use a constructed table for an example. This small test table will have a clustered index, which orders the rows within one leaf (or data) page as follows (you’ll find this code in _na]pa[p-*omh in the code download): EB$OAHA?PK>FA?P[E@$#p-#%%EOJKPJQHH @NKLP=>HApCK ?NA=PAP=>HAp-$_-EJP(_.?D=N$555%(_/R=N?D=N$-,%% EJOANPEJPKp-R=HQAO$-,,(#_.#(##% EJOANPEJPKp-R=HQAO$.,,(#_.#(##% EJOANPEJPKp-R=HQAO$/,,(#_.#(##% EJOANPEJPKp-R=HQAO$0,,(#_.#(##% EJOANPEJPKp-R=HQAO$1,,(#_.#(##% EJOANPEJPKp-R=HQAO$2,,(#_.#(##% EJOANPEJPKp-R=HQAO$3,,(#_.#(##% EJOANPEJPKp-R=HQAO$4,,(#_.#(##% ?NA=PA?HQOPANA@EJ@ATe-KJp-$_-% TheÊ>ÛiÀ>}iÊÃâiÊvÊ>ÊÀÜÊÊÌ
iÊVÕÃÌiÀi`Ê`iÝÊi>vÊ«>}iÊiÝVÕ`}ÊÌiÀ>ÊÛiÀ
i>`®ÊÃÊ ÌÊÕÃÌÊÌ
iÊÃÕÊvÊÌ
iÊ>ÛiÀ>}iÊÃâiÊvÊÌ
iÊVÕÃÌiÀi`Ê`iÝÊVÕÃÆÊ̽ÃÊÌ
iÊÃÕÊvÊÌ
iÊ>ÛiÀ>}iÊ ÃâiÊvÊ>ÊÌ
iÊVÕÃÊÊÌ
iÊÌ>Li]ÊÃViÊÌ
iÊi>vÊ«>}iÊvÊÌ
iÊVÕÃÌiÀi`Ê`iÝÊ>`ÊÌ
iÊ`>Ì>Ê«>}iÊ vÊÌ
iÊÌ>LiÊ>ÀiÊÌ
iÊÃ>i°Ê/
iÀivÀi]ÊÌ
iÊ>ÛiÀ>}iÊÃâiÊvÊ>ÊÀÜÊÊÌ
iÊVÕÃÌiÀi`Ê`iÝÊÃÊ>ÃÊvÜÃ\
rÊÛiÀ>}iÊÃâiÊvÊQV£R®Ê³ÊÛiÀ>}iÊÃâiÊvÊQVÓR®Ê³ÊÛiÀ>}iÊÃâiÊvÊQVÎR®ÊLÞÌià rÊ-âiÊvÊ /®Ê³Ê-âiÊvÊ ,®®Ê³ÊÛiÀ>}iÊÃâiÊvÊ`>Ì>ÊÊQVÎR®ÊLÞÌià rÊ{ʳÊʳÊäÊrÊ£]ääÎÊLÞÌià /
iÊ>ÝÕÊÃâiÊvÊ>ÊÀÜÊÊ-+Ê-iÀÛiÀÊÃÊn]äÈäÊLÞÌiðÊ/
iÀivÀi]ÊvÊÌ
iÊÌiÀ>ÊÛiÀhead is not very high, all eight rows can be accommodated in a single 8KB page. To determine the number of leaf pages assigned to the e- clustered index, execute the OAHA?P statement against ouo*`i[`^[ej`at[lduoe_]h[op]po: OAHA?Po*]rc[bn]ciajp]pekj[ej[lan_ajp (o*bn]ciajp[_kqjp (o*l]ca[_kqjp (o*]rc[l]ca[ol]_a[qoa`[ej[lan_ajp (o*na_kn`[_kqjp (]rc[na_kn`[oeva[ej[^upao BNKIouo*`i[`^[ej`at[lduoe_]h[op]po$@>[E@$#=`rajpqnaSkngo.,,4#%( K>FA?P[E@$J#`^k*p-#%(JQHH(JQHH( #O]ilha`#%=Oo
C H A P T E R 8 N F R A G M E N T A T I O N A N A L Y S I S
You can see the results of this query in Figure 8-4.
Figure 8-4. Physical layout of index eFrom the l]ca[_kqjp column in this output, you can see that the number of pages assigned to the clustered index is 1. You can also see the average space used, 100, in the ]rc[ l]ca[ol]_a[qoa`[ej[lan_ajp column. From this you can infer that the page has no free space left to expand the content of _/, which is of type R=N?D=N$-,% and is currently empty.
NNote I’ll analyze more of the information provided by ouo*`i[`^[ej`at[lduoe_]h[op]po in the “Analyzing the Amount of Fragmentation” section later in this chapter.
Therefore, if you attempt to expand the content of column _/ for one of the rows as follows, it should cause a page split: QL@=PApOAP_/9#=```]p]# SDANA_-9.,, Selecting the data from ouo*`i[`^[ej`at[lduoe_]h[op]po results in the information in Figure 8-5.
Figure 8-5. e- index after a data update From the output in Figure 8-5, you can see that SQL Server has added a new page to the index. On a page split, SQL Server generally moves half the total number of rows in the original page to the new page. Therefore, the rows in the two pages are distributed as shown in Figure 8-6.
213
214
CHAPTER 8 N F RA G MENTA TION A NA L YS IS
Figure 8-6. Page split caused by an QL@=PA statement From the preceding tables, you can see that the page split caused by the QL@=PA statement results in an internal fragmentation of data in the leaf pages. If the new leaf page can’t be written physically next to the original leaf page, there will be external fragmentation as well. For a large table with a high amount of fragmentation, a larger number of leaf pages will be required to hold all the index rows. To confirm the resultant distribution of rows shown in the previous pages, you can add three trailing rows to the first leaf page and four trailing rows to the second page (p-[ejoanp* omh in the download):
C H A P T E R 8 N F R A G M E N T A T I O N A N A L Y S I S
EJOANPEJPKp-R=HQAO$0-,(#_.#(##% EJOANPEJPKp-R=HQAO$0.,(#_.#(##% EJOANPEJPKp-R=HQAO$0/,(#_.#(##% EJOANPEJPKp-R=HQAO$5,,(#_.#(##% EJOANPEJPKp-R=HQAO$-,,,(#_.#(##% EJOANPEJPKp-R=HQAO$--,,(#_.#(##% EJOANPEJPKp-R=HQAO$-.,,(#_.#(##% These seven new rows are accommodated in the existing two leaf pages without causing a page split. You can confirm this by querying ouo*`i[`^[ej`at[lduoe_]h[op]po again (Figure 8-7).
Figure 8-7. Pages after the addition of more rows
Page Split by an INSERT Statement To understand how a page split can be caused by an EJOANP statement, create the same test table (_na]pa[p-*omh) as you did previously, with the eight initial rows and the clustered index. Since the single index leaf page is completely filled, any attempt to add an intermediate row as follows should cause a page split in the leaf page: EJOANPEJPKp-R=HQAO$--,(#_.#(##% You can verify this by examining the output of ouo*`i[`^[ej`at[lduoe_]h[op]po (Figure 8-8).
Figure 8-8. Pages after insert As explained previously, half the rows from the original leaf page are moved to the new page. Once space is cleared in the original leaf page, the new row is added in the appropriate À`iÀÊÌÊÌ
iÊÀ}>Êi>vÊ«>}i°Ê iÊ>Ü>ÀiÊÌ
>ÌÊ>ÊÀÜÊÃÊ>ÃÃV>Ìi`ÊÜÌ
ÊÞÊiÊ«>}iÆÊÌÊV>ÌÊ span multiple pages. Figure 8-9 shows the resultant distribution of rows in the two pages.
215
216
CHAPTER 8 N F RA G MENTA TION A NA L YS IS
Figure 8-9. Page split caused by an EJOANP statement From the previous index pages, you can see that the page split caused by the EJOANP statement spreads the rows sparsely across the leaf pages, causing internal fragmentation. It often causes external fragmentation also, since the new leaf page may not be physically adjacent to the original page. For a large table with a high amount of fragmentation, the page splits caused by the EJOANP statement will require a larger number of leaf pages to accommodate all the index rows. To verify the row distribution shown in the index pages, you can run p-[ejoanp*omh again, adding three more rows to the first page and four more rows to the second page: EJOANPEJPKp-R=HQAO$-.,(#_.#(##% EJOANPEJPKp-R=HQAO$-/,(#_.#(##% EJOANPEJPKp-R=HQAO$-0,(#_.#(##% EJOANPEJPKp-R=HQAO$5,,(#_.#(##% EJOANPEJPKp-R=HQAO$-,,,(#_.#(##% EJOANPEJPKp-R=HQAO$--,,(#_.#(##% EJOANPEJPKp-R=HQAO$-.,,(#_.#(##%
C H A P T E R 8 N F R A G M E N T A T I O N A N A LY S I S
The result is the same as for the previous example: these seven new rows can be accom`>Ìi`ÊÊÌ
iÊÌÜÊiÝÃÌ}Êi>vÊ«>}iÃÊÜÌ
ÕÌÊV>ÕÃ}Ê>ÞÊ«>}iÊÃ«Ì°Ê ÌiÊÌ
>ÌÊÊÌ
iÊvÀÃÌÊ page, new rows are added in between the other rows in the page. This won’t cause a page split, since free space is available in the page. What about when you have to add rows to the trailing end of an index? In this case, even if a new page is required, it won’t split any existing page. For example, adding a new row with _- equal to 1,300 will require a new page, but it won’t cause a page split since the row isn’t added in an intermediate position. Therefore, if new rows are added in the order of the clustered index, then the index rows will be always added at the trailing end of the index, preventing the page splits otherwise caused by the EJOANP statements. Fragmentation caused by page splits hurts data-retrieval performance, as you will see next.
Fragmentation Overhead Both internal and external fragmentations adversely affect data-retrieval performance. External fragmentation causes a noncontiguous sequence of index pages on the disk, with new leaf pages far from the original leaf pages, and their physical ordering different from their logical ordering. Consequently, a range scan on an index will need more switches between the corresponding extents than ideally required, as explained earlier in the chapter. Also, a range scan on an index will be unable to benefit from read-ahead operations performed on the disk. If the pages are arranged contiguously, then a read-ahead operation can read pages in advance without much head movement. For better performance, it is preferable to use sequential I/O, since this can read a whole extent (eight 8KB pages together) in a single disk I/O operation. By contrast, a noncontiguous layout of pages requires nonsequential or random I/O operations to retrieve the index pages from the disk, and a random I/O operation can read only 8KB of data in a single disk operation (this may be acceptable, however, if you are retrieving only one row). In the case of internal fragmentation, rows are distributed sparsely across a large number of pages, increasing the number of disk I/O operations required to read the index pages into memory and increasing the number of logical reads required to retrieve multiple index rows from memory. As mentioned earlier, even though it increases the cost of data retrieval, a little internal fragmentation can be beneficial, because it allows you to perform EJOANP and QL@=PA queries without causing page splits. For queries that don’t have to traverse a series of pages to retrieve the data, fragmentation can have minimal impact. To understand how fragmentation affects the cost of a query, create a test table with a clustered index, and insert a highly fragmented data set in the table. Since an EJOANP operation in between an ordered data set can cause a page split, you can easily create the fragmented data set by adding rows in the following order (_na]pa[p-[bn]ciajpa`*omh in the code download):
217
218
CHAPTER 8 N F RA G MENTA TION A NA L YS IS
EB$OAHA?PK>FA?P[E@$#p-#% %EOJKPJQHH @NKLP=>HApCK ?NA=PAP=>HAp$_-EJP (_.EJP (_/EJP (_0?D=N$.,,,%% ?NA=PA?HQOPANA@EJ@ATe-KJp-$_-%7 SEPDJqio =O$OAHA?P-=Oj QJEKJ=HH OAHA?Pj'BNKIJqio SDANAj8.% EJOANPEJPKp-$_-(_.(_/(_0% OAHA?Pj (j (j (#]# BNKIJqio7 SEPDJqio =O$OAHA?P-=Oj QJEKJ=HH OAHA?Pj'BNKIJqio SDANAj8.% EJOANPEJPKp-$_-(_.(_/(_0% OAHA?P0-)j (j (j (#]# BNKIJqio To determine the number of logical reads required to retrieve a small result set and a large result set from this fragmented table, execute the two OAHA?P statements with OP=PEOPE?OEK and PEIA set to KJ (op]peope_o*omh in the download): OAHA?P&BNKIp-SDANA_->APSAAJ.-=J@.1))Na]`o2nkso OAHA?P&BNKIp-SDANA_->APSAAJ-=J@0,))Na]`o]hhnkso
C H A P T E R 8 N F R A G M E N T A T I O N A N A LY S I S
The number of logical reads performed by the individual queries is, respectively, as follows: P]^ha#p-#*O_]j_kqjp-(hkce_]hna]`o2 ?LQpeia9,io(ah]loa`peia9.4io* P]^ha#p-#*O_]j_kqjp-(hkce_]hna]`o-1 ?LQpeia9,io(ah]loa`peia9.2,io* To evaluate how the fragmented data set affects the number of logical reads, rearrange the index leaf pages physically by rebuilding the clustered index: =HPANEJ@ATe-KJ`^k*p-NA>QEH@ With the index leaf pages rearranged in the proper order, rerun op]peope_o*omh. The number of logical reads required by the preceding two OAHA?P statements reduces to 5 and 13, respectively: P]^ha#p-#*O_]j_kqjp-(hkce_]hna]`o1 ?LQpeia9,io(ah]loa`peia9/,io* P]^ha#p-#*O_]j_kqjp-(hkce_]hna]`o-/ ?LQpeia9,io(ah]loa`peia9.-5io* ÌVi]ÊÌ
Õ}
]ÊÌ
>ÌÊÌ
iÊiÝiVÕÌÊÌiÊÜ>ýÌÊÀ>`V>ÞÊÀi`ÕVi`]ÊLiV>ÕÃiÊ`À««}Ê>Ê single page from the read just isn’t likely to increase speed much. The cost overhead because of fragmentation usually increases in line with the number of rows retrieved, because this involves reading a greater number of out-of-order pages. For point queries (queries retrieving only one row), fragmentation doesn’t usually matter, since the row is retrieved from one leaf page only, but this isn’t always the case. Because of the internal structure of the index, fragmentation may increase the cost of even a point query. For instance, the following OAHA?P statement (oejchaop]p*omh in the download) performs two logical reads with the leaf pages rearranged properly, but it requires three logical reads on the fragmented data set. To see this in action, run _na]pa[p-[bn]ciajpa`*omhÊ>}>°Ê ÜÊÀÕÊÌ
ÃʵÕiÀÞÊÜÌ
ÊOP=PEOPE?OEK and PEIA enabled: OAHA?P&BNKIp-SDANA_-9-,))Na]`-nks The resulting message in the query window for this script is as follows: P]^ha#p-#*O_]j_kqjp-(hkce_]hna]`o/ ?LQpeia9,io(ah]loa`peia9,io* Once more, rebuild the index using this script: =HPANEJ@ATe-KJ`^k*p-NA>QEH@ ,Õ}ÊÌ
iÊi>ÀiÀÊOAHA?P statement again results in the following output: P]^ha#p-#*O_]j_kqjp-(hkce_]hna]`o. ?LQpeia9,io(ah]loa`peia9,io* ,iiLiÀ]ÊÌ
ÃÊÌiÃÌÊÃÊÊ>ÊÛiÀÞÊÃ>ÊÃV>i]ÊLÕÌÊÌ
iÊÕLiÀÊvÊÀi>`ÃÊÜ>ÃÊ`iVÀi>Ãi`ÊLÞÊ a third. Imagine what reducing a third of the number of reads against a table with millions of rows could accomplish.
219
220
C H A P T E R 8 N F R A G M E N T A T I O N A N A L Y S I S
NNote The lesson from this section is that, for better query performance, it is important to analyze the amount of fragmentation in an index and rearrange it if required.
Analyzing the Amount of Fragmentation You canÊ>>ÞâiÊÌ
iÊfragmentation ratio of an index by using the ouo*`i[`^[ej`at[lduoe_]h[ op]po dynamic management function. For a table with a clustered index, the fragmentation of the clustered index is congruous with the fragmentation of the data pages, since the leaf pages of the clustered index and data pages are the same. ouo*`i[`^[ej`at[lduoe_]h[op]po also indicates the amount of fragmentation in a heap table (or a table with no clustered index). Since a heap table doesn’t require any row ordering, the logical order of the pages isn’t relevant for the heap table. The output of ouo*`i[`^[ej`at[lduoe_]h[op]po shows information on the pages and extents of an index (or a table). A row is returned for each level of the B-tree in the index. A single row for each allocation unit in a heap is returned. As explained earlier, in SQL Server, eight VÌ}ÕÕÃÊn Ê«>}iÃÊ>ÀiÊ}ÀÕ«i`ÊÌ}iÌ
iÀÊÊ>ÊiÝÌiÌÊÌ
>ÌÊÃÊÈ{ ÊÊÃâi°ÊÀÊÛiÀÞÊÃ>Ê tables (much less than 64KB), the pages in an extent can belong to more than one index or table—these are called mixed extents. If there are too many small tables in the database, mixed extents help SQL Server conserve disk space. As a table (or an index) grows and requests more than eight pages, SQL Server creates an extent dedicated to the table (or index) and assigns the pages from this extent. Such an extent is called a uniform extent, and it serves up to eight page requests for the same table (or index). Uniform extents help SQL Server lay out the pages of a table (or an index) contiguously. They also reduce the number of page creation requests by an eighth, since a set of eight pages is created in the form of an extent. /Ê>>ÞâiÊÌ
iÊvÀ>}iÌ>ÌÊvÊ>Ê`iÝ]Êi̽ÃÊÀiVÀi>ÌiÊÌ
iÊÌ>LiÊÜÌ
ÊÌ
iÊvÀ>}iÌi`Ê data set used in the “Fragmentation Overhead” section (_na]pa[p-[bn]ciajpa`*omh). You can obtain the fragmentation detail of the clustered index (Figure 8-10) by executing the query against the ouo*`i[`^[ej`at[lduoe_]h[op]po dynamic view used earlier: OAHA?Po*]rc[bn]ciajp]pekj[ej[lan_ajp (o*bn]ciajp[_kqjp (o*l]ca[_kqjp (o*]rc[l]ca[ol]_a[qoa`[ej[lan_ajp (o*na_kn`[_kqjp (]rc[na_kn`[oeva[ej[^upao BNKIouo*`i[`^[ej`at[lduoe_]h[op]po$@>[E@$#=`rajpqnaSkngo.,,4#%( K>FA?P[E@$J#`^k*p-#%(JQHH(JQHH( #O]ilha`#%=Oo
Figure 8-10. Fragmented statistics
C H A P T E R 8 N F R A G M E N T A T I O N A N A LY S I S
The dynamic management function ouo*`i[`^[ej`at[lduoe_]h[op]po scans the pages of an index to return the data. You can control the level of the scan, which affects the speed and the accuracy of the scan. To quickly check the fragmentation of an index, use the Limited option. You can obtain an increased accuracy with only a moderate decrease in speed by using the Sample option, as in the previous example, which scans 1 percent of the pages. For the most accuracy, use the Detailed scan, which hits all the pages in an index. If the index has fewer than 10,000 pages and you select the Sample mode, then the Detailed mode is used instead. This means that despite the choice made in the earlier query, the Detailed scan mode was used. By defining the different parameters, you can get fragmentation information on different sets of data. By removing the K>FA?P[E@ function in the earlier query and supplying a JQHH value, the query would return information on all indexes within the database. You can also specify the index you want information on or even the partition with a partitioned index. The output from ouo*`i[`^[ej`at[lduoe_]h[op]po includes 21 different columns. I ÃiiVÌi`ÊÌ
iÊL>ÃVÊÃiÌÊvÊVÕÃÊÕÃi`ÊÌÊ`iÌiÀiÊÌ
iÊvÀ>}iÌ>ÌÊ>`ÊÃâiÊvÊ>Ê`iÝ°Ê This output represents the following: Ê
UÊ ]rc[bn]ciajp]pekj[ej[lan_ajp: This number represents the logical average fragmentation for indexes and heaps as a percentage. If the table is a heap and the mode is Sampled, then this value will be JQHH. If average fragmentation is less than 10 to 20 percent, fragmentation is unlikely to be an issue. If the index is between 20 and 40 percent, fragmentation might be an issue, but it can generally be resolved by defragmenting the index throughÊ>Ê`iÝÊÀiÀ}>â>ÌÊÀiÊvÀ>ÌÊÊ`iÝÊÀiÀ}>â>ÌÊ and index ÀiLÕ`ÊÃÊ>Û>>LiÊÊÌ
iʺÀ>}iÌ>ÌÊ,iÃÕÌûÊÃiVÌ®°Ê>À}iÃV>iÊ fragmentation, usually greater than 40 percent, may require an index rebuild. Your system may have different requirements than these general numbers
Ê
UÊ bn]ciajp[_kqjp: This number represents the number of fragments, or separated groups of pages, that make up the index. It’s a useful number to understand how the index is distributed, especially when compared to the l]ca[_kqjp value. bn]ciajp[_kqjp is JQHH when the sampling mode is Sampled.
Ê
UÊ l]ca[_kqjp: This number is a literal count of the number of index or data pages that >iÊÕ«ÊÌ
iÊÃÌ>ÌÃÌV°Ê/
ÃÊÕLiÀÊÃÊ>Êi>ÃÕÀiÊvÊÃâiÊLÕÌÊV>Ê>ÃÊ
i«Ê`V>ÌiÊ vÀ>}iÌ>̰ÊvÊÞÕÊÜÊÌ
iÊÃâiÊvÊÌ
iÊ`>Ì>ÊÀÊ`iÝ]ÊÞÕÊV>ÊV>VÕ>ÌiÊ
ÜÊ>ÞÊ rows can fit on a page. If you then correlate this to the number of rows in the table, you should get a number close to the l]ca[_kqjp value. If the l]ca[_kqjp value is VÃ`iÀ>LÞÊ
}
iÀ]ÊÞÕÊ>ÞÊLiÊ}Ê>ÌÊ>ÊvÀ>}iÌ>ÌÊÃÃÕi°Ê,iviÀÊÌÊÌ
iÊ]rc[ bn]ciajp]pekj[ej[lan_ajp value for a precise measure.
Ê
UÊ ]rc[l]ca[ol]_a[qoa`[ej[lan_ajp: To get an idea of the amount of space allocated within the pages of the index, use this number. This value is JQHH when the sampling mode is Limited.
Ê
UÊ na_kn`[_kqjp: Simply put, this is the number of records represented by the statistics. For indexes, this is the number of records within the current level of the B-tree as represented from the scanning mode. (Detailed scans will show all levels of the B-tree, not simply the leaf level.) For heaps, this number represents the records present, but this number may not correlate precisely to the number of rows in the table since a heap may have two records after an update and a page split.
Ê
UÊ ]rc[na_kn`[oeva[ej[^upao: This number simply represents a useful measure for the amount of data stored within the index or heap record.
221
222
C H A P T E R 8 N F R A G M E N T A T I O N A N A L Y S I S
,Õ}Êouo*`i[`^[ej`at[lduoe_]h[op]po with a Detailed scan will return multiple rows for a given index. That is, multiple rows are displayed if that index spans more than one level. Multiple levels exist in an index when that index spans more than a single page. To see what this looks like and to observe some of the other columns of data present in the dynamic management function, run the query this way: OAHA?Po*& BNKIouo*`i[`^[ej`at[lduoe_]h[op]po$@>[E@$#=`rajpqnaSkngo.,,4#%( K>FA?P[E@$J#`^k*p-#%(JQHH(JQHH( #@ap]eha`#%=Oo To make the data readable, I’ve broken down the resulting data table into three pieces in a Ã}iÊ}À>«
VÆÊÃiiÊ}ÕÀiÊn££°
Figure 8-11. Detailed scan of fragmented index As you can see, two rows were returned, representing the leaf level of the index (ej`at[ harah = 0) and representing the first level of the B-tree (ej`at[harah = 1), which is the second row. You can see the additional information offered by ouo*`i[`^[ej`at[lduoe_]h[op]po that can provide more detailed analysis of your indexes. For example, you can see the minimum >`Ê>ÝÕÊÀiVÀ`ÊÃâiÃ]Ê>ÃÊÜiÊ>ÃÊÌ
iÊ`iÝÊ`i«Ì
ÊÌ
iÊÕLiÀÊvÊiÛiÃÊÊÌ
iÊ ÌÀii®Ê and how many records are on each level. A lot of this information will be less useful for basic fragmentation analysis, which is why I chose to limit the number of columns in the samples as well as use the Sampled scan mode.
Analyzing the Fragmentation of a Small Table Don’t be overly concerned with the output of ouo*`i[`^[ej`at[lduoe_]h[op]po for small tables. For a small table or index with fewer than eight pages, SQL Server uses mixed extents for the pages. For example, if a table (Oi]hhP]^ha- or its clustered index) contains only two pages, then SQL Server allocates the two pages from a mixed extent instead of dedicating an extent to the table. The mixed extent may contain pages of other small tables/indexes also, as shown in Figure 8-12.
C H A P T E R 8 N F R A G M E N T A T I O N A N A LY S I S
Figure 8-12. Mixed extent The distribution of pages across multiple mixed extents may lead you to believe that there is a high amount of external fragmentation in the table or the index, when in fact this is by design in SQL Server and is therefore perfectly acceptable. To understand how the fragmentation information of a small table or index may look, create a small table with a clustered index (_na]pa[oi]hh[p-[bn]ciajpa`*omh in the download): EB$OAHA?PK>FA?P[E@$#p-#%%EOJKPJQHH @NKLP=>HApCK ?NA=PAP=>HAp-$_-EJP(_.EJP(_/EJP(_0?D=N$.,,,%% @A?H=NA<jEJP OAP<j9SDEHA<j89.4 >ACEJ EJOANPEJPKp-R=HQAO$<j(<j(<j(#]#% OAP<j9<j'AJ@ ?NA=PA?HQOPANA@EJ@ATe-KJp-$_-% In the preceding table, with each EJPÊÌ>}Ê{ÊLÞÌiÃ]ÊÌ
iÊ>ÛiÀ>}iÊÀÜÊÃâiÊÃÊÓ]ä£ÓÊrÊ{ʳÊ{Ê ³Ê{ʳÊÓ]äää®ÊLÞÌiðÊ/
iÀivÀi]Ê>Ê`iv>ÕÌÊn Ê«>}iÊV>ÊVÌ>ÊÕ«ÊÌÊvÕÀÊÀÜðÊvÌiÀÊ>ÊÓnÊÀÜÃÊ are added to the table, a clustered index is created to physically arrange the rows and reduce fragmentation to a minimum. With the minimum internal fragmentation, seven (= 28 / 4) pages are required for the clustered index (or the base table). Since the number of pages is not more than eight, SQL Server uses pages from mixed extents for the clustered index (or the base table). If the mixed extents used for the clustered index are not side by side, then the output of ouo*`i[`^[ej`at[lduoe_]h[op]po may express a high amount of external fragmentation. But as a SQL user, you can’t reduce the resultant external fragmentation. Figure 8-13 shows the output of ouo*`i[`^[ej`at[lduoe_]h[op]po.
223
224
C H A P T E R 8 N F R A G M E N T A T I O N A N A L Y S I S
Figure 8-13. Fragmentation of a small clustered index From the output of ouo*`i[`^[ej`at[lduoe_]h[op]po]ÊÞÕÊV>Ê>>ÞâiÊÌ
iÊvÀ>}iÌ>ÌÊ of the small clustered index (or the table) as follows: Ê
UÊ ]rc[bn]ciajp]pekj[ej[lan_ajp: Although this index may cross to multiple extents, the fragmentation shown here is not an indication of external fragmentation because this index is being stored on mixed extents.
Ê
UÊ ]rc[l]ca[ol]_a[qoa`[ej[lan_ajp: This shows that all or most of the data is stored well within the seven pages displayed in the l]ca[_kqjp field. This eliminates the possibility of logical fragmentation.
Ê
UÊ bn]ciajp[_kqjp: This shows that the data is fragmented and stored on more than one extent, but since it’s less than eight pages long, SQL Server doesn’t have much choice about where it stores the data.
In spite of the preceding misleading values, a small table (or index) with fewer than eight pages is simply unlikely to benefit from efforts to remove the fragmentation because it will be stored on mixed extents. Once you determine that fragmentation in an index (or a table) needs to be dealt with, you need to decide which defragmentation technique to use. The factors affecting this decision, and the different techniques, are explained in the following section.
Fragmentation Resolutions You can resolve fragmentation in an index by rearranging the index rows and pages so that their physical and logical orders match. To reduce external fragmentation, you can physically reorder the leaf pages of the index to follow the logical order of the index. You achieve this through the following techniques: Ê
UÊ À««}Ê>`ÊÀiVÀi>Ì}ÊÌ
iÊ`iÝ
Ê
UÊ ,iVÀi>Ì}ÊÌ
iÊ`iÝÊÜÌ
ÊÌ
iÊ@NKL[ATEOPEJC clause
Ê
UÊ ÝiVÕÌ}ÊÌ
iÊ=HPANEJ@ATNA>QEH@ statement on the index
Ê
UÊ ÝiVÕÌ}ÊÌ
iÊ=HPANEJ@ATNAKNC=JEVA statement on the index
C H A P T E R 8 N F R A G M E N T A T I O N A N A LY S I S
Dropping and Re-creating the Index One of the apparently easiest ways to remove fragmentation in an index is to drop the index and then re-create it. Dropping and re-creating the index reduces fragmentation the most, since it allows SQL Server to use completely new pages for the index and populate them appropriately with the existing data. This avoids both internal and external fragmentation. Unfortunately, this method has a large number of shortcomings: Ê
UÊ Blocking: This technique of defragmentation adds a high amount of overhead on the system, and it causes blocking. Dropping and re-creating the index blocks all other requests on the table (or on any other index on the table). It can also be blocked by other requests against the table.
Ê
UÊ Missing index: With the index dropped, and possibly being blocked and waiting to be re-created, queries against the table will not have the index available for use. This can lead to the poor performance that the index was intended to remedy.
Ê
UÊ Nonclustered indexes: If the index being dropped is a clustered index, then all the nonclustered indexes on the table have to be rebuilt after the cluster is dropped. They then have to be rebuilt again after the cluster is re-created. This leads to further blocking and other problems such as stored procedure recompiles (covered in detail in Chapter 10).
Ê
UÊ Unique constraints: Indexes that are used to define a primary key or a unique constraint cannot be removed using the @NKLEJ@AT statement. Also, both unique constraints and primary keys can be referred to by foreign key constraints. Prior to dropping the primary key, all foreign keys that reference the primary key would have to be removed first. Although this is possible, this is a risky and time-consuming method for defragmenting an index.
For all these reasons, dropping and re-creating the index is not a recommended technique for a production database, especially at anything outside off-peak times.
Re-creating the Index with the DROP_EXISTING Clause To avoid the overhead of rebuilding the nonclustered indexes twice while rebuilding a clustered index, use the @NKL[ATEOPEJC clause of the ?NA=PAEJ@AT statement. This re-creates the clustered index in one atomic step, avoiding re-creating the nonclustered indexes since the clustered index key values used by the row locators remain the same. To rebuild a clustered key in one atomic step using the @NKL[ATEOPEJC clause, execute the ?NA=PAEJ@AT statement as follows: ?NA=PAQJEMQA?HQOPANA@EJ@ATlg_hKJp-$_-% SEPD$@NKL[ATEOPEJC9KJ% You can use the @NKL[ATEOPEJC clause for both clustered and nonclustered indexes, and even to convert a VÕÃÌiÀi`Ê`iÝÊÌÊ>ÊVÕÃÌiÀi`Ê`iݰÊÜiÛiÀ]ÊÞÕÊV>½ÌÊÕÃiÊÌÊÌÊVÛiÀÌÊ a clustered index to a nonclustered index.
225
226
CHAPTER 8 N F RA G MENTA TION A NA L YS IS
The drawbacks of this defragmentation technique are as follows: Ê
UÊ Blocking: Similar to the @NKL and ?NA=PA methods, this technique also causes and faces blocking from other queries accessing the table (or any index on the table).
Ê
UÊ Index with constraints: Unlike the first method, the ?NA=PAEJ@AT statement with @NKL[ATEOPEJC can be used to re-create indexes with constraints. If the constraint is a primary key or the unique constraint is associated with a foreign key, then failing to include the QJEMQA keyword in the ?NA=PA statement will result in an error like this:
Ioc-5,3(Harah-2(Op]pa-(Heja?]jjkpna_na]paej`at#LG[J]ia#*Pdajasej`at`abejepekj`kaojkpi]p_dpda _kjopn]ejp^aejcajbkn_a`^updaateopejcej`at* Ê
UÊ Table with multiple fragmented indexes: As table data fragments, the indexes often become fragmented as well. If this defragmentation technique is used, then all the indexes on the table have to be identified and rebuilt individually.
You can avoid the last two limitations associated with this technique by using =HPANEJ@AT NA>QEH@, as explained next.
Executing the ALTER INDEX REBUILD Statement =HPANEJ@ATNA>QEH@ rebuilds an index in one atomic step, just like ?NA=PAEJ@AT with the @NKL[ATEOPEJC clause. Since =HPANEJ@ATNA>QEH@ also rebuilds the index physically, it allows SQL Server to assign fresh pages to reduce both internal and external fragmentation to a minimum. But unlike ?NA=PAEJ@AT with the @NKL[ATEOPEJC clause, it allows an index (supporting either the LNEI=NUGAU or QJEMQA constraint) to be rebuilt dynamically without dropping and re-creating the constraints. To understand the use of =HPANEJ@ATNA>QEH@ to defragment an index, consider the vÀ>}iÌi`ÊÌ>LiÊÕÃi`ÊÊÌ
iʺÀ>}iÌ>ÌÊ"ÛiÀ
i>`»Ê>`ʺ>Þâ}ÊÌ
iÊÕÌÊvÊÀ>}mentation” sections (_na]pa[p-[bn]ciajpa`*omh). This table is repeated here: EB$OAHA?PK>FA?P[E@$#p-#% %EOJKPJQHH @NKLP=>HApCK ?NA=PAP=>HAp$_-EJP (_.EJP (_/EJP (_0?D=N$.,,,%% ?NA=PA?HQOPANA@EJ@ATe-KJp-$_-%7
C H A P T E R 8 N F R A G M E N T A T I O N A N A L Y S I S
SEPDJqio =O$OAHA?P-=Oj QJEKJ=HH OAHA?Pj'BNKIJqio SDANAj8.% EJOANPEJPKp-$_-(_.(_/(_0% OAHA?Pj (j (j (#]# BNKIJqio7 SEPDJqio =O$OAHA?P-=Oj QJEKJ=HH OAHA?Pj'BNKIJqio SDANAj8.% EJOANPEJPKp-$_-(_.(_/(_0% OAHA?P0-)j (j (j (#]# BNKIJqio If you take a look at the current fragmentation, you can see that it is both internally and externally fragmented (Figure 8-14).
Figure 8-14. Internal and external fragmentation You can defragment the clustered index (or the table) by using the =HPANEJ@ATNA>QEH@ statement: =HPANEJ@ATe-KJ`^k*p-NA>QEH@ Figure 8-15 shows the resultant output of the standard OAHA?P statement against ouo* `i[`^[ej`at[lduoe_]h[op]po.
Figure 8-15. Fragmentation resolved by =HPANEJ@ATNA>QEH@
227
228
CHAPTER 8 N F RA G MENTA TION A NA L YS IS
Compare the preceding results of the query in Figure 8-15 with that from the earlier results in Figure 8-14. You can see that both internal and external fragmentation have been Ài`ÕVi`ÊivvViÌÞ°ÊiÀi½ÃÊ>Ê>>ÞÃÃÊvÊÌ
iÊÕÌ«ÕÌ\ Ê
UÊ Internal fragmentation: The table hasÊ{ÓÊÀÜÃÊÜÌ
Ê>Ê>ÛiÀ>}iÊÀÜÊÃâiÊÓ]䣰ÎnÊLÞÌiÃ®Ê that allows a maximum of four rows per page. If the rows are highly defragmented to reduce the internal fragmentation to a minimum, then there should be 11 data pages in the table (or leaf pages in the clustered index). You can observe the following in the preceding output: Ê UÊ
ÕLiÀÊvÊi>vÊÀÊ`>Ì>®Ê«>}iÃÊrÊl]ca[_kqjp = 11
Ê UÊ ÕÌÊvÊ`ivÀ>}iÌ>ÌÊÊ>Ê«>}iÊrÊ]rc[l]ca[ol]_a[qoa`[ej[lan_ajp = 95.33 percent Ê
UÊ External fragmentation: A minimum of two extents is required to hold the 11 pages. For a minimum of external fragmentation, there should not be any gap between the two extents, and all pages should be physically arranged in the logical order of the index. The preceding output illustrates the number of out-of-order pages = ]rc[ bn]ciajp]pekj[ej[lan_ajp = 27.27 percent. Although this may not be a perfect level of vÀ>}iÌ>Ì]ÊLi}Ê}Ài>ÌiÀÊÌ
>ÊÓäÊ«iÀViÌ]ÊÌ
ÃÊÃÊ>`iµÕ>ÌiÊVÃ`iÀ}ÊÌ
iÊÃâiÊvÊ the index.
As shown previously, the =HPANEJ@ATNA>QEH@ technique effectively reduces fragmentation. You can also use it to rebuild all the indexes of a table in one statement: =HPANEJ@AT=HHKJ`^k*pAlthough this is the most effective defragmentation technique, it does have some overhead and limitations: Ê
UÊ Blocking: Similar to the previous two index-rebuilding techniques, =HPANEJ@AT NA>QEH@ introduces blocking in the system. It blocks all other queries trying to access the table (or any index on the table). It can also be blocked by those queries.
Ê
UÊ Transaction rollback: Since =HPANEJ@ATNA>QEH@ is fully atomic in action, if it is stopped before completion, then all the defragmentation actions performed up to that time are lost. You can run =HPANEJ@ATNA>QEH@ using the KJHEJA keyword, which will reduce the locking mechanisms, but it will increase the time involved in rebuilding the index.
Executing the ALTER INDEX REORGANIZE Statement =HPANEJ@ATNAKNC=JEVA reduces the fragmentation of an index without rebuilding the index. It reduces external fragmentation by rearranging the existing leaf pages of the index in the logical order of the index key. It compacts the rows within the pages, reducing internal fragmentation, and discards the resultant empty pages. This technique doesn’t use any new pages for defragmentation. To avoid the blocking overhead associated with =HPANEJ@ATNA>QEH@, this technique uses a nonatomic online approach. As it proceeds through its steps, it requests a small number of locks for a short period. Once each step is done, it releases the locks and proceeds to the next step. While trying to access a page, if it finds that the page is being used, it skips that page and
C H A P T E R 8 N F R A G M E N T A T I O N A N A L Y S I S
229
never returns to the page again. This allows other queries to run on the table along with the =HPANEJ@ATNAKNC=JEVA operation. Also, if this operation is stopped intermediately, then all the defragmentation steps performed up to then are preserved. Since =HPANEJ@ATNAKNC=JEVA doesn’t use any new pages to reorder the index and it skips the locked pages, the amount of defragmentation provided by this approach is usually less than that of =HPANEJ@ATNA>QEH@. To observe the relative effectiveness of =HPANEJ@AT NAKNC=JEVA compared to =HPANEJ@ATNA>QEH@, rebuild the test table (_na]pa[p-[bn]ciajpa`* omh) used in the previous section on =HPANEJ@ATNA>QEH@. Ü]ÊÌÊÀi`ÕViÊÌ
iÊvÀ>}iÌ>ÌÊvÊÌ
iÊVÕÃÌiÀi`Ê`iÝ]ÊÕÃiÊ=HPANEJ@ATNAKNC=JEVA as follows: =HPANEJ@ATe-KJ`^k*p-NAKNC=JEVA Figure 8-16 shows the resultant output from ouo*`i[`^[ej`at[lduoe_]h[op]po.
Figure 8-16. Results of =HPANEJ@ATNAKNC=JEVA From the output, you can see that =HPANEJ@ATNAKNC=JEVA doesn’t reduce fragmentation as effectively as =HPANEJ@ATNA>QEH@, as shown in the previous section. For a highly fragmented index, the =HPANEJ@ATNAKNC=JEVA operation can take much longer than rebuilding the index. Also, if an index spans multiple files, =HPANEJ@ATNAKNC=JEVA doesn’t migrate pages LiÌÜiiÊÌ
iÊviðÊÜiÛiÀ]ÊÌ
iÊ>ÊLiivÌÊvÊÕÃ}Ê=HPANEJ@ATNAKNC=JEVA is that it allows other queries to access the table (or the indexes) simultaneously. />LiÊn£ÊÃÕ>ÀâiÃÊÌ
iÊV
>À>VÌiÀÃÌVÃÊvÊÌ
iÃiÊvÕÀÊdefragmentation techniques. Table 8-1. Characteristics of Four Defragmentation Techniques
Characteristics/Issues
Drop and Create Index
Create Index with @NKL[ ATEOPEJC
=HPAN EJ@AT NA>QEH@
=HPAN EJ@AT NAKNC=JEVA
,iLÕ`ÊVÕÃÌiÀi`Ê`iÝiÃÊÊ clustered index fragmentation
Twice
Missing indexes
Yes
Defragment index with constraints
}
ÞÊV«iÝ
Easy
Easy
Yes
Yes
Moderately complex
Concurrency with others
Low
Low
Medium, depending on concurrent user activity
}
Intermediate cancellation
Dangerous with no transaction
Progress lost
Progress lost
Progress preserved
Degree of defragmentation
}
}
}
Moderate to low
Apply new fill factor
Yes
Yes
Yes
Statistics are updated
Yes
Yes
Yes
Defragment multiple indexes together
230
CHAPTER 8 N F RA G MENTA TION A NA L YS IS
You can also reduce internal fragmentation by compressing more rows within a page, reducing free spaces within the pages. The maximum amount of compression that can be done within the leaf pages of an index is controlled by the fill factor, as you will see next. When dealing with very large databases and the indexes associated, it may become necessary to split the tables and the indexes up across servers and/or disks using partitioning. Indexes on partitions can also become fragmented as the data within the partition changes. When dealing with a portioned index, you will need to determine whether you want to either NAKNC=JEVA or NA>QEH@ one, some, or all partitions as part of the =HPANEJ@AT command. Partitioned indexes cannot be rebuilt online. Keep in mind that doing anything that affects all partitions is likely to be a very costly operation. One of the new features of SQL Server 2008 is data compression. If compression is specified on an index, even on a partitioned index, you must be sure to set the compression while performing the =HPANEJ@ATÊ«iÀ>ÌÊÌÊÜ
>ÌÊÌÊÜ>ÃÊLivÀiÆÊvÊÞÕÊ`½Ì]ÊÌÊÜÊLiÊÃÌ]Ê>`Ê you’ll have to rebuild the index again. This is especially important for nonclustered indexes, which will not inherit the compression setting from the table.
Significance of the Fill Factor The internal fragmentation of an index is reduced by compressing more rows per leaf page in an index. Compressing more rows within a leaf page reduces the total number of pages required for the index and in turn decreases disk I/O and the logical reads required to retrieve a range of index rows. On the other hand, if the index key values are highly transactional, then having fully compressed index pages will cause page splits. Therefore, for a transactional table, >Ê}`ÊL>>ViÊLiÌÜiiÊ>Ýâ}ÊÌ
iÊÕLiÀÊvÊÀÜÃÊÊ>Ê«>}iÊ>`Ê>Û`}Ê«>}iÊëÌÃÊÃÊ required. SQL Server allows you to control the amount of free space within the leaf pages of the index by using the fill factor. If you know that there will be enough EJOANP queries on the table or QL@=PA queries on the index key columns, then you can pre-add free space to the index leaf «>}iÊÕÃ}ÊÌ
iÊvÊv>VÌÀÊÌÊâiÊ«>}iÊëÌðÊvÊÌ
iÊÌ>LiÊÃÊÀi>`Þ]ÊÞÕÊV>ÊVÀi>ÌiÊÌ
iÊ index with a high fill factor to reduce the number of index pages. The default fill factor is 0, which means the leaf pages are packed to 100 percent, although some free space is left in the branch nodes of the B-tree structure. The fill factor for an index is applied only when the index is created. As keys are inserted and updated, the density of rows ÊÌ
iÊ`iÝÊiÛiÌÕ>ÞÊÃÌ>LâiÃÊÜÌ
Ê>Ê>ÀÀÜÊÀ>}i°ÊÃÊÞÕÊÃ>ÜÊÊÌ
iÊ«ÀiÛÕÃÊV
>«ÌiÀ½ÃÊ sections on page splits caused by QL@=PA and EJOANP, when a page split occurs, generally half the original page is moved to a new page, which happens irrespective of the fill factor used during the index creation. To understand the significance of the fill factor, let’s use a small test table (_na]pa[p-[ behh*omh in the download) with 24 rows: EB$OAHA?PK>FA?P[E@$#p-#% %EOJKPJQHH @NKLP=>HApCK
C H A P T E R 8 N F R A G M E N T A T I O N A N A L Y S I S
?NA=PAP=>HAp-$_-EJP(_.?D=N$555%%7 SEPDJqio =O$OAHA?P-=Oj QJEKJ=HH OAHA?Pj'BNKIJqio SDANAj8.0 % EJOANPEJPKp-$_-(_.% OAHA?Pj&-,, (#]# BNKIJqio Compress the maximum number of rows in the leaf (or data) page by creating a clustered index with the default fill factor: ?NA=PA?HQOPANA@EJ@ATe-KJp-$_-% -ViÊÌ
iÊ>ÛiÀ>}iÊÀÜÊÃâiÊÃÊ£]ä£äÊLÞÌiÃ]Ê>ÊVÕÃÌiÀi`Ê`iÝÊi>vÊ«>}iÊÀÊÌ>LiÊ`>Ì>Ê«>}i®Ê can contain a maximum of eight rows. Therefore, at least three leaf pages are required for the 24 rows. You can confirm this in the ouo*`i[`^[ej`at[lduoe_]h[op]po output shown in Figure 8-17.
Figure 8-17. Fill factor set to default value of 0 ÌiÊÌ
>ÌÊ]rc[l]ca[ol]_a[qoa`[ej[lan_ajp is 100 percent, since the default fill factor allows the maximum number of rows to be compressed in a page. Since a page cannot contain a part row to fill the page fully, ]rc[l]ca[ol]_a[qoa`[ej[lan_ajp will be often a little less than 100 percent, even with the default fill factor. To prevent page splits caused by EJOANP and QL@=PA operations, create some free space within the leaf (or data) pages by re-creating the clustered index with a fill factor as follows: =HPANEJ@ATe-KJ`^k*p-NA>QEH@ SEPD$ BEHHB=?PKN9 31% Because each page has a total space for eight rows, a fill factor of 75 percent will allow six rows per page. Thus, for 24 rows, the number of leaf pages should increase to four, as in the ouo*`i[`^[ej`at[lduoe_]h[op]po output shown in Figure 8-18.
Figure 8-18. Fill factor set to 75
231
232
C H A P T E R 8 N F R A G M E N T A T I O N A N A L Y S I S
ÌiÊÌ
>ÌÊ]rc[l]ca[ol]_a[qoa`[ej[lan_ajp is about 75 percent, as set by the fill factor. This allows two more rows to be inserted in each page without causing a page split. You can confirm this by adding two rows to the first set of six rows (_- = 100 – 600, contained in the first page): EJOANPEJPKp-R=HQAO$--,(#]#%)).1pdnks EJOANPEJPKp-R=HQAO$-.,(#]#%)).2pdnks Figure 8-19 shows the current fragmentation.
Figure 8-19. Fragmentation after new records From the output, you can see that the addition of the two rows has not added any pages to the index. Accordingly, ]rc[l]ca[ol]_a[qoa`[ej[lan_ajp increased from 74.99 percent to 81.25 percent. With the addition of two rows to the set of the first six rows, the first page should be completely full (eight rows). Any further addition of rows within the range of the first eight rows should cause a page split and thereby increase the number of index pages to five: EJOANPEJPKp-R=HQAO$-/,(#]#%)).3pdnks ÜÊouo*`i[`^[ej`at[lduoe_]h[op]po displays the difference in Figure 8-20.
Figure 8-20. Number of pages goes up ÌiÊÌ
>ÌÊiÛiÊÌ
Õ}
ÊÌ
iÊvÊv>VÌÀÊvÀÊÌ
iÊ`iÝÊÃÊÇxÊ«iÀViÌ]Ê=rc*L]ca@ajoepu$bqhh% has decreased to 67.49 percent, which can be computed as follows:
Avg. Page Density (full) = Average rows per page / Maximum rows per page = (27 / 5) / 8 = 67.5% From the preceding example, you can see that the fill factor is applied when the index is created. But later, as the data is modified, it has no significance. Irrespective of the fill factor, whenever a page splits, the rows of the original page are distributed between two pages, and ]rc[l]ca[ol]_a[qoa`[ej[lan_ajp settles accordingly. Therefore, if you use a nondefault fill factor, you should ensure that the fill factor is reapplied regularly to maintain its effect.
C H A P T E R 8 N F R A G M E N T A T I O N A N A LY S I S
You can reapply a fill factor by re-creating the index or by using =HPANEJ@ATNAKNC=JEVA or =HPANEJ@ATNA>QEH@, as was shown. =HPANEJ@ATNAKNC=JEVA takes the fill factor specified during the index creation into account. =HPANEJ@ATNA>QEH@ also takes the original fill factor into account, but it allows a new fill factor to be specified, if required. Without periodic maintenance of the fill factor, for both default and nondefault fill factor settings, ]rc[l]ca[ol]_a[qoa`[ej[lan_ajp for an index (or a table) eventually settles within a narrow range. Therefore, in most cases, without manual maintenance of the fill factor, the default fill factor is generally good enough. You should also consider one final aspect when deciding upon the fill factor. Even for a heavy OLTP application, the number of database reads typically outnumbers writes by a factor of 5 to 10. Specifying a fill factor other than the default can degrade read performance by an amount inversely proportional to the fill factor setting, since it spreads keys over a wider area. Before setting the fill factor at a database-wide level, use Performance Monitor to compare the OMHOanran6>qbbanI]j]canXL]cana]`o+oa_ counter to the OMHOanran6>qbbanI]j]canXL]ca snepao+oa_ counter, and use the fill factor option only if writes are a substantial fraction of reads (greater than 30 percent).
Automatic Maintenance In a database with a great deal of transactions, tables and indexes become fragmented over time. Thus, to improve performance, you should check the fragmentation of the tables and indexes regularly, and you should defragment the ones with a high amount of fragmentation. You can do this analysis for a database by following these steps: 1. `iÌvÞÊ>ÊÕÃiÀÊÌ>LiÃÊÊÌ
iÊVÕÀÀiÌÊ`>Ì>L>ÃiÊÌÊ>>ÞâiÊvÀ>}iÌ>̰ 2. Determine fragmentation of every user table and index. 3. Determine user tables and indexes that require defragmentation by taking into account the following considerations: Ê UÊ Ê
}
ÊiÛiÊvÊvÀ>}iÌ>ÌÊÜ
iÀiÊ]rc[bn]ciajp]pekj[ej[lan_ajp is greater than 20 percent Ê UÊ
ÌÊ>ÊÛiÀÞÊÃ>ÊÌ>LiÉ`iÝpÌ
>ÌÊÃ]Êl]ca[_kqjp is greater than 8
4. Defragment tables and indexes with high fragmentation. A sample SQL stored procedure (Ej`at@abn]c*omh in the download) is included here for easy reference. It performs the following actions: Ê
UÊ 7>ÃÊ>Ê`>Ì>L>ÃiÃÊÊÌ
iÊÃÞÃÌiÊ>`Ê`iÌviÃÊ`iÝiÃÊÊÕÃiÀÊÌ>LiÃÊÊi>V
Ê`>Ì>base that meets the fragmentation criteria and saves them in a temporary table
Ê
UÊ >Ãi`ÊÊÌ
iÊiÛiÊvÊvÀ>}iÌ>Ì]ÊÀiÀ}>âiÃÊ}
ÌÞÊvÀ>}iÌi`Ê`iÝiÃÊ>`Ê rebuilds those that are highly fragmented
233
234
CHAPTER 8 N F RA G MENTA TION A NA L YS IS
iÀi½ÃÊ
ÜÊÌÊ>>ÞâiÊ>`ÊÀiÃÛiÊ`>Ì>L>ÃiÊvÀ>}iÌ>ÌÊÃÌÀiÊÌ
ÃÊÜ
iÀiÊ>««À«À>ÌiÊ ÊÞÕÀÊÃÞÃÌiÆÊÜiÊ
>ÛiÊ>Ê`iÃ}>Ìi`Ê`>Ì>L>ÃiÊvÀÊiÌiÀ«ÀÃiiÛiÊÃVÀ«Ìî\ ?NA=PALNK?A@QNAEj`at@abn]c =O @A?H=NA<@>J]iaJR=N?D=N$.11% (
FA?P[E@9K>FA?P[E@$J#Bn]c#%% @NKLP=>HABn]c ?NA=PAP=>HABn]c $@>J]iaJR=N?D=N$.11% (P]^haJ]iaJR=N?D=N$.11% (O_dai]J]iaJR=N?D=N$.11% (Ej`atJ]iaJR=N?D=N$.11% (=rcBn]ciajp@A?EI=H% ATA?ol[iobkna]_d`^#EJOANPEJPKBn]c$ @>J]ia( P]^haJ]ia( O_dai]J]ia( Ej`atJ]ia( =rcBn]ciajp %OAHA?P##;##=O@>J]ia (p*J]ia=OP]^haJ]ia (o_*J]ia=OO_dai]J]ia (e*j]ia=OEj`atJ]ia (o*]rc[bn]ciajp]pekj[ej[lan_ajp BNKI;*ouo*`i[`^[ej`at[lduoe_]h[op]po$@>[E@$##;##%(JQHH(JQHH( JQHH(##O]ilha`##%=Oo FKEJ;*ouo*ej`ataoe KJo*K^fa_p[E`9e*K^fa_p[e` =J@o*Ej`at[e`9e*Ej`at[e` FKEJ;*ouo*p]^haop KJe*K^fa_p[e`9p*K^fa_p[E` FKEJ;*ouo*o_dai]oo_ KJp*o_dai][e`9o_*O?DAI=[E@
C H A P T E R 8 N F R A G M E N T A T I O N A N A LY S I S
SDANAo*]rc[bn]ciajp]pekj[ej[lan_ajp:., =J@p*PULA9##Q## =J@o*l]ca[_kqjp:4 KN@AN>UP]^haJ]ia(Ej`atJ]ia# @A?H=NA_Heop?QNOKN BKNOAHA?P&BNKIBn]c KLAJ_Heop BAP?DJATPBNKI_Heop EJPK<@>J]ia(
ACEJ EBAPSAAJ.,*,=J@0,*, >ACEJ OAP<@abn]c9J#=HPANEJ@AT#'<Ej`atJ]ia'#KJ#'<@>J]ia'#*#' J]ia'#*#'ACEJ OAP<@abn]c9J#=HPANEJ@AT#'<Ej`atJ]ia'#KJ#'<@>J]ia'#*#' QEH@# ATA?ol[ata_qpaomh<@abn]c LNEJP#Na^qeh`ej`at6#'<@>J]ia'#*#'J]ia(HABn]c CK
235
236
C H A P T E R 8 N F R A G M E N T A T I O N A N A L Y S I S
To automate the fragmentation analysis process, you can create a SQL Server job from SQL Server Enterprise Manager by following these simple steps: 1. Open Management Studio, right-click the SQL Server Agent icon, and select iÜÊ¢ Job. 2. "ÊÌ
iÊiiÀ>Ê«>}iÊvÊÌ
iÊ iÜÊLÊ`>}ÊLÝ]ÊiÌiÀÊÌ
iÊLÊ>iÊ>`ÊÌ
iÀÊ`iÌ>Ã]Ê as shown in Figure 8-21.
Figure 8-21. Entering the job name and details 3. "ÊÌ
iÊ-Ìi«ÃÊ«>}iÊvÊÌ
iÊ iÜÊLÊ`>}ÊLÝ]ÊVVÊ iÜ]Ê>`ÊiÌiÀÊÌ
iÊ-+ÊV>`Ê for the user database, as shown in Figure 8-22. 4. "ÊÌ
iÊ`Û>Vi`Ê«>}iÊvÊÌ
iÊ iÜÊLÊ-Ìi«Ê`>}ÊLÝ]ÊiÌiÀÊ>ÊÕÌ«ÕÌÊviÊ>iÊÌÊ report the fragmentation analysis outcome, as shown in Figure 8-23.
C H A P T E R 8 N F R A G M E N T A T I O N A N A L Y S I S
Figure 8-22. Entering the SQL command for the user database
Figure 8-23. Entering an output file name
237
238
C H A P T E R 8 N F R A G M E N T A T I O N A N A L Y S I S
5. ,iÌÕÀÊÌÊÌ
iÊ iÜÊLÊ`>}ÊLÝÊLÞÊVV}Ê"° 6. "ÊÌ
iÊ-V
i`ÕiÃÊ«>}iÊvÊÌ
iÊ iÜÊLÊ`>}ÊLÝ]ÊVVÊ iÜÊ-V
i`Õi]Ê>`ÊiÌiÀÊ>Ê appropriate schedule to run the SQL Server job, as shown in Figure 8-24.
Figure 8-24. Entering a job schedule Schedule this stored procedure to execute during nonpeak hours. To be certain about the usage pattern of your database, log the OMHOanran6OMHOp]peope_oX>]p_dNamqaopo+ oa_ performance counter for a complete day. It will show you the fluctuation in load on the database. (I explain this performance counter in detail in Chapter 2.) 7. ,iÌÕÀÊÌÊÌ
iÊ iÜÊLÊ`>}ÊLÝÊLÞÊVV}ÊÌ
iÊ"ÊLÕḬ̀ 8. "ViÊÞÕ½ÛiÊiÌiÀi`Ê>ÊÌ
iÊvÀ>Ì]ÊVVÊ"ÊÊÌ
iÊ iÜÊLÊ`>}ÊLÝÊÌÊVÀiate the SQL Server job. A SQL Server job is created that schedules the ol[Ej`at@abn]c stored procedure to run at a regular (weekly) time interval. 9. Ensure that SQL Server Agent is running so that the SQL Server job will run automatically according to the set schedule. /
iÊ-+ÊLÊÜÊ>ÕÌ>ÌV>ÞÊ>>ÞâiÊ>`Ê`ivÀ>}iÌÊÌ
iÊvÀ>}iÌ>ÌÊvÊi>V
Ê database every Sunday at 1 a.m. Figure 8-25 shows the corresponding output of the Bn]ciajp]pekjKqplqp*ptp file.
C H A P T E R 8 N F R A G M E N T A T I O N A N A L Y S I S
Figure 8-25. Bn]ciajp]pekjKqplqp*ptp file output /
iÊÕÌ«ÕÌÊÃ
ÜÃÊÌ
>ÌÊÌ
iÊLÊ>>Þâi`ÊÌ
iÊvÀ>}iÌ>ÌÊvÊÌ
iÊ`>Ì>L>ÃiÊ>`Ê`iÌvi`Ê>Ê ÃiÀiÃÊvÊ`iÝiÃÊvÀÊ`ivÀ>}iÌ>Ì]ÊëiVvV>ÞÊvÀÊÀiÀ}>â>̰Ê-ÕLÃiµÕiÌÞ]ÊÌÊ`ivÀ>}ments the index. The stored procedure defragmented only the database object that was highly fragmented. Thus, the next run of the SQL job generally won’t identify these same indexes for defragmentation.
Summary As you learned in this chapter, in a highly transactional database page splits caused by EJOANP and QL@=PA statements fragment the tables and indexes, increasing the cost of data retrieval. You can avoid these page splits by maintaining free spaces within the pages using the fill factor. Since the fill factor is applied only during index creation, you should reapply it at regular intervals to maintain its effectiveness. You can determine the amount of fragmentation in an index (or a table) using ouo*`i[`^[lduoe_]h[op]po. Upon determining a high amount of fragmentation, you can use either =HPANEJ@ATNA>QEH@ or =HPANEJ@ATNAKNC=JEVA, depending on the required amount of defragmentation and database concurrency. Defragmentation rearranges the data so that its physical order on the disk matches its }V>ÊÀ`iÀÊÊÌ
iÊÌ>LiÉ`iÝ]ÊÌ
ÕÃÊ«ÀÛ}ÊÌ
iÊ«iÀvÀ>ViÊvʵÕiÀiðÊÜiÛiÀ]ÊÕiÃÃÊ Ì
iÊ«ÌâiÀÊ`iV`iÃÊÕ«Ê>ÊivviVÌÛiÊiÝiVÕÌÊ«>ÊvÀÊÌ
iʵÕiÀÞ]ʵÕiÀÞÊ«iÀvÀ>ViÊiÛiÊ >vÌiÀÊ`ivÀ>}iÌ>ÌÊV>ÊÀi>Ê«À°Ê/
iÀivÀi]ÊÌÊÃÊ«ÀÌ>ÌÊÌÊ
>ÛiÊÌ
iÊ«ÌâiÀÊÕÃiÊ efficient techniques to generate cost-effective execution plans. In the next chapter, I will delve deeply into execution plan generation and the techniques Ì
iÊ«ÌâiÀÊÕÃiÃÊÌÊ`iV`iÊÕ«Ê>ÊivviVÌÛiÊiÝiVÕÌÊ«>°
239
CHAPTER
9
Execution Plan Cache Analysis T
he performance of any query depends on the effectiveness of the execution plan decided upon by the optimizer, as you learned in previous chapters. Because the overall time required to execute a query is the sum of the time required to generate the execution plan plus the time required to execute the query based on this execution plan, it is important that the cost of generating the execution plan itself is low. The cost incurred when generating the execution plan depends on the process of generating the execution plan, the process of caching the plan, and the reusability of the plan from the plan cache. In this chapter, you will learn how an execution plan is generated and how to analyze the execution plan cache for plan reusability. In this chapter, I cover the following topics: Ê
UÊ ÝiVÕÌÊ«>Ê}iiÀ>ÌÊ>`ÊV>V
}
Ê
UÊ /
iÊ-+Ê-iÀÛiÀÊV«iÌÃÊÕÃi`ÊÌÊ}iiÀ>ÌiÊ>ÊiÝiVÕÌÊ«>
Ê
UÊ -ÌÀ>Ìi}iÃÊÌÊ«ÌâiÊÌ
iÊVÃÌÊvÊiÝiVÕÌÊ«>Ê}iiÀ>Ì
Ê
UÊ >VÌÀÃÊ>vviVÌ}Ê«>À>iÊ«>Ê}iiÀ>Ì
Ê
UÊ ÜÊÌÊ>>ÞâiÊiÝiVÕÌÊ«>ÊV>V
}
Ê
UÊ +ÕiÀÞÊ«>Ê
>Ã
Ê>`ʵÕiÀÞÊ
>Ã
Ê>ÃÊiV
>ÃÃÊvÀÊ`iÌvÞ}ʵÕiÀiÃÊÌÊÌÕi
Ê
UÊ ÝiVÕÌÊ«>ÃÊ}iÊÜÀ}Ê>`Ê«>À>iÌiÀÊÃvv}
Ê
UÊ 7>ÞÃÊÌÊ«ÀÛiÊÌ
iÊÀiÕÃ>LÌÞÊvÊiÝiVÕÌÊ«>ÊV>V
}
Execution Plan Generation As you knowÊLÞÊÜ]Ê-+Ê-iÀÛiÀÊÕÃiÃÊ>ÊVÃÌL>Ãi`Ê«Ìâ>Ì technique to determine the processing strategy of a query. The optimizer considers both the metadata of the database objects and the current distribution statistics of the columns referred to in the query when deciding which index and join strategies should be used.
241
242
CHAPTER 9 N EXEC U TION P L A N C A C HE A NA L YS IS
The cost-based optimization allows a database developer to concentrate on implementing a business rule, rather than on the exact syntax of the query. At the same time, the process of determining the query processing strategy remains quite complex and can VÃÕiÊ>Êv>ÀÊ>ÕÌÊvÊÀiÃÕÀViðÊ-+Ê-iÀÛiÀÊÕÃiÃÊ>ÊÕLiÀ of techniques to optimize resource consumption: Ê
UÊ -ÞÌ>ÝL>Ãi`Ê«Ìâ>ÌÊvÊÌ
iʵÕiÀÞ
Ê
UÊ /ÀÛ>Ê«>Ê>ÌV
ÊÌÊ>Û`Ê`i«Ì
ʵÕiÀÞÊ«Ìâ>ÌÊvÀÊëiʵÕiÀiÃ
Ê
UÊ `iÝÊ>`ÊÊÃÌÀ>Ìi}iÃÊL>Ãi`ÊÊVÕÀÀiÌÊ`ÃÌÀLÕÌÊÃÌ>ÌÃÌVÃ
Ê
UÊ +ÕiÀÞÊ«Ìâ>ÌÊÊÕÌ«iÊ«
>ÃiÃÊÌÊVÌÀÊÌ
iÊVÃÌÊvÊ«Ìâ>Ì
Ê
UÊ ÝiVÕÌÊ«>ÊV>V
}ÊÌÊ>Û`ÊÌ
iÊÀi}iiÀ>ÌÊvʵÕiÀÞÊ«>à The following techniques are performed in order, as shown in the vÜV
>ÀÌÊÊ}ÕÀiÊ£\
Ê
UÊ *>ÀÃiÀ
Ê
UÊ }iLÀâiÀ
Ê
UÊ +ÕiÀÞÊ«ÌâiÀ
Ê
UÊ ÝiVÕÌÊ«>Ê}iiÀ>Ì]ÊV>V
}]Ê>`Ê
>Ã
Ê«>Ê}iiÀ>Ì
Ê
UÊ +ÕiÀÞÊiÝiVÕÌ
Figure 9-1. SQL Server techniques to optimize query execution i̽ÃÊÌ>iÊ>ÊÊ>ÌÊÌ
iÃiÊÃÌi«ÃÊÊÀiÊ`iÌ>°
C H A P T E R 9 N E X E C U T I O N P LA N C A C H E A N A LY S I S
Parser 7
iÊ>ʵÕiÀÞÊÃÊÃÕLÌÌi`]Ê-+Ê-iÀÛiÀÊ«>ÃÃià it to the parser within the relational engine. (This Ài>Ì>Êi}iÊÃÊiÊvÊÌ
iÊÌÜÊ>Ê«>ÀÌÃÊvÊ-+Ê-iÀÛiÀ]ÊÜÌ
ÊÌ
iÊÌ
iÀÊLi}Ê the storage engine, which is responsible for data access, modifications, and caching.) The relational engine takes care of parsing, name and type resolution (the algebrizer), and optimization. It also executes a query as per the query execution plan and requests data from the storage engine. The parser checks an incoming query, validating it for the correct syntax. The query is terminated if a syntax error is detected. If multiple queries are submitted together as a batch as follows (note the error in syntax): ?NA=PAP=>HAp-$_-EJP% EJOANPEJPKp-R=HQAO$-% ?AEHAGP&BNKIp-))Annkn6Eia]jp(OAHA?P&BNKIpCK then the parser checks the complete batch together for syntax and cancels the complete batch when it detects a syntax error. (Note that more than one syntax error may appear in a batch, but the parser goes no further than the first one.) On validating a query for correct syntax, the parser generates an internal data structure called a parse tree for the algebrizer. The parser and algebrizer taken together are called query compilation.
Algebrizer The parse tree generated by the parser is passed to the algebrizer for processing. The algebrizer resolves all the names of the different objects, meaning the tables, the columns, and so ]ÊÌ
>ÌÊ>ÀiÊLi}ÊÀiviÀiVi`ÊÊÌ
iÊ/-+°ÊÌÊ>ÃÊ`iÌviÃÊ>ÊÌ
iÊÛ>ÀÕÃÊ`>Ì>ÊÌÞ«iÃÊLi}Ê processed. It even checks for the location of aggregates (such as CNKQL>U and I=T). The output of all these verifications and resolutions is a binary set of data called a query processor tree. To see the algebrizer in action, if the following batch query (]hca^nevan[paop*omh in the download) is submitted: ?NA=PAP=>HAp-$_-EJP%7 EJOANPEJPKpR=HQAO$-%7 OAHA?P#>abknaAnnkn# (_BNKIp-=Op7 OAHA?P#annkn# (_BNKIjk[p-7))Annkn6P]^ha`kaoj#pateop OAHA?P#]bpanannkn#_BNKIp-=Op7 then the first three statements before the error statement are executed, and the errant statement and the one after it are cancelled.
243
244
CHAPTER 9 N EXEC U TION P L A N C A C HE A NA L YS IS
If a query contains an implicit data conversion, then the normalization process adds an appropriate step to the query tree. The process also performs some syntax-based optimiza̰ÊÀÊiÝ>«i]ÊvÊÌ
iÊvÜ}ʵÕiÀÞÊoujp]t[klpeieva*omh in the download) is submitted: OAHA?P& BNKIO]hao*O]haoKn`anDa]`an=Ookd SDANAokd*O]haoKn`anE@>APSAAJ2.1,,=J@2.11, Ì
iÊÌ
iÊÃÞÌ>ÝL>Ãi`Ê«Ìâ>ÌÊÌÀ>ÃvÀÃÊÌ
iÊÃÞÌ>ÝÊvÊÌ
iʵÕiÀÞ]Ê>ÃÊÃ
ÜÊÊ}ÕÀiÊÓ]Ê where >APSAAJ becomes :9 and 89.
Figure 9-2. Syntax-based optimization ÀÊÃÌÊ >Ì>Ê ivÌÊ>}Õ>}iÊ ®ÊÃÌ>ÌiiÌÃÊÃÕV
Ê>ÃÊ?NA=PAP=>HA, ?NA=PALNK?, and so on), after passing through the algebrizer, the query is compiled directly for execution, ÃViÊÌ
iÊ«ÌâiÀÊii`ÊÌÊV
ÃiÊ>}ÊÕÌ«iÊ«ÀViÃÃ}ÊÃÌÀ>Ìi}iðÊÀÊiÊ ÊÃÌ>Ìiment in particular, ?NA=PAEJ@AT, the optimizer can determine an efficient processing strategy based on other existing indexes on the table, as explained in Chapter 4. ÀÊÌ
ÃÊÀi>Ã]ÊÞÕÊÜÊiÛiÀÊÃiiÊ>ÞÊÀiviÀiViÊÌÊ?NA=PAP=>HA in an execution plan, although you will see reference to ?NA=PAEJ@AT. If theÊÀ>âi`ʵÕiÀÞÊÃÊ>Ê >Ì>Ê>«Õ>ÌÊ>}Õ>}iÊ ®ÊÃÌ>ÌiiÌÊÃÕV
Ê>ÃÊOAHA?P, EJOANP, QL@=PA, or @AHAPA), then the query processor tree is passed to the optimizer to decide the processing strategy for the query.
Optimization Based on the complexity of a query, including the number of tables referred to and the indexes available, there may be several ways to execute the query contained in the query processor ÌÀii°Ê Ý
>ÕÃÌÛiÞÊV«>À}ÊÌ
iÊVÃÌÊvÊ>ÊÌ
iÊÜ>ÞÃÊvÊiÝiVÕÌ}Ê>ʵÕiÀÞÊV>ÊÌ>iÊ>ÊVÃ`iÀable amount of time, which may sometimes override the benefit of finding the most optimized µÕiÀÞ°Ê}ÕÀiÊÎÊÃ
ÜÃÊÌ
>Ì]ÊÌÊavoid a high optimization overhead compared to the actual execution cost of the query, the optimizer adopts different techniques, namely: Ê
UÊ /ÀÛ>Ê«>Ê>ÌV
Ê
UÊ ÕÌ«iÊ«Ìâ>ÌÊ«
>ÃiÃ
Ê
UÊ *>À>iÊ«>Ê«Ìâ>Ì
C H A P T E R 9 N E X E C U T I O N P LA N C A C H E A N A LY S I S
Figure 9-3. Query optimization steps
245
246
CHAPTER 9 N EXEC U TION P L A N C A C HE A NA L YS IS
Trivial Plan Match -iÌiÃÊÌ
iÀiÊ}
ÌÊLiÊÞÊiÊÜ>ÞÊÌÊiÝiVÕÌiÊ>ʵÕiÀÞ°ÊÀÊiÝ>«i]Ê>Ê
i>«ÊÌ>LiÊÜÌ
ÊÊ indexes can be accessed in only one way: via a table scan. To avoid the runtime overhead of «Ìâ}ÊÃÕV
ʵÕiÀiÃ]Ê-+Ê-iÀÛiÀÊ>Ì>ÃÊ>ÊÃÌÊvÊÌÀÛ>Ê«>ÃÊÌÊV
ÃiÊvÀ°ÊvÊÌ
iÊ«Ìmizer finds a match, then a similar plan is generated for the query without any optimization.
Multiple Optimization Phases ÀÊ>ÊV«iÝʵÕiÀÞ]ÊÌ
i number of alternative processing strategies to be analyzed can be very high, and it may take a long time to evaluate each option. Therefore, instead of analyzing all the possible processing strategies together, the optimizer breaks them into multiple configurations, each consisting of different index and join techniques. The index variations consider different indexing aspects, such as single-column index, V«ÃÌiÊ`iÝ]Ê`iÝÊVÕÊÀ`iÀ]ÊVÕÊ`iÃÌÞ]Ê>`ÊÃÊvÀÌ
°Ê->ÀÞ]ÊÌ
iÊÊÛ>À>ÌÃÊVÃ`iÀÊÌ
iÊ`vviÀiÌÊÊÌiV
µÕiÃÊ>Û>>LiÊÊ-+Ê-iÀÛiÀ\ÊiÃÌi`Ê«Ê]ÊiÀ}iÊ ]Ê>`Ê
>Ã
ʰÊ
>«ÌiÀÊÎÊVÛiÀÃÊÌ
iÃiÊÊÌiV
µÕiÃÊÊ`iÌ>°® The optimizer considers the statistics of the columns referred to in the SDANA clause to evaluate the effectiveness of the index and the join strategies. Based on the current statistics, it evaluates the cost of the configurations in multiple optimization phases. The cost includes many v>VÌÀÃ]ÊVÕ`}ÊLÕÌÊÌÊÌi`ÊÌ®ÊÕÃ>}iÊvÊ *1]ÊiÀÞ]Ê>`Ê`ÃÊÉ"ÊÀiµÕÀi`ÊÌÊiÝiVÕÌiÊ the query. After each optimization phase, the optimizer evaluates the cost of the processing strategy. If the cost is found to be cheap enough, then the optimizer stops further iteration through the optimization phases and quits the optimization process. Otherwise, it keeps iterating through the optimization phases to determine a cost-effective processing strategy. -iÌiÃÊ>ʵÕiÀÞÊV>ÊLiÊÃÊV«iÝÊÌ
>ÌÊÌ
iÊ«ÌâiÀÊii`ÃÊÌÊiÝÌiÃÛiÞÊÌiÀ>ÌiÊ Ì
ÀÕ}
ÊÌ
iÊ«Ìâ>ÌÊ«
>ÃiðÊ7
iÊ«Ìâ}ÊÌ
iʵÕiÀÞ]ÊvÊÌÊv`ÃÊÌ
>ÌÊÌ
iÊVÃÌÊvÊÌ
iÊ processing strategy is more than the cost threshold for parallelism, then it evaluates the cost vÊ«ÀViÃÃ}ÊÌ
iʵÕiÀÞÊÕÃ}ÊÕÌ«iÊ *1ðÊ"Ì
iÀÜÃi]ÊÌ
iÊ«ÌâiÀÊ«ÀVii`ÃÊÜÌ
ÊÌ
iÊ serial plan. You can find out some detail of what occurred during the multiple optimization phases via two sources. Take, for example, this query (jkj[pnere]h[mqanu*omh): OAHA?Pokd*O]haoKn`anJqi^an (ok`*Kn`anMpu (ok`*HejaPkp]h (ok`*QjepLne_a (ok`*QjepLne_a@eo_kqjp (l*WJ]iaY=OLnk`q_pJ]ia (l*Lnk`q_pJqi^an (lo*WJ]iaY=OLnk`q_pOq^?]packnuJ]ia (l_*WJ]iaY=OLnk`q_p?]packnuJ]ia BNKIO]hao*O]haoKn`anDa]`an=Ookd FKEJO]hao*O]haoKn`an@ap]eh=Ook` KJokd*O]haoKn`anE@9ok`*O]haoKn`anE@ FKEJLnk`q_pekj*Lnk`q_p=Ol KJok`*Lnk`q_pE@9l*Lnk`q_pE@ FKEJLnk`q_pekj*Lnk`q_pIk`ah=Oli KJl*Lnk`q_pIk`ahE@9li*Lnk`q_pIk`ahE@
C H A P T E R 9 N E X E C U T I O N P LA N C A C H E A N A LY S I S
FKEJLnk`q_pekj*Lnk`q_pOq^_]packnu=Olo KJl*Lnk`q_pOq^_]packnuE@9lo*Lnk`q_pOq^_]packnuE@ FKEJLnk`q_pekj*Lnk`q_p?]packnu=Ol_ KJlo*Lnk`q_p?]packnuE@9l_*Lnk`q_p?]packnuE@ SDANAokd*?qopkianE@9.5214 7
iÊÌ
ÃʵÕiÀÞÊÃÊÀÕ]ÊÌ
iÊiÝiVÕÌÊ«>ÊÊ}ÕÀiÊ{]Ê>ÊÌÀÛ>Ê«>ÊvÀÊÃÕÀi]ÊÃÊ returned.
Figure 9-4. Nontrivial execution plan I realize that this execution plan is a little hard to read. The important point to take away is that it involves quite a few tables, each with indexes and statistics that all had to be taken into account to arrive at this execution plan. The first place you can go to look for information >LÕÌÊÌ
iÊ«ÌâiÀ½ÃÊÜÀÊÊÌ
ÃÊiÝiVÕÌÊ«>ÊÃÊÌ
iÊ«À«iÀÌÞÊÃ
iiÌÊvÊÌ
iÊ/-+ÊOAHA?P «iÀ>ÌÀÊ>ÌÊÌ
iÊv>ÀÊivÌÊvÊÌ
iÊiÝiVÕÌÊ«>°Ê}ÕÀiÊxÊÃ
ÜÃÊÌ
iÊ«À«iÀÌÞÊÃ
iḭ
Figure 9-5. OAHA?P operator property sheet
247
248
CHAPTER 9 N EXEC U TION P L A N C A C HE A NA L YS IS
-Ì>ÀÌ}Ê>ÌÊÌ
iÊÌ«]ÊÞÕÊV>ÊÃiiÊvÀ>ÌÊ`ÀiVÌÞÊÀi>Ìi`ÊÌÊÌ
iÊVÀi>ÌÊ>`Ê«Ìâ>tion of this execution plan: Ê
UÊ /
iÊÃâiÊvÊÌ
iÊV>V
i`Ê«>]ÊÜ
V
ÊÃÊ{äÊLÞÌiÃ
Ê
UÊ /
iÊÕLiÀÊvÊ *1ÊVÞViÃÊÕÃi`ÊÌÊV«iÊÌ
iÊ«>]ÊÜ
V
ÊÃÊÓ£ÊÃ
Ê
UÊ /
iÊ>ÕÌÊvÊiÀÞÊÕÃi`]ÊÜ
V
ÊÃÊÇ{{
Ê
UÊ /
iÊV«iÊÌi]ÊÜ
V
ÊÃÊÓ£ÊÃ
The "«Ìâ>ÌÊiÛiÊ«À«iÀÌÞÊOp]paiajpKlpiHarahÊÊÌ
iÊ8Ê«>®ÊÃ
ÜÃÊÜ
>ÌÊÌÞ«iÊ vÊ«ÀViÃÃ}ÊVVÕÀÀi`ÊÜÌ
ÊÌ
iÊ«ÌâiÀ°ÊÊÌ
ÃÊV>Ãi]Ê1Êi>ÃÊÌ
>ÌÊÌ
iÊ«ÌâiÀÊ``Ê a full optimization. This is further displayed in the property ,i>ÃÊvÀÊ >ÀÞÊ/iÀ>ÌÊvÊ -Ì>ÌiiÌ]ÊÜ
V
ÊÃÊ`Ê Õ}
Ê*>ÊÕ`°Ê-]ÊÌ
iÊ«ÌâiÀÊÌÊÓ£ÊÃÊÌÊÌÀ>VÊ`ÜÊ>Ê plan that it deemed good enough in this situation. You can also see the +ÕiÀÞ*>>Ã
ÊÛ>Õi]Ê also known as the fingerprint, for the execution plan (you can find more details on this in the ÃiVÌʺ+ÕiÀÞÊ*>Ê>Ã
Ê>`Ê+ÕiÀÞÊ>Ã
»®° The second source for optimizer information is the dynamic management view ouo* `i[ata_[mqanu[klpeievan[ejbk. ThisÊ 6ÊÃÊ>Ê>}}Ài}>ÌÊvÊÌ
iÊ«Ìâ>ÌÊiÛiÌÃÊÛiÀÊ Ìi°ÊÌÊܽÌÊÃ
ÜÊÌ
iÊ`Û`Õ>Ê«Ìâ>ÌÃÊvÀÊ>Ê}ÛiʵÕiÀÞ]ÊLÕÌÊÌÊÜÊÌÀ>VÊÌ
iÊ«Ìâ>ÌÃÊ«iÀvÀi`°Ê/
ÃÊýÌÊ>ÃÊi`>ÌiÞÊ
>`ÞÊvÀÊÌÕ}Ê>Ê`Û`Õ>ʵÕiÀÞ]ÊLÕÌÊvÊÞÕÊ are working on reducing the costs of a workload over time, being able to track this information can help you determine whether your query tuning is making a positive difference, at i>ÃÌÊÊÌiÀÃÊvÊ«Ìâ>ÌÊÌi°Ê-iÊvÊÌ
iÊ`>Ì>ÊÀiÌÕÀi`ÊÃÊvÀÊÌiÀ>Ê-+Ê-iÀÛiÀÊÕÃiÊ Þ°Ê}ÕÀiÊÈÊÃ
ÜÃÊ>ÊÌÀÕV>Ìi`ÊiÝ>«iÊvÊÌ
iÊÕÃivÕÊ`>Ì>ÊÀiÌÕÀi`ÊÊÌ
iÊÀiÃÕÌÃÊvÀÊÌ
iÊ following query: OAHA?P?kqjpan (K__qnnaj_a (R]hqa BNKIouo*`i[ata_[mqanu[klpeievan[ejbk
Figure 9-6. Output from ouo*`i[ata_[mqanu[klpeievan[ejbk Running this query before and after a set of optimizations can show you the changes that have occurred in the number and type of optimizations completed.
Parallel Plan Optimization The optimizer considers various factors while evaluating the cost of processing a query using a «>À>iÊ«>°Ê-iÊvÊÌ
iÃiÊv>VÌÀÃÊ>ÀiÊ>ÃÊvÜÃ\
C H A P T E R 9 N E X E C U T I O N P LA N C A C H E A N A LY S I S
Ê
UÊ
ÕLiÀÊvÊ *1ÃÊ>Û>>LiÊÌÊ-+Ê-iÀÛiÀ
Ê
UÊ -+Ê-iÀÛiÀÊi`Ì
Ê
UÊ Û>>LiÊiÀÞ
Ê
UÊ /Þ«iÊvʵÕiÀÞÊLi}ÊiÝiVÕÌi`
Ê
UÊ
ÕLiÀÊvÊÀÜÃÊÌÊLiÊ«ÀViÃÃi`ÊÊ>Ê}ÛiÊÃÌÀi>
Ê
UÊ
ÕLiÀÊvÊ>VÌÛiÊVVÕÀÀiÌÊViVÌÃ
If only oneÊ *1ÊÃÊ>Û>>LiÊÌÊ-+Ê-iÀÛiÀ]ÊÌ
iÊÌ
iÊ«ÌâiÀÊܽÌÊVÃ`iÀÊ>Ê«>À>iÊ «>°Ê/
iÊÕLiÀÊvÊ *1ÃÊ>Û>>LiÊÌÊ-+Ê-iÀÛiÀÊV>ÊLiÊÀiÃÌÀVÌi`ÊÕÃ}ÊÌ
iÊaffinity mask ÃiÌÌ}ÊvÊÌ
iÊ-+Ê-iÀÛiÀÊVv}ÕÀ>̰Ê/
iÊ>vvÌÞÊ>ÃÊÛ>ÕiÊÃÊ>ÊLÌ>«ÊÊÌ
>ÌÊ>ÊLÌÊÀi«ÀiÃiÌÃÊ>Ê *1]ÊÜÌ
ÊÌ
iÊÀ}
ÌÃÌÊLÌÊ«ÃÌÊÀi«ÀiÃiÌ}Ê *1ä°ÊÀÊiÝ>«i]ÊÌÊ>ÜÊ-+Ê -iÀÛiÀÊÌÊÕÃiÊÞÊ *1äÊÌÊ *1ÎÊÊ>Êi}
ÌÜ>ÞÊLÝ]ÊiÝiVÕÌiÊÌ
iÃiÊÃÌ>ÌiiÌÃÊ]bbejepu[ i]og*omh in the download): QOAi]opan ATA?ol[_kjbecqna#odks]`r]j_a`klpekj#(#-# NA?KJBECQNA ATA?ol[_kjbecqna#]bbejepui]og#(-1))>epi]l6,,,,---NA?KJBECQNA This configuration takes effect immediately. ]bbejepui]og is a special setting, and I recommend that you use it onlyÊÊÃÌ>ViÃÊÜ
iÀiÊÌ>}ÊVÌÀÊ>Ü>ÞÊvÀÊ-+Ê-iÀÛiÀÊ >iÃÊÃiÃi]ÊÃÕV
Ê>ÃÊÜ
iÊÞÕÊ
>ÛiÊÕÌ«iÊÃÌ>ViÃÊvÊ-+Ê-iÀÛiÀÊÀÕ}ÊÊÌ
iÊÃ>iÊ >V
iÊ>`ÊÞÕÊÜ>ÌÊÌÊÃ>ÌiÊÌ
iÊvÀÊi>V
ÊÌ
iÀ°Ê/ÊÃiÌÊ>vvÌÞÊ«>ÃÌÊÎÓÊ«ÀViÃÃÀÃ]Ê you have to use the ]bbejepu20i]ogÊ«ÌÊÌ
>ÌÊÃÊ>Û>>LiÊÞÊÊÌ
iÊÈ{LÌÊÛiÀÃÊvÊ-+Ê -iÀÛiÀ°Ê9ÕÊV>Ê>ÃÊL`ÊÉ"ÊÌÊ>ÊëiVvVÊÃiÌÊvÊ«ÀViÃÃÀÃÊÕÃ}ÊÌ
iÊ>vvÌÞÊ>ÃÊÉ"Ê«ÌÊ in the same way.
ÛiÊvÊÕÌ«iÊ *1ÃÊ>ÀiÊ>Û>>LiÊÌÊ-+Ê-iÀÛiÀ]ÊvÊ>Ê`Û`Õ>ʵÕiÀÞÊÃÊÌÊ>Üi`ÊÌÊ ÕÃiÊÀiÊÌ
>ÊiÊ *1ÊvÀÊiÝiVÕÌ]ÊÌ
iÊÌ
iÊ«ÌâiÀÊ`ÃV>À`ÃÊÌ
iÊ«>À>iÊ«>Ê«Ì°Ê /
iÊ>ÝÕÊÕLiÀÊvÊ *1ÃÊÌ
>ÌÊV>ÊLiÊÕÃi`ÊvÀÊ>Ê«>À>iʵÕiÀÞÊÃÊ}ÛiÀi`ÊLÞÊÌ
iÊi]t `acnaakbl]n]hhaheoiÊÃiÌÌ}ÊvÊÌ
iÊ-+Ê-iÀÛiÀÊVv}ÕÀ>̰Ê/
iÊ`iv>ÕÌÊÛ>ÕiÊÃÊä]ÊÜ
V
Ê >ÜÃÊ>ÊÌ
iÊ *1ÃÊ>Û>i`ÊLÞÊÌ
iÊ]bbejepui]og setting) to be used for a parallel query. If you Ü>ÌÊÌÊ>ÜÊ«>À>iʵÕiÀiÃÊÌÊÕÃiÊÊÀiÊÌ
>ÊÌÜÊ *1ÃÊÕÌÊvÊ *1äÊÌÊ *1Î]ÊÌi`ÊLÞÊ the preceding ]bbejepui]og setting, execute the following statements (l]n]hhaheoi*omh in the download): QOAi]opan ATA?ol[_kjbecqna#odks]`r]j_a`klpekj#(#-# NA?KJBECQNA ATA?ol[_kjbecqna#i]t`acnaakbl]n]hhaheoi#(. NA?KJBECQNA This change takes effect immediately, without any restart. The i]t`acnaakbl]n]hhaheoi setting can also be controlled at a query level using the I=T@KL query hint: OAHA?P&BNKIp-SDANA_-9-KLPEKJ$I=T@KL.%
249
250
CHAPTER 9 N EXEC U TION P L A N C A C HE A NA L YS IS
Changing the i]t`acnaakbl]n]hhaheoi setting is best determined by the needs of your system. To limit contention with the operating system, I will usually set the i]t`acnaakb l]n]hhaheoiÊÃiÌÌ}ÊÌÊiÊiÃÃÊÌ
>ÊÌ
iÊÕLiÀÊÊÌ
iÊÃiÀÛiÀ]Ê>`ʽÊÃiÌÊ>Ê>vvÌÞÊ>ÃÊÌÊ Ì
ÃiÊ *1ÃÊ>ÃÊÜi° -ViÊ«>À>iʵÕiÀiÃÊÀiµÕÀiÊÀiÊiÀÞ]ÊÌ
iÊ«ÌâiÀÊ`iÌiÀiÃÊÌ
iÊ>ÕÌÊvÊ memory available before choosing a parallel plan. The amount of memory required increases with the degree of parallelism. If the memory requirement of the parallel plan for a given `i}ÀiiÊvÊ«>À>iÃÊV>ÌÊLiÊÃ>ÌÃvi`]ÊÌ
iÊ-+Ê-iÀÛiÀÊ`iVÀi>ÃiÃÊÌ
iÊ`i}ÀiiÊvÊ«>À>iÃÊ automatically or completely abandons the parallel plan for the query in the given workload context. +ÕiÀiÃÊÜÌ
Ê>ÊÛiÀÞÊ
}
Ê *1ÊÛiÀ
i>`Ê>ÀiÊÌ
iÊLiÃÌÊV>``>ÌiÃÊvÀÊ>Ê«>À>iÊ«>°Ê
Ý>«iÃÊVÕ`iÊ}Ê>À}iÊÌ>LiÃ]Ê«iÀvÀ}ÊÃÕLÃÌ>Ì>Ê>}}Ài}>ÌÃ]Ê>`ÊÃÀÌ}Ê>À}iÊ ÀiÃÕÌÊÃiÌÃ]Ê>ÊVÊ«iÀ>ÌÃÊÊÀi«ÀÌ}ÊÃÞÃÌiÃÊiÃÃÊÃÊÊ"/*ÊÃÞÃÌiî°ÊÀÊëiÊ queries usually found in transaction-processing applications, the additional coordination required to initialize, synchronize, and terminate a parallel plan outweighs the potential performance benefit. 7
iÌ
iÀÊÀÊÌÊ>ʵÕiÀÞÊÃÊëiÊÃÊ`iÌiÀi`ÊLÞÊV«>À}ÊÌ
iÊiÃÌ>Ìi`ÊiÝiVÕÌÊÌiÊ of the query with a cost threshold. This cost threshold is controlled by the _koppdnaodkh` bknl]n]hhaheoiÊÃiÌÌ}ÊvÊÌ
iÊ-+Ê-iÀÛiÀÊVv}ÕÀ>Ì°Ê ÞÊ`iv>ÕÌ]ÊÌ
ÃÊÃiÌÌ}½ÃÊÛ>ÕiÊÃÊx]Ê Ü
V
Êi>ÃÊÌ
>ÌÊvÊÌ
iÊiÃÌ>Ìi`ÊiÝiVÕÌÊÌiÊvÊÌ
iÊÃiÀ>Ê«>ÊÃÊÀiÊÌ
>ÊxÊÃiV`Ã]ÊÌ
iÊ Ì
iÊ«ÌâiÀÊVÃ`iÀÃÊ>Ê«>À>iÊ«>ÊvÀÊÌ
iʵÕiÀÞ°ÊÀÊiÝ>«i]ÊÌÊ`vÞÊÌ
iÊVÃÌÊÌ
ÀiÃ
`Ê ÌÊÈÊÃiV`Ã]ÊiÝiVÕÌiÊÌ
iÊvÜ}ÊÃÌ>ÌiiÌÃÊl]n]hhaheoi[pdnaodkh`*omh in the download): QOAi]opan ATA?ol[_kjbecqna#odks]`r]j_a`klpekj#(#-# NA?KJBECQNA ATA?ol[_kjbecqna#_koppdnaodkh`bknl]n]hhaheoi#(2 NA?KJBECQNA /
ÃÊV
>}iÊÌ>iÃÊivviVÌÊi`>ÌiÞ]ÊÜÌ
ÕÌÊ>ÞÊÀiÃÌ>À̰ÊvÊÞÊiÊ *1ÊÃÊ>Û>>LiÊ ÌÊ-+Ê-iÀÛiÀ]ÊÌ
iÊÌ
ÃÊÃiÌÌ}ÊÃÊ}Ài`°Ê½ÛiÊvÕ`ÊÌ
>ÌÊ"/*ÊÃÞÃÌiÃÊÃÕvviÀÊÜ
iÊÌ
iÊVÃÌÊ Ì
ÀiÃ
`ÊvÀÊ«>À>iÃÊÃÊÃiÌÊÌ
ÃÊܰÊ1ÃÕ>ÞÊVÀi>Ã}ÊÌ
iÊÛ>ÕiÊÌÊÃiÜ
iÀiÊLiÌÜiiÊ£xÊ >`ÊÓxÊÜÊLiÊLiivV>° The Ê>VÌʵÕiÀiÃÊEJOANP, QL@=PA, and @AHAPA®Ê>ÀiÊiÝiVÕÌi`ÊÃiÀ>Þ°ÊÜiÛiÀ]ÊÌ
iÊ OAHA?P portion of an EJOANP statement and the SDANA clause of an QL@=PA or a @AHAPA statement can be executed in parallel. The actual data changes are applied serially to the database. Also, if the optimizer determines that the number of rows affected is too low, it does not introduce parallel operators. Note that, even at execution time, -+Ê-iÀÛiÀÊ`iÌiÀiÃÊÜ
iÌ
iÀÊÌ
iÊVÕÀÀiÌÊÃÞÃÌiÊ workload and configuration information allow for parallel query execution. If parallel query iÝiVÕÌÊÃÊ>Üi`]Ê-+Ê-iÀÛiÀÊ`iÌiÀiÃÊÌ
iÊ«Ì>ÊÕLiÀÊvÊÌ
Ài>`ÃÊ>`ÊëÀi>`ÃÊÌ
iÊ iÝiVÕÌÊvÊÌ
iʵÕiÀÞÊ>VÀÃÃÊÌ
ÃiÊÌ
Ài>`ðÊ7
iÊ>ʵÕiÀÞÊÃÌ>ÀÌÃÊ>Ê«>À>iÊiÝiVÕÌ]ÊÌÊÕÃiÃÊ Ì
iÊÃ>iÊÕLiÀÊvÊÌ
Ài>`ÃÊÕÌÊV«ḭÊ-+Ê-iÀÛiÀÊÀiiÝ>iÃÊÌ
iÊ«Ì>ÊÕLiÀÊvÊ threads before executing the parallel query the next time. Once the processing strategy is finalized by using either a serial plan or a parallel plan, the optimizer generates the execution plan for the query. The execution plan contains the detailed processing strategy decided by the optimizer to execute the query. This includes steps such as data retrieval, result set joins, result set ordering, and so on. A detailed explanation of how
C H A P T E R 9 N E X E C U T I O N P LA N C A C H E A N A LY S I S
ÌÊ>>ÞâiÊÌ
iÊ«ÀViÃÃ}ÊÃÌi«ÃÊVÕ`i`ÊÊ>ÊiÝiVÕÌÊ«>ÊÃÊ«ÀiÃiÌi`ÊÊ
>«ÌiÀÊΰÊ/
iÊ execution plan generated for the query is saved in the plan cache for future reuse.
Execution Plan Caching The execution planÊvÊ>ʵÕiÀÞÊ}iiÀ>Ìi`ÊLÞÊÌ
iÊ«ÌâiÀÊÃÊÃ>Ûi`ÊÊ>ÊëiV>Ê«>ÀÌÊvÊ-+Ê-iÀÛiÀ½ÃÊiÀÞÊ«ÊV>i`ÊÌ
iÊplan cache or procedure cache. (The procedure cache is part of the -+Ê-iÀÛiÀÊLÕvviÀÊV>V
iÊ>`ÊÃÊiÝ«>i`ÊÊ
>«ÌiÀÊÓ°®Ê->Û}ÊÌ
iÊ«>ÊÊ>ÊV>V
iÊ>ÜÃÊ-+Ê -iÀÛiÀÊÌÊ>Û`ÊÀÕ}ÊÌ
ÀÕ}
ÊÌ
iÊÜ
iʵÕiÀÞÊ«Ìâ>ÌÊ«ÀViÃÃÊ>}>ÊÜ
iÊÌ
iÊÃ>iÊ µÕiÀÞÊÃÊÀiÃÕLÌÌi`°Ê-+Ê-iÀÛiÀÊÃÕ««ÀÌÃÊ`vviÀiÌÊÌiV
µÕiÃÊÃÕV
Ê>ÃÊplan cache aging and plan cache types to increase the reusability of the cached plans. It also stores two binary values called the query hash and the query plan hash.
NNote I discuss the techniques supported by SQL Server for improving the effectiveness of execution plan reuse later in this chapter.
Components of the Execution Plan The execution plan generated by the optimizer contains two components: Ê
UÊ Query plan: This represents the commands that specify all the physical operations required to execute a query.
Ê
UÊ Execution context: This maintains the variable parts of a query within the context of a given user. I will cover these components in more detail in the next sections.
Query Plan The query plan is a reentrant, read-only data structure, with commands that specify all the physical operations required to execute the query. The reentrant property allows the query plan to be accessed concurrently by multiple connections. The physical operations include specifications on which tables and indexes to access, how and in what order they should be accessed, the type of join operations to be performed between multiple tables, and so forth. ÊÕÃiÀÊVÌiÝÌÊÃÊÃÌÀi`ÊÊÌ
iʵÕiÀÞÊ«>°ÊÀÊ>ÊÃ}iʵÕiÀÞ]ÊÌ
iÀiÊV>ÊLiÊÌÜÊV«iÃÊvÊÌ
iÊ query plan: the serial plan and the parallel plan (regardless of the degree of parallelism).
Execution Context The execution context is another data structure that maintains the variable part of the query. Although the server keeps track of the execution plans in the procedure cache, these plans are context neutral. Therefore, each user executing the query will have a separate execution context that holds data specific to their execution, such as parameter values and connection details.
251
252
CHAPTER 9 N EXEC U TION P L A N C A C HE A NA L YS IS
Aging of the Execution Plan The procedure cache isÊ«>ÀÌÊvÊ-+Ê-iÀÛiÀ½ÃÊLÕvviÀÊV>V
i]ÊÜ
V
Ê>ÃÊ
`ÃÊ`>Ì>Ê«>}iðÊÃÊ new execution plans are added to the procedure cache, the size of the procedure cache keeps }ÀÜ}]Ê>vviVÌ}ÊÌ
iÊÀiÌiÌÊvÊÕÃivÕÊ`>Ì>Ê«>}iÃÊÊiÀÞ°Ê/Ê>Û`ÊÌ
Ã]Ê-+Ê-iÀÛiÀÊ dynamically controls the retention of the execution plans in the procedure cache, retaining the frequently used execution plans and discarding plans that are not used for a certain period of time. -+Ê-iÀÛiÀÊii«ÃÊÌÀ>VÊvÊÌ
i frequency of an execution plan's reuse by associating an age vi`ÊÌḚ̂Ê7
iÊ>ÊiÝiVÕÌÊ«>ÊÃÊ}iiÀ>Ìi`]ÊÌ
iÊ>}iÊvi`ÊÃÊ««Õ>Ìi`ÊÜÌ
ÊÌ
iÊVÃÌÊvÊ}ierating the plan. A complex query requiring extensive optimization will have an age field value higher than that for a simpler query. At regular intervals, the age fields of all the execution plans in the procedure cache are `iVÀiiÌi`ÊLÞÊ-+Ê-iÀÛiÀ½ÃÊlazy writer process (which manages most of the background «ÀViÃÃiÃÊÊ-+Ê-iÀÛiÀ®°ÊvÊ>ÊiÝiVÕÌÊ«>ÊÃÊÌÊÀiÕÃi`ÊvÀÊ>Ê}ÊÌi]ÊÌ
iÊÌ
iÊ>}iÊvi`Ê ÜÊiÛiÌÕ>ÞÊLiÊÀi`ÕVi`ÊÌÊä°Ê/
iÊV
i>«iÀÊÌ
iÊiÝiVÕÌÊ«>ÊÜ>ÃÊÌÊ}iiÀ>Ìi]ÊÌ
iÊÃiÀÊÌÃÊ >}iÊvi`ÊÜÊLiÊÀi`ÕVi`ÊÌÊä°Ê"ViÊ>ÊiÝiVÕÌÊ«>½ÃÊ>}iÊvi`ÊÀi>V
iÃÊä]ÊÌ
iÊ«>ÊLiViÃÊ >ÊV>``>ÌiÊvÀÊÀiÛ>ÊvÀÊiÀÞ°Ê-+Ê-iÀÛiÀÊÀiÛiÃÊ>Ê«>ÃÊÜÌ
Ê>Ê>}iÊvi`ÊvÊäÊ from the procedure cache when memory pressure increases to such an extent that there is no }iÀÊiÕ}
ÊvÀiiÊiÀÞÊÌÊÃiÀÛiÊiÜÊÀiµÕiÃÌðÊÜiÛiÀ]ÊvÊ>ÊÃÞÃÌiÊ
>ÃÊiÕ}
ÊiÀÞÊ and free memory pages are available to serve new requests, execution plans with an age field vÊäÊV>ÊÀi>ÊÊÌ
iÊ«ÀVi`ÕÀiÊV>V
iÊvÀÊ>Ê}ÊÌiÊÃÊÌ
>ÌÊÌ
iÞÊV>ÊLiÊÀiÕÃi`Ê>ÌiÀ]ÊvÊ required. As well as aging, execution plans can also find their age field incremented by the cost vÊ}iiÀ>Ì}ÊÌ
iÊ«>ÊiÛiÀÞÊÌiÊÌ
iÊ«>ÊÃÊÀiÕÃi`°ÊÀÊiÝ>«i]ÊÃÕ««ÃiÊÞÕÊ
>ÛiÊÌÜÊ iÝiVÕÌÊ«>ÃÊÜÌ
Ê}iiÀ>ÌÊVÃÌÃÊiµÕ>ÊÌÊ£ääÊ>`Ê£ä°Ê/
iÀÊÃÌ>ÀÌ}Ê>}iÊvi`ÊÛ>ÕiÃÊÜÊ Ì
iÀivÀiÊLiÊ£ääÊ>`Ê£ä]ÊÀiëiVÌÛiÞ°ÊvÊLÌ
ÊiÝiVÕÌÊ«>ÃÊ>ÀiÊÀiÕÃi`Êi`>ÌiÞ]ÊÌ
iÀÊ >}iÊvi`ÃÊÜÊLiÊVÀiiÌi`ÊÌÊÓääÊ>`ÊÓä]ÊÀiëiVÌÛiÞ°Ê7Ì
ÊÌ
iÃiÊ>}iÊvi`ÊÛ>ÕiÃ]ÊÌ
iÊ>âÞÊ ÜÀÌiÀÊÜÊLÀ}Ê`ÜÊÌ
iÊVÃÌÊvÊÌ
iÊÃiV`Ê«>ÊÌÊäÊÕV
Êi>ÀiÀÊÌ
>ÊÌ
>ÌÊvÊÌ
iÊvÀÃÌÊi]Ê unless the second plan is reused more often. Therefore, even if a costly plan is reused less frequently than a cheaper plan, because of the effect of the cost on the age field, the costly plan can remain at a nonzero age value for a longer period of time.
Analyzing the Execution Plan Cache You can obtain a lot of information about the execution plans in the procedure cache by accessing the dynamic management view ouo*`i[ata_[_]_da`[lh]jo: OAHA?P&BNKIouo*`i[ata_[_]_da`[lh]jo />LiÊ£ÊÃ
ÜÃÊÃiÊvÊÌ
iÊÕÃivÕÊvÀ>ÌÊ«ÀÛ`i`ÊLÞÊouo*`i[ata_[_]_da`[lh]jo Ì
ÃÊÃÊi>ÃiÀÊÌÊÀi>`ÊÊÀ`ÊÛiÜ®°
C H A P T E R 9 N E X E C U T I O N P LA N C A C H E A N A LY S I S
Table 9-1. ouo*`i[ata_[_]_da`[lh]jo
Column Name
Description
nab_kqjpo
Represents the number of other objects in the cache referencing this plan.
qoa_kqjpo
The number of times this object has been used since it was added to the cache.
oeva[ej[^upao
The size of the plan stored in the cache.
_]_dak^fpula
7
>Ì type of plan this is. There are several, but of particular interest are these: Compiled plan: A completed execution plan Compiled plan stub: A marker used for ad hoc queries (you can find more `iÌ>ÃÊÊÌ
iʺ`ÊVÊ7À>`»ÊÃiVÌÊvÊÌ
ÃÊV
>«ÌiÀ® *>ÀÃiÊÌÀii\ÊÊ«>ÊÃÌÀi`ÊvÀÊ>VViÃÃ}Ê>ÊÛiÜ
K^fpula
The type of object that generated the plan. Again, there are several but these are of particular interest: *ÀV *Ài«>Ài` Ad hoc 6iÜ
Lh]j[d]j`ha
The identifier for this plan in memory. It is used to retrieve query text and execution plans.
1Ã}ÊÌ
iÊ 6Êouo*`i[ata_[_]_da`[lh]jo all by itself gets you only part of the informa̰Ê/
iÊiÝÌÊÌÜÊ«>ÀÌÃÊV>ÊLiÊÕÃÌÊ>ÃÊ«ÀÌ>̰Ê1Ã}ÊÌ
iÊ`Þ>VÊ>>}iiÌÊvÕVÌÊ ouo*`i[ata_[mqanu[lh]j$lh]j[d]j`ha% in combination with ouo*`i[ata_[_]_da`[lh]jo will also bring back the 8ÊiÝiVÕÌÊ«>ÊÌÃivÊÃÊÌ
>ÌÊÞÕÊV>Ê`ë>ÞÊÌÊ>`ÊÜÀÊÜÌ
Ḛ̂ÊvÊÞÕÊ then bring in ouo*`i[ata_[omh[patp$lh]j[d]j`ha%, you can also retrieve the original query ÌiÝ̰Ê/
ÃÊ>ÞÊÌÊÃiiÊÕÃivÕÊÜ
iÊÞÕ½ÀiÊÀÕ}ÊÜʵÕiÀiÃÊvÀÊÌ
iÊiÝ>«iÃÊ
iÀi]ÊLÕÌÊ when you go to your production system and begin to pull in execution plans from the cache, it might be handy to have the original query. To get detailed performance metrics about the cached plan, you can use ouo*`i[ata_[mqanu[op]po to return that data. Among other pieces vÊ`>Ì>]ÊÌ
iʵÕiÀÞÊ
>Ã
Ê>`ʵÕiÀÞÊ«>Ê
>Ã
Ê>ÀiÊÃÌÀi`ÊÊÌ
ÃÊ ° ÊÌ
iÊvÜ}ÊÃiVÌÃ]ʽÊiÝ«ÀiÊ
ÜÊÌ
iÊ«>ÊV>V
iÊÜÀÃÊÜÌ
Ê>VÌÕ>ʵÕiÀiÃÊÕÃ}Ê ouo*`i[ata_[_]_da`[lh]jo andÊÌ
iÊÌ
iÀÊ 6ÃÊ>`Ê Ã°
Execution Plan Reuse 7
iÊ>ʵÕiÀÞÊÃÊÃÕLÌÌi`]Ê-+Ê-iÀÛiÀÊV
iVÃ the procedure cache for a matching execution «>°ÊvÊiÊÃÊÌÊvÕ`]ÊÌ
iÊ-+Ê-iÀÛiÀÊ«iÀvÀÃÊÌ
iʵÕiÀÞÊV«>ÌÊ>`Ê«Ìâ>ÌÊÌÊ }iiÀ>ÌiÊ>ÊiÜÊiÝiVÕÌÊ«>°ÊÜiÛiÀ]ÊvÊÌ
iÊ«>ÊiÝÃÌÃÊÊÌ
iÊ«ÀVi`ÕÀiÊV>V
i]ÊÌÊÃÊÀiÕÃi`Ê with the private execution context. This saves theÊ *1ÊVÞViÃÊÌ
>ÌÊÌ
iÀÜÃiÊÜÕ`Ê
>ÛiÊLii spent on the plan generation. +ÕiÀiÃÊ>ÀiÊÃÕLÌÌi`ÊÌÊ-+Ê-iÀÛiÀÊÜÌ
Êfilter criteria to limit the size of the result set. /
iÊÃ>iʵÕiÀiÃÊ>ÀiÊvÌiÊÀiÃÕLÌÌi`ÊÜÌ
Ê`vviÀiÌÊÛ>ÕiÃÊvÀÊÌ
iÊvÌiÀÊVÀÌiÀ>°ÊÀÊiÝ>ple, consider the following query:
253
254
CHAPTER 9 N EXEC U TION P L A N C A C HE A NA L YS IS
OAHA?Pokd*O]haoKn`anJqi^an (okd*Kn`an@]pa (ok`*Kn`anMpu (ok`*HejaPkp]h BNKIO]hao*O]haoKn`anDa]`an=Ookd FKEJO]hao*O]haoKn`an@ap]eh=Ook` KJokd*O]haoKn`anE@9ok`*O]haoKn`anE@ SDANAokd*?qopkianE@9.525, =J@ok`*lnk`q_pe`93-7
iÊÌ
ÃʵÕiÀÞÊÃÊÃÕLÌÌi`]ÊÌ
iÊ«ÌâiÀÊVÀi>ÌiÃÊ>ÊiÝiVÕÌÊ«>Ê>`ÊÃ>ÛiÃÊÌÊÊÌ
iÊ procedure cache to reuse in the future. If this query is resubmitted with a different filter criterion value—for example, okd*?qopkianE@9.51,,—it will be beneficial to reuse the existing execution plan for the previously supplied filter criterion value. But whether the execution plan created for one filter criterion value can be reused for another filter criterion value `i«i`ÃÊÊ
ÜÊÌ
iʵÕiÀÞÊÃÊÃÕLÌÌi`ÊÌÊ-+Ê-iÀÛiÀ° /
iʵÕiÀiÃÊÀÊÜÀ>`®ÊÃÕLÌÌi`ÊÌÊ-+Ê-iÀÛiÀÊV>ÊLiÊLÀ>`ÞÊV>ÃÃvi`ÊÕ`iÀÊÌÜÊ categories that determine whether the execution plan will be reusable as the value of the variable parts of the query changes: Ê
UÊ `Ê
V
Ê
UÊ *Ài«>Ài`
NTip To test the output of ouo*`i[ata_[_]_da`[lh]jo for this chapter, it will be necessary to remove the plans from cache on occasion by executing @>??BNAALNK??=?DA. Do not run this on your production server since flushing the cache will require all execution plans to be rebuilt as they are executed, placing a serious strain on your production system for no good reason.
Ad Hoc Workload +ÕiÀiÃÊV>ÊLiÊÃÕLÌÌi`ÊÌÊ-+Ê-iÀÛiÀÊÜÌ
ÕÌÊiÝ«VÌÞÊÃ>Ì}ÊÌ
iÊÛ>À>LiÃÊvÀÊÌ
iÊ query. These types of queries executed without explicitly converting the variable parts of the query into parameters are referred to as ad hoc workloads ÀʵÕiÀiî°ÊÀÊiÝ>«i]ÊVÃ`iÀÊ this query (]`dk_*omh in the download): OAHA?Pokd*O]haoKn`anJqi^an (okd*Kn`an@]pa (ok`*Kn`anMpu (ok`*HejaPkp]h BNKIO]hao*O]haoKn`anDa]`an=Ookd FKEJO]hao*O]haoKn`an@ap]eh=Ook` KJokd*O]haoKn`anE@9ok`*O]haoKn`anE@ SDANAokd*?qopkianE@9.525, =J@ok`*lnk`q_pe`93--
C H A P T E R 9 N E X E C U T I O N P LA N C A C H E A N A LY S I S
If the query is submitted as is, without explicitly converting either of the variable values to a parameter (that can be supplied to the query when executed), then the query is an ad hoc query. In this query, the filter criterion value is embedded in the query itself and is not explicitly parameterized to isolate it from the query. This means that you cannot reuse the execution «>ÊvÀÊÌ
ÃʵÕiÀÞÊÕiÃÃÊÞÕÊÕÃiÊÌ
iÊÃ>iÊÛ>À>Li°ÊÜiÛiÀ]ÊÌ
iÊÛ>À>LiÊ«>ÀÌÃÊvÊÌ
iʵÕiÀiÃÊ can be explicitly parameterized in three different ways that are jointly categorized as a prepared workload.
Prepared Workload Prepared workloads (or queries) explicitly parameterize the variable parts of the query so that Ì
iʵÕiÀÞÊ«>ÊýÌÊÌi`ÊÌÊÌ
iÊÛ>ÕiÊvÊÌ
iÊÛ>À>LiÊ«>ÀÌðÊÊ-+Ê-iÀÛiÀ]ʵÕiÀiÃÊV>ÊLiÊÃÕLmitted as prepared workloads using the following three methods: Ê
UÊ Stored procedures: AllowsÊÃ>Û}Ê>ÊViVÌÊvÊ-+ÊÃÌ>ÌiiÌÃÊÌ
>ÌÊV>Ê>VVi«ÌÊ>`Ê return user-supplied parameters
Ê
UÊ ol[ata_qpaomh: AllowsÊiÝiVÕÌ}Ê>Ê-+ÊÃÌ>ÌiiÌÊÀÊ>Ê-+ÊL>ÌV
ÊÌ
>ÌÊ>ÞÊVÌ>Ê ÕÃiÀÃÕ««i`Ê«>À>iÌiÀÃ]ÊÜÌ
ÕÌÊÃ>Û}ÊÌ
iÊ-+ÊÃÌ>ÌiiÌÊÀÊL>ÌV
Ê
UÊ Prepare/execute model: AllowsÊ>Ê-+ÊViÌÊÌÊÀiµÕiÃÌÊÌ
iÊ}iiÀ>ÌÊvÊ>ʵÕiÀÞÊ«>Ê that can be reused during subsequent executions of the query with different parameter Û>ÕiÃ]ÊÜÌ
ÕÌÊÃ>Û}ÊÌ
iÊ-+ÊÃÌ>ÌiiÌîÊÊ-+Ê-iÀÛiÀ
ÀÊiÝ>«i]ÊÌ
iÊOAHA?P statement shown previously can be explicitly parameterized using a stored procedure as follows (ol>]oe_O]haoEjbk*omh in the download): EB$OAHA?PK>FA?P[E@$#ol>]oe_O]haoEjbk#%%EOJKPJQHH @NKLLNK?`^k*ol>]oe_O]haoEjbk CK ?NA=PALNK?`^k*ol>]oe_O]haoEjbk
255
256
CHAPTER 9 N EXEC U TION P L A N C A C HE A NA L YS IS
Plan Reusability of an Ad Hoc Workload 7
iÊ>ʵÕiÀÞÊÃÊÃÕLÌÌi`Ê>ÃÊ>Ê>`Ê
VÊÜÀ>`]Ê-+Ê-iÀÛiÀÊ}iiÀ>ÌiÃÊÌ
iÊiÝiVÕÌÊ«>Ê and decides whether to cache the plan based upon the cost of generating the execution plan. vÊÌ
iÊVÃÌÊvÊ}iiÀ>Ì}ÊÌ
iÊiÝiVÕÌÊ«>ÊÃÊÛiÀÞÊV
i>«]ÊÌ
iÊ-+Ê-iÀÛiÀÊ>ÞÊÌÊV>V
iÊÌ
iÊ plan to conserve the size of the procedure cache based on the resources available. Instead of v`}ÊÌ
iÊ«ÀVi`ÕÀiÊV>V
iÊÜÌ
ÊV
i>«Ê>`Ê
VʵÕiÀiÃ]Ê-+Ê-iÀÛiÀÊÀi}iiÀ>ÌiÃÊÌ
iÊiÝiVÕÌÊ plan when the query is resubmitted. ÀÊ>`Ê
VʵÕiÀiÃÊÜÌ
Ê
}
iÀÊiÝiVÕÌÊ«>Ê}iiÀ>ÌÊVÃÌÃ]Ê-+Ê-iÀÛiÀÊÃ>ÛiÃÊÌ
iÊ execution plan in the procedure cache. The values of the variable parts of an ad hoc query are included in the query plan and are not saved separately in the execution context, meaning that you cannot reuse the execution plan for this query unless you use exactly the same variable as you have seen. To understand this, consider the following ad hoc query (]`dk_*omh in the download): OAHA?Pokd*O]haoKn`anJqi^an (okd*Kn`an@]pa (ok`*Kn`anMpu (ok`*HejaPkp]h BNKIO]hao*O]haoKn`anDa]`an=Ookd FKEJO]hao*O]haoKn`an@ap]eh=Ook` KJokd*O]haoKn`anE@9ok`*O]haoKn`anE@ SDANAokd*?qopkianE@9.525, =J@ok`*lnk`q_pe`93-The execution plan generated for this ad hoc query is based on the exact text of the query, Ü
V
ÊVÕ`iÃÊViÌÃ]ÊV>Ãi]ÊÌÀ>}Êë>ViÃ]Ê>`Ê
>À`ÊÀiÌÕÀðÊ9Õ½Ê
>ÛiÊÌÊÕÃiÊÌÊÌÊ«ÕÊ the information out of ouo*`i[ata_[_]_da`[lh]jo: OAHA?P_*qoa_kqjpo (_*_]_dak^fpula (_*k^fpula BNKIouo*`i[ata_[_]_da`[lh]jo_ ?NKOO=LLHUouo*`i[ata_[omh[patp$_*lh]j[d]j`ha%p SDANAp*PATP9#OAHA?Pokd*O]haoKn`anJqi^an (okd*Kn`an@]pa (ok`*Kn`anMpu (ok`*HejaPkp]h BNKIO]hao*O]haoKn`anDa]`an=Ookd FKEJO]hao*O]haoKn`an@ap]eh=Ook` KJokd*O]haoKn`anE@9ok`*O]haoKn`anE@ SDANAokd*?qopkianE@9.525, =J@ok`*lnk`q_pe`93--# }ÕÀiÊÇÊÃ
ÜÃÊÌ
iÊÕÌ«ÕÌÊvÊouo*`i[ata_[_]_da`[lh]jo.
Figure 9-7. ouo*`i[ata_[_]_da`[lh]jo output
C H A P T E R 9 N E X E C U T I O N P LA N C A C H E A N A LY S I S
9ÕÊV>ÊÃiiÊvÀÊ}ÕÀiÊÇÊÌ
>ÌÊ>ÊV«i`Ê«>ÊÃÊ}iiÀ>Ìi`Ê>`ÊÃ>Ûi`ÊÊÌ
iÊ«ÀVi`ÕÀiÊ cache for the preceding ad hoc query. To find the specific query, I used the query itself in the SDANA clause. You can see that this plan has been used once up until now (qoa_kqjpo9-). If Ì
ÃÊ>`Ê
VʵÕiÀÞÊÃÊÀiiÝiVÕÌi`]Ê-+Ê-iÀÛiÀÊÀiÕÃiÃÊÌ
iÊiÝÃÌ}ÊiÝiVÕÌ>LiÊ«>ÊvÀÊÌ
iÊ«ÀVi`ÕÀiÊV>V
i]Ê>ÃÊÃ
ÜÊÊ}ÕÀiÊn°
Figure 9-8. Reusing the executable plan from the procedure cache Ê}ÕÀiÊn]ÊÞÕÊV>ÊÃiiÊÌ
>ÌÊÌ
i qoa_kqjpoÊvÀÊÌ
iÊ«ÀiVi`}ʵÕiÀÞ½ÃÊiÝiVÕÌ>LiÊ«>Ê
>ÃÊ VÀi>Ãi`ÊÌÊÓ]ÊVvÀ}ÊÌ
>ÌÊÌ
iÊiÝÃÌ}Ê«>ÊvÀÊÌ
ÃʵÕiÀÞÊ
>ÃÊLiiÊÀiÕÃi`°ÊvÊÌ
ÃʵÕiÀÞÊÃÊ executed repeatedly, the existing plan will be reused every time. -ViÊÌ
iÊ«>Ê}iiÀ>Ìi`ÊvÀÊÌ
iÊ«ÀiVi`}ʵÕiÀÞÊVÕ`iÃÊÌ
iÊvÌiÀÊVÀÌiÀ value, the reusability of the plan is limited to the use of the same filter criterion value. Reexecute ]`dk_-* omh, but change okd*?qopkianE@ to .51,,: OAHA?P_*qoa_kqjpo (_*_]_dak^fpula (_*k^fpula BNKIouo*`i[ata_[_]_da`[lh]jo_ ?NKOO=LLHUouo*`i[ata_[omh[patp$_*lh]j[d]j`ha%p SDANAp*PATP9#OAHA?Pokd*O]haoKn`anJqi^an (okd*Kn`an@]pa (ok`*Kn`anMpu (ok`*HejaPkp]h BNKIO]hao*O]haoKn`anDa]`an=Ookd FKEJO]hao*O]haoKn`an@ap]eh=Ook` KJokd*O]haoKn`anE@9ok`*O]haoKn`anE@ SDANAokd*?qopkianE@9.51,, =J@ok`*lnk`q_pe`93--# /
iÊiÝÃÌ}Ê«>ÊV>½ÌÊLiÊÀiÕÃi`]Ê>`ÊvÊÌ
i ouo*`i[ata_[_]_da`[lh]joÊÃÊÀiÀÕÊ>ÃÊÃ]ÊÞÕ½Ê ÃiiÊÌ
>ÌÊÌ
iÊiÝiVÕÌÊVÕÌÊ
>ýÌÊVÀi>Ãi`Ê}ÕÀiÊ®°
Figure 9-9. ouo*`i[ata_[_]_da`[lh]jo shows that the existing plan is not reused. ÃÌi>`]ʽÊ>`ÕÃÌÊÌ
i query against ouo*`i[ata_[_]_da`[lh]jo: OAHA?P_*qoa_kqjpo (_*_]_dak^fpula (_*k^fpula (p*patp BNKIouo*`i[ata_[_]_da`[lh]jo_ ?NKOO=LLHUouo*`i[ata_[omh[patp$_*lh]j[d]j`ha%p
257
258
CHAPTER 9 N EXEC U TION P L A N C A C HE A NA L YS IS
SDANAp*PATPHEGA#OAHA?Pokd*O]haoKn`anJqi^an (okd*Kn`an@]pa (ok`*Kn`anMpu (ok`*HejaPkp]h BNKIO]hao*O]haoKn`anDa]`an=Ookd FKEJO]hao*O]haoKn`an@ap]eh=Ook` KJokd*O]haoKn`anE@9ok`*O]haoKn`anE@!# 9ÕÊV>ÊÃiiÊÌ
iÊÕÌ«ÕÌÊvÀÊÌ
ÃʵÕiÀÞÊÊ}ÕÀiÊ£ä°
Figure 9-10. ouo*`i[ata_[_]_da`[lh]jo showing that the existing plan can’t be reused ÀÊÌ
i ouo*`i[ata_[_]_da`[lh]joÊÕÌ«ÕÌÊÊ}ÕÀiÊn]ÊÞÕÊV>ÊÃiiÊÌ
>ÌÊÌ
iÊ«ÀiÛÕÃÊ «>ÊvÀÊÌ
iʵÕiÀÞÊ
>ýÌÊLiiÊÀiÕÃi`ÆÊÌ
iÊVÀÀië`} qoa_kqjpo value remained at the `ÊÛ>ÕiÊvÊÓ°ÊÃÌi>`ÊvÊÀiÕÃ}ÊÌ
iÊiÝÃÌ}Ê«>]Ê>ÊiÜÊ«>ÊÃÊ}iiÀ>Ìi`ÊvÀÊÌ
iʵÕiÀÞÊ>`Ê is saved in the procedure cache with a new lh]j[d]j`ha. If this ad hoc query is reexecuted repeatedly with different filter criterion values, a new execution plan will be generated every time. The inefficient reuse of the execution plan for this ad hoc query increases the load on the
*1ÊLÞÊVÃÕ}Ê>``Ì>Ê *1ÊVÞViÃÊÌÊÀi}iiÀ>ÌiÊÌ
iÊ«>° To summarize, ad hoc plan caching uses statement-level caching and is limited to an iÝ>VÌÊÌiÝÌÕ>Ê>ÌV
°ÊvÊ>Ê>`Ê
VʵÕiÀÞÊÃÊÌÊV«iÝ]Ê-+Ê-iÀÛiÀÊV>Ê«VÌÞÊ«>À>iÌiÀâiÊ the query to increase plan reusability by using a feature called simple parameterization. The definition of a simple query for simple parameterization is limited to fairly simple cases such as ad hoc queries with only one table. As shown in the previous example, a query requiring a join operation cannot be autoparameterized.
Optimize for an Ad Hoc Workload If your server is going to primarily support ad hoc queries, it is possible to achieve a small degree of performance improvement. One server option is called klpeievabkn]`dk_ sknghk]`o°Ê >L}ÊÌ
ÃÊvÀÊÌ
iÊÃiÀÛiÀÊV
>}iÃÊÌ
iÊÜ>ÞÊÌ
iÊi}iÊ`i>ÃÊÜÌ
Ê>`Ê
VʵÕiÀiÃ°Ê ÃÌi>`ÊvÊ}iiÀ>Ì}Ê>ÊvÕÊV«i`Ê«>ÊvÀÊÌ
iʵÕiÀÞÊÌ
iÊvÀÃÌÊÌiÊ̽ÃÊV>i`]Ê>ÊV«i`Ê plan stub is created. The stub does not have a full execution plan associated, saving the time of generating that execution plan and the storage space required for it. This option can be enabled without rebooting the server: ol[_kjbecqna#klpeievabkn]`dk_sknghk]`o#(-7 CK NA?KJBECQNA7 After changing the option, flush the cache, and then rerun the query ]`dk_*omh°Ê`vÞÊ the query against ouo*`i[ata_[_]_da`[lh]jo so that you include the oeva[ej[^upao column, >`ÊÌ
iÊÀÕÊÌÊÌÊÃiiÊÌ
iÊÀiÃÕÌÃÊÊ}ÕÀiÊ££°
C H A P T E R 9 N E X E C U T I O N P LA N C A C H E A N A LY S I S
Figure 9-11. ouo*`i[ata_[_]_da`[lh]jo showing a compiled plan stub }ÕÀiÊ££ÊÃ
ÜÃÊÊÌ
i _]_dak^fpula column that the new object in the cache is a com«i`Ê«>ÊÃÌÕL°Ê-ÌÕLÃÊV>ÊLiÊVÀi>Ìi`ÊvÀÊÌÃÊÀiʵÕiÀiÃÊÜÌ
ÊiÃÃÊ«>VÌÊÊÌ
iÊÃiÀÛiÀÊÌ
>Ê full compiled plans. But the next time an ad hoc query is executed, a fully compiled plan is created. To see this in action, run the query ]`dk_*omh, and check the results in ouo*`i[ata_[ _]_da`[lh]jo]Ê>ÃÊÃ
ÜÊÊ}ÕÀiÊ£Ó°
Figure 9-12. The compiled plan stub has become a compiled plan. Not only did the _]_dak^fpula change, but now that a full compiled plan was created, including an execution plan, a new lh]j[d]j`haÊÜ>ÃÊVÀi>Ìi`Ê>ÃÊÜi°Ê>Þ]ÊÌÊÃiiÊÌ
iÊÀi>Ê difference between a stub and a full plan, check the oeva[ej[^upaoÊVÕÊÊ}ÕÀiÊ££Ê>`Ê }ÕÀiÊ£Ó°Ê/
iÊÃâiÊV
>}i`ÊvÀÊÎÓäÊÊÌ
iÊÃÌÕLÊÌÊÈx]xÎÈÊÊÌ
iÊvÕÊ«>°Ê/
ÃÊÃ
ÜÃÊ«Àicisely the savings available when working with lots of ad hoc queries. Before proceeding, be sure to disable klpeievabkn]`dk_sknghk]`o: ol[_kjbecqna#klpeievabkn]`dk_sknghk]`o#(,7 CK NA?KJBECQNA7
Simple Parameterization 7
iÊ>Ê>`Ê
VʵÕiÀÞÊÃÊÃÕLÌÌi`]Ê-+Ê-iÀÛiÀÊ>>ÞâiÃÊÌ
iʵÕiÀÞÊÌÊ`iÌiÀiÊÜ
V
Ê«>ÀÌÃÊ of the incoming text might be parameters. It looks at the variable parts of the ad hoc query to determine whether it will be safe to parameterize them automatically and use the parameters (instead of the variable parts) in the query so that the query plan can be independent of the variable values. This feature of automatically converting the variable part of a query into a parameter, even though not parameterized explicitly (using a prepared workload technique), is called simple parameterization. ÕÀ}ÊëiÊ«>À>iÌiÀâ>Ì]Ê-+Ê-iÀÛiÀÊiÃÕÀiÃÊÌ
>ÌÊvÊÌ
iÊ>`Ê
VʵÕiÀÞÊÃÊVÛiÀÌi`Ê ÌÊ>Ê«>À>iÌiÀâi`ÊÌi«>Ìi]ÊÌ
iÊV
>}iÃÊÊÌ
iÊ«>À>iÌiÀÊÛ>ÕiÃÊܽÌÊÜ`iÞÊV
>}iÊÌ
iÊ «>ÊÀiµÕÀiḭÊ"Ê`iÌiÀ}ÊÌ
iÊëiÊ«>À>iÌiÀâ>ÌÊÌÊLiÊÃ>vi]Ê-+Ê-iÀÛiÀÊVÀi>ÌiÃÊ a parameterized template for the ad hoc query and saves the parameterized plan in the procedure cache. /
iÊ«>À>iÌiÀâi`Ê«>ÊÃÊÌÊL>Ãi`ÊÊÌ
iÊ`Þ>VÊÛ>ÕiÃÊÕÃi`ÊÊÌ
iʵÕiÀÞ°Ê-ViÊÌ
iÊ plan is generated for a parameterized template, it can be reused when the ad hoc query is reexecuted with different values for the variable parts. /ÊÕ`iÀÃÌ>`ÊÌ
iÊëiÊ«>À>iÌiÀâ>ÌÊvi>ÌÕÀiÊvÊ-+Ê-iÀÛiÀ]ÊVÃ`iÀÊÌ
iÊvÜ}Ê query (oeilha[l]n]iapanev]pekj*omh in the download): OAHA?P]*& BNKILanokj*=``naoo=O] SDANA]*=``naooE@90.
259
260
CHAPTER 9 N EXEC U TION P L A N C A C HE A NA L YS IS
7
iÊÌ
ÃÊ>`Ê
VʵÕiÀÞÊÃÊÃÕLÌÌi`]Ê-+Ê-iÀÛiÀÊV>ÊÌÀi>ÌÊÌ
ÃʵÕiÀÞÊ>ÃÊÌÊÃÊvÀÊ«>ÊVÀi>̰ÊÜiÛiÀ]ÊLivÀiÊÌ
iʵÕiÀÞÊÃÊiÝiVÕÌi`]Ê-+Ê-iÀÛiÀÊÌÀiÃÊÌÊ`iÌiÀiÊÜ
iÌ
iÀÊÌÊV>ÊLiÊ safely parameterized. On determining that the variable part of the query can be parameterized ÜÌ
ÕÌÊ>vviVÌ}ÊÌ
iÊL>ÃVÊÃÌÀÕVÌÕÀiÊvÊÌ
iʵÕiÀÞ]Ê-+Ê-iÀÛiÀÊ«>À>iÌiÀâiÃÊÌ
iʵÕiÀÞÊ>`Ê generates a plan for the parameterized query. You can observe this from the ouo*`i[ata_[ _]_da`[lh]joÊÕÌ«ÕÌÊÃ
ÜÊÊ}ÕÀiʣΰ
Figure 9-13. ouo*`i[ata_[_]_da`[lh]jo output showing an autoparameterized plan The qoa_kqjpo of the executable plan for the parameterized query appropriately repÀiÃiÌÃÊÌ
iÊÕLiÀÊvÊÀiÕÃiÃÊ>ÃÊ£°ÊÃ]ÊÌiÊÌ
>ÌÊÌ
iÊk^fpula for the autoparameterized executable plan is no longer =`dk_ÆÊÌÊÀiviVÌÃÊÌ
iÊv>VÌÊÌ
>ÌÊÌ
iÊ«>ÊÃÊvÀÊ>Ê«>À>iÌiÀâi`Ê µÕiÀÞ]Ê*Ài«>Ài`° The original ad hoc query, even though not executed, gets compiled to create the query tree required for the simple parameterization of the query. The compiled plan for the ad hoc query may or may not be saved in the plan cache depending on the resources available. But LivÀiÊVÀi>Ì}ÊÌ
iÊiÝiVÕÌ>LiÊ«>ÊvÀÊÌ
iÊ>`Ê
VʵÕiÀÞ]Ê-+Ê-iÀÛiÀÊv}ÕÀi`ÊÕÌÊÌ
>ÌÊÌÊÜ>ÃÊÃ>viÊ to autoparameterize and thus autoparameterized the query for further processing. This is visLiÊ>ÃÊÌ
iÊ
}
}
Ìi`ÊiÊÊ}ÕÀiʣΰ -ViÊÌ
ÃÊ>`Ê
VʵÕiÀÞÊ
>ÃÊLiiÊ>ÕÌ«>À>iÌiÀâi`]Ê-+Ê-iÀÛiÀÊÜÊÀiÕÃiÊÌ
iÊiÝÃÌ}Ê execution plan if you reexecute oeilha[l]n]iapanev]pekj*omh with a different value for the variable part: OAHA?P]*& bnkiLanokj*=``naoo=O] sdana]*W=``naooE@Y91.Ìlnarekqor]hqas]o0. }ÕÀiÊ£{ÊÃ
ÜÃÊÌ
iÊÕÌ«ÕÌÊvÊouo*`i[ata_[_]_da`[lh]jo.
Figure 9-14. ouo*`i[ata_[_]_da`[lh]jo output showing reuse of the autoparameterized plan ÀÊ}ÕÀiÊ£{]ÊÞÕÊV>ÊÃiiÊÌ
>ÌÊ>Ì
Õ}
Ê>ÊiÜÊ«>Ê
>ÃÊLiiÊ}iiÀ>Ìi`ÊvÀÊÌ
ÃÊ>`Ê
VÊ query, the ad hoc one using an =``naooE`ÊÛ>ÕiÊvÊxÓ]ÊÌ
iÊiÝÃÌ}Ê«Ài«>Ài`Ê«>ÊÃÊÀiÕÃi`Ê>ÃÊ indicated by the increase in the corresponding qoa_kqjpoÊÛ>ÕiÊÌÊÓ°Ê/
iÊ>`Ê
VʵÕiÀÞÊV>ÊLiÊ reexecuted repeatedly with different filter criterion values, reusing the existing execution plan. There is one more aspect to note in the parameterized query for which the execution plan ÃÊV>V
i`°ÊÊ}ÕÀiÊ£ä]ÊLÃiÀÛiÊÌ
>ÌÊÌ
iÊL`ÞÊvÊÌ
iÊ«>À>iÌiÀâi`ʵÕiÀÞÊ`iýÌÊiÝ>VÌÞÊ >ÌV
ÊÜÌ
ÊÌ
>ÌÊvÊÌ
iÊ>`Ê
VʵÕiÀÞÊÃÕLÌÌi`°ÊÀÊÃÌ>Vi]ÊÊÌ
iÊ>`Ê
VʵÕiÀÞ]ÊÌ
iÊÜÀ`ÃÊ bnki and sdana are in lowercase, and the =``naooE@ column is enclosed in square brackets.
C H A P T E R 9 N E X E C U T I O N P LA N C A C H E A N A LY S I S
261
"ÊÀi>â}ÊÌ
>ÌÊÌ
iÊ>`Ê
VʵÕiÀÞÊV>ÊLiÊÃ>viÞÊ>ÕÌ«>À>iÌiÀâi`]Ê-+Ê-iÀÛiÀÊ«VÃÊ>ÊÌiplate that can be used instead of the exact text of the query. To understand the significance of this, consider the following query: OAHA?P]*& BNKILanokj*=``naoo=O] SDANA]*=``naooE@>APSAAJ0,=J@2, }ÕÀiÊ£xÊÃ
ÜÃÊÌ
iÊÕÌ«ÕÌÊvÊouo*`i[ata_[_]_da`[lh]jo.
Figure 9-15. ouo*`i[ata_[_]_da`[lh]jo output showing plan simple parameterization using a template ÀÊ}ÕÀiÊ£x]ÊÞÕÊV>ÊÃiiÊÌ
>ÌÊ-+Ê-iÀÛiÀÊ>ÕÌ«>À>iÌiÀâi`ÊÌ
iÊ>`Ê
VʵÕiÀÞÊLÞÊ picking up a template with a pair of :9 and 89 operators, which are equivalent to the >APSAAJ operator. That means instead of resubmitting the preceding ad hoc query using the >APSAAJ clause, if a similar query using a pair of :9 and 89ÊÃÊÃÕLÌÌi`]Ê-+Ê-iÀÛiÀÊÜÊLiÊ>LiÊÌÊÀiÕÃiÊ Ì
iÊiÝÃÌ}ÊiÝiVÕÌÊ«>°Ê/ÊVvÀÊÌ
ÃÊLi
>ÛÀ]Êi̽ÃÊ`vÞÊÌ
iÊ>`Ê
VʵÕiÀÞÊ>ÃÊvÜÃ\ OAHA?P]*& BNKILanokj*=``naoo=O] SDANA]*=``naooE@:90,=J@]*=``naooE@892, }ÕÀiÊ£ÈÊÃ
ÜÃÊÌ
iÊÕÌ«ÕÌÊvÊouo*`i[ata_[_]_da`[lh]jo.
Figure 9-16. ouo*`i[ata_[_]_da`[lh]jo output showing reuse of the autoparameterized plan ÀÊ}ÕÀiÊ£È]ÊÞÕÊV>ÊÃiiÊÌ
>ÌÊÌ
iÊiÝÃÌ}Ê«>ÊÃÊÀiÕÃi`]ÊiÛiÊÌ
Õ}
ÊÌ
iʵÕiÀÞÊÃÊ syntactically different from the query executed earlier. The autoparameterized plan generated LÞÊ-+Ê-iÀÛiÀÊ>ÜÃÊÌ
iÊiÝÃÌ}Ê«>ÊÌÊLiÊÀiÕÃi`ÊÌÊÞÊÜ
iÊÌ
iʵÕiÀÞÊÃÊÀiÃÕLÌÌi`Ê with different variable values but also for queries with the same template form.
Simple Parameterization Limits -+Ê-iÀÛiÀÊà highly conservative during simple parameterization, because the cost of a bad plan can far outweigh the cost of generating a new plan. The conservative approach prevents -+Ê-iÀÛiÀÊvÀ creating an unsafe autoparameterized plan. Thus, simple parameterization is limited to fairly simple cases, such as ad hoc queries with only one table. An ad hoc query with >ÊÊ«iÀ>ÌÊLiÌÜiiÊÌÜÊÀÊÀi®ÊÌ>LiÃÊ>ÃÊÃ
ÜÊÊÌ
iÊi>ÀÞÊ«>ÀÌÊvÊÌ
iʺ*>Ê,iÕÃ>LÌÞÊvÊ>Ê`ÊVÊ7À>`»ÊÃiVÌ®ÊÃÊÌÊVÃ`iÀi`ÊÃ>viÊvÀÊëiÊ«>À>iÌiÀâ>̰ In a scalable system, do not rely on simple parameterization for plan reusability. The sim«iÊ«>À>iÌiÀâ>ÌÊvi>ÌÕÀiÊvÊ-+Ê-iÀÛiÀÊ>iÃÊ>Êi`ÕV>Ìi`Ê}ÕiÃÃÊ>ÃÊÌÊÜ
V
ÊÛ>À>LiÃÊ>`Ê VÃÌ>ÌÃÊV>ÊLiÊ«>À>iÌiÀâi`°ÊÃÌi>`ÊvÊÀiÞ}ÊÊ-+Ê-iÀÛiÀÊvÀÊëiÊ«>À>iÌiÀâ>Ì]Ê you should actually specify it programmatically while building your application.
262
CHAPTER 9 N EXEC U TION P L A N C A C HE A NA L YS IS
Forced Parameterization vÊÌ
iÊÃÞÃÌiÊÞÕ½Ài working on consists of primarily ad hoc queries, you may want to attempt to increase the number of queries that accept parameterization. You can modify a database to attempt to force, within certain restrictions, all queries to be parameterized just like in simple parameterization. To do this, you have to change the database option L=N=IAPANEV=PEKJ to BKN?A@ using =HPAN@=P=>=OA like this: =HPAN@=P=>=OA=`rajpqnaSkngo.,,4OAPL=N=IAPANEV=PEKJBKN?A@ But before you do, try running this query, which takes only two string literals as inputs and so could be a candidate for simple parameterization (bkn_a`[l]n]iapanev]pekj*omh in the download): OAHA?Pa]*Ai]eh=``naoo (a*>enpd@]pa (]*?epu BNKILanokj*Lanokj=Ol FKEJDqi]jNaokqn_ao*Ailhkuaa=Oa KJl*>qoejaooAjpepuE@9a*>qoejaooAjpepuE@ FKEJLanokj*>qoejaooAjpepu=``naoo=O^a] KJa*>qoejaooAjpepuE@9^a]*>qoejaooAjpepuE@ FKEJLanokj*=``naoo=O] KJ^a]*=``naooE@9]*=``naooE@ FKEJLanokj*Op]paLnkrej_a=Ool KJ]*Op]paLnkrej_aE@9ol*Op]paLnkrej_aE@ FKEJLanokj*Ai]eh=``naoo=Oa] KJl*>qoejaooAjpepuE@9a]*>qoejaooAjpepuE@ SDANAa]*Ai]eh=``naooHEGA#`]re`!# =J@ol*Op]paLnkrej_a?k`a9#S=#7 7
iÊÞÕÊÀÕÊÌ
ÃʵÕiÀÞ]ÊëiÊ«>À>iÌiÀâ>ÌÊÃÊÌÊ>««i`]Ê>ÃÊÞÕÊV>ÊÃiiÊÊ }ÕÀiʣǰ
Figure 9-17. A more complicated query doesn’t get parameterized. No prepared plans are visible in the output from ouo*`i[ata_[_]_da`[lh]jo. But if you use the previous script to set L=N=IAPANEV=PEKJ to BKN?A@, clear the cache, and rerun the query, the output from ouo*`i[ata_[_]_da`[lh]jo changes so that the output looks different, as shown in }ÕÀiÊ£n°
C H A P T E R 9 N E X E C U T I O N P LA N C A C H E A N A LY S I S
Figure 9-18. Forced parameterization changes the plan. ÜÊ>Ê«Ài«>Ài`Ê«>ÊÃÊÛÃLiÊÊÌ
iÊÌ
À`ÊÀܰÊÜiÛiÀ]ÊÞÊ>ÊÃ}iÊ«>À>iÌiÀÊÜ>ÃÊ supplied, <,r]n_d]n$4,,,%. If you get the full text of the prepared plan out of ouo*`i[ata_[ mqanu[patp and format it, it looks like this: $<,r]n_d]n$4,,,%% oaha_pa]*Ai]eh=``naoo (a*>enpd@]pa (]*?epu bnkiLanokj*Lanokj]ol fkejDqi]jNaokqn_ao*Ailhkuaa]oa kjl*>qoejaooAjpepuE@9a*>qoejaooAjpepuE@ fkejLanokj*>qoejaooAjpepu=``naoo]o^a] kja*>qoejaooAjpepuE@9^a]*>qoejaooAjpepuE@ fkejLanokj*=``naoo]o] kj^a]*=``naooE@9]*=``naooE@ fkejLanokj*Op]paLnkrej_a]ool kj]*Op]paLnkrej_aE@9ol*Op]paLnkrej_aE@ fkejLanokj*Ai]eh=``naoo]oa] kjl*>qoejaooAjpepuE@9a]*>qoejaooAjpepuE@ sdanaa]*Ai]eh=``naoohega#`]re`!# ]j`ol*Op]paLnkrej_a?k`a9<, Because of its restrictions, forced parameterization was unable to substitute anything for the string #`]re`!#, but it was able to for the string #S=#°Ê7ÀÌ
ÊÌ}ÊÃÊÌ
>ÌÊÌ
iÊÛ>À>LiÊ Ü>ÃÊ`iV>Ài`Ê>ÃÊ>ÊvÕÊn]äääi}Ì
ÊR=N?D=N instead of the three-character J?D=N like the actual column in the Lanokj*Op]paLnkrej_a table. Although you have a parameter, it might lead to implicit data conversions that could prevent the use of an index. Before you start using forced parameterization, the following list of restrictions may give you information to help you decide whether forced parameterization will work in your dataL>Ãi°Ê/
ÃÊÃÊ>Ê«>ÀÌ>ÊÃÌÆÊvÀÊÌ
iÊV«iÌiÊÃÌ]Ê«i>ÃiÊVÃÕÌÊ ÃÊ"i°® Ê
UÊ EJOANPÅATA?QPA queries
Ê
UÊ -Ì>ÌiiÌÃÊÃ`iÊ«ÀVi`ÕÀiÃ]ÊÌÀ}}iÀÃ]Ê>`ÊÕÃiÀ`ivi`ÊvÕVÌÃÊÃViÊÌ
iÞÊ>Ài>`ÞÊ have execution plans
Ê
UÊ iÌÃ`iÊ«Ài«>Ài`ÊÃÌ>ÌiiÌÃÊÞÕ½Êv`ÊÀiÊ`iÌ>ÊÊÌ
iÃiÊ>ÌiÀÊÊÌ
ÃÊV
>«ÌiÀ®
Ê
UÊ +ÕiÀiÃÊÜÌ
ÊÌ
iʵÕiÀÞÊ
ÌÊNA?KILEHA
Ê
UÊ *>ÌÌiÀÊ>`ÊiÃV>«iÊV>ÕÃiÊ>À}ÕiÌÃÊÕÃi`ÊÊ>ÊHEGA statement (as shown earlier)
263
264
CHAPTER 9 N EXEC U TION P L A N C A C HE A NA L YS IS
This gives you an idea of the types of restrictions placed on forced parameterization. ÀVi`Ê«>À>iÌiÀâ>ÌÊÃÊÀi>ÞÊ}}ÊÌÊLiÊ
i«vÕÊÞÊvÊÞÕÊ>ÀiÊÃÕvviÀ}ÊvÀÊ>À}iÊ >ÕÌÃÊvÊV«iÃÊ>`ÊÀiV«iÃÊLiV>ÕÃiÊvÊ>`Ê
VʵÕiÀiðÊÞÊÌ
iÀÊ>`ÊܽÌÊLiivÌÊ from the use of forced parameterization. Before continuing, change the database back to OEILHAL=N=IAPANEV=PEKJ: =HPAN@=P=>=OA=`rajpqnaSkngo.,,4OAPL=N=IAPANEV=PEKJOEILHA
Plan Reusability of a Prepared Workload iv}ʵÕiÀiÃÊ>à a prepared workload allows the variable parts of the queries to be explicitly «>À>iÌiÀâi`°Ê/
ÃÊi>LiÃÊ-+Ê-iÀÛiÀÊÌÊ}iiÀ>ÌiÊ>ʵÕiÀÞÊ«>ÊÌ
>ÌÊÃÊÌÊÌi`ÊÌÊÌ
iÊÛ>À>LiÊ parts of the query, and it keeps the variable parts separate in an execution context. As you saw ÊÌ
iÊ«ÀiÛÕÃÊÃiVÌ]Ê-+Ê-iÀÛiÀÊÃÕ««ÀÌÃÊÌ
ÀiiÊÌiV
µÕiÃÊÌÊÃÕLÌÊ>Ê«Ài«>Ài`ÊÜÀ>`\ Ê
UÊ -ÌÀi`Ê«ÀVi`ÕÀiÃ
Ê
UÊ ol[ata_qpaomh
Ê
UÊ *Ài«>ÀiÉiÝiVÕÌiÊ`i
In the sections that follow, I cover each of these techniques in more depth and point out Ü
iÀiÊ̽ÃÊ«ÃÃLiÊvÀÊ«>À>iÌiÀâi`ÊiÝiVÕÌÊ«>ÃÊÌÊV>ÕÃiÊ«ÀLið
Stored Procedures 1Ã}ÊÃÌÀi`Ê«ÀVi`ÕÀià is a standard technique for improving the effectiveness of plan V>V
}°Ê7
iÊÌ
iÊÃÌÀi`Ê«ÀVi`ÕÀiÊÃÊV«i`]Ê>ÊVLi`Ê«> is generated for all the -+ÊÃÌ>ÌiiÌÃÊÜÌ
ÊÌ
iÊÃÌÀi`Ê«ÀVi`ÕÀi°Ê/
iÊiÝiVÕÌÊ«>Ê}iiÀ>Ìi`ÊvÀÊÌ
iÊÃÌÀi`Ê procedure can be reused whenever the stored procedure is reexecuted with different parameter values. In addition to checking ouo*`i[ata_[_]_da`[lh]jo, you can track the execution plan caching for stored procedures using the *ÀviÀḚ̂Ê*ÀviÀÊ«ÀÛ`iÃÊÌ
iÊiÛiÌÃÊÃÌi`ÊÊ/>LiÊÓÊ to track the plan caching for stored procedures. Table 9-2. Events to Analyze Plan Caching for the Opkna`Lnk_a`qnao Event Class
Event
Description
OL6?]_daDep
*> is found in the cache
OL6?]_daIeoo
*> is not found in the cache
OL6Ata_?kjpatpDep
ÝiVÕÌ context for the stored procedure is found in the cache
To track the stored procedure plan V>V
}ÊÕÃ}Ê*ÀviÀ]ÊÞÕÊV>ÊÕÃiÊÌ
iÃiÊiÛiÌÃÊ>}Ê ÜÌ
ÊÌ
iÊÌ
iÀÊÃÌÀi`Ê«ÀVi`ÕÀiÊiÛiÌÃÊ>`Ê`>Ì>ÊVÕÃÊÃ
ÜÊÊ/>LiÊΰ
C H A P T E R 9 N E X E C U T I O N P LA N C A C H E A N A LY S I S
Table 9-3. Data Columns to Analyze Plan Caching for Opkna`Lnk_a`qnao Event Class
Event
Data Column
OL6?]_daDep
Arajp?h]oo
OL6?]_daIeoo
Patp@]p]
OL6?kilhapa`
HkcejJ]ia
OL6Ata_?kjpatpDep
OLE@
OL6Op]npejc
Op]npPeia
OL6Opip?kilhapa` To understand how stored procedures can improve plan caching, reexamine the procedure created earlier called ol>]oe_O]haoEjbk. The procedure (ol>]oe_O]haoEjbk*omh) is repeated here for clarity: EB$OAHA?PK>FA?P[E@$#ol>]oe_O]haoEjbk#%%EOJKPJQHH @NKLLNK?`^k*ol>]oe_O]haoEjbk CK ?NA=PALNK?`^k*ol>]oe_O]haoEjbk ]oe_O]haoEjbk
Figure 9-19. ouo*`i[ata_[_]_da`[lh]jo output showing stored procedure plan caching ÀÊ}ÕÀiÊ£]ÊÞÕÊV>ÊÃiiÊÌ
>ÌÊ>ÊV«i`Ê«>ÊvÊÌÞ«iÊLnk_ is generated and cached for the stored procedure. The qoa_kqjpoÊÛ>ÕiÊvÊÌ
iÊiÝiVÕÌ>LiÊ«>ÊÃÊ£ÊÃViÊÌ
iÊÃÌÀi`Ê«Àcedure is executed only once.
265
266
CHAPTER 9 N EXEC U TION P L A N C A C HE A NA L YS IS
}ÕÀiÊÓäÊÃ
ÜÃÊÌ
iÊ*ÀviÀÊÌÀ>ViÊÕÌ«ÕÌÊvÀÊÌ
ÃÊÃÌÀi`Ê«ÀVi`ÕÀiÊiÝiVÕ̰
Figure 9-20. Profiler trace output showing that the stored procedure plan isn’t easily found in the cache ÀÊÌ
iÊ*ÀviÀÊÌÀ>ViÊÕÌ«ÕÌ]ÊÞÕÊV>ÊÃiiÊÌ
>ÌÊÌ
iÊ«>ÊvÀÊÌ
iÊÃÌÀi`Ê«ÀVi`ÕÀiÊÃÊÌÊ vÕ`ÊÊÌ
iÊV>V
i°Ê7
iÊÌ
iÊÃÌÀi`Ê«ÀVi`ÕÀiÊÃÊiÝiVÕÌi`ÊÌ
iÊvÀÃÌÊÌi]Ê-+Ê-iÀÛiÀÊÃÊ in the procedure cache and fails to find any cache entry for the procedure ol>]oe_O]haoEjbk, causing an OL6?]_daIeooÊiÛḭÊ"ÊÌÊv`}Ê>ÊV>V
i`Ê«>]Ê-+Ê-iÀÛiÀÊ>iÃÊ>ÀÀ>}iiÌÃÊ ÌÊV«iÊÌ
iÊÃÌÀi`Ê«ÀVi`ÕÀi°Ê-ÕLÃiµÕiÌÞ]Ê-+Ê-iÀÛiÀÊ}iiÀ>ÌiÃÊ>`ÊÃ>ÛiÃÊÌ
iÊ«>Ê>`Ê proceeds with the execution of the stored procedure. If this stored procedure is reexecuted to retrieve a result set for ]oe_O]haoEjbk
Figure 9-21. ouo*`i[ata_[_]_da`[lh]jo output showing reuse of the stored procedure plan 9ÕÊV>Ê>ÃÊVvÀÊÌ
iÊÀiÕÃiÊvÊÌ
iÊiÝiVÕÌÊ«>ÊvÀÊÌ
iÊ*ÀviÀÊÌÀ>ViÊÕÌ«ÕÌ]Ê>ÃÊ Ã
ÜÊÊ}ÕÀiÊÓÓ°
Figure 9-22. Profiler trace output showing reuse of the stored procedure plan ÀÊÌ
iÊ*ÀviÀÊÌÀ>ViÊÕÌ«ÕÌ]ÊÞÕÊV>ÊÃiiÊÌ
>ÌÊÌ
iÊiÝÃÌ}Ê«>ÊÃÊvÕ`ÊÊÌ
iÊ«ÀVi`ÕÀiÊ V>V
i°Ê"ÊÃi>ÀV
}ÊÌ
iÊV>V
i]Ê-+Ê-iÀÛiÀÊv`ÃÊÌ
iÊiÝiVÕÌ>LiÊ«>ÊvÀÊÌ
iÊÃÌÀi`Ê«ÀVi`ÕÀiÊ l- causing an OL6?]_daDepÊiÛḭÊ"ViÊÌ
iÊiÝÃÌ}ÊiÝiVÕÌÊ«>ÊÃÊvÕ`]Ê-+ÊÀiÕÃiÃÊÌ
iÊ plan to execute the stored procedure. A few other aspects of stored procedures are worth considering: Ê
UÊ -ÌÀi`Ê«ÀVi`ÕÀiÃÊ>ÀiÊV«i`ÊÊvÀÃÌÊiÝiVÕ̰
Ê
UÊ -ÌÀi`Ê«ÀVi`ÕÀiÃÊ
>ÛiÊÌ
iÀÊ«iÀvÀ>ViÊLiivÌÃ]ÊÃÕV
Ê>ÃÊÀi`ÕV}ÊiÌÜÀÊÌÀ>vvV°
Ê
UÊ -ÌÀi`Ê«ÀVi`ÕÀiÃÊ
>ÛiÊ>``Ì>ÊLiivÌÃ]ÊÃÕV
Ê>ÃÊÌ
iÊÃ>ÌÊvÊÌ
iÊ`>Ì>°
C H A P T E R 9 N E X E C U T I O N P LA N C A C H E A N A LY S I S
Stored Procedures Are Compiled on First Execution The execution planÊvÊ>ÊÃÌÀi`Ê«ÀVi`ÕÀiÊÃÊ}iiÀ>Ìi`ÊÜ
iÊÌÊÃÊiÝiVÕÌi`ÊÌ
iÊvÀÃÌÊÌi°Ê7
iÊ the stored procedure is created, it is only parsed and saved in the database. No normalization and optimization processes are performed during the stored procedure creation. This allows a stored procedure to be created before creating all the objects accessed by the stored proVi`ÕÀi°ÊÀÊiÝ>«i]ÊÞÕÊV>ÊVÀi>ÌiÊÌ
iÊvÜ}ÊÃÌÀi`Ê«ÀVi`ÕÀi]ÊiÛiÊÜ
iÊÌ>LiÊjk[preferred to in the stored procedure does not exist: EB$OAHA?PK>FA?P[E@$#l-#%%EOJKPJQHH @NKLLNK?lCK ?NA=PALNK?l=O OAHA?P_-BNKIjk[p-))P]^hajk[p-`kaoj#pateop CK The stored procedure will be created successfully, since the normalization process to bind the referred object to the query tree (generated by the command parser during the stored procedure execution) is not performed during the stored procedure creation. The stored procedure will report the error when it is first executed (if table jk[p- is not created by then), since the stored procedure is compiled the first time it is executed. Other Performance Benefits of Stored Procedures Besides improving the performance through execution plan reusability, stored procedures provide the following performance benefits: Ê
UÊ Business logic is close to the data: The parts of the business logic that perform extensive «iÀ>ÌÃÊÊ`>Ì>ÊÃÌÀi`ÊÊÌ
iÊ`>Ì>L>ÃiÊÃ
Õ`ÊLiÊ«ÕÌÊÊÃÌÀi`Ê«ÀVi`ÕÀiÃ]ÊÃViÊ-+Ê -iÀÛiÀ½ÃÊi}iÊÃÊiÝÌÀiiÞÊ«ÜiÀvÕÊvÀÊÀi>Ì>Ê>`ÊÃiÌÊÌ
iÀÞÊ«iÀ>Ìð
Ê
UÊ Network traffic is reduced: The database application, across the network, sends just the name of the stored procedure and the parameter values. Only the processed result set ÃÊÀiÌÕÀi`ÊÌÊÌ
iÊ>««V>̰Ê/
iÊÌiÀi`>ÌiÊ`>Ì>Ê`iýÌÊii`ÊÌÊLiÊ«>ÃÃi`ÊL>VÊ and forth between the application and the database.
Additional Benefits of Stored Procedures -iÊvÊÌ
iÊÌ
iÀÊLiivÌÃÊ«ÀÛ`i`ÊLÞÊÃÌÀi`Ê«ÀVi`ÕÀiÃÊ>ÀiÊ>ÃÊvÜÃ\ Ê
UÊ The application is isolated from data structure changes. If all critical data access is made through stored procedures, then when the database schema changes, the stored procedures can be re-created without affecting the application code that accesses the data through the stored procedures. In fact, the application accessing the database need not even be stopped.
Ê
UÊ Security is increased°Ê1ÃiÀ privileges on database tables can be restricted and can be allowed only through the standard business logic implemented in the stored proce`ÕÀi°ÊÀÊiÝ>«i]ÊvÊÞÕÊÜ>ÌÊÕÃiÀÊq- to be restricted from physically deleting rows from table p- and to be allowed to mark only the rows virtually deleted through stored procedure l-ÊLÞÊÃiÌÌ}ÊÌ
iÊÀÜýÊÃÌ>ÌÕÃÊ>ÃÊ#@ahapa`#, then you can execute the @AJU and CN=JP commands as follows:
267
268
CHAPTER 9 N EXEC U TION P L A N C A C HE A NA L YS IS
EB$OAHA?PK>FA?P[E@$#p-#%%EOJKPJQHH @NKLP=>HApCK ?NA=PAP=>HAp-$_-EJP(op]pqoR=N?D=N$3%% EJOANPEJPKp-R=HQAO$-(#Jas#% CK EB$OAHA?PK>FA?P[E@$#l-#%%EOJKPJQHH @NKLLNK?lCK ?NA=PALNK?l<_-EJP =O QL@=PAp-OAPop]pqo9#@ahapa`#SDANA_-9<_CK ))Lnarajpqoanq-bnki`ahapejcnkso @AJU@AHAPAKJp-PKq))=hhksqoanq-pki]ng]nks]o#`ahapa`# CN=JPATA?QPAKJl-PKqCK This assumes the existence of user q-. Note that if the query within the stored procedure l- is built dynamically as a string (ÌÊ>ÞÊ«iÀÃÃÊÌÊÌ
iʵÕiÀÞ]ÊÃViÊÌ
iÊ`Þ>VʵÕiÀÞÊ Ã½ÌÊÌÀi>Ìi`Ê>ÃÊ«>ÀÌÊvÊÌ
iÊÃÌÀi`Ê«ÀVi`ÕÀi\ EB$OAHA?PK>FA?P[E@$#l-#%%EOJKPJQHH @NKLLNK?lCK ?NA=PALNK?l<_-EJP =O @A?H=NALiÊÌÊ>ÀÊÌ
iÊÀÜÊ>ÃÊ#@ahapa`# using the stored procedure l-. (I cover the aspects of using a dynamic query in the stored procedure in the next chapter.) Ê
UÊ There is a single point of administration: All the business logic implemented in stored procedures is maintained as part of the database and can be managed centrally on the database itself. Of course, this benefit is highly relative, depending on whom you ask. /Ê}iÌÊ>Ê`vviÀiÌÊ«]Ê>ÃÊ>Ê t
C H A P T E R 9 N E X E C U T I O N P LA N C A C H E A N A LY S I S
-ViÊÃÌÀi`Ê«ÀVi`ÕÀiÃÊ>ÀiÊÃ>Ûi`Ê>ÃÊ`>Ì>L>ÃiÊLiVÌÃ]ÊÌ
iÞÊ>``Êmaintenance overhead ÌÊÌ
iÊ`>Ì>L>ÃiÊ>`ÃÌÀ>̰Ê>ÞÊÌiÃ]ÊÞÕÊ>ÞÊii`ÊÌÊiÝiVÕÌiÊÕÃÌÊiÊÀÊ>ÊviÜʵÕiries from the application. If these singleton queries are executed frequently, you should aim to reuse their execution plans to improve performance. But creating stored procedures for these individual singleton queries adds a large number of stored procedures to the database, increasing the database administrative overhead significantly. To avoid the maintenance overhead of using stored procedures and yet derive the benefit of plan reuse, submit the singleton queries as a prepared workload using the ol[ata_qpaomh system stored procedure.
sp_executesql ol[ata_qpaomh is a system stored procedure that provides a mechanism to submit one or more queries as a prepared workload. It allows the variable parts of the query to be explicitly parameterized, and it can therefore provide execution plan reusability as effective as a stored procedure. The OAHA?P statement ol>]oe_O]haoEjbk*omh can be submitted through ol[ ata_qpaomh as follows (ata_qpaomh*omh in the download): @A?H=NA<mqanuJR=N?D=N$I=T% @A?H=NAÃÊÌ
iÊ«ÕÌÊ«>À>iÌiÀð The output of ouo*`i[ata_[_]_da`[lh]joÊÃÊÃ
ÜÊiÝÌÊÃiiÊ}ÕÀiÊÓή\ OAHA?P_*qoa_kqjpo (_*_]_dak^fpula (_*k^fpula (p*patp BNKIouo*`i[ata_[_]_da`[lh]jo_ ?NKOO=LLHUouo*`i[ata_[omh[patp$_*lh]j[d]j`ha%p
269
270
CHAPTER 9 N EXEC U TION P L A N C A C HE A NA L YS IS
Figure 9-23. ouo*`i[ata_[_]_da`[lh]jo output showing a parameterized plan generated using ol[ata_qpaomh Ê}ÕÀiÊÓÎ]ÊÞÕÊV>ÊÃiiÊÌ
>ÌÊÌ
iÊ«>ÊÃÊ}iiÀ>Ìi`ÊvÀÊÌ
iÊ«>À>iÌiÀâi`Ê«>ÀÌÊvÊÌ
iÊ query submitted through ol[ata_qpaomh]ÊiÊÓ°Ê-ViÊÌ
iÊ«>ÊÃÊÌÊÌi`ÊÌÊÌ
iÊÛ>À>LiÊ«>ÀÌÊvÊ the query, the existing execution plan can be reused if this query is resubmitted with a different value for one of the parameters (`*Lnk`q_pE@9333) as follows: Å ATA?ol[ata_qpaomh<mqanu(
Figure 9-24. ouo*`i[ata_[_]_da`[lh]jo output showing reuse of the parameterized plan generated using ol[ata_qpaomh ÀÊ}ÕÀiÊÓ{]ÊÞÕÊV>ÊÃiiÊÌ
>ÌÊÌ
iÊiÝÃÌ}Ê«> is reused (qoa_kqjpo is . on the plan ÊiÊÓ®ÊÜ
iÊÌ
iʵÕiÀÞÊÃÊÀiÃÕLÌÌi`ÊÜÌ
Ê>Ê`vviÀiÌÊÛ>À>LiÊÛ>Õi°ÊvÊÌ
ÃʵÕiÀÞÊÃÊÀiÃÕLmitted many times with different values for the variable part, the existing execution plan can be reused without regenerating new execution plans. The query for which the plan is created (the patp column) matches the exact textual string of the parameterized query submitted through ol[ata_qpaomh. Therefore, if the same query is submitted from different parts of the application, ensure that the same textual string is used Ê>Ê«>ViðÊÀÊiÝ>«i]ÊvÊÌ
iÊÃ>iʵÕiÀÞÊÃÊÀiÃÕLÌÌi`ÊÜÌ
Ê>ÊÀÊ`vV>ÌÊÊÌ
iÊ query string (sdana in lowercase instead of uppercase letters): Å OAP<mqanu9J#OAHA?Pokd*O]haoKn`anJqi^an (okd*Kn`an@]pa (ok`*Kn`anMpu (ok`*HejaPkp]h BNKIO]hao*O]haoKn`anDa]`an=Ookd FKEJO]hao*O]haoKn`an@ap]eh=Ook` KJokd*O]haoKn`anE@9ok`*O]haoKn`anE@
C H A P T E R 9 N E X E C U T I O N P LA N C A C H E A N A LY S I S
sdanaokd*?qopkianE@9
Figure 9-25. ouo*`i[ata_[_]_da`[lh]jo output showing sensitivity of the plan generated using ol[ata_qpaomh In general, use ol[ata_qpaomh to explicitly parameterize queries to make their execution plans reusable when the queries are resubmitted with different values for the variable parts. This provides the performance benefit of reusable plans without the overhead of managing any persistent object as required for stored procedures. This feature is exposed by both " Ê>`Ê" ÊÌ
ÀÕ}
ÊOMHAta_@ena_p and E?kii]j`SepdL]n]iapano]ÊÀiëiVÌÛiÞ°ÊÃʰ /Ê `iÛi«iÀÃÊÀÊÕÃiÀÃÊvÊ "° /Ê "ÊÓ°ÇÊÀÊ
}
iÀ®]ÊÞÕÊV>ÊÃÕLÌÊÌ
iÊ«ÀiVi`}ÊOAHA?P ÃÌ>ÌiiÌÊÕÃ}Ê "Ê?kii]j` and L]n]iapano°ÊvÊÞÕÊÃiÌÊÌ
iÊ "Ê?kii]j`Lnal]na` property to B=HOA and use "Ê?kii]j` (#OAHA?P&BNKIKn`an@ap]eho`(Kn`anokSDANA `*Kn`anE@9k*Kn`anE@]j``*Lnk`q_pE@9;#) with "ÊL]n]iapano]Ê "° /ÊÜÊÃi`ÊÌ
iÊ OAHA?P statement using ol[ata_qpaomh. Along with the parameters, ol[ata_qpaomh sends the entire query string across the netÜÀÊiÛiÀÞÊÌiÊÌ
iʵÕiÀÞÊÃÊÀiiÝiVÕÌi`°Ê9ÕÊV>Ê>Û`ÊÌ
ÃÊLÞÊÕÃ}ÊÌ
iÊ«Ài«>ÀiÉiÝiVÕÌiÊ modelÊvÊ" Ê>`Ê" ÊÀÊ" ʰ /®°
Prepare/Execute Model " Ê>`Ê" Ê«ÀÛ`iÊ>Ê«Ài«>ÀiÉiÝiVÕÌiÊ`iÊÌÊÃÕLÌʵÕiÀiÃÊ>ÃÊ>Ê«Ài«>Ài`Ê ÜÀ>`°ÊiÊol[ata_qpaomh, this model allows the variable parts of the queries to be paramiÌiÀâi`ÊiÝ«VÌÞ°Ê/
iÊ«Ài«>ÀiÊ«
>ÃiÊ>ÜÃÊ-+Ê-iÀÛiÀÊÌÊ}iiÀ>ÌiÊÌ
iÊiÝiVÕÌÊ«>ÊvÀÊÌ
iÊ query and return a handle of the execution plan to the application. This execution plan handle is used by the execute phase to execute the query with different parameter values. This model V>ÊLiÊÕÃi`ÊÞÊÌÊÃÕLÌʵÕiÀiÃÊÌ
ÀÕ}
Ê" ÊÀÊ" ]Ê>`ÊÌÊV>½ÌÊLiÊÕÃi`ÊÜÌ
Ê-+Ê -iÀÛiÀÊÌÃivpµÕiÀiÃÊÜÌ
ÊÃÌÀi`Ê«ÀVi`ÕÀiÃÊV>½ÌÊLiÊiÝiVÕÌi`ÊÕÃ}ÊÌ
ÃÊ`i° /
iÊ-+Ê-iÀÛiÀÊ" Ê`ÀÛiÀÊ«ÀÛ`iÃÊÌ
iÊOMHLnal]na and OMHAta_qpaÊ*ÃÊÌÊÃÕ««ÀÌÊ Ì
iÊ«Ài«>ÀiÉiÝiVÕÌiÊ`i°Ê/
iÊ-+Ê-iÀÛiÀÊ" Ê«ÀÛ`iÀÊiÝ«ÃiÃÊÌ
ÃÊ`iÊÌ
ÀÕ}
ÊÌ
iÊ E?kii]j`Lnal]naÊÌiÀv>Vi°Ê/
iÊ" ʰ /Ê«ÀÛ`iÀÊvÊ "° /ÊLi
>ÛiÃÊÃ>ÀÞ°
271
272
CHAPTER 9 N EXEC U TION P L A N C A C HE A NA L YS IS
NNote For a detailed description of how to use the prepare/execute model in a database application, please refer to the MSDN article “Preparing SQL Statements” (dppl6++io`j*ie_nkokbp*_ki+aj)qo+ he^n]nu+io-311.4*]olt).
Parameter Sniffing Although the goal of a well-defined workload is to get a plan into the cache that will be reused, ÌÊÃÊ«ÃÃLiÊÌÊ}iÌÊ>Ê«>ÊÌÊÌ
iÊV>V
iÊÌ
>ÌÊÞÕÊ`½ÌÊÜ>ÌÊÌÊÀiÕÃi°Ê/
iÊvÀÃÌÊÌiÊ>Ê«ÀVi`ÕÀiÊÃÊV>i`ÊLÞÊ-+Ê-iÀÛiÀ]ÊÌ
iÊÛ>ÕiÃÊÕÃi`Ê>ÀiÊVÕ`i`Ê>ÃÊ>Ê«>ÀÌÊvÊ}iiÀ>Ì}ÊÌ
iÊ«>°ÊvÊ Ì
iÃiÊÛ>ÕiÃÊ>ÀiÊÀi«ÀiÃiÌ>ÌÛiÊvÊÌ
iÊ`>Ì>Ê>`ÊÃÌ>ÌÃÌVÃ]ÊÌ
iÊÞÕ½Ê}iÌÊ>Ê}`Ê«>ÊÌ
>ÌÊÜÊ be beneficial to most executions of the stored procedure. But if the data is skewed in some fashion, it can seriously impact the performance of the query. ÀÊiÝ>«i]ÊÌ>iÊÌ
iÊvÜ}ÊÃÌÀi`Ê«ÀVi`ÕÀiÊol=``naoo>u?epu*omh in the download): EB$OAHA?PK>FA?P[E@$#ol=``naoo>u?epu#% %EOJKPJQHH @NKLLNK?`^k*ol=``naoo>u?epu CK ?NA=PALNK?`^k*ol=``naoo>u?epuu?epu `ÊiÝiVÕÌÊÌiÃÊ>ÃÊÜiÊ>ÃÊÌ
iʵÕiÀÞÊ«>ÊÊ }ÕÀiÊÓÈ\ P]^ha#=``naoo#*O_]j_kqjp-(hkce_]hna]`o.-2 P]^ha#Op]paLnkrej_a#*O_]j_kqjp-(hkce_]hna]`o/ ?LQpeia9,io(ah]loa`peia9-23io*
C H A P T E R 9 N E X E C U T I O N P LA N C A C H E A N A LY S I S
Figure 9-26. Execution plan of ol=``naoo>u?epu If the stored procedure is run again but this time with a different parameter: ATA?`^k*ol=``naoo>u?epu Ê`vviÀiÌÊÃiÌÊvÊÉ"Ê>`ÊiÝiVÕÌÊÌiÃÊLÕÌÊÌ
iÊÃ>iÊiÝiVÕÌÊ«>\ P]^ha#=``naoo#*O_]j_kqjp-(hkce_]hna]`o.-2 P]^ha#Op]paLnkrej_a#*O_]j_kqjp-(hkce_]hna]`o/ ?LQpeia9-2io(ah]loa`peia931io* /
iÊÉ"ÊÃÊÀÕ}
ÞÊÌ
iÊÃ>iÊÃViÊÌ
iÊÃ>iÊiÝiVÕÌÊ«>ÊÃÊÀiÕÃi`°Ê9ÕÊV>ÊÛiÀvÞÊ this by taking a look at the output from ouo*`i[ata_[_]_da`[lh]joÊÊ}ÕÀiÊÓÇ®°
Figure 9-27. Output from ouo*`i[ata_[_]_da`[lh]jo verifies procedure reuse To show how parameters affect execution plans, you can reverse the order of the execuÌÊvÊÌ
iÊ«ÀVi`ÕÀiðÊÀÃÌÊvÕÃ
ÊÌ
iÊLÕvviÀÊV>V
iÊLÞÊÀÕ}Ê@>??BNAALNK??=?DA. Then rerun the queries in reverse order. The first query, using the parameter Iajpkn, results in the follow}ÊÉ"Ê>`ÊiÝiVÕÌÊ«>Ê}ÕÀiÊÓn®\ P]^ha#Op]paLnkrej_a#*O_]j_kqjp,(hkce_]hna]`o. P]^ha#=``naoo#*O_]j_kqjp-(hkce_]hna]`o.-2 ?LQpeia9,io(ah]loa`peia932io
Figure 9-28. The execution plan changes.
273
274
CHAPTER 9 N EXEC U TION P L A N C A C HE A NA L YS IS
}ÕÀiÊÓnÊÃÊÌÊÌ
iÊÃ>iÊiÝiVÕÌÊ«>Ê>ÃÊÌ
>ÌÊÃ
ÜÊÊ}ÕÀiÊÓȰÊ/
iÊÕLiÀÊvÊ reads drops slightly, but the execution time stays roughly the same. The second execution, using Hkj`kjÊ>ÃÊÌ
iÊÛ>ÕiÊvÀÊÌ
iÊ«>À>iÌiÀ]ÊÀiÃÕÌÃÊÊÌ
iÊvÜ}ÊÉ"Ê>`ÊiÝiVÕÌÊÌiÃ\ P]^ha#Op]paLnkrej_a#*O_]j_kqjp,(hkce_]hna]`o424 P]^ha#=``naoo#*O_]j_kqjp-(hkce_]hna]`o.-2 ?LQpeia9,io(ah]loa`peia9.0/io* This time the reads are radically higher, and the execution time was increased by more Ì
>Ê£ääÊðÊ/
iÊ«>ÊVÀi>Ìi`ÊÊÌ
iÊvÀÃÌÊiÝiVÕÌÊvÊÌ
iÊ«ÀVi`ÕÀiÊÜÌ
ÊÌ
iÊ«>À>iÌiÀÊHkj`kj VÀi>Ìi`Ê>Ê«>ÊLiÃÌÊÃÕÌi`ÊÌÊÀiÌÀiÛiÊÌ
iÊ£]äää³ÊÀÜÃÊÌ
>ÌÊ>ÌV
ÊÌ
>ÌÊVÀÌiÀ>ÊÊÌ
iÊ`>Ì>L>Ãi°Ê Then the next execution of the procedure using the parameter value Iajpkn did well enough ÕÃ}ÊÌ
iÊÃ>iÊ«>Ê}iiÀ>Ìi`ÊLÞÊÌ
iÊvÀÃÌÊiÝiVÕ̰Ê7
iÊÌ
iÊÀ`iÀÊÃÊÀiÛiÀÃi`]Ê>ÊiÜÊiÝiVÕtion plan was created for the value Iajpkn that did not work at all well for the value Hkj`kj. This is a case where having an execution plan in the cache can hurt the performance of your queries. Once you identify the issue for a plan that works well some of the time but `iýÌÊÌ
iÀÃ]ÊÞÕÊ>Û`ÊÀÊvÝÊÌ
ÃÊ«ÀLiÊÊ>ÊÕLiÀÊvÊÜ>ÞÃ\ Ê
UÊ 9ÕÊV>ÊvÀViÊ>ÊÀiV«iÊvÊÌ
iÊ«>ÊiÌ
iÀÊ>ÌÊÌ
iÊÌi of execution by running ol[ na_kileha against the procedure prior to executing it or by getting it to recompile each time it executes by using the SEPDNA?KILEHA option.
Ê
UÊ ,i>ÃÃ}Ê«ÕÌÊ«>À>iÌiÀÃÊÌÊV>Ê«>À>iÌiÀðÊ/
ÃÊ««Õ>ÀÊvÝÊvÀViÃÊÌ
iÊ«ÌâiÀÊÌÊ make a best guess at the values likely to be used by looking at the statistics of the data being referenced, which can and does eliminate the values being taken into account. The problem with the solution is it eliminates the values being taken into account. You may get worse performing procedures overall.
Ê
UÊ 9ÕÊV>ÊÕÃiÊ>ʵÕiÀÞ hint, KLPEIEVABKN, when you create the procedure and supply it with known good parameters that will generate a plan that works well for most of your µÕiÀiðÊÜiÛiÀ]ÊÕ`iÀÃÌ>`ÊÌ
>ÌÊÃiÊ«iÀViÌ>}iÊvÊÌ
iʵÕiÀiÃÊܽÌÊÜÀÊÜiÊ with the parameter supplied.
Ê
UÊ 9ÕÊV>ÊÕÃiÊ>Ê«>Ê}Õ`i]ÊÜ
V
ÊÃÊ>ÊiV
>ÃÊÌÊ}iÌÊ>ʵÕiÀÞÊÌÊLi
>ÛiÊ>ÊViÀÌ>Ê way without making modifications to the procedure. This will be covered in detail in
>«ÌiÀÊ£ä°
Just remember that most of the time parameterized queries and plan reuse are not a problem. This type of situation is somewhat rare.
Query Plan Hash and Query Hash 7Ì
Ê-+Ê-iÀÛiÀÊÓään]ÊiÜÊvÕVÌ>ÌÞÊ>ÀÕ`ÊiÝiVÕÌÊ«>ÃÊ>`ÊÌ
iÊV>V
iÊÜ>ÃÊÌÀduced called the query plan hash and the query hash. These are binary objects using an algorithm against the query or the query plan to generate the binary hash value. These are useful for a very common practice in developing known as copy and paste. You will find that ÛiÀÞÊVÊ«>ÌÌiÀÃÊ>`Ê«À>VÌViÃÊÜÊLiÊÀi«i>Ìi`ÊÌ
ÀÕ}
ÕÌÊÞÕÀÊV`i°Ê1`iÀÊÌ
iÊLiÃÌÊ circumstances, this is a very good thing, because you will see the best types of queries, joins, set-based operations, and so on, copied from one procedure to another as needed. But sometimes, you will see the worst possible practices repeated over and over again in your code. This is where the query hash and the query plan hash come into play.
C H A P T E R 9 N E X E C U T I O N P LA N C A C H E A N A LY S I S
You can retrieve the query plan hash and the query hash from ouo*`i[ata_[mqanu[op]po or ouo*`i[ata_[namqaopo. Although this is a mechanism for identifying queries and their plans, Ì
iÊ
>Ã
ÊÛ>ÕiÃÊ>ÀiÊÌÊÕµÕi°Ê ÃÃ>ÀÊ«>ÃÊV>Ê>ÀÀÛiÊ>ÌÊÌ
iÊÃ>iÊ
>Ã
]ÊÃÊÞÕÊV>½ÌÊÀiÞÊ on this as an alternate primary key. To see the hash values in action, create two queries (mqanud]od-*omh and mqanud]od.*omh in the download): OAHA?P& BNKILnk`q_pekj*Lnk`q_p=Ol FKEJLnk`q_pekj*Lnk`q_pOq^_]packnu=Olo KJl*Lnk`q_pOq^_]packnuE@9lo*Lnk`q_pOq^_]packnuE@ FKEJLnk`q_pekj*Lnk`q_p?]packnu=Ol_ KJlo*Lnk`q_p?]packnuE@9l_*Lnk`q_p?]packnuE@ SDANAl_*WJ]iaY9#>egao# =J@lo*WJ]iaY9#Pkqnejc>egao#
OAHA?P& BNKILnk`q_pekj*Lnk`q_p=Ol FKEJLnk`q_pekj*Lnk`q_pOq^_]packnu=Olo KJl*Lnk`q_pOq^_]packnuE@9lo*Lnk`q_pOq^_]packnuE@ FKEJLnk`q_pekj*Lnk`q_p?]packnu=Ol_ KJlo*Lnk`q_p?]packnuE@9l_*Lnk`q_p?]packnuE@ sdanal_*WJ]iaY9#>egao# ]j`lo*WJ]iaY9#Nk]`>egao# Note that the only substantial difference between the two queries is that Lnk`q_pOq^?]packnu*J]ia is different, Pkqnejc>egao in one and Nk]`>egao in the other. ÜiÛiÀ]Ê>ÃÊÌiÊÌ
>ÌÊÌ
iÊSDANA and =J@ keywords in mqanud]od.*omh are lowercase. After you execute each of these queries, you can see the results of these format changes from ouo*`i[ata_[mqanu[op]poÊÊ}ÕÀiÊÓÊvÀÊÌ
iÊvÜ}ʵÕiÀÞ\ OAHA?Po*ata_qpekj[_kqjp (o*mqanu[d]od (o*mqanu[lh]j[d]od (p*patp BNKIouo*`i[ata_[mqanu[op]poo ?NKOO=LLHUouo*`i[ata_[omh[patp$o*lh]j[d]j`ha%p
Figure 9-29. ouo*`i[ata_[mqanu[op]po showing the plan hash values Two different plans were created because these are not parameterized queries, they are too complex to be considered for simple parameterization, and forced parameterization is off. These two plans have identical hash values because they varied only in terms of the values
275
276
CHAPTER 9 N EXEC U TION P L A N C A C HE A NA L YS IS
passed. The differences in case did not matter to the query hash or the query plan hash value. If, however, you changed the OAHA?P criteria in mqanud]od.*omh: OAHA?Pl*Lnk`q_pE@ BNKILnk`q_pekj*Lnk`q_p=Ol FKEJLnk`q_pekj*Lnk`q_pOq^_]packnu=Olo KJl*Lnk`q_pOq^_]packnuE@9lo*Lnk`q_pOq^_]packnuE@ FKEJLnk`q_pekj*Lnk`q_p?]packnu=Ol_ KJlo*Lnk`q_p?]packnuE@9l_*Lnk`q_p?]packnuE@ SDANAl_*WJ]iaY9#>egao# ]j`lo*WJ]iaY9#Pkqnejc>egao# then the values would be retrieved from ouo*`i[ata_[mqanu[op]po]Ê>ÃÊÃ
ÜÊÊ}ÕÀiÊÎä]Ê and the query would have changes.
Figure 9-30. ouo*`i[ata_[mqanu[op]po showing a different hash Although the basic structure of the query is the same, the change in the columns returned was enough to change the query hash value and the query plan hash value. Because differences in data distribution and indexes can cause the same query to come up with two different plans, the mqanu[d]od can be the same, and the mqanu[lh]j[d]od can be different. To illustrate this, create two new queries (mqanulh]jd]od-*omh and mqanulh]jd]od.* omh in the download): OAHA?Pl*WJ]iaY( pd]*Pn]jo]_pekj@]pa( pd]*Pn]jo]_pekjPula( pd]*Mq]jpepu( pd]*=_pq]h?kop BNKILnk`q_pekj*Pn]jo]_pekjDeopknu=n_derapd] FKEJLnk`q_pekj*Lnk`q_pl KJpd]*Lnk`q_pE@9l*Lnk`q_pE@ SDANAL*Lnk`q_pE@902-7
OAHA?Pl*WJ]iaY( pd]*Pn]jo]_pekj@]pa( pd]*Pn]jo]_pekjPula( pd]*Mq]jpepu( pd]*=_pq]h?kop BNKILnk`q_pekj*Pn]jo]_pekjDeopknu=n_derapd] FKEJLnk`q_pekj*Lnk`q_pl KJpd]*Lnk`q_pE@9l*Lnk`q_pE@ SDANAl*Lnk`q_pE@93-.7
C H A P T E R 9 N E X E C U T I O N P LA N C A C H E A N A LY S I S
iÊÌ
iÊÀ}>ʵÕiÀiÃÊÕÃi`Êi>ÀiÀ]ÊÌ
iÃiʵÕiÀiÃÊÛ>ÀÞÊÞÊLÞÊÌ
iÊÛ>ÕiÃÊ«>ÃÃi`ÊÌÊÌ
iÊ Lnk`q_pE@ÊVÕ°Ê7
iÊLÌ
ʵÕiÀiÃÊ>ÀiÊÀÕ]ÊÞÕÊV>ÊÃiiVÌÊ`>Ì>ÊvÀÊouo*`i[ata_[mqanu[ op]po to seeÊÌ
iÊ
>Ã
ÊÛ>ÕiÃÊ}ÕÀiÊΣ®°
Figure 9-31. Differences in the mqanu[lh]j[d]od You can see the mqanu[d]od values are identical, but the mqanu[lh]j[d]od values are different. This is because the execution plans created, based on the statistics for the values passed ]Ê>ÀiÊÀ>`V>ÞÊ`vviÀiÌ]Ê>ÃÊÞÕÊV>ÊÃiiÊÊ}ÕÀiÊÎÓ°
Figure 9-32. Different parameters result in radically different plans The query plan hash and the query hash values can be useful tools for tracking down VÊÃÃÕiÃÊLiÌÜiiÊ`ë>À>ÌiʵÕiÀiÃ]ÊLÕÌÊ>ÃÊÞÕ½ÛiÊÃii]ÊÌ
iÞ½ÀiÊÌÊ}}ÊÌÊÀiÌÀiÛiÊ>Ê accurate set of information in every possibility. They do add yet another useful tool in identifying other places where query performance could be poor. They can also be used to track execution plans over time. You can capture the mqanu[lh]j[d]od for a query after deploying it to production and then watch it over time to see whether it changes because of data changes. 7Ì
ÊÌ
ÃÊÞÕÊV>Ê>ÃÊii«ÊÌÀ>VÊvÊ>}}Ài}>Ìi`ʵÕiÀÞÊÃÌ>ÌÃÊLÞÊ«>]ÊÀiviÀiV}Êouo*`i[ata_[ mqanu[op]po, although, remember, the aggregated data is reset when the server is restarted. ii«ÊÌ
iÃiÊÌÃÊ mind while tuning your queries.
277
278
CHAPTER 9 N EXEC U TION P L A N C A C HE A NA L YS IS
Execution Plan Cache Recommendations The basic purpose of the plan cache is to improve performance by reusing execution plans. /
ÕÃ]ÊÌÊÃÊ«ÀÌ>ÌÊÌÊiÃÕÀiÊÌ
>ÌÊÞÕÀÊiÝiVÕÌÊ«>ÃÊ>VÌÕ>ÞÊ>ÀiÊÀiÕÃ>Li°Ê-ViÊÌ
iÊ«>Ê reusability of ad hoc queries is inefficient, it is generally recommended that you rely on prepared workload techniques as much as possible. To ensure efficient use of the plan cache, follow these recommendations: Ê
UÊ Ý«VÌÞÊ«>À>iÌiÀâiÊÛ>À>LiÊ«>ÀÌÃÊvÊ>ʵÕiÀÞ°
Ê
UÊ 1ÃiÊÃÌÀi`Ê«ÀVi`ÕÀiÃÊÌÊ«iiÌÊLÕÃiÃÃÊvÕVÌ>ÌÞ°
Ê
UÊ 1ÃiÊol[ata_qpaomh to avoid stored procedure maintenance.
Ê
UÊ 1ÃiÊÌ
iÊ«Ài«>ÀiÉiÝiVÕÌiÊ`iÊÌÊ>Û`ÊÀiÃi`}Ê>ʵÕiÀÞÊÃÌÀ}°
Ê
UÊ Û`Ê>`Ê
VʵÕiÀið
Ê
UÊ 1ÃiÊol[ata_qpaomh over ATA?QPA for dynamic queries.
Ê
UÊ *>À>iÌiÀâiÊÛ>À>LiÊ«>ÀÌÃÊvʵÕiÀiÃÊÜÌ
ÊV>Ài°
Ê
UÊ Û`Ê`vÞ}ÊiÛÀiÌÊÃiÌÌ}ÃÊLiÌÜiiÊViVÌð
Ê
UÊ Û`ÊÌ
iÊ«VÌÊÀiÃÕÌÊvÊLiVÌÃÊʵÕiÀið i̽ÃÊÌ>iÊ>ÊVÃiÀÊÊ>ÌÊÌ
iÃiÊ«Ìð
Explicitly Parameterize Variable Parts of a Query A query is often run several times, with the only difference between each run being that there are different values for the variable parts. Their plans can be reused, however, if the static and Û>À>LiÊ«>ÀÌÃÊvÊÌ
iʵÕiÀÞÊV>ÊLiÊÃi«>À>Ìi`°ÊÌ
Õ}
Ê-+Ê-iÀÛiÀÊ
>ÃÊ>ÊëiÊ«>À>iÌiÀization feature and a forced parameterization feature, they have severe limitations. Always perform parameterization explicitly using the standard prepared workload techniques.
Create Stored Procedures to Implement Business Functionality If you have explicitly parameterized your query, then placing it in a stored procedure brings Ì
iÊLiÃÌÊÀiÕÃ>LÌÞÊ«ÃÃLi°Ê-ViÊÞÊÌ
iÊ«>À>iÌiÀÃÊii`ÊÌÊLiÊÃiÌÊ>}ÊÜÌ
ÊÌ
iÊÃÌÀi`Ê «ÀVi`ÕÀiÊ>i]ÊiÌÜÀÊÌÀ>vvVÊÃÊÀi`ÕVi`°Ê-ViÊÃÌÀi`Ê«ÀVi`ÕÀiÃÊ>ÀiÊ«ÀiV«i`]ÊÌ
iÞÊ run faster than ad hoc queries. And stored procedures can also maintain a single parameterized plan for the set of queries included within the stored procedure, instead of maintaining a large number of small plans for the individual queries. This prevents the plan cache from being flooded with separate plans for the individual queries.
Code with sp_executesql to Avoid Stored Procedure Maintenance If the object maintenance required for the stored procedures becomes a consideration or you are using client-side generated queries, then use ol[ata_qpaomh to submit the queries as «Ài«>Ài`ÊÜÀ>`ðÊ1iÊÌ
iÊÃÌÀi`Ê«ÀVi`ÕÀiÊ`i]Êol[ata_qpaomhÊ`iýÌÊVÀi>ÌiÊ>ÞÊ persistent objects in the database. ol[ata_qpaomh is suited to execute a singleton query or a small batch query.
C H A P T E R 9 N E X E C U T I O N P LA N C A C H E A N A LY S I S
The complete business logic implemented in a stored procedure can also be submitted with ol[ata_qpaomhÊ>ÃÊ>Ê>À}iʵÕiÀÞÊÃÌÀ}°ÊÜiÛiÀ]Ê>ÃÊÌ
iÊV«iÝÌÞÊvÊÌ
iÊLÕÃiÃÃÊ}VÊ increases, it becomes difficult to create and maintain a query string for the complete logic.
Implement the Prepare/Execute Model to Avoid Resending a Query String ol[ata_qpaomh requires the query string to be sent across the network every time the query is reexecuted. It also requires the cost of a query string match at the server to identify the correë`}ÊiÝiVÕÌÊ«>ÊÊÌ
iÊ«ÀVi`ÕÀiÊV>V
i°ÊÊÌ
iÊV>ÃiÊvÊ>Ê" ÊÀÊ" ÊÀÊ" Ê ° /®Ê>««V>Ì]ÊÞÕÊV>ÊÕÃiÊÌ
iÊ«Ài«>ÀiÉiÝiVÕÌiÊ`iÊÌÊ>Û`ÊÀiÃi`}ÊÌ
iʵÕiÀÞÊÃÌÀ}Ê during multiple executions, since only the plan handle and parameters need to be submitted. ÊÌ
iÊ«Ài«>ÀiÉiÝiVÕÌiÊ`i]ÊÃViÊ>Ê«>Ê
>`iÊÃÊÀiÌÕÀi`ÊÌÊÌ
iÊ>««V>Ì]ÊÌ
iÊ«>Ê V>ÊLiÊÀiÕÃi`ÊLÞÊÌ
iÀÊÕÃiÀÊViVÌÃÆÊÌÊÃÊÌÊÌi`ÊÌÊÌ
iÊÕÃiÀÊÜ
ÊVÀi>Ìi`ÊÌ
iÊ«>°
Avoid Ad Hoc Queries ÊÌÊ`iÃ}ÊiÜÊ>««V>ÌÃÊÕÃ}Ê>`Ê
VʵÕiÀiÃtÊ/
iÊiÝiVÕÌÊ«>ÊVÀi>Ìi`ÊvÀÊ>Ê>`Ê
VÊ query cannot be reused when the query is resubmitted with a different value for the variable «>ÀÌÃ°Ê ÛiÊÌ
Õ}
Ê-+Ê-iÀÛiÀÊ
>ÃÊÌ
iÊëiÊ«>À>iÌiÀâ>ÌÊ>`ÊvÀVi`Ê«>À>iÌiÀâ>ÌÊ vi>ÌÕÀiÃÊÌÊÃ>ÌiÊÌ
iÊÛ>À>LiÊ«>ÀÌÃÊvÊÌ
iʵÕiÀÞ]ÊLiV>ÕÃiÊvÊÌ
iÊÃÌÀVÌÊVÃiÀÛ>ÌÛiiÃÃÊvÊ-+Ê -iÀÛiÀÊÊ«>À>iÌiÀâ>Ì]ÊÌ
iÊvi>ÌÕÀiÊÃÊÌi`ÊÌÊëiʵÕiÀiÃÊÞ°ÊÀÊLiÌÌiÀÊ«>ÊÀiÕÃability, submit the queries as prepared workloads.
Prefer sp_executesql over EXECUTE for Dynamic Queries -+ʵÕiÀÞÊÃÌÀ}à generated dynamically within stored procedures or a database application should be executed using ol[ata_qpaomh instead of the ATA?QPA command. The ATA?QPA com>`Ê`iýÌÊ>ÜÊÌ
iÊÛ>À>LiÊ«>ÀÌÃÊvÊÌ
iʵÕiÀÞÊÌÊLiÊiÝ«VÌÞÊ«>À>iÌiÀâi`° To understand the preceding comparison between ol[ata_qpaomh and ATA?QPA, consider Ì
iÊ`Þ>VÊ-+ʵÕiÀÞÊÃÌÀ}ÊÕÃi`ÊÌÊiÝiVÕÌiÊÌ
iÊOAHA?P statement in ]`dk_[olnk_*omh: @A?H=NA<jR=N?D=N$/% OAP<j9#332# @A?H=NA`ÊÌ
iÀiLÞÊv>ViÃÊÌ
iÊVÃiÀÛ>ÌÛiiÃÃÊvÊëiÊ«>À>iÌiÀâ>̰ÊÀÊ«ÀÛi`Ê «>ÊV>V
iÊÀiÕÃ>LÌÞ]ÊiÝiVÕÌiÊÌ
iÊ`Þ>VÊ-+ÊÃÌÀ}Ê>ÃÊ>Ê«>À>iÌiÀâi`ʵÕiÀÞÊÕÃ}Êol[ ata_qpaomh:
279
280
CHAPTER 9 N EXEC U TION P L A N C A C HE A NA L YS IS
@A?H=NA<jEJP OAP<j9332 @A?H=NA
ÝiVÕÌ}ÊÌ
iʵÕiÀÞÊ>ÃÊ>ÊiÝ«VÌÞÊ«>À>iÌiÀâi`ʵÕiÀÞÊÕÃ}Êol[ata_qpaomh generates a parameterized plan for the query and thereby increases the execution plan reusability.
Parameterize Variable Parts of Queries with Care Be careful while converting variable parts of a query into parameters. The range of values for some variables may vary so drastically that the execution plan for a certain range of values >ÞÊÌÊLiÊÃÕÌ>LiÊvÀÊÌ
iÊÌ
iÀÊÛ>ÕiðÊ/
ÃÊV>Êi>`ÊÌÊ«>À>iÌiÀÊÃvv}°Ê i>ÊÜÌ
ÊÌ
ÃÊ>ÃÊ needed within the situation.
Do Not Allow Implicit Resolution of Objects in Queries -+Ê-iÀÛiÀÊ>ÜÃÊÕÌ«iÊ`>Ì>L>Ãi objects with the same name to be created under different ÃV
i>ðÊÀÊiÝ>«i]ÊÌ>LiÊp- can be created using two different schemas (q- and q.) under their individual ownership. The default owner in most systems is `^k (database owner). If user q- executes the following query: OAHA?P&BNKIp-SDANA_-9Ì
iÊ-+Ê-iÀÛiÀÊvÀÃÌÊÌÀiÃÊÌÊv`ÊÜ
iÌ
iÀÊÌ>LiÊp- exists for user q-½ÃÊ`iv>ÕÌÊÃV
i>°ÊvÊÌ]Ê then it tries to find whether table p- exists for the `^k user. This implicit resolution allows user q- to create another instance of table p- under a different schema and access it temporarily (using the same application code) without affecting other users. On a production database, I recommend using the schema owner and avoiding implicit resolution. If not, using implicit resolution adds the following overhead on a production server: Ê
UÊ ÌÊÀiµÕÀiÃÊÀiÊÌiÊÌÊ`iÌvÞÊÌ
iÊLiVÌð
Ê
UÊ ÌÊ`iVÀi>Ãià the effectiveness of plan cache reusability.
C H A P T E R 9 N E X E C U T I O N P LA N C A C H E A N A LY S I S
Summary -+Ê-iÀÛiÀ½ÃÊVÃÌL>Ãi`ʵÕiÀÞÊ«ÌâiÀÊ`iV`iÃÊÕ«Ê>ÊivviVÌÛiÊiÝiVÕÌÊ«>ÊÌÊL>Ãi`Ê on the exact syntax of the query but by evaluating the cost of executing the query using different processing strategies. The cost evaluation of using different processing strategies is done in multiple optimization phases to avoid spending too much time optimizing a query. Then, the execution plans are cached to save the cost of execution plan generation when the same queries areÊÀiiÝiVÕÌi`°Ê/Ê«ÀÛiÊÌ
iÊÀiÕÃ>LÌÞÊvÊV>V
i`Ê«>Ã]Ê-+Ê-iÀÛiÀÊÃÕ««ÀÌÃÊ`vviÀiÌÊ techniques for execution plan reuse when the queries are rerun with different values for the variable parts. 1Ã}ÊÃÌÀi`Ê«ÀVi`ÕÀiÃÊÃÊÕÃÕ>ÞÊÌ
iÊLiÃÌÊÌiV
µÕiÊÌÊ«ÀÛiÊiÝiVÕÌÊ«>ÊÀiÕÃ>LÌÞ°Ê-+Ê-iÀÛiÀÊ}iiÀ>ÌiÃÊ>Ê«>À>iÌiÀâi`ÊiÝiVÕÌÊ«>ÊvÀÊÌ
iÊÃÌÀi`Ê«ÀVi`ÕÀiÃÊÃÊÌ
>ÌÊ the existing plan can be reused when the stored procedure is rerun with the same or different «>À>iÌiÀÊÛ>ÕiðÊÜiÛiÀ]ÊvÊÌ
iÊiÝÃÌ}ÊiÝiVÕÌÊ«>ÊvÀÊ>ÊÃÌÀi`Ê«ÀVi`ÕÀiÊÃÊÛ>`>Ìi`]Ê Ì
iÊ«>ÊV>½ÌÊLiÊÀiÕÃi`ÊÜÌ
ÕÌÊ>ÊÀiV«>Ì]Ê`iVÀi>Ã}ÊÌ
iÊivviVÌÛiiÃÃÊvÊ«>ÊV>V
iÊ reusability. In the next chapter, I will discuss how to troubleshoot and resolve unnecessary stored procedure plan recompilations.
281
CHAPTER
10
Stored Procedure Recompilation S
tored procedures improve the reusability of an execution plan by explicitly converting the variable parts of the queries into parameters. This allows execution plans to be reused when the queries are resubmitted with the same or different values for the variable parts. Since stored procedures are mostly used to implement complex business rules, a typical stored procedure contains a complex set of SQL statements, making the price of generating the execution plan of a stored procedure a bit costly. Therefore, it is usually beneficial to reuse the existing execution plan of a stored procedure instead of generating a new plan. However, sometimes the existing plan may not be applicable, or it may not provide the best processing strategy during reuse. SQL Server resolves this condition by recompiling statements within stored procedures to generate a new execution plan. In this chapter, I cover the following topics: Ê
UÊ /
iÊLiivÌÃÊ>`Ê`À>ÜL>VÃÊvÊÀiV«>Ì
Ê
UÊ ÜÊÌÊ`iÌvÞÊÌ
iÊÃÌ>ÌiiÌÃÊV>ÕÃ}ÊÀiV«>Ì
Ê
UÊ ÜÊÌÊ>>ÞâiÊÌ
iÊV>ÕÃiÃÊvÊÀiV«>ÌÃ
Ê
UÊ 7>ÞÃÊÌÊ>Û`ÊÀiV«>ÌÃ
Benefits and Drawbacks of Recompilation The recompilation of stored procedures can be both beneficial and harmful. Sometimes, it may be beneficial to consider a new processing strategy for a query instead of reusing the existing plan, especially if the data distribution in the table (or the corresponding statistics) has changed or new indexes are added to the table. Recompiles in SQL Server 2008 are at the statement level. This increases the overall number of recompiles that can occur within a procedure, but it reduces the effects and overhead of recompiles in general. Statement-level recompiles reduce overhead because they recompile an individual statement only rather
283
284
C H A P T E R 1 0 N S T O R E D P R O C E D U R E R E C O M P I L A T I O N
than all the statements within a procedure, whereas the older method of recompiles caused a procedure, in its entirety, to be recompiled over and over. Despite this smaller footprint for recompiles, it’s something to be reduced and controlled as much as possible. To understand how the recompilation of an existing plan can sometimes be beneficial, assume you need to retrieve some information from the Lnk`q_pekj*SkngKn`an table. The stored procedure may look like this (olSkngKn`an*omh in the download): EB$OAHA?PK>FA?P[E@$#`^k*olSkngKn`an#% %EOJKPJQHH @NKLLNK?A@QNA`^k*olSkngKn`an7 CK ?NA=PALNK?A@QNA`^k*olSkngKn`an =O OAHA?Psk*SkngKn`anE@ (sk*Lnk`q_pE@ (sk*Opk_ga`Mpu BNKILnk`q_pekj*SkngKn`an=Osk SDANAsk*Opk_ga`Mpu>APSAAJ1,,=J@3,,7 7Ì
ÊÌ
iÊVÕÀÀiÌÊ`iÝiÃ]ÊÌ
iÊiÝiVÕÌÊ«>ÊvÀÊÌ
iÊOAHA?P statement, which is part of the stored procedure plan, scans the index LG[SkngKn`an[E@, as shown in Figure 10-1.
Figure 10-1. Execution plan for the stored procedure This plan is saved in the procedure cache so that it can be reused when the stored procedure is reexecuted. But if a new index is added on the table as follows, then the existing plan won’t be the most efficient processing strategy to execute the query: ?NA=PAEJ@ATET[PaopKJLnk`q_pekj*SkngKn`an$Opk_ga`Mpu(Lnk`q_pE@% Since index ET[Paop can serve as a covering index for the OAHA?P statement, the cost of a bookmark lookup can be avoided by using index ET[Paop instead of scanning LG[SkngKn`an[ SkngKn`anE@. SQL Server automatically detects this change and thereby recompiles the existing plan to consider the benefit of using the new index. This results in a new execution plan for the stored procedure (when executed), as shown in Figure 10-2.
Figure 10-2. New execution plan for the stored procedure In this case, it is beneficial to spend extra CPU cycles to recompile the stored procedure so that you generate a better execution plan.
C H A P T E R 1 0 N S T O R E D P R O C E D U R E R E C O M P I L A T I O N
SQL Server automatically detects the conditions that require a recompilation of the existing plan. SQL Server follows certain rules in determining when the existing plan needs to be recompiled. If a specific implementation of a stored procedure falls within the rules of recompilation (execution plan aged out, OAP options changed, and so on), then the stored procedure will be recompiled every time it meets the requirements for a recompile, and SQL Server may not generate a better execution plan. To see this in action, you’ll need a different stored procedure. The following procedure returns all the rows from the SkngKn`an table (olSkngKn`an=hh* omh in the download): EB$OAHA?PK>FA?P[E@$#`^k*olSkngKn`an=hh#% %EOJKPJQHH @NKLLNK?A@QNA`^k*olSkngKn`an=hh7 CK ?NA=PALNK?A@QNA`^k*olSkngKn`an=HH =O OAHA?P& BNKILnk`q_pekj*SkngKn`an=Osk Before executing this procedure, drop the index ET[Paop: @NKLEJ@ATLnk`q_pekj*SkngKn`an*ET[Paop 7
iÊÞÕÊiÝiVÕÌiÊÌ
ÃÊ«ÀVi`ÕÀi]ÊÌ
iÊOAHA?P statement returns the complete data set (all rows and columns) from the table and is therefore best served through a table scan on the table SkngKn`an. As explained in Chapter 4, the processing of the OAHA?P statement won’t benefit from a nonclustered index on any of the columno. Therefore, ideally, creating the nonclustered index, as follows, before the execution of the stored procedure shouldn’t matter: ATA?`^k*olSkngKn`an=hh CK ?NA=PAEJ@ATET[PaopKJLnk`q_pekj*SkngKn`an$Opk_ga`Mpu(Lnk`q_pE@% CK ATA?`^k*olSkngKn`an=hh))=bpan_na]pekjkbej`atET[Paop But the stored procedure execution after the index creation faces recompilation, as shown in the corresponding Profiler trace output in Figure 10-3.
Figure 10-3. Nonbeneficial recompilation of the stored procedure The OL6Na_kileha event was used to trace the statement recompiles. You can also use the OMH6OpipNa_kileha event, even with stored procedures. In this case, the recompilation is of no real benefit to the stored procedure. But unfortunately, it falls within the conditions that cause SQL Server to recompile the stored procedure on every execution. This makes plan
285
286
CHAPTER 10 N ST OR ED P R OC EDU R E R EC OMP IL A TIO N
caching for the stored procedure ineffective and wastes CPU cycles in regenerating the same plan on this execution. Therefore, it is important to be aware of the conditions that cause the recompilation of stored procedures and to make every effort to avoid those conditions when implementing stored procedures. I will discuss these conditions next, after identifying which statements cause SQL Server to recompile the stored procedure in the respective case.
Identifying the Statement Causing Recompilation SQL Server can recompile individual statements within a procedure or the entire procedure. Thus, to find the cause of recompilation, it’s important to identify the SQL statement that can’t reuse the existing plan. You can use the Profiler tool to track stored procedure recompilation. You can also use Profiler to identify the stored procedure statement that caused the recompilation. Table 10-1 shows the relevant events and data columns you can use (olnk_Pn]_a*p`b in the download). Table 10-1. Events and Data Columns to Analyze Stored Procedure Recompilation for Opkna`Lnk_a`qnao Event Class
Event
Data Column
OL6?kilhapa`
Arajp?h]oo
OL6Na_kileha
Patp@]p]
OL6Op]npejc
ArajpOq^?h]oo
OL6Opip?kilhapa` (Optional)
OLE@
OL6OpipOp]npejc (Optional)
Op]npPeia
Consider the following simple stored procedure (_na]pa[l-*omh in the download): EB$OAHA?PK>FA?P[E@$#`^k*l-#%%EOJKPJQHH @NKLLNK?`^k*l-7 CK ?NA=PALNK?`^k*l=O ?NA=PAP=>HAp-$_-EJP%7 EJOANPEJPKp-$ _%R=HQAO$0.%7))`]p]_d]jca_]qoaona_kileha CK On executing this stored procedure the first time, you get the Profiler trace output shown in Figure 10-4. ATA?`^k*l-7
C H A P T E R 1 0 N S T O R E D P R O C E D U R E R E C O M P I L A T I O N
Figure 10-4. Profiler trace output showing an OL6Opip?kilhapa` event causing recompilation In Figure 10-4, you can see that you have a recompilation event (OL6Na_kileha), indicating Ì
>ÌÊÌ
iÊÃÌÀi`Ê«ÀVi`ÕÀiÊÜiÌÊÌ
ÀÕ}
ÊÀiV«>̰Ê7
iÊ>ÊÃÌÀi`Ê«ÀVi`ÕÀiÊÃÊiÝiVÕÌi`Ê for the first time, SQL Server compiles the stored procedure and generates an execution plan, as explained in the previous chapter. Since execution plans are maintained in volatile memory only, they get dropped when SQL Server is restarted. On the next execution of the stored procedure, after the server restart, SQL Server once again compiles the stored procedure and generates the execution plan. These compilations aren’t treated as a stored procedure recompilation, since a plan didn’t exist in the cache for reuse. An OL6Na_kileha event indicates that a plan was already there but couldn’t be reused.
NNote I discuss the significance of the ArajpOq^?h]oo data column later in the “Analyzing Causes of Recompilation” section.
To see which statement caused the recompile, look at the Patp@]p] column within the OL6Na_kileha event. It shows specifically the statement being recompiled. You can also identify the stored procedure statement causing the recompilation by using the OL6OpipOp]npejc event in combination with a recompile event. The OL6OpipOp]npejc event immediately before the OL6Na_kileha event indicates the stored procedure statement that caused the recompilation, as shown in Figure 10-5. It’s generally easier to use the Patp@]p] column, but in very complicated procedures it might make sense to use the OL6OpipOp]npejc event.
Figure 10-5. Profiler trace output showing an OL6OpipOp]npejc event causing recompilation Note that after the stored procedure recompilation, the stored procedure statement that caused the recompilation is started again to execute with the new plan. You may use either the OL6OpipOp]npejc event or the OL6Opip?kilhapa` event to identify the stored procedure statement causing the recompilation; using both the events will duplicate the information, and the OL6Na_kileha event will further duplicate the information.
287
288
CHAPTER 10 N ST OR ED P R OC EDU R E R EC OMP IL A TIO N
Analyzing Causes of Recompilation To improve performance, it isÊ«ÀÌ>ÌÊÌ
>ÌÊÞÕÊ>>ÞâiÊÌ
iÊV>ÕÃiÃÊvÊÀiV«>̰Ê"vÌi]Ê recompilation may not be necessary, and you can avoid it to improve performance. Knowing the different conditions that result in recompilation helps you evaluate the cause of a recompilation and determine how to avoid recompiling when it isn’t necessary. Stored procedure recompilation occurs for the following reasons: Ê
UÊ /
iÊschema of regular tables, temporary tables, or views referred to in the stored procedure statement have changed. Schema changes include changes to the metadata of the table or the indexes on the table.
Ê
UÊ `}ÃÊÃÕV
Ê>ÃÊ`iv>ÕÌÃÉÀÕiîÊÌ the columns of regular or temporary tables have changed.
Ê
UÊ -Ì>ÌÃÌVÃ on the table indexes or columns have changed past a certain threshold.
Ê
UÊ ÊLiVÌÊ``ÊÌÊiÝÃÌÊÜ
iÊÌ
iÊÃÌÀi`Ê«ÀVi`ÕÀiÊÜ>ÃÊV«i`]ÊLÕÌÊÌÊÜ>ÃÊVÀi>Ìi`Ê during execution. This is called deferred object resolution, which is the cause of the preceding recompilation.
Ê
UÊ OAP options have changed.
Ê
UÊ /
iÊiÝiVÕÌÊ«>ÊÜ>ÃÊ>}i`Ê>`Ê`i>V>Ìi`°
Ê
UÊ ÊiÝ«VÌÊV>ÊÜ>ÃÊ>`iÊÌÊÌ
iÊol[na_kileha system stored procedure.
Ê
UÊ /
iÀiÊÜ>ÃÊ>ÊiÝ«VÌÊÕÃiÊvÊÌ
iÊNA?KILEHA clause.
You can see these changes in Profiler. The cause is indicated by the ArajpOq^?h]oo data column value for the OL6Na_kileha event, as shown in Table 10-2. Table 10-2. ArajpOq^?h]oo Data Column Reflecting Causes of Recompilation ArajpOq^?h]oo
Description
1
Schema or bindings to regular table or view changed
2
Statistics changed
3
"LiVÌÊ``ÊÌÊiÝÃÌÊÊÌ
iÊÃÌÀi`Ê«ÀVi`ÕÀiÊ«>ÊLÕÌÊÜ>ÃÊVÀi>Ìi`Ê`ÕÀ}Ê execution
4
OAP options changed
5
Schema or bindings to temporary table changed
6
Schema or bindings of remote rowset changed
7
BKN>NKSOA permissions changed
8
Query notification environment changed
9
MPI view changed
10
Cursor options changed
11
SEPDNA?KILEHA option invoked
Let’s look at some of the reasons listed in Table 10-2 for recompilation in more detail and discuss what you can do to avoid them.
C HA P T E R 1 0 N S T O R E D P R O C E D U R E R E C O M P I LA T I O N
Schema or Bindings Changes 7
i the schema or bindings to a view, regular table, or temporary table change, the existing stored procedure execution plan becomes invalid. The stored procedure must be recompiled LivÀiÊiÝiVÕÌ}Ê>ÞÊÃÌ>ÌiiÌÊÌ
>ÌÊÀiviÀÃÊÌÊÃÕV
Ê>ÊLiV̰Ê-+Ê-iÀÛiÀÊ>ÕÌ>ÌV>ÞÊ`iÌiVÌÃÊ this situation and recompiles the stored procedure.
NNote I talk about recompilation due to schema changes in more detail in the “Benefits and Drawbacks of Recompilation” section.
Statistics Changes SQL Server keeps track of the number of changes to the table. If the number of changes exceeds the recompilation threshold (RT) value, then SQL Server automatically updates the ÃÌ>ÌÃÌVÃÊÜ
iÊÌ
iÊÌ>LiÊÃÊÀiviÀÀi`ÊÌÊÊÌ
iÊÃÌÀi`Ê«ÀVi`ÕÀi]Ê>ÃÊÞÕÊÃ>ÜÊÊ
>«ÌiÀÊǰÊ7
iÊ the condition for the automatic update of statistics is detected, SQL Server automatically recompiles the stored procedure, along with the statistics update. The RT is determined by a set of formula that depend on the table being a permanent table or a temporary table (not a table variable) and how many rows are in the table. Table 10-3 shows the basic formula so that you can determine when you can expect to see a statement recompile because of data changes. Table 10-3. Formula for Determining Data Changes
Type of Table
Formula
Permanent table
If number of rows (n) <= 500, RT = 500 IF n > 500, RT = 500 + .2 * n
Temporary table
If n < 6, RT = 6 If 6 <= n <= 500, RT = 500 IF n > 500, RT = 500 + .2 * n
To understand how statistics changes can cause recompilation, consider the following example (op]po[_d]jcao*omh in the download). The stored procedure is executed the first time with only one row in the table. Before the second execution of the stored procedure, a large number of rows are added to the table.
NNote Please ensure that the =QPK[QL@=PA[OP=PEOPE?O setting for the database is KJ. You can determine the =QPKQL@=PAOP=PEOPE?O setting by executing the following query: OAHA?P@=P=>=OALNKLANPU AT$#=`rajpqnaSkngo.,,4#(#Eo=qpkQl`]paOp]peope_o#%.
289
290
CHAPTER 10 N ST OR ED P R OC EDU R E R EC OMP IL A TIO N
EBATEOPO$OAHA?P& BNKIouo*k^fa_po SDANAK>FA?P[E@9K>FA?P[E@$J#W`^kY*WJasKn`anoY#% =J@PULAEJ$J#Q#%% @NKLP=>HAW`^kY*WJasKn`anoY7 CK OAHA?P& EJPK`^k*JasKn`ano BNKIo]hao*O]haoKn`an@ap]eh7 CK ?NA=PAEJ@ATET[JasKn`ano[Lnk`q_pE@KJ`^k*JasKn`ano$Lnk`q_pE@%7 CK EBATEOPO$OAHA?P& BNKIouo*k^fa_po SDANAk^fa_p[e`9K>FA?P[E@$J#W`^kY*WolJasKn`anoY#% =J@pulaEJ$J#L#(J#L?#%% @NKLLNK?A@QNAW`^kY*WolJasKn`anoY7 CK ?NA=PALNK?A@QNA`^k*olJasKn`ano =O OAHA?Pjsk*Kn`anMpu (jsk*?]nneanPn]_gejcJqi^an BNKI`^k*JasKn`anojsk SDANALnk`q_pE@94537 CK OAPOP=PEOPE?OTIHKJ7 ATA?`^k*olJasKn`ano7 OAPOP=PEOPE?OTIHKBB7 CK Next, still in op]po[_d]jcao*omh, you modify a number of rows before reexecuting the stored procedure: QL@=PA`^k*JasKn`ano OAPLnk`q_pE@9453 SDANALnk`q_pE@>APSAAJ4,,=J@5,,7 CK OAPOP=PEOPE?OTIHKJ7 ATA?`^k*olJasKn`ano7 OAPOP=PEOPE?OTIHKBB7 CK The first time, SQL Server executes the OAHA?P statement of the stored procedure using an Ej`atOaag operation, as shown in Figure 10-6.
C H A P T E R 1 0 N S T O R E D P R O C E D U R E R E C O M P I L A T I O N
NNote Please ensure that the setting for the graphical execution plan is KBB; otherwise, the output of OP=PEOPE?OTIH won’t display.
Figure 10-6. Execution plan prior to data changes 7
iÊÀiiÝiVÕÌ}ÊÌ
iÊÃÌÀi`Ê«ÀVi`ÕÀi]Ê-+Ê-iÀÛiÀÊ>ÕÌ>ÌV>ÞÊ`iÌiVÌÃÊÌ
>ÌÊÌ
iÊÃÌ>ÌÃtics on the index have changed. This causes a recompilation of the OAHA?P statement within the «ÀVi`ÕÀi]ÊÜÌ
ÊÌ
iÊ«ÌâiÀÊ`iÌiÀ}Ê>ÊLiÌÌiÀÊ«ÀViÃÃ}ÊÃÌÀ>Ìi}Þ]ÊLivÀiÊiÝiVÕÌ}ÊÌ
iÊ OAHA?P statement within the stored procedure, as you can see in Figure 10-7.
Figure 10-7. Effect of statistics change on the execution plan Figure 10-8 shows the corresponding Profiler trace output (with the =qpkOp]po event added).
Figure 10-8. Effect of statistics change on the stored procedure recompilation In Figure 10-7, you can see that to execute the OAHA?P statement during the second execution of the stored procedure, a recompilation was required. From the value of ArajpOq^?h]oo (.ÌOp]peope_o?d]jca`), you can understand that the recompilation was due to the statistics change. As part of creating the new plan, the statistics are automatically updated, as indicated by the =qpkOp]po event. You can also verify the automatic update of the statistics using the @>??ODKS[OP=PEOPE?O statement, as explained in Chapter 7.
291
292
C H A P T E R 1 0 N S T O R E D P R O C E D U R E R E C O M P I L A T I O N
Deferred Object Resolution Stored proceduresÊvÌiÊ`Þ>V>ÞÊVÀi>ÌiÊ>`ÊÃÕLÃiµÕiÌÞÊ>VViÃÃÊ`>Ì>L>ÃiÊLiVÌðÊ7
iÊ such a stored procedure is executed for the first time, the first execution plan won’t contain Ì
iÊvÀ>ÌÊ>LÕÌÊÌ
iÊLiVÌÃÊÌÊLiÊVÀi>Ìi`Ê`ÕÀ}ÊÀÕÌi°Ê/
ÕÃ]ÊÊÌ
iÊvÀÃÌÊiÝiVÕÌÊ «>]ÊÌ
iÊ«ÀViÃÃ}ÊÃÌÀ>Ìi}ÞÊvÀÊÌ
ÃiÊLiVÌÃÊÃÊ`iviÀÀi`ÊÕÌÊÌ
iÊÀÕÌiÊvÊÌ
iÊÃÌÀi`Ê«ÀVi`ÕÀi°Ê7
iÊ>Ê ÊÃÌ>ÌiiÌÊÜÌ
ÊÌ
iÊÃÌÀi`Ê«ÀVi`ÕÀi®ÊÀiviÀÀ}ÊÌÊiÊvÊÌ
ÃiÊLiVÌÃÊÃÊ executed, the stored procedure is recompiled to generate a new plan containing the process}ÊÃÌÀ>Ìi}ÞÊvÀÊÌ
iÊLiV̰ Both a regular table and a local temporary table can be created within a stored procedure to hold intermediate result sets. The recompilation of the stored procedure due to deferred LiVÌÊÀiÃÕÌÊLi
>ÛiÃÊ`vviÀiÌÞÊvÀÊ>ÊÀi}Õ>ÀÊÌ>LiÊÜ
iÊV«>Ài`ÊÌÊ>ÊV>ÊÌi«À>ÀÞÊ table, as explained in the following section.
Recompilation Due to a Regular Table To understand the stored procedure recompilation issue by creating a regular table within the stored procedure, consider the following example (nacqh]n*omh in the download): EB$OAHA?PK>FA?P[E@$#`^k*l-#%%EOJKPJQHH @NKLLNK?`^k*l-7 CK ?NA=PALNK?`^k*l=O ?NA=PAP=>HA`^k*l-[p-$_-EJP%7))Ajoqnap]^ha`kaoj#pateop OAHA?P&BNKI`^k*l-[p-7))?]qoaona_kileh]pekj @NKLP=>HA`^k*l-[p-7 CK ATA?`^k*l-7))Benopata_qpekj ATA?`^k*l-7))Oa_kj`ata_qpekj 7
iÊÌ
iÊÃÌÀi`Ê«ÀVi`ÕÀiÊÃÊiÝiVÕÌi`ÊvÀÊÌ
iÊvÀÃÌÊÌi]Ê>ÊiÝiVÕÌÊ«>ÊÃÊ}iiÀ>Ìi`Ê before the actual execution of the stored procedure. If the table created within the stored procedure doesn’t exist (as expected in the preceding code) before the stored procedure is created, then the plan won’t contain the processing strategy for the OAHA?P statement referring to the table. Thus, to execute the OAHA?P statement, the stored procedure needs to be recompiled, as shown in Figure 10-9.
Figure 10-9. Profiler trace output showing a stored procedure recompilation because of a regular table
C H A P T E R 1 0 N S T O R E D P R O C E D U R E R E C O M P I L A T I O N
You can see that the OAHA?P statement is recompiled when it’s executed the second time. Dropping the table within the stored procedure during the first execution doesn’t drop the stored procedure plan saved in the procedure cache. During the subsequent execution of the stored procedure, the existing plan includes the processing strategy for the table. However, because of the re-creation of the table within the stored procedure, SQL Server considers it a change to the table schema. Therefore, SQL Server recompiles the stored procedure before executing the OAHA?P statement during the subsequent execution of the stored procedure. The value of the ArajpOq^?h]oo for the corresponding OL6Na_kileha event reflects the cause of the recompilation.
Recompilation Due to a Local Temporary Table Most of the time in the stored procedure you create local temporary tables instead of regular tables. To understand how differently the local temporary tables affect stored procedure ÀiV«>Ì]Ê`vÞÊÌ
iÊ«ÀiVi`}ÊiÝ>«iÊLÞÊÕÃÌÊÀi«>V}ÊÌ
iÊÀi}Õ>ÀÊÌ>LiÊÜÌ
Ê>ÊV>Ê temporary table: EB$OAHA?PK>FA?P[E@$#`^k*l-#%%EOJKPJQHH @NKLLNK?`^k*l-7 CK ?NA=PALNK?`^k*l=O ?NA=PAP=>HAl-[p-$_-EJP%7))`aoecj]paohk_]hpailp]^ha OAHA?P&BNKIl-[p-7))?]qoaona_kileh]pekjkj-opata_qpekj @NKLP=>HAl-[p-7))Klpekj]h CK ATA?`^k*l-7))Benopata_qpekj ATA?`^k*l-7))Oa_kj`ata_qpekj Since a local temporary table is automatically dropped when the execution of a stored procedure finishes, it’s not necessary to drop the temporary table explicitly. But, following good programming practice, you can drop the local temporary table as soon as its work is done. Figure 10-10 shows the Profiler trace output for the preceding example.
Figure 10-10. Profiler trace output showing a stored procedure recompilation because of a local temporary table You can see that the stored procedure is recompiled when executed for the first time. The cause of the recompilation, as indicated by the corresponding ArajpOq^?h]oo value, is the same as the cause of the recompilation on a regular table. However, note that when the stored procedure is reexecuted, it isn’t recompiled, unlike the case with a regular table.
293
294
CHAPTER 10 N ST OR ED P R OC EDU R E R EC OMP IL A TIO N
The schema of a local temporary table during subsequent execution of the stored procedure remains exactly the same as during the previous execution. A local temporary table isn’t available outside the scope of the stored procedure, so its schema can’t be altered in any way between multiple executions. Thus, SQL Server safely reuses the existing plan (based on the previous instance of the local temporary table) during the subsequent execution of the stored procedure and thereby avoids the recompilation.
NNote To avoid recompilation, it makes sense to hold the intermediate result sets in the stored procedure using local temporary tables, instead of using temporarily created regular tables as an alternative.
SET Options Changes The execution plan of a stored procedure is dependent on the environment settings. If the environment settings are changed within a stored procedure, then SQL Server recompiles the stored procedure on every execution. For example, consider the following code (oap*omh in the download): EB$OAHA?PK>FA?P[E@$#`^k*l-#%%EOJKPJQHH @NKLLNK?`^k*l-7 CK ?NA=PALNK?`^k*l=O OAHA?P#]#'jqhh'#^#7))-op OAP?KJ?=P[JQHH[UEAH@O[JQHHKBB7 OAHA?P#]#'jqhh'#^#7)).j` OAP=JOE[JQHHOKBB7 OAHA?P#]#'jqhh'#^#7))/n` CK ATA?`^k*l-7))Benopata_qpekj ATA?`^k*l-7))Oa_kj`ata_qpekj Changing the OAP options in the stored procedure causes SQL Server to recompile the stored procedure before executing the statement after the OAP statement. Thus, this stored procedure is recompiled twice: once before executing the second OAHA?P statement and once before executing the third OAHA?P statement. The Profiler trace output in Figure 10-11 shows this.
C H A P T E R 1 0 N S T O R E D P R O C E D U R E R E C O M P I L A T I O N
Figure 10-11. Profiler trace output showing a stored procedure recompilation because of a OAP option change If the procedure were reexecuted, you wouldn’t see a recompile since those are now part of the execution plan. Since OAPJK?KQJP does not change the environment settings, unlike the OAP statements used to change the ANSI settings as shown previously, OAPJK?KQJP does not cause stored procedure recompilation. I explain how to use OAPJK?KQJP in detail in Chapter 11.
Execution Plan Aging SQL Server managesÊÌ
iÊÃâiÊvÊÌ
iÊ«ÀVi`ÕÀiÊV>V
iÊLÞÊ>Ì>}ÊÌ
iÊ>}iÊvÊÌ
iÊiÝiVÕtion plans in the cache, as you saw in Chapter 9. If a stored procedure is not reexecuted for a long time, then the age field of the execution plan can come down to 0, and the plan can LiÊÀiÛi`ÊvÀÊÌ
iÊV>V
iÊLiV>ÕÃiÊvÊiÀÞÊÃ
ÀÌ>}i°Ê7
iÊÌ
ÃÊ
>««iÃÊ>`ÊÌ
iÊÃÌÀi`Ê procedure is reexecuted, a new plan will be generated and cached in the procedure cache. However, if there is enough memory in the system, unused plans are not removed from the cache until memory pressure increases.
Explicit Call to sp_recompile SQL Server automatically recompiles stored procedures when the schema changes or statistics are altered enough. It also provides the ol[na_kileha system stored procedure to manually mark stored procedures for recompilation. This stored procedure can be called on a table, view, stored procedure, or trigger. If it is called on a stored procedure or a trigger, then the stored procedure or trigger is recompiled the next time it is executed. Calling ol[na_kileha on >ÊÌ>LiÊÀÊ>ÊÛiÜÊ>ÀÃÊ>ÊÌ
iÊÃÌÀi`Ê«ÀVi`ÕÀiÃÊ>`ÊÌÀ}}iÀÃÊÌ
>ÌÊÀiviÀÊÌÊÌ
iÊÌ>LiÉÛiÜÊvÀÊ recompilation the next time they are executed. For example, if ol[na_kileha is called on table p-, all the stored procedures and triggers that refer to table p- are marked for recompilation and are recompiled the next time they are executed: ol[na_kileha#p-# You can use ol[na_kileha to cancel the reuse of an existing plan when executing dynamic queries with ol[ata_qpaomh. As demonstrated in the previous chapter, you should not paramiÌiÀâiÊÌ
iÊÛ>À>LiÊ«>ÀÌÃÊvÊ>ʵÕiÀÞÊÜ
ÃiÊÀ>}iÊvÊÛ>ÕiÃÊ>ÞÊÀiµÕÀiÊ`vviÀiÌÊ«ÀViÃÃ}Ê strategies for the query. For instance, reconsidering the corresponding example, you know that the second execution of the query reuses the plan generated for the first execution. The example is repeated here for easy reference:
295
296
CHAPTER 10 N ST OR ED P R OC EDU R E R EC OMP IL A TIO N
@>??BNAALNK??=?DA7))?ha]npdalnk_a`qna_]_da CK @A?H=NA<mqanuJR=N?D=N$I=T%7 @A?H=NAÊÃ}
ÌÊ«ÀLiÊ
iÀi\ÊÞÕÊÜÊiÞÊÜ>ÌÊÌÊÀiiÝiVÕÌiÊÌ
iÊvÀÃÌÊÃÌ>ÌiiÌÊ >}>°Ê7Ì
ÊÌ
iÊ«>ÊiÝÃÌ}ÊÊÌ
iÊV>V
i]Ê-+Ê-iÀÛiÀÊÜÊÀiÕÃiÊÌ
iÊ«>ÊÌ
iÊEj`atO_]j operation on the O]haoKn`anDa]`an table) for the first statement even though an Ej`atOaag operation (using the index on the filter criterion column okd*?qopkianE@) would have been optimal. One way of avoiding this problem can be to create a stored procedure for the query and use a SEPDNA?KILEHA clause, as explained next.
Explicit Use of the RECOMPILE Clause SQL Server allows a stored procedure to be explicitly recompiled using the NA?KILEHA clause with the ?NA=PALNK?A@QNA or ATA?QPA statement. These methods decrease the effectiveness of plan reusability, so you should consider them only under the specific circumstances explained in the following sections.
RECOMPILE Clause with the CREATE PROCEDURE Statement Sometimes the plan requirements of a stored procedure may vary as the parameter values to the stored procedure change. In such a case, reusing the plan with different parameter values
C H A P T E R 1 0 N S T O R E D P R O C E D U R E R E C O M P I L A T I O N
may degrade the performance of the stored procedure. You can avoid this by using the NA?KILEHA clause with the ?NA=PALNK?A@QNA statement. For example, for the query in the preceding section, you can create a stored procedure with the NA?KILEHA clause: EB$OAHA?PK>FA?P[E@$#`^k*ol?qopkianHeop#%%EOJKPJQHH @NKLLNK?`^k*ol?qopkianHeop CK ?NA=PALNK?A@QNA`^k*ol?qopkianHeop
Figure 10-12. Effect of the NA?KILEHA clause used in stored procedure creation
297
298
CHAPTER 10 N ST OR ED P R OC EDU R E R EC OMP IL A TIO N
RECOMPILE Clause with the EXECUTE Statement As shown previously, specific parameter values in a stored procedure may require a different plan, depending upon the nature of the values. You can take the NA?KILEHA clause out of the stored procedure and use it on a case-by-case basis when you execute the stored procedure, as follows: ATA?`^k*ol?qopkianHeopÀÞ°Ê/
iÊiÜÊ«>ÊýÌÊV>V
i`]Ê>`ÊÌÊ`iýÌÊ>vviVÌÊÌ
iÊiÝÃÌ}Ê«>°Ê7
iÊÌ
iÊÃÌÀi`Ê procedure is executed without the NA?KILEHA clause, the plan is cached as usual. This provides some control over reusability of the existing plan cache rather than using the NA?KILEHA clause with the ?NA=PALNK?A@QNA statement. Since the plan for the stored procedure when executed with the NA?KILEHA clause is not cached, the plan is regenerated every time the stored procedure is executed with the NA?KILEHA clause. However, for better performance, instead of using NA?KILEHA, you should consider creating separate stored procedures, one for each set of parameter values that requires a different plan.
Avoiding Recompilations Sometimes recompilation is beneficial, but at other times it is worth avoiding. If a new index is created on a column referred to in the SDANA or FKEJ clause of a query, it makes sense to regenerate the execution plans of stored procedures referring to the table so they can benefit from using the index. However, if recompilation is deemed detrimental to performance, you can avoid it by following these implementation practices: Ê
UÊ ÊÌÊÌiÀi>ÛiÊ Ê>`Ê ÊÃÌ>ÌiiÌð
Ê
UÊ Û`ÊÀiV«>ÌÊV>ÕÃi`ÊLÞÊÃÌ>ÌÃÌVÃÊV
>}iÃ\ Ê UÊ 1ÃiÊÌ
iÊGAALBETA@LH=J option. Ê UÊ Ã>LiÊÌ
iÊ>ÕÌÊÕ«`>ÌiÊÃÌ>ÌÃÌVÃÊvi>ÌÕÀiÊÊÌ
iÊÌ>Li°
Ê
UÊ 1ÃiÊÌ>LiÊÛ>À>Lið
Ê
UÊ Û`ÊV
>}}ÊOAP options within the stored procedure.
Ê
UÊ 1ÃiÊÌ
iÊKLPEIEVABKN query hint.
Ê
UÊ 1ÃiÊ«>Ê}Õ`ið
Do Not Interleave DDL and DML Statements In stored procedures, DDL statements are often used to create local temporary tables and to change their schema (including adding indexes). Doing so can affect the validity of the existing plan and can cause recompilation when the stored procedure statements referring to the
C H A P T E R 1 0 N S T O R E D P R O C E D U R E R E C O M P I L A T I O N
tables are executed. To understand how the use of DDL statements for local temporary tables can cause repetitive recompilation of the stored procedure, consider the following example (``h*omh in the download): EB$OAHA?PK>FA?P[E@$#`^k*olPailP]^ha#% %EOJKPJQHH @NKLLNK?`^k*olPailP]^ha CK ?NA=PALNK?`^k*olPailP]^ha =O ?NA=PAP=>HAIuPailP]^ha$E@EJP(@o_JR=N?D=N$1,%% EJOANPEJPKIuPailP]^ha$ E@( @o_ %OAHA?PLnk`q_pIk`ahE`(WJ]iaY BNKILnk`q_pekj*Lnk`q_pIk`ah=Oli7))Jaa`o-opna_kileh]pekj OAHA?P&BNKIIuPailP]^ha=Oipp7 ?NA=PA?HQOPANA@EJ@ATePaopKJIuPailP]^ha$E@%7 OAHA?P& BNKIIuPailP]^ha=Oipp7))Jaa`o.j`na_kileh]pekj ?NA=PAP=>HAp.$_-EJP%7 OAHA?P& BNKIp.7 ))Jaa`o/n`na_kileh]pekj CK ATA?olPailP]^ha))Benopata_qpekj The stored procedure has interleaved DDL and DML statements. Figure 10-13 shows the Profiler trace output of the preceding code.
Figure 10-13. Profiler trace output showing recompilation because of DDL and DML interleaving You can see that the stored procedure is recompiled four times: Ê
UÊ /
iÊiÝiVÕÌÊ«>Ê}iiÀ>Ìi`ÊvÀÊ>ÊÃÌÀi`Ê«ÀVi`ÕÀiÊÜ
iÊÌÊÃÊvÀÃÌÊiÝiVÕÌi`Ê`iýÌÊ contain any information about local temporary tables. Therefore, the first generated plan can never be used to access the temporary table using a DML statement.
Ê
UÊ /
iÊÃiV`ÊÀiV«>ÌÊViÃÊvÀÊÌ
iÊV
>}iÃÊiVÕÌiÀi`ÊÊÌ
iÊ`>Ì>ÊVÌ>i`Ê within the table as it gets loaded.
299
300
CHAPTER 10 N ST OR ED P R OC EDU R E R EC OMP IL A TIO N
Ê
UÊ /
iÊÌ
À`ÊÀiV«>ÌÊÃÊ`ÕiÊÌÊ>ÊÃV
i>ÊV
>}iÊÊÌ
iÊvÀÃÌÊÌi«À>ÀÞÊÌ>LiÊ (IuPailP]^ha). The creation of the index on IuPailP]^ha invalidates the existing plan, causing a recompilation when the table is accessed again. If this index had been created before the first recompilation, then the existing plan would have remained valid for the second OAHA?P statement too. Therefore, you can avoid this recompilation by putting the ?NA=PAEJ@AT DDL statement above all DML statements referring to the table.
Ê
UÊ /
iÊvÕÀÌ
ÊÀiV«>ÌÊvÊÌ
iÊÃÌÀi`Ê«ÀVi`ÕÀiÊ}iiÀ>ÌiÃÊ>Ê«>ÊÌÊVÕ`iÊÌ
iÊ«Àcessing strategy for p.. The existing plan has no information about p. and therefore cannot be used to access p. using the third OAHA?P statement. If the ?NA=PAP=>HA DDL statement for p. had been placed before all the DML statements that could cause a recompilation, then the first recompilation itself would have included the information on p., avoiding the third recompilation.
Avoiding Recompilations Caused by Statistics Change In theʺ>Þâ}Ê >ÕÃiÃÊvÊ,iV«>Ì»ÊÃiVÌ]ÊÞÕÊÃ>ÜÊÌ
>ÌÊ>ÊV
>}iÊÊÃÌ>ÌÃÌVÃÊÃÊiÊ of the causes of recompilation. On a simple table with uniform data distribution, recompilation due to a change of statistics may generate a plan identical to the previous plan. In such situations, recompilation can be unnecessary and should be avoided if it is too costly. You have two techniques to avoid recompilations caused by statistics change: Ê
UÊ 1ÃiÊÌ
iÊGAALBETA@LH=J option.
Ê
UÊ Ã>LiÊÌ
iÊ>ÕÌÊÕ«`>ÌiÊÃÌ>ÌÃÌVÃÊvi>ÌÕÀiÊÊÌ
iÊÌ>Li°
Using the KEEPFIXED PLAN Option SQL Server provides a GAALBETA@LH=J option to avoid recompilations due to a statistics change. To understand how you can use GAALBETA@LH=J, consider op]po[_d]jcao*omh with an appropriate modification to use the GAALBETA@LH=J option: ))?na]pa]oi]hhp]^hasepdkjanks]j`]jej`at EB$OAHA?PK>FA?P[E@$#`^k*p-#% %EOJKPJQHH @NKLP=>HA`^k*p-7 CK ?NA=PAP=>HA`^k*p-$_-EJP(_.?D=N$1,%%7 EJOANPEJPK`^k*pR=HQAO$-(#.#%7 ?NA=PAJKJ?HQOPANA@EJ@ATe-KJp-$_-%7 ))?na]pa]opkna`lnk_a`qnanabanaj_ejcpdalnarekqop]^ha EB$OAHA?PK>FA?P[E@$#`^k*l-#% %EOJKPJQHH @NKLLNK?`^k*l-7 CK ?NA=PALNK?`^k*l-
C H A P T E R 1 0 N S T O R E D P R O C E D U R E R E C O M P I L A T I O N
=O OAHA?P& BNKIpSDANA_-9KLPEKJ$GAALBETA@LH=J%7 CK ))Benopata_qpekjkbopkna`lnk_a`qnasepd-nksejpdap]^ha ATA?`^k*l-7 ))Benopata_qpekj ))=``i]junksopkpdap]^hapk_]qoaop]peope_o_d]jca SEPDJqio =O$OAHA?P-=Oj QJEKJ=HH OAHA?Pj'BNKIJqio SDANAj8-,,, % EJOANPEJPKpOAHA?P(J BNKIJqio KLPEKJ$I=TNA?QNOEKJ-,,,%7 CK ))Naata_qpapdaopkna`lnk_a`qnasepd]_d]jcaejop]peope_o ATA?`^k*l-))Sepd_d]jcaej`]p]`eopne^qpekj Figure 10-14 shows the Profiler trace output.
Figure 10-14. Profiler trace output showing the role of the GAALBETA@LH=J option in reducing recompilation You can see that, unlike in the earlier example with changes in data, there’s no =qpk Op]po event (see Figure 10-8). Consequently, there’s no recompilation. Therefore, by using the GAALBETA@LH=J option, you can avoid recompilation due to a statistics change.
NNote Before you consider using this option, ensure that any new plans that would have been generated are not superior to the existing plan.
301
302
CHAPTER 10 N ST OR ED P R OC EDU R E R EC OMP IL A TIO N
Disable Auto Update Statistics on the Table You can also avoid recompilation due to a statistics update by disabling the automatic statistics update on the relevant table. For example, you can disable the auto update statistics feature on table p- as follows: ATA?ol[]qpkop]po#p-#(#KBB# If you disable this feature on the table before inserting the large number of rows that causes statistics change, then you can avoid the recompilation due to a statistics change. However, be very cautious with this technique, since outdated statistics can adversely >vviVÌÊÌ
iÊivviVÌÛiiÃÃÊvÊÌ
iÊVÃÌL>Ãi`Ê«ÌâiÀ]Ê>ÃÊ`ÃVÕÃÃi`ÊÊ
>«ÌiÀÊǰÊÃ]Ê>ÃÊ explained in Chapter 7, if you disable the automatic update of statistics, you should have a -+ÊLÊÌÊÕ«`>Ìi the statistics regularly.
Using Table Variables One of the variable types supported by SQL Server 2008 is the table variable. You can create the table variable data type like other data types, using the @A?H=NA statement. It behaves like a local variable, and you can use it inside a stored procedure to hold intermediate result sets, as you do using a temporary table. You can avoid the recompilations caused by a temporary table if you use a table variable. Since statistics are not created for table variables, the different recompilation issues associated with temporary tables are not applicable to it. For instance, consider _na]pa[l-*omh used ÊÌ
iÊÃiVÌʺ`iÌvÞ}ÊÌ
iÊ-Ì>ÌiiÌÊ >ÕÃ}Ê,iV«>̰»ÊÌÊÃÊÀi«i>Ìi`Ê
iÀiÊvÀÊÞÕÀÊ reference: EB$OAHA?PK>FA?P[E@$#`^k*l-#%%EOJKPJQHH @NKLLNK?`^k*l-7 CK ?NA=PALNK?`^k*l=O ?NA=PAP=>HAp-$_-EJP%7 EJOANPEJPKp-$ _%R=HQAO$0.%7))`]p]_d]jca_]qoaona_kileha CK ATA?`^k*l-7))Benopata_qpekj iV>ÕÃiÊvÊ`iviÀÀi`ÊLiVÌÊÀiÃÕÌ]ÊÌ
iÊÃÌÀi`Ê«ÀVi`ÕÀiÊÃÊÀiV«i`Ê`ÕÀ}ÊÌ
iÊvÀÃÌÊ execution. You can avoid this recompilation caused by the temporary table by using the table variable as follows:
C H A P T E R 1 0 N S T O R E D P R O C E D U R E R E C O M P I L A T I O N
EB$OAHA?PK>FA?P[E@$#`^k*l-#%%EOJKPJQHH @NKLLNK?`^k*lCK ?NA=PALNK?`^k*l=O @A?H=NAHA$_-EJP% EJOANPEJPK
Figure 10-15. Profiler trace output showing the role of a table variable in resolving recompilation Additional benefits of using the table variables are as follows: Ê
UÊ No transaction log overhead: No transaction log activities are performed for table variables, whereas they are for both regular and temporary tables.
Ê
UÊ No lock overhead: Since table variables are treated like local variables (not database LiVÌî]ÊÌ
iÊV}ÊÛiÀ
i>`Ê>ÃÃV>Ìi`ÊÜÌ
ÊÀi}Õ>ÀÊÌ>LiÃÊ>`ÊÌi«À>ÀÞÊÌ>LiÃÊ`iÃÊ not exist.
Ê
UÊ No rollback overhead: Since no transaction log activities are performed for table variables, no rollback overhead is applicable for table variables. For example, consider the following code (nkhh^]_g*omh in the download): @A?H=NAHA$_-EJP% EJOANPEJPKACEJPN=J EJOANPEJPK=?G OAHA?P&BNKI=?G statement won’t roll back the second row insertion into the table variable.
303
304
CHAPTER 10 N ST OR ED P R OC EDU R E R EC OMP IL A TIO N
However, table variables have their limitations. The main ones are as follows: Ê
UÊ
ÊDDL statement can be executed on the table variable once it is created, which means no indexes or constraints can be added to the table variable later. Constraints can be specified only as part of the table variable’s @A?H=NA statement. Therefore, only one index can be created on a table variable, using the LNEI=NUGAU or QJEMQA constraint.
Ê
UÊ
Êstatistics are created for table variables, which means they resolve as single-row tables in execution plans. This is not an issue when the table actually contains only >ÊÃ>ʵÕ>ÌÌÞÊvÊ`>Ì>]Ê>««ÀÝ>ÌiÞÊiÃÃÊÌ
>Ê£ääÊÀÜðÊÌÊLiViÃÊ>Ê>ÀÊ«iÀformance problem when the table variable contains more data since appropriate decisions regarding the right sorts of operations within an execution plan are completely dependent on statistics.
Ê
UÊ /
iÊvÜ}ÊÃÌ>ÌiiÌÃ are not supported on the table variables: Ê UÊ EJOANPEJPKP]^haR]ne]^haATA?Opkna`Lnk_a`qna Ê UÊ OAHA?POaha_pHeopEJPKP]^haR]ne]^haBNKIP]^ha Ê UÊ OAPP]^haR]ne]^ha9R]hqa
Avoiding Changing SET Options Within a Stored Procedure It is generally recommended that you not change the environment settings within a stored procedure and thus avoid recompilation because the OAP options changed. For ANSI compatibility, it is recommended that you keep the following OAP options KJ: Ê
UÊ =NEPD=>KNP
Ê
UÊ ?KJ?=P[JQHH[UEAH@O[JQHH
Ê
UÊ MQKPA@[E@AJPEBEAN
Ê
UÊ =JOE[JQHHO
Ê
UÊ =JOE[L=@@EJC
Ê
UÊ =JOE[S=NJEJCO
JQIANE?[NKQJ@=>KNP should be KBB. Although the following approach is not recommended, you can avoid the recompilation caused by some of these OAP options changes by resetting the options for the connection as shown in the following modifications to oap*omh: EB$OAHA?PK>FA?P[E@$#l-#%%EOJKPJQHH @NKLLNK?lCK ?NA=PALNK?l=O OAHA?P#]#'jqhh'#^#))-op OAP?KJ?=P[JQHH[UEAH@O[JQHHKBB
C H A P T E R 1 0 N S T O R E D P R O C E D U R E R E C O M P I L A T I O N
OAHA?P#]#'jqhh'#^#)).j` OAP=JOE[JQHHOKBB OAHA?P#]#'jqhh'#^#))/n` CK OAP?KJ?=P[JQHH[UEAH@O[JQHHKBB OAP=JOE[JQHHOKBB ATA?lOAP?KJ?=P[JQHH[UEAH@O[JQHHKJ))Naoappk`ab]qhp OAP=JOE[JQHHOKJ))Naoappk`ab]qhp Figure 10-16 shows the Profiler trace output.
Figure 10-16. Profiler trace output showing effect of the ANSI OAP options on stored procedure recompilation You can see that there were fewer recompilations when compared to the original oap*omh code (Figure 10-11). Out of the OAP options listed previously, the =JOE[JQHHO and MQKPA@[ E@AJPEBEAN options are saved as part of the stored procedure when it is created. Therefore, setting these options in the connection outside the stored procedure won’t affect any recompilation issues; only re-creating the stored procedure can change these settings.
Using OPTIMIZE FOR Query Hint Although you may not always be able to reduce or eliminate recompiles, using the KLPEIEVA BKN query hint will help ensure you get the plan you want when the recompile does occur. The KLPEIEVABKN query hint uses parameter values supplied by you to compile the plan, regardless of the values of the parameter passed in by the calling application. For an example, examine ol?qopkianHeop from earlier in the chapter. You know that if this procedure receives certain values, it will need to create a new plan. Knowing your data, you also know two more important facts: the frequency that this query will return small data sets is exceedingly small, and when this query uses the wrong plan, performance suffers. Rather than recompiling it over and over again, modify it so that it creates the plan that works best most of the time: EB$OAHA?PK>FA?P[E@$#`^k*ol?qopkianHeop#%%EOJKPJQHH @NKLLNK?`^k*ol?qopkianHeop CK ?NA=PALNK?A@QNA`^k*ol?qopkianHeop
305
306
C H A P T E R 1 0 N S T O R E D P R O C E D U R E R E C O M P I L A T I O N
BNKIO]hao*O]haoKn`anDa]`an=Ookd FKEJO]hao*O]haoKn`an@ap]eh=Ook` KJokd*O]haoKn`anE@9ok`*O]haoKn`anE@ SDANAokd*?qopkianE@:9ÞÊÀi>Ã]ÊÌÊ>Ü>ÞÃÊ}iÌÃÊ the same execution plan. To test this, execute the procedure this way: ATA?`^k*ol?qopkianHeop
Figure 10-17. SEPDNA?KILEHA forces identical execution plans. Unlike earlier in the chapter, recompiling the procedure now does not result in a new execution plan. Instead, the same plan is generated, regardless of input, because the query «ÌâiÀÊ
>ÃÊÀiViÛi`ÊÃÌÀÕVÌÃÊÌÊÕÃiÊÌ
iÊÛ>ÕiÊÃÕ««i`]ÊÌÊÌ
iʵÕiÀÞÊLiÊ«Ìâi`ÊÕÃ}ÊKLPEIEVABKNQJGKSJ. This is almost the opposite of the KLPEIEVABKNÊ
̰Ê7
>ÌÊÞÕÊ>ÀiÊ`ÀiVÌ}ÊÌ
iÊ«ÀViÃÃÀÊÌÊ`ÊÃÊ«iÀvÀÊÌ
iÊ«Ìâ>ÌÊ based on statistics, always, and to ignore the actual values passed when the query is optiâi`°Ê9ÕÊV>ÊÕÃiÊÌÊÊVL>ÌÊÜÌ
ÊKLPEIEVABKN8r]hqa:°ÊÌÊÜÊ«ÌâiÊvÀÊÌ
iÊÛ>ÕiÊ supplied on that parameter but will use statistics on all other parameters.
C HA P T E R 1 0 N S T O R E D P R O C E D U R E R E C O M P I LA T I O N
Using Plan Guides A «>Ê}Õ`iÊ>ÜÃÊÞÕÊÌÊÕÃiʵÕiÀÞÊ
ÌÊÀÊÌ
iÀÊ«Ìâ>ÌÊÌiV
µÕiÃÊÜÌ
ÕÌÊ
>Û}Ê to modify the query or procedure text. This is especially useful when you have a third-party product with poorly performing procedures you need to tune but can’t modify. As part of the «Ìâ>ÌÊ«ÀViÃÃ]ÊvÊ>Ê«>Ê}Õ`iÊiÝÃÌÃÊÜ
iÊ>Ê«ÀVi`ÕÀiÊÃÊV«i`ÊÀÊÀiV«i`]ÊÌÊÜÊ use that guide to create the execution plan. In the previous section, I showed you how using KLPEIEVABKN would affect the execution plan created on a procedure. The following is the query from the original procedure, with no hints (lh]j[cqe`a*omh in the download): EB$OAHA?PK>FA?P[E@$#`^k*ol?qopkianHeop#%%EOJKPJQHH @NKLLNK?`^k*ol?qopkianHeop CK EB$OAHA?PK>FA?P[E@$#`^k*ol?qopkianHeop#%%EOJKPJQHH @NKLLNK?`^k*ol?qopkianHeop CK ?NA=PALNK?A@QNA`^k*ol?qopkianHeop FA?P#(
307
308
C H A P T E R 1 0 N S T O R E D P R O C E D U R E R E C O M P I L A T I O N
Now, when the procedure is executed with each of the different parameters, even with the NA?KILEHA being forced, Figure 10-18 shows the results: ATA?`^k*ol?qopkianHeop
Figure 10-18. Using a plan guide to apply the KLPEIEVABKN query hint The results are the same as when the procedure was modified, but in this case, no modification was necessary. Various types of plan guides exist. The previous example is an object plan guide, which is >Ê}Õ`iÊ>ÌV
i`ÊÌÊ>Ê«>ÀÌVÕ>ÀÊLiVÌÊÊÌ
iÊ`>Ì>L>Ãi]ÊÊÌ
ÃÊV>ÃiÊol?qopkianHeop. You can also create plan guides for ad hoc queries that come into your system repeatedly by creating a SQL plan guide that looks for particular SQL statements. Instead of a procedure, the following query gets passed to your system and needs an KLPEIEVA BKN query hint: OAHA?Pokd*O]haoKn`anJqi^an (okd*Kn`an@]pa (ok`*Kn`anMpu (ok`*HejaPkp]h BNKIO]hao*O]haoKn`anDa]`an=Ookd FKEJO]hao*O]haoKn`an@ap]eh=Ook` KJokd*O]haoKn`anE@9ok`*O]haoKn`anE@ SDANAokd*?qopkianE@:9Running this query results in the execution plan you see in Figure 10-19.
C H A P T E R 1 0 N S T O R E D P R O C E D U R E R E C O M P I L A T I O N
Figure 10-19. Query uses a different execution plan from the one wanted To get a query plan guide, you first need to know the precise format used by the query ÊV>ÃiÊ«>À>iÌiÀâ>Ì]ÊvÀVi`ÊÀÊëi]ÊV
>}iÃÊÌ
iÊÌiÝÌÊvÊÌ
iʵÕiÀÞ°Ê/
iÊÌiÝÌÊ
>ÃÊÌÊ be precise. If your first attempt at a query plan guide looked like this (^]`[cqe`a*omh in the download): ATA?QPAol[_na]pa[lh]j[cqe`a <j]ia9J#Iu>]`OMHCqe`a#( HADEJP$okd(BKN?AOAAG%%#7 then you’ll still get the same execution plan when running the select query. This is because the query doesn’t look like what was typed in for the ^]`[cqe`a*omh plan guide. Several things are different such as the spacing and the case on the FKEJ statement. You can drop this bad plan guide using the T-SQL statement: ATA?QPAol[_kjpnkh[lh]j[cqe`a]`OMHCqe`a# Inputting the correct syntax will create a new plan: ATA?QPAol[_na]pa[lh]j[cqe`a <j]ia9J#IuCkk`OMHCqe`a#(
309
310
C H A P T E R 1 0 N S T O R E D P R O C E D U R E R E C O M P I L A T I O N
SDANAokd*?qopkianE@:9-#( HADEJP$okd(BKN?AOAAG%%#7 Now when the query is run, a completely different plan is created, as shown in Figure 10-20.
Figure 10-20. The plan guide forces a new execution plan on the same query. One other option exists when you have a plan in the cache that you think performs the way you want. You can capture that plan into a plan guide to ensure that the next time the query is run, the same plan is executed. You accomplish this by running ol[_na]pa[lh]j[ cqe`a[bnki[d]j`ha. To test it, first clear out the procedure cache so you can control exactly which query plan is used: @>??BNAALNK??=?DA 7Ì
ÊÌ
iÊ«ÀVi`ÕÀiÊV>V
iÊVi>ÀÊ>`ÊÌ
iÊiÝÃÌ}Ê«>Ê}Õ`i]ÊIuCkk`OMHCqe`a, in place, rerun the query. It will use the plan guide to arrive at the execution plan displayed in Figure 10-20. To see whether this plan can be kept, first drop the plan guide that is forcing the Ej`atOaag operation: ATA?QPAol[_kjpnkh[lh]j[cqe`aEJ=NU$20% (
C HA P T E R 1 0 N S T O R E D P R O C E D U R E R E C O M P I LA T I O N
ATA?QPAol[_na]pa[lh]j[cqe`a[bnki[d]j`ha<j]ia9J#Bkn_a`Lh]jCqe`a#(
Summary As you learned in this chapter, stored procedure recompilation can both benefit and hurt performance. Recompilations that generate better plans improve the performance of the stored procedure. However, recompilations that regenerate the same plan consume extra CPU cycles without any improvement in processing strategy. Therefore, you should look closely at recompilations to determine their usefulness. You can use Profiler to identify which stored procedure statement caused the recompilation, and you can determine the cause from the ArajpOq^?h]oo data column value in Profiler and trace output. Once you determine the cause of the recompilation, you can apply different techniques to avoid the unnecessary recompilations. Up until now, you have seen how to benefit from proper indexing and plan caching. However, the performance benefit of these techniques depends on the way the queries are `iÃ}i`°Ê/
iÊVÃÌL>Ãi`Ê«ÌâiÀÊvÊ-+Ê-iÀÛiÀÊÌ>iÃÊV>ÀiÊvÊ>ÞÊvÊÌ
iʵÕiÀÞÊ`iÃ}Ê issues. However, you should adopt a number of best practices while designing queries. In the next chapter, I will cover some of the common query design issues that affect performance.
311
CHAPTER
11
Query Design Analysis A
database schema may include a number of performance-enhancement features such as indexes, statistics, and stored procedures. But none of these features guarantees good performance if your queries are written badly in the first place. The SQL queries may not be able to use the available indexes effectively. The structure of the SQL queries may add avoidable overhead to the query cost. Queries may be attempting to deal with data in a row-by-row fashion (or to quote Jeff Moden, Row By Agonizing Row, which is abbreviated to RBAR and pronounced “reebar”) instead of in logical sets. To improve the performance of a database application, it is important to understand the cost associated with varying ways of writing a query. In this chapter, I cover the following topics: Ê
UÊ Ã«iVÌÃÊvʵÕiÀÞÊ`iÃ}ÊÌ
>ÌÊ>vviVÌÊ«iÀvÀ>Vi
Ê
UÊ ÜʵÕiÀÞÊ`iÃ}ÃÊÕÃiÊ`iÝiÃÊivviVÌÛiÞ
Ê
UÊ /
iÊÀiÊvÊ«ÌâiÀÊ
ÌÃÊʵÕiÀÞÊ«iÀvÀ>Vi
Ê
UÊ /
iÊÀiÊvÊ`>Ì>L>ÃiÊVÃÌÀ>ÌÃÊʵÕiÀÞÊ«iÀvÀ>Vi
Ê
UÊ +ÕiÀÞÊ`iÃ}ÃÊÌ
>ÌÊ>ÀiÊiÃÃÊÀiÃÕÀViÊÌiÃÛi
Ê
UÊ +ÕiÀÞÊ`iÃ}ÃÊÌ
>ÌÊÕÃiÊÌ
iÊ«ÀVi`ÕÀiÊV>V
iÊivviVÌÛiÞ
Ê
UÊ +ÕiÀÞÊ`iÃ}ÃÊÌ
>ÌÊÀi`ÕViÊiÌÜÀÊÛiÀ
i>`
Ê
UÊ /iV
µÕiÃÊÌÊÀi`ÕViÊÌ
iÊÌÀ>Ã>VÌÊVÃÌÊvÊ>ʵÕiÀÞ
Query Design Recommendations When you need to run a query, you can often use many different approaches to get the same data. In many cases, the optimizer generates the same plan, irrespective of the structure of the µÕiÀÞ°ÊÜiÛiÀ]ÊÊÃiÊÃÌÕ>ÌÃÊÌ
iʵÕiÀÞÊÃÌÀÕVÌÕÀiÊܽÌÊ>ÜÊÌ
iÊ«ÌâiÀÊÌÊÃiiVÌÊÌ
iÊ LiÃÌÊ«ÃÃLiÊ«ÀViÃÃ}ÊÃÌÀ>Ìi}Þ°ÊÌÊÃÊ«ÀÌ>ÌÊÌ
>ÌÊÞÕÊÜÊÜ
iÊÌ
ÃÊ
>««iÃÊ>`ÊÜ
>ÌÊ you can do to avoid it.
313
314
CHAPTER 11 N Q U ER Y DES IG N A NA L YS IS
Ê}iiÀ>]Êii«ÊÌ
iÊvÜ}ÊÀiVi`>ÌÃÊÊ`ÊÌÊiÃÕÀiÊÌ
iÊLiÃÌÊ«iÀvÀ>Vi\ Ê
UÊ "«iÀ>ÌiÊÊÃ>ÊÀiÃÕÌÊÃiÌð
Ê
UÊ 1ÃiÊ`iÝiÃÊivviVÌÛiÞ°
Ê
UÊ Û`Ê«ÌâiÀÊ
Ìð
Ê
UÊ 1ÃiÊ`>Ê>`ÊÀiviÀiÌ>ÊÌi}ÀÌÞ°
Ê
UÊ Û`ÊÀiÃÕÀViÌiÃÛiʵÕiÀið
Ê
UÊ ,i`ÕViÊÌ
iÊÕLiÀÊvÊiÌÜÀÊÀÕ`ÌÀ«Ã°
Ê
UÊ ,i`ÕViÊÌ
iÊÌÀ>Ã>VÌÊVÃ̰
Careful testing is essential to identify the query form that provides the best performance in a specific database environment. You should be conversant with writing and comparing different SQL query forms so you can evaluate the query form that provides the best performance in a given environment.
Operating on Small Result Sets To improve the performance of a query, limit the amount of data it operates on, includ}ÊLÌ
ÊVÕÃÊ>`ÊÀÜðÊ"«iÀ>Ì}ÊÊÃ>ÊÀiÃÕÌÊÃiÌÃÊÀi`ÕViÃÊÌ
iÊ>ÕÌÊvÊÀiÃÕÀViÃÊ consumed by a query and increases the effectiveness of indexes. Two of the rules you should vÜÊÌÊÌÊÌ
iÊ`>Ì>ÊÃi̽ÃÊÃâiÊ>ÀiÊ>ÃÊvÜÃ\ Ê
UÊ ÌÊÌ
iÊÕLiÀÊvÊVÕÃÊÊÌ
iÊÃiiVÌÊÃ̰
Ê
UÊ 1ÃiÊ
}
ÞÊÃiiVÌÛiÊSDANA clauses to limit the rows returned.
̽ÃÊ«ÀÌ>ÌÊÌÊÌiÊÌ
>ÌÊÞÕÊÜÊLiÊ>Ãi`ÊÌÊÀiÌÕÀÊÌiÃÊvÊÌ
ÕÃ>`ÃÊvÊÀÜÃÊÌÊ>Ê "/*ÊÃÞÃÌi°ÊÕÃÌÊLiV>ÕÃiÊÃiiÊÌiÃÊÞÕÊÌ
ÃiÊ>ÀiÊÌ
iÊLÕÃiÃÃÊÀiµÕÀiiÌÃÊ`iýÌÊ i>ÊÌ
iÞÊ>ÀiÊÀ}
̰ÊÕ>ÊLi}ÃÊ`½ÌÊ«ÀViÃÃÊÌiÃÊvÊÌ
ÕÃ>`ÃÊvÊÀÜðÊ6iÀÞÊviÜÊ
Õ>Ê Li}ÃÊ>ÀiÊV>«>LiÊvÊ«ÀViÃÃ}ÊÌ
ÕÃ>`ÃÊvÊÀÜÃ°Ê iÊ«Ài«>Ài`ÊÌÊ«ÕÃ
ÊL>VÊÊÌ
iÃiÊ requests, and be able to justify your reasons.
Limit the Number of Columns in select_list 1ÃiÊ> minimum set of columns in the select list of a OAHA?P statement. Do not use columns that are not required in the output result set. For instance, do not use OAHA?P& to return all columns. OAHA?P& statements render covered indexes ineffective, since it is impractical to include all columns in an index. For example, consider the following query: OAHA?PWJ]iaY (PannepknuE@ BNKIO]hao*O]haoPannepknu=Oop SDANAop*WJ]iaY9#=qopn]he]# A covering index on the J]iaÊVÕÊ>`ÊÌ
ÀÕ}
ÊÌ
iÊVÕÃÌiÀi`ÊiÞ]ÊLnk`q_pE@) serves the µÕiÀÞʵÕVÞÊÌ
ÀÕ}
ÊÌ
iÊ`iÝÊÌÃiv]ÊÜÌ
ÕÌÊ>VViÃÃ}ÊÌ
iÊVÕÃÌiÀi`Ê`iݰÊ7
iÊÞÕÊ
>ÛiÊ OP=PEOPE?OEK and OP=PEOPE?OPEIA switched on, you get the following number of logical reads and execution time, as well as the corresponding execution plan (shown in Figure 11-1):
C H A P T E R 1 1 N Q U E R Y D E S I G N A N A L Y S I S
P]^ha#O]haoPannepknu#*O_]j_kqjp,(hkce_]hna]`o. ?LQpeia9,io(ah]loa`peia9-3io*
Figure 11-1. Execution plan showing the benefit of referring to a limited number of columns If this query is modified to include all columns in the select list as follows, then the previous covering index becomes ineffective, because all the columns required by this query are not included in that index: OAHA?P& BNKIO]hao*O]haoPannepknu=Oop SDANAop*WJ]iaY9#=qopn]he]# Subsequently, the base table (or the clustered index) containing all the columns has to be accessed, as shown next (see Figure 11-2). The number of logical reads and the execution time have both increased: P]^ha#O]haoPannepknu#*O_]j_kqjp,(hkce_]hna]`o0 ?LQpeia9,io(ah]loa`peia9.4io
Figure 11-2. Execution plan showing the added cost of referring to too many columns As shown in Figure 11-2, the fewer the columns in the select list, the better the query pervÀ>Vi°Ê-iiVÌ}ÊÌÊ>ÞÊVÕÃÊ>ÃÊVÀi>ÃiÃÊ`>Ì>ÊÌÀ>ÃviÀÊ>VÀÃÃÊÌ
iÊiÌÜÀ]ÊvÕÀÌ
iÀ degrading performance.
Use Highly Selective WHERE Clauses As explained in Chapter 4, the selectivity of a column referred to in the SDANA clause governs the use of an index on the column. A request for a large number of rows from a table may not LiivÌÊvÀÊÕÃ}Ê>Ê`iÝ]ÊiÌ
iÀÊLiV>ÕÃiÊÌÊV>½ÌÊÕÃiÊ>Ê`iÝÊ>ÌÊ>ÊÀ]ÊÊÌ
iÊV>ÃiÊvÊ>ÊVÕÃÌiÀi`Ê`iÝ]ÊLiV>ÕÃiÊvÊÌ
iÊÛiÀ
i>`ÊVÃÌÊvÊÌ
iÊL>ÀÊÕ«°Ê/ÊiÃÕÀiÊÌ
iÊÕÃiÊvÊ indexes, the columns referred to in the SDANA clause should be highly selective. Most of the time, an end user concentrates on a limited number of rows at a time. Therefore, you should design database applications to request data incrementally as the user navigates through the data. For applications that rely on a large amount of data for
315
316
CHAPTER 11 N Q U ER Y DES IG N A NA L YS IS
data analysis or reporting, consider using data analysis solutions such as Analysis Services. Remember, returning huge result setsÊÃÊVÃÌÞ]Ê>`ÊÌ
ÃÊ`>Ì>ÊÃÊÕiÞÊÌÊLiÊÕÃi`ÊÊÌÃÊ entirety.
Using Indexes Effectively It is extremely important to have effective indexes on database tables to improve performance. ÜiÛiÀ]ÊÌÊÃÊiµÕ>ÞÊ«ÀÌ>ÌÊÌÊiÃÕÀiÊÌ
>ÌÊÌ
iʵÕiÀiÃÊ>ÀiÊ`iÃ}i`Ê«À«iÀÞÊÌÊÕÃiÊÌ
iÃiÊ indexes effectively. These are some of the query design rules you should follow to improve the use of indexes: Ê
UÊ Û`ÊÃ>À}>LiÊÃi>ÀV
ÊV`Ìð
Ê
UÊ Û`Ê>ÀÌ
iÌVÊ«iÀ>ÌÀÃÊÊÌ
iÊSDANA clause column.
Ê
UÊ Û`ÊvÕVÌÃÊÊÌ
iÊSDANA clause column. I cover each of these rules in detail in the following sections.
Avoid Nonsargable Search Conditions A sargable predicate in a query is one in which an index can be used. The word is a contraction vʺ-i>ÀV
Ê,ÕiÌÊ °»Ê/
iÊ«ÌâiÀ½ÃÊ>LÌÞÊÌÊLiivÌÊvÀÊ>Ê`iÝÊdepends on the selectivity of the search condition, which in turn depends on the selectivity of the column(s) referred to in the SDANA clause. The search predicate used on the column(s) in the SDANA clause determines whether an index operation on the column can be performed. The sargable search conditions listed in Table 11-1 generally allow the optimizer to use an index on the column(s) referred to in the SDANA clause. The sargable search conditions gener>ÞÊ>ÜÊ-+Ê-iÀÛiÀÊÌÊÃiiÊÌÊ>ÊÀÜÊÊÌ
iÊ`iÝÊ>`ÊÀiÌÀiÛiÊÌ
iÊÀÜÊÀÊÌ
iÊ>`>ViÌÊÀ>}iÊ of rows until the search condition remains true). "ÊÌ
iÊÌ
iÀÊ
>`]ÊÌ
iÊnonsargable search conditions listed in Table 11-1 generally prevent the optimizer from using an index on the column(s) referred to in the SDANA clause. /
iÊiÝVÕÃÊÃi>ÀV
ÊV`ÌÃÊ}iiÀ>ÞÊ`½ÌÊ>ÜÊ-+Ê-iÀÛiÀÊÌÊ«iÀvÀÊEj`atOaag operations as supported by the sargable search conditions. For example, the 9 condition requires scanning all the rows to identify the matching rows. Table 11-1. Common Sargable and Nonsargable Search Conditions
Type
Search Conditions
Sargable
Inclusion conditions 9, :, :9, 8, 89, and >APSAAJ, and some HEGA conditions such as
HEGA#8hepan]h:!# Nonsargable
Exclusion conditions 8:, 9, :, 8, JKPATEOPO, JKPEJ, and JKPHEGA EJ, KN, and some HEGA conditions such as HEGA#!8hepan]h:#
/ÀÞÊÌÊ«iiÌÊÜÀ>ÀÕ`ÃÊvÀÊÌ
iÃiÊÃ>À}>LiÊÃi>ÀV
ÊV`ÌÃÊÌÊ«ÀÛiÊ performance. In some cases, it may be possible to rewrite a query to avoid a nonsargable search condition. For example, consider replacing an EJ/KN search condition with a >APSAAJ condition, as described in the following section.
C H A P T E R 1 1 N Q U E R Y D E S I G N A N A L Y S I S
BETWEEN vs. IN/OR Consider the following query, which uses the search condition EJ: OAHA?Pok`*& BNKIO]hao*O]haoKn`an@ap]eh=Ook` SDANAok`*O]haoKn`anE@EJ$1-4.1(1-4.2(1-4.3(1-4.4% You can replace the nonsargable search condition in this query with a >APSAAJ clause as follows: OAHA?Pok`*& BNKIO]hao*O]haoKn`an@ap]eh=Ook` SDANAok`*O]haoKn`anE@>APSAAJ1-4.1=J@1-4.4 "ÊÌ
iÊv>ViÊvÊÌ]ÊÌ
iÊiÝiVÕÌÊ«>ÊvÊLÌ
ÊÌ
iʵÕiÀiÃÊ>««i>ÀÃÊÌÊLiÊÌ
iÊÃ>i]Ê>ÃÊÃ
ÜÊ in the execution plan in Figure 11-3.
Figure 11-3. Execution plan for a simple OAHA?P statement using a >APSAAJ clause ÜiÛiÀ]ÊÌ>}Ê>ÊVÃiÀÊÊ>ÌÊÌ
iÊiÝiVÕÌÊ«>ÃÊÀiÛi>ÃÊÌ
iÊ`vviÀiViÊÊÌ
iÀÊ`>Ì> retrieval mechanism, as shown in Figure 11-4. The left box is the EJ condition, and the right box is the >APSAAJ condition.
Figure 11-4. Execution plan details for an EJ condition (left) and a >APSAAJ condition (right)
317
318
CHAPTER 11 N Q U ER Y DES IG N A NA L YS IS
As shown in Figure 11-4, SQL Server resolved the EJ condition containing four values into four KN conditions. Accordingly, the clustered index (LG[O]haoPannepknu[PannepknuE`) is accessed four times (O_]j_kqjp0) to retrieve rows for the four KN conditions, as shown in the following corresponding OP=PEOPE?OEKÊÕÌ«Õ̰Ê"ÊÌ
iÊÌ
iÀÊ
>`]ÊÌ
iÊ>APSAAJ condition is resolved into a pair of :9 and 89 conditions, as shown in Figure 11-4. SQL Server accesses the clustered index only once (O_]j_kqjp-) from the first matching row until the match condition is true, as shown in the following corresponding OP=PEOPE?OEK and MQANUPEIA output: Ê
UÊ 7Ì
ÊÌ
iÊEJ condition: P]^ha#O]haoKn`an@ap]eh#*O_]j_kqjp0(hkce_]hna]`o.?LQpeia9,io(ah]loa`peia9.,4io*
Ê
UÊ 7Ì
ÊÌ
iÊ>APSAAJ condition: P]^ha#O]haoKn`an@ap]eh#*O_]j_kqjp-(hkce_]hna]`o3 ?LQpeia9,io(ah]loa`peia9-2-io*
Replacing the search condition EJ with >APSAAJ decreases the number of logical reads for Ì
ÃʵÕiÀÞÊvÀÊi}
ÌÊÌÊÌܰÊÃÊÕÃÌÊÃ
Ü]Ê>Ì
Õ}
ÊLÌ
ʵÕiÀiÃÊÕÃiÊ>ÊVÕÃÌiÀi`Ê`iÝÊÃiiÊ on Kn`anE@, the optimizer locates the range of rows much faster with the >APSAAJ clause than with the EJÊV>ÕÃi°Ê/
iÊÃ>iÊÌ
}Ê
>««iÃÊÜ
iÊÞÕÊÊ>ÌÊÌ
iÊ>APSAAJ condition and the KN clause. Therefore, if there is a choice between using EJ/KN and the >APSAAJ search condition, always choose the >APSAAJ condition, because it is generally much more efficient than the EJ/KN condition. In fact, you should go one step further and use the combination of :9 and 89 instead of the >APSAAJ clause. Not every SDANA clause that uses exclusion search conditions prevents the optimizer from using the index on the column referred to in the search condition. In many cases, the SQL Server 2008 optimizer does a wonderful job of converting the exclusion search condition to a sargable search condition. To understand this, consider the following two search conditions, which I discuss in the sections that follow: Ê
UÊ /
iÊHEGA condition
Ê
UÊ /
iÊ8 condition vs. the :9 condition
LIKE Condition While using the HEGA search condition, try to use one or more leading characters in the SDANA V>ÕÃiÊvÊ«ÃÃLi°Ê1Ã}Êi>`}ÊV
>À>VÌiÀÃÊÊÌ
iÊHEGA clause allows the optimizer to convert the HEGA condition to an index-friendly search condition. The greater the number of leading characters in the HEGA condition, the better the optimizer is able to determine an effective index. Be aware that using a wildcard character as the leading character in the HEGA condition prevents the optimizer from performing a OAAG (or a narrow-range scan) on the index; it relies on scanning the complete table instead. To understand this ability of the SQL Server 2008 optimizer, consider the following OAHA?P statement that uses the HEGA condition with a leading character: OAHA?P_*?qnnaj_u?k`a BNKIO]hao*?qnnaj_u=O_ SDANA_*WJ]iaYHEGA#E_a!#
C H A P T E R 1 1 N Q U E R Y D E S I G N A N A L Y S I S
The SQL Server 2008 optimizer does this conversion automatically, as shown in Figure 11-5.
Figure 11-5. Execution plan showing automatic conversion of a HEGA clause with a trailing % sign to an indexable search condition As you can see, the optimizer automatically converts the HEGA condition to an equivalent pair of :9 and 8 conditions. You can therefore rewrite this OAHA?P statement to replace the HEGA condition with an indexable search condition as follows: OAHA?P_*?qnnaj_u?k`a BNKIO]hao*?qnnaj_u=O_ SDANA_*WJ]iaY:9#E_a# =J@_*WJ]iaY8#E_B# Note that, in both cases, the number of logical reads, the execution time for the query with the HEGA condition, and the manually converted sargable search condition are all the same. Thus, if you include leading characters in the HEGA clause, the SQL Server 2008 optimizer optimizes the search condition to allow the use of indexes on the column.
!< Condition vs. >= Condition Even though both the 8 and :9 search conditions retrieve the same result set, they may perform different operations internally. The :9 comparison operator allows the optimizer to use an index on the column referred to in the search argument, because the 9 part of the operator >ÜÃÊÌ
iÊ«ÌâiÀÊÌÊÃiiÊÌÊ>ÊÃÌ>ÀÌ}Ê«ÌÊÊÌ
iÊ`iÝÊ>`Ê>VViÃÃÊ>ÊÌ
iÊ`iÝÊÀÜÃÊvÀÊ Ì
iÀiÊÜ>À`°Ê"ÊÌ
iÊÌ
iÀÊ
>`]ÊÌ
iÊ8Ê«iÀ>ÌÀÊ`iýÌÊ
>ÛiÊ>Ê9 element and needs to access the column value for every row.
319
320
C H A P T E R 1 1 N Q U E R Y D E S I G N A N A L Y S I S
"ÀÊ`iÃÊ̶ÊÃÊiÝ«>i`ÊÊ
>«ÌiÀÊ]ÊÌ
iÊ-+Ê-iÀÛiÀÊ«ÌâiÀÊ«iÀvÀÃÊÃÞÌ>ÝL>Ãi`Ê optimization, before executing a query, to improve performance. This allows SQL Server to Ì>iÊV>ÀiÊvÊÌ
iÊ«iÀvÀ>ViÊVViÀÊÜÌ
ÊÌ
iÊ8 operator by converting it to :9, as shown in the execution plan in Figure 11-6 for the two following OAHA?P statements: OAHA?P& BNKILqn_d]oejc*Lqn_d]oaKn`anDa]`an=Olkd SDANAlkd*Lqn_d]oaKn`anE@:9.531 OAHA?P& BNKILqn_d]oejc*Lqn_d]oaKn`anDa]`an=Olkd SDANAlkd*Lqn_d]oaKn`anE@8.531 As you can see, the optimizer often provides you with the flexibility of writing queries in the preferred T-SQL syntax without sacrificing performance. Although the SQL Server optimizer can automatically optimize query syntax to improve performance in many cases, you should not rely on it to do so. It is a good practice to write efficient queries in the first place.
Figure 11-6. Execution plan showing automatic transformation of a nonindexable 8 operator to an indexable :9 operator
Avoid Arithmetic Operators on the WHERE Clause Column 1Ã} an arithmetic operator on a column in the SDANA clause prevents the optimizer from using the index on the column. For example, consider the following OAHA?P statement: OAHA?P& BNKILqn_d]oejc*Lqn_d]oaKn`anDa]`an=Olkd SDANAlkd*Lqn_d]oaKn`anE@&.9/0,, A multiplication operator, &, has been applied on the column in the SDANA clause. You can avoid this on the column by rewriting the OAHA?P statement as follows:
C H A P T E R 1 1 N Q U E R Y D E S I G N A N A L Y S I S
OAHA?P& BNKILqn_d]oejc*Lqn_d]oaKn`anDa]`an=Olkd SDANAlkd*Lqn_d]oaKn`anE@9/0,,+. The table has a clustered index on the Lqn_d]oaKn`anE@ column. As explained in Chapter 4, an Ej`atOaag operation on this index will be suitable for this query since it returns only one row. Even though both queries return the same result set, the use of the multiplication operator on the Lqn_d]oaKn`anE@ column in the first query prevents the optimizer from using the index on the column, as you can see in Figure 11-7.
Figure 11-7. Execution plan showing the detrimental effect of an arithmetic operator on a SDANA clause column Their corresponding OP=PEOPE?OEK and PEIA outputs are as follows: Ê
UÊ 7Ì
ÊÌ
iÊ& operator on the Lqn_d]oaKn`anE@ column: P]^ha#Lqn_d]oaKn`anDa]`an#*O_]j_kqjp-(hkce_]hna]`o-, ?LQpeia9,io(ah]loa`peia924io*
Ê
UÊ 7Ì
ÊÊ«iÀ>ÌÀÊÊÌ
iÊLqn_d]oaKn`anE@ column: P]^ha#Lqn_d]oaKn`anDa]`an#*O_]j_kqjp,(hkce_]hna]`o. ?LQpeia9,io(ah]loa`peia921io*
Therefore, to use the indexes effectively and improve query performance, avoid using arithmetic operators on column(s) in the SDANA clause.
NNote For small result sets, even though an index seek is usually a better data-retrieval strategy than a table scan (or a complete clustered index scan), for very small tables (in which all data rows fit on one page) a table scan can be cheaper. I explain this in more detail in Chapter 4.
321
322
C H A P T E R 1 1 N Q U E R Y D E S I G N A N A L Y S I S
Avoid Functions on the WHERE Clause Column In the same way as arithmetic operators, functions on SDANA clause columns also hurt query performance—and for the same reasons. Try to avoid using functions on SDANA clause columns, as shown in the following two examples: Ê
UÊ OQ>OPNEJC vs. HEGA
Ê
UÊ >ÌiÊ«>ÀÌÊV«>ÀÃ
SUBSTRING vs. LIKE In the following OAHA?P statement (oq^opnejc*omh in the download), using the OQ>OPNEJC function prevents the use of the index on the OdelLkop]h?k`a column: OAHA?P`*J]ia BNKIDqi]jNaokqn_ao*@al]npiajp=O` SDANAOQ>OPNEJC$`*WJ]iaY(-(-%9#B# Figure 11-8 illustrates this.
Figure 11-8. Execution plan showing the detrimental effect of using the OQ>OPNEJC function on a SDANA clause column As you can see, using the OQ>OPNEJC function prevented the optimizer from using the index on the WJ]iaY column. This function on the column made the optimizer use a clustered index scan. In the absence of the clustered index on the @al]npiajpE@ column, a table scan would have been performed. You can redesign this OAHA?P statement to avoid the function on the column as follows: OAHA?P`*J]ia BNKIDqi]jNaokqn_ao*@al]npiajp=O` SDANA`*WJ]iaYHEGA#B!# This query allows the optimizer to choose the index on the WJ]iaY column, as shown in }ÕÀiÊ££°
Figure 11-9. Execution plan showing the benefit of not using the OQ>OPNEJC function on a SDANA clause column
C H A P T E R 1 1 N Q U E R Y D E S I G N A N A L Y S I S
Date Part Comparison SQL Server can store date and time data as separate fields or as a combined @=PAPEIA field Ì
>ÌÊ
>ÃÊLÌ
°ÊÌ
Õ}
ÊÞÕÊ>ÞÊii`ÊÌÊii«Ê`>ÌiÊ>`ÊÌiÊ`>Ì>ÊÌ}iÌ
iÀÊÊiÊvi`]ÊÃitimes you want only the date, which usually means you have to apply a conversion function to extract the date part from the @=PAPEIA data type. Doing this prevents the optimizer from choosing the index on the column, as shown in the following example. First, there needs to be a good index on the @=PAPEIAÊVÕÊvÊiÊvÊÌ
iÊÌ>LiðÊ1ÃiÊ O]hao*O]haoKn`anDa]`an, and create the following index: EBATEOPO$OAHA?P& BNKIouo*ej`atao SDANAk^fa_p[e`9K>FA?P[E@$J#WO]haoY*WO]haoKn`anDa]`anY#% =J@j]ia9J#ET[Paop#% @NKLEJ@ATW=G[O]haoKn`anDa]`an[nkscqe`YKJWO]haoY*WO]haoKn`anDa]`anY CK ?NA=PAEJ@ATET[PaopKJO]hao*O]haoKn`anDa]`an$Kn`an@]pa% To retrieve all rows from O]hao*O]haoKn`anDa]`an with Kn`an@]pa in the month of April in the year 2002, you can execute the following OAHA?P statement (`]papeia*omh): OAHA?Pokd*O]haoKn`anE@ (okd*Kn`an@]pa BNKIO]hao*O]haoKn`anDa]`an=Ookd FKEJO]hao*O]haoKn`an@ap]eh=Ook` KJokd*O]haoKn`anE@9ok`*O]haoKn`anE@ SDANA@=PAL=NP$uu(okd*Kn`an@]pa%9.,,. =J@@=PAL=NP$ii(okd*Kn`an@]pa%90 1Ã}ÊÌ
i @=PAL=NP function on the column Kn`an@]pa prevents the optimizer from considering index ET[Paop on the column and instead causes a table scan, as shown in Figure 11-10.
Figure 11-10. Execution plan showing the detrimental effect of using the @=PAL=NP function on a SDANA clause column This is the output of OAPOP=PEOPE?OEK and PEIA: P]^ha#Skngp]^ha#*O_]j_kqjp,(hkce_]hna]`o, P]^ha#O]haoKn`an@ap]eh#*O_]j_kqjp-(hkce_]hna]`o..4 P]^ha#O]haoKn`anDa]`an#*O_]j_kqjp-(hkce_]hna]`o2?LQpeia9/-io(ah]loa`peia923io*
323
324
C H A P T E R 1 1 N Q U E R Y D E S I G N A N A L Y S I S
The date part comparison can be done without applying the function on the @=PAPEIA column: OAHA?Pokd*O]haoKn`anE@ (okd*Kn`an@]pa BNKIO]hao*O]haoKn`anDa]`an=Ookd FKEJO]hao*O]haoKn`an@ap]eh=Ook` KJokd*O]haoKn`anE@9ok`*O]haoKn`anE@ SDANAokd*Kn`an@]pa:9#.,,.),0),-# =J@okd*Kn`an@]pa8#.,,.),1),-# This allows the optimizer to consider index ET[Paop that was created on the @=PAPEIA column, as shown in Figure 11-11.
Figure 11-11. Execution plan showing the benefit of not using the ?KJRANP function on a SDANA clause column This is the output of OAPOP=PEOPE?O EK and PEIA: P]^ha#O]haoKn`an@ap]eh#*O_]j_kqjp.00(hkce_]hna]`o4-0 P]^ha#O]haoKn`anDa]`an#*O_]j_kqjp-(hkce_]hna]`o/ ?LQpeia9,io(ah]loa`peia9-,5io Therefore, to allow the optimizer to consider an index on a column referred to in the SDANA clause, always avoid using a function on the indexed column. This increases the effectiveness vÊ`iÝiÃ]ÊÜ
V
ÊV>Ê«ÀÛiʵÕiÀÞÊ«iÀvÀ>Vi°ÊÊÌ
ÃÊÃÌ>Vi]ÊÌ
Õ}
]Ê̽ÃÊÜÀÌ
ÊÌing that the performance on the O]haoKn`anDa]`an table increased, but the optimizer chose a Jaopa`Hkkl join rather than the D]od of the plan shown in Figure 11-10. This increased overall performance because of the extra scans against the O]haoKn`an@ap]eh table. Be sure to drop the index created earlier: @NKLEJ@ATO]hao*O]haoKn`anDa]`an*ET[Paop7
Avoiding Optimizer Hints -+Ê-iÀÛiÀ½ÃÊVÃÌL>Ãi` optimizer dynamically determines the processing strategy for a query, based on the current table/index structure and data. This dynamic behavior can be overridden ÕÃ}Ê«ÌâiÀÊ
ÌÃ]ÊÌ>}ÊÃiÊvÊÌ
iÊ`iVÃÃÊ>Ü>ÞÊvÀÊÌ
iÊ«ÌâiÀÊLÞÊÃÌÀÕVÌ}ÊÌÊÌÊ ÕÃiÊ>ÊViÀÌ>Ê«ÀViÃÃ}ÊÃÌÀ>Ìi}Þ°Ê/
ÃÊ>iÃÊÌ
iÊ«ÌâiÀÊLi
>ÛÀÊÃÌ>ÌVÊ>`Ê`iýÌÊ>ÜÊÌÊ to dynamically update the processing strategy as the table/index structure or data changes.
C H A P T E R 1 1 N Q U E R Y D E S I G N A N A LY S I S
Since it is usually difficult to outsmart the optimizer, the usual recommendation is to avoid optimizer hints. Generally, it is beneficial to let the optimizer determine a cost-effective processing strategy based on the data distribution statistics, indexes, and other factors. Forcing the optimizer (with hints) to use a specific processing strategy hurts performance more often than not, as shown in the following examples for these hints: Ê
UÊ FKEJ hint
Ê
UÊ EJ@AT hint
Ê
UÊ BKN?ALH=J hint
JOIN Hint As explained in Chapter 3, the optimizer dynamically determines a cost-effective FKEJ strategy between two data sets, based on the table/index structure and data. Table 11-2 presents a summary of the FKEJ types supported by SQL Server 2008 for easy reference. Table 11-2. FKEJ Types Supported by SQL Server 2008 FKEJ Type
Index on Joining Columns
Usual Size of Joining Tables
Presorted FKEJ Clause
Nested loop
Inner table must. "ÕÌiÀÊÌ>LiÊ«ÀiviÀ>Li°
Small
"«Ì>
Merge
Both tables must. "«Ì>ÊV`Ì\Ê Clustered or covering index on both.
Large
Yes
>Ã
Inner table not indexed.
Any "«Ì>ÊV`Ì\Ê Inner table large. "ÕÌiÀÊÌ>LiÊÃ>°
No
NNote The outer table is usually the smaller of the two joining tables.
You can instruct SQL Server to use a specific FKEJ type by using the FKEJ hints in Table 11-3. Table 11-3. FKEJ Hints FKEJ Type
FKEJ Hint
Nested loop
HKKLFKEJ
Merge
IANCAFKEJ
>Ã
D=ODFKEJ
325
326
C H A P T E R 1 1 N Q U E R Y D E S I G N A N A L Y S I S
To understand how the use of FKEJ hints can affect performance, consider the following OAHA?P statement (fkej*omh in the download): OAHA?Po*WJ]iaY=OOpknaJ]ia (l*WH]opJ]iaY'#(#'l*WBenopJ]iaY BNKIWO]haoY*WOpknaYo FKEJWO]haoY*O]haoLanokj=Ool KJo*O]haoLanokjE@9ol*>qoejaooAjpepuE@ FKEJDqi]jNaokqn_ao*Ailhkuaa=Oa KJol*>qoejaooAjpepuE@9a*>qoejaooAjpepuE@ FKEJLanokj*Lanokj=Ol KJa*>qoejaooAjpepuE@9l*>qoejaooAjpepuE@ Figure 11-12 shows the execution plan.
Figure 11-12. Execution plan showing a simple join between two tables As you can see, SQL Server dynamically decided to use a HKKLFKEJ to add the data from the Lanokj*Lanokj table and to add a D=ODFKEJ for the O]hao*O]haoLanokj and O]hao*Opkna tables. As demonstrated in Chapter 3, for simple queries affecting a small result set, the HKKL FKEJ generally provides better performance than a D=ODFKEJ or IANCAFKEJ. Since the number of rows coming from the O]hao*O]haoLanokjÊÌ>LiÊÃÊÀi>ÌÛiÞÊÃ>]ÊÌÊ}
ÌÊviiÊiÊÞÕÊVÕ`Ê force the FKEJ to be a HKKLÊiÊÌ
Ã\ OAHA?Po*WJ]iaY=OOpknaJ]ia (l*WH]opJ]iaY'#(#'l*WBenopJ]iaY BNKIWO]haoY*WOpknaYo FKEJWO]haoY*O]haoLanokj=Ool KJo*O]haoLanokjE@9ol*>qoejaooAjpepuE@ FKEJDqi]jNaokqn_ao*Ailhkuaa=Oa KJol*>qoejaooAjpepuE@9a*>qoejaooAjpepuE@ FKEJLanokj*Lanokj=Ol KJa*>qoejaooAjpepuE@9l*>qoejaooAjpepuE@ KLPEKJ$HKKLFKEJ% When this query is run, the execution plan changes, as you can see in Figure 11-13.
C H A P T E R 1 1 N Q U E R Y D E S I G N A N A L Y S I S
Figure 11-13. Cost of a query with and without a FKEJ hint The corresponding OP=PEOPE?OEK and PEIA outputs for each query are as follows: Ê
UÊ 7Ì
ÊÊFKEJ hint: P]^ha#Lanokj#*O_]j_kqjp,(hkce_]hna]`o.-11 P]^ha#Ailhkuaa#*O_]j_kqjp,(hkce_]hna]`o-0,. P]^ha#O]haoLanokj#*O_]j_kqjp,(hkce_]hna]`o-0,. P]^ha#Opkna#*O_]j_kqjp-(hkce_]hna]`o-,/ ?LQpeia9,io(ah]loa`peia9-,,io*
Ê
UÊ 7Ì
Ê>ÊFKEJ hint: P]^ha#Lanokj#*O_]j_kqjp,(hkce_]hna]`o.-11 P]^ha#Ailhkuaa#*O_]j_kqjp,(hkce_]hna]`o-0,. P]^ha#O]haoLanokj#*O_]j_kqjp,(hkce_]hna]`o-0,. P]^ha#Opkna#*O_]j_kqjp-(hkce_]hna]`o-,/ ?LQpeia9-2io(ah]loa`peia9-/5io*
You can see that the query with the FKEJÊ
ÌÊÌ>iÃÊ}iÀÊÌÊÀÕÊÌ
>ÊÌ
iʵÕiÀÞÊÜÌ
ÕÌÊ Ì
iÊ
̰ÊÌÊ>ÃÊ>``ÃÊÛiÀ
i>`ÊÌÊÌ
iÊ *1° FKEJ hints force the optimizer to ignore its own optimization strategy and use instead the strategy specified by the query. FKEJ hints generally hurt query performance because of the following factors: Ê
UÊ ÌÃÊ«ÀiÛiÌÊ>ÕÌ«>À>iÌiÀâ>̰
Ê
UÊ /
iÊ«ÌâiÀÊÃÊ«ÀiÛiÌi`ÊvÀÊ`Þ>V>ÞÊ`iV`}ÊÌ
iÊ}ÊÀ`iÀÊvÊÌ
iÊÌ>Lið
/
iÀivÀi]ÊÌÊ>iÃÊÃiÃiÊÌÊÌÊÕÃiÊÌ
iÊFKEJ hint but to instead let the optimizer dynamically determine a cost-effective processing strategy.
INDEX Hints As mentioned earlier, using an arithmetic operator on a SDANA clause column prevents the optimizer from choosing the index on the column. To improve performance, you can rewrite the query without using the arithmetic operator on the SDANA clause, as shown in the
327
328
CHAPTER 11 N Q U ER Y DES IG N A NA L YS IS
VÀÀië`}ÊiÝ>«i°ÊÌiÀ>ÌÛiÞ]ÊÞÕÊ>ÞÊiÛiÊÌ
ÊvÊvÀV}ÊÌ
iÊ«ÌâiÀÊÌÊÕÃiÊÌ
iÊ index on the column with an EJ@ATÊ
ÌÊ>ÊÌÞ«iÊvÊ«ÌâiÀÊ
Ì®°ÊÜiÛiÀ]ÊÃÌÊvÊÌ
iÊÌi]Ê it is better to avoid the EJ@AT hint and let the optimizer behave dynamically. To understand the effect of an EJ@AT hint on query performance, consider the example «ÀiÃiÌi`ÊÊÌ
iʺÛ`ÊÀÌ
iÌVÊ"«iÀ>ÌÀÃÊÊÌ
iÊ7 , Ê >ÕÃiÊ Õ»ÊÃiV̰Ê/
iÊ multiplication operator on the Lqn_d]oaKn`anE@ column prevented the optimizer from choosing the index on the column. You can use an EJ@AT hint to force the optimizer to use the index on the Kn`anE@ column as follows: OAHA?P& BNKILqn_d]oejc*Lqn_d]oaKn`anDa]`an=Olkd SEPD$EJ@AT$LG[Lqn_d]oaKn`anDa]`an[Lqn_d]oaKn`anE@%% SDANAlkd*Lqn_d]oaKn`anE@&.9/0,, Note the relative cost of using the EJ@AT hint in comparison to not using the EJ@AT hint, as shown in Figure 11-14. Also, note the difference in the number of logical reads shown in the following OP=PEOPE?O EK outputs: Ê
UÊ
Ê
ÌÊÜÌ
ÊÌ
iÊ>ÀÌ
iÌVÊ«iÀ>ÌÀÊÊÌ
iÊSDANA clause column): P]^ha#Lqn_d]oaKn`anDa]`an#*O_]j_kqjp-(hkce_]hna]`o-, ?LQpeia9,io(ah]loa`peia9.,2io*
Ê
UÊ
Ê
ÌÊÜÌ
ÕÌÊÌ
iÊ>ÀÌ
iÌVÊ«iÀ>ÌÀÊÊÌ
iÊSDANA clause column): P]^ha#Lqn_d]oaKn`anDa]`an#*O_]j_kqjp,(hkce_]hna]`o. ?LQpeia9-2io(ah]loa`peia9/.io*
Ê
UÊ EJ@AT hint: P]^ha#Lqn_d]oaKn`anDa]`an#*O_]j_kqjp-(hkce_]hna]`o00 ?LQpeia9-2io(ah]loa`peia93-io*
From the relative cost of execution plans and number of logical reads, it is evident that the query with the EJ@AT hint actually impaired the query performance. Even though it allowed the optimizer to use the index on the Lqn_d]oaKn`anE@ column, it did not allow the optimizer to determine the proper index-access mechanism. Consequently, the optimizer used the index scan to access just one row. In comparison, avoiding the arithmetic operator on the SDANA clause column and not using the EJ@AT hint allowed the optimizer not only to use the index on the Lqn_d]oaKn`anE@ column but also to determine the proper index access iV
>Ã\Ê`iÝÊÃii° Therefore, in general, let the optimizer choose the best indexing strategy for the query, >`Ê`½ÌÊÛiÀÀ`iÊÌ
iÊ«ÌâiÀÊLi
>ÛÀÊÕÃ}Ê>ÊEJ@AT hint. Also, not using EJ@AT hints allows the optimizer to decide the best indexing strategy dynamically as the data changes over time.
C H A P T E R 1 1 N Q U E R Y D E S I G N A N A L Y S I S
Figure 11-14. Cost of a query with and without an EJ@AT hint
Using Domain and Referential Integrity Domain and referential integrity help define and enforce valid values for a column, maintaining the integrity of the database. This is done through column/table constraints. Since data access is usually one of the most costly operations in a query execution, avoiding redundant data access helps the optimizer reduce the query execution time. Domain and referential integrity help the SQL Server 2008 optimizer analyze valid data values without physically accessing the data, which reduces query time. To understand how this happens, consider the following examples: Ê
UÊ /
iÊJKPJQHH constraint
Ê
UÊ iV>À>ÌÛiÊÀiviÀiÌ>ÊÌi}ÀÌÞÊ ,®
NOT NULL Constraint The JKPJQHH column constraint is used to implement domain integrity by defining the fact that a JQHH value cannot be entered in a particular column. SQL Server automatically enforces this fact at runtime to maintain the domain integrity for that column. Also, defining the JKP JQHH column constraint helps the optimizer generate an efficient processing strategy when the EOJQHH function is used on that column in a query.
329
330
C H A P T E R 1 1 N Q U E R Y D E S I G N A N A L Y S I S
To understand the performance benefit of the JKPJQHH column constraint, consider the following example. In the following example, these two queries are intended to return every value that does not equal #>#. These two queries are running against similarly sized columns, each of which will require a table scan in order to return the data (jqhh*omh in the download): OAHA?Pl*BenopJ]ia BNKILanokj*Lanokj=Ol SDANAl*BenopJ]ia8#># KNl*Benopj]ia:9#?#7 OAHA?Pl*Ie``haJ]ia BNKILanokj*Lanokj=Ol SDANAl*Ie``haJ]ia8#># KNl*Ie``haJ]ia:9#?#7 The two queries use identical execution plans, as you can see in Figure 11-15.
Figure 11-15. Table scans caused by a lack of indexes Since the column Lanokj*Ie``haJ]ia can contain JQHH, the data returned is incomplete. This is because, by definition, although a JQHH value meets the necessary criteria of not being in any way equal to >]ÊÞÕÊV>½ÌÊÀiÌÕÀÊJQHH values in this manner. An added KN clause is neciÃÃ>ÀÞ°Ê/
>ÌÊÜÕ`Êi>Ê`vÞ}ÊÌ
iÊÃiV`ʵÕiÀÞÊiÊÌ
Ã\ OAHA?Pl*BenopJ]ia BNKILanokj*Lanokj=Ol SDANAl*BenopJ]ia8#># KNl*Benopj]ia:9#?#7 OAHA?Pl*Ie``haJ]ia BNKILanokj*Lanokj=Ol SDANAl*Ie``haJ]ia8#># KNl*Ie``haJ]ia:9#?# KNl*Ie``haJ]iaEOJQHH7
C H A P T E R 1 1 N Q U E R Y D E S I G N A N A L Y S I S
Also, as shown in the missing index statements in the execution plan in Figure 11-15, these two queries can benefit from having indexes created on their tables. Creating test `iÝiÃÊiÊÌ
iÊvÜ}ÊÃ
Õ`ÊÃ>ÌÃvÞÊÌ
iÊÀiµÕÀiiÌÃ\ ?NA=PAEJ@ATET[Paop-KJLanokj*Lanokj$Ie``haJ]ia%7 ?NA=PAEJ@ATET[paop.KJLanokj*Lanokj$BenopJ]ia%7 When the queries are reexecuted, Figure 11-16 shows the resultant execution plan for the two OAHA?P statements.
Figure 11-16. Effect of the EOJQHH option being used ÃÊÃ
ÜÊÊ}ÕÀiÊ£££È]ÊÌ
iÊ«ÌâiÀÊÜ>ÃÊ>LiÊÌÊÌ>iÊ>`Û>Ì>}iÊvÊÌ
iÊ`iÝÊET[Paop. on the Lanokj*BenopJ]ia column to get a nice clean Ej`atOaagÊ«iÀ>̰Ê1vÀÌÕ>ÌiÞ]ÊÌ
iÊ requirements for processing the JQHH columns were very different. The index ET[Paop- was not used in the same way. Instead, three constants were created for each of the three criteria defined within the query. These were then joined together through the ?kj_]paj]pekj operation, sorted and merged prior to scanning the index three times through the Jaopa`Hkkl operator to arrive at the result set. Although it appears, from the estimated costs in the execution plan, that this was the less costly query (43 percent compared to 57 percent), OP=PEOPE?O EK and PEIA tell the more accurate story, which is that the JQHH queries were more costly: P]^ha#Lanokj#*O_]j_kqjp.(hkce_]hna]`o15 ?LQpeia9,io(ah]loa`peia9//2io* vs. P]^ha#Lanokj#*O_]j_kqjp/(hkce_]hna]`o0. ?LQpeia9,io(ah]loa`peia902-io*
331
332
CHAPTER 11 N Q U ER Y DES IG N A NA L YS IS
Be sure to drop the test indexes that were created: @NKLEJ@ATlanokj*Lanokj*et[paop. @NKLEJ@ATLanokj*Lanokj*ET[PaopAs much as possible, you should attempt to leave JQHH values out of the database. ÜiÛiÀ]ÊÜ
iÊ`>Ì>ÊÃÊÕÜ]Ê`iv>ÕÌÊÛ>ÕiÃÊ>ÞÊÌÊLiÊ«ÃÃLi°Ê/
>̽ÃÊÜ
iÊJQHH will ViÊL>VÊÌÊÌ
iÊ`iÃ}°ÊÊv`ÊÌÊÕ>Û`>Li]ÊLÕÌÊ̽ÃÊÃiÌ
}ÊÌÊâiÊ>ÃÊÕV
Ê>ÃÊ you can. When it is unavoidable and you will be dealing with JQHHÊÛ>ÕiÃ]Êii«ÊÊ`ÊÌ
>ÌÊÞÕÊ can use a filtered index that removes JQHH values from the index, thereby improving the performance of that index. This was detailed in Chapter 4. Sparse columns, introduced in SQL Server 2008, offer another option to help you deal with JQHH values. Sparse columns are primarily aimed at storing JQHH values more efficiently and therefore reduce space, at a sacrifice in performance. This option is specifically targeted at business intelligence (BI) databases, ÌÊ"/*Ê`>Ì>L>ÃiÃ]ÊÜ
iÀi large amounts of JQHH values in fact tables are a normal part of the design.
Declarative Referential Integrity Declarative referential integrity is used to define referential integrity between a parent table and a child table. It ensures that a record in the child table exists only if the corresponding record in the parent table exists. The only exception to this rule is that the child table can contain a JQHHÊÛ>ÕiÊvÀÊÌ
iÊ`iÌviÀÊÌ
>ÌÊÃÊÌ
iÊÀÜÃÊvÊÌ
iÊV
`ÊÌ>LiÊÌÊÌ
iÊÀÜÃÊvÊÌ
iÊ«>ÀiÌÊ table. For all other values of the identifier in the child, a corresponding value must exist in the parent table. In SQL Server, DRI is implemented using a LNEI=NUGAU constraint on the parent table and a BKNAECJGAU constraint on the child table. 7Ì
Ê ,ÊiÃÌ>LÃ
i`ÊLiÌÜiiÊÌÜÊÌ>LiÃÊ>`ÊÌ
iÊvÀi}ÊiÞÊVÕÃÊvÊÌ
iÊV
`ÊÌ>LiÊ set to JKPJQHH, the SQL Server 2008 optimizer is assured that for every record in the child table, the parent table has a corresponding record. Sometimes this can help the optimizer improve performance, because accessing the parent table is not necessary to verify the existence of a parent record for a corresponding child record. To understand the performance benefit of implementing declarative referential integrity, i̽ÃÊVÃ`iÀÊ>ÊiÝ>«i°ÊÀÃÌÊi>ÌiÊÌ
iÊÀiviÀiÌ>ÊÌi}ÀÌÞÊLiÌÜiiÊÌÜÊÌ>LiÃ]ÊLanokj* =``naoo and Lanokj*Op]paLnkrej_a, using this script: EBATEOPO$OAHA?P& BNKIouo*bknaecj[gauo SDANAk^fa_p[e`9 K>FA?P[E@$J#WLanokjY*WBG[=``naoo[Op]paLnkrej_a[Op]paLnkrej_aE@Y#% =J@l]najp[k^fa_p[e`9K>FA?P[E@$J#WLanokjY*W=``naooY#%% =HPANP=>HAWLanokjY*W=``naooY @NKL?KJOPN=EJPWBG[=``naoo[Op]paLnkrej_a[Op]paLnkrej_aE@Y
C H A P T E R 1 1 N Q U E R Y D E S I G N A N A L Y S I S
Consider the following OAHA?P statement (lnk`*omh in the download): OAHA?P]*=``naooE@ (ol*Op]paLnkrej_aE@ BNKILanokj*=``naoo=O] FKEJLanokj*Op]paLnkrej_a=Ool KJ]*Op]paLnkrej_aE@9ol*Op]paLnkrej_aE@ SDANA]*=``naooE@9.3./07 Note that the OAHA?P statement fetches the value of the Op]paLnkrej_aE@ column from the parent table (Lanokj*=``naoo). If the nature of the data requires that for every product (identified by Op]paLnkrej_aE`) in the child table (Lanokj*Op]paLnkrej_a) the parent table (Lanokj* =``naoo) contains a corresponding product, then you can rewrite the preceding OAHA?P statement as follows (lnk`*omh in the download): OAHA?P]*=``naooE@ (]*Op]paLnkrej_aE@ BNKILanokj*=``naoo=O] FKEJLanokj*Op]paLnkrej_a=Ool KJ]*Op]paLnkrej_aE@9ol*Op]paLnkrej_aE@ SDANA]*=``naooE@9.3./07 Both OAHA?P statements should return the same result set. Even the optimizer generates the same execution plan for both the OAHA?P statements, as shown in Figure 11-17.
Figure 11-17. Execution plan when DRI is not defined between the two tables To understand how declarative referential integrity can affect query performance, replace the BKNAECJGAU dropped earlier: =HPANP=>HAWLanokjY*W=``naooY SEPD?DA?G =@@?KJOPN=EJPWBG[=``naoo[Op]paLnkrej_a[Op]paLnkrej_aE@Y BKNAECJGAU$WOp]paLnkrej_aE@Y%NABANAJ?AOWLanokjY*WOp]paLnkrej_aY$WOp]paLnkrej_aE@Y%7
NNote There is now referential integrity between the tables.
333
334
C H A P T E R 1 1 N Q U E R Y D E S I G N A N A L Y S I S
Figure 11-18 shows the resultant execution plans for the two OAHA?P statements.
Figure 11-18. Execution plans showing the benefit of defining DRI between the two tables As you can see, the execution plan of the second OAHA?P statement is highly optimized: the Lanokj*Op]paLnkrej_a table is not accessed. With the declarative referential integrity in place (and =``naoo*Op]paLnkrej_a set to JKPJQHH), the optimizer is assured that for every record in the child table, the parent table contains a corresponding record. Therefore, the FKEJ clause between the parent and child tables is redundant in the second OAHA?P statement, with no other data requested from the parent table. 9ÕÊ«ÀL>LÞÊ>Ài>`ÞÊiÜÊÌ
>ÌÊ`>Ê>`ÊÀiviÀiÌ>ÊÌi}ÀÌÞÊ>ÀiÊ>Ê`Ê/
}]ÊLÕÌÊ you can see that they not only ensure data integrity but also improve performance. As just illustrated, domain and referential integrity provide more choices to the optimizer to generate cost-effective execution plans and improve performance. /Ê>V
iÛiÊÌ
iÊ«iÀvÀ>ViÊLiivÌÊvÊ ,]Ê>ÃÊiÌi`Ê«ÀiÛÕÃÞ]ÊÌ
iÊvÀi}ÊiÞÊ columns in the child table should be JKPJQHH°Ê"Ì
iÀÜÃi]ÊÌ
iÀiÊV>ÊLiÊÀÜÃÊÜÌ
ÊvÀi}Ê iÞÊVÕÊÛ>ÕiÃÊ>ÃÊJQHH) in the child table with no representation in the parent table. That ܽÌÊ«ÀiÛiÌÊÌ
iÊ«ÌâiÀÊvÀÊ>VViÃÃ}ÊÌ
iÊ«À>ÀÞÊÌ>LiÊLnk`) in the previous query. By default—that is, if the JKPJQHHÊ>ÌÌÀLÕÌiÊýÌÊiÌi`ÊvÀÊ>ÊVÕpÌ
iÊVÕÊV>Ê have JQHH values. Considering the benefit of the JKPJQHH attribute and the other benefits iÝ«>i`ÊÊÌ
ÃÊÃiVÌ]Ê>Ü>ÞÃÊ>ÀÊÌ
i attribute of a column as JKPJQHH if JQHHÊýÌÊ>Ê valid value for that column.
Avoiding Resource-Intensive Queries Many database functionalities can be implemented using a variety of query techniques. The >««À>V
ÊÞÕÊÃ
Õ`ÊÌ>iÊÃÊÌÊÕÃiʵÕiÀÞÊÌiV
µÕiÃÊÌ
>ÌÊ>ÀiÊÛiÀÞÊÀiÃÕÀViÊvÀi`ÞÊ>`ÊÃiÌÊ based. A few techniques you can use to reduce the footprint of a query are as follows: Ê
UÊ Û`Ê`>Ì>ÊÌÞ«iÊVÛiÀð
Ê
UÊ 1ÃiÊATEOPO over ?KQJP$&% to verify data existence.
Ê
UÊ 1ÃiÊQJEKJ=HH over QJEKJ.
C H A P T E R 1 1 N Q U E R Y D E S I G N A N A LY S I S
Ê
UÊ 1ÃiÊ`iÝiÃÊvÀÊ>}}Ài}>ÌiÊ>`ÊÃÀÌÊ«iÀ>Ìð
Ê
UÊ Û`ÊV>ÊÛ>À>LiÃÊÊ>ÊL>ÌV
ʵÕiÀÞ°
Ê
UÊ iÊV>ÀivÕÊ>}ÊÃÌÀi`Ê«ÀVi`ÕÀið I cover these points in more detail in the next sections.
Avoid Data Type Conversion SQL Server allows, in some instances (defined by the large table of data conversions available Ê ÃÊ"i®]Ê>ÊÛ>ÕiÉVÃÌ>ÌÊÜÌ
different but compatible data types to be compared ÜÌ
Ê>ÊVÕ½ÃÊ`>Ì>°Ê-+Ê-iÀÛiÀÊ>ÕÌ>ÌV>ÞÊVÛiÀÌÃÊÌ
iÊ`>Ì>ÊvÀÊiÊ`>Ì>ÊÌÞ«iÊÌÊ another. This process is called implicit data type conversion. Although useful, implicit conversion adds overhead to the query optimizer. To improve performance, use a variable/constant with the same data type as that of the column to which it is compared. To understand how implicit data type conversion affects performance, consider the following example (_kjranoekj*omh in the download): EBATEOPO$OAHA?P& BNKIouo*k^fa_po SDANAk^fa_p[e`9K>FA?P[E@$J#W`^kY*Wp-Y#% =J@pulaEJ$J#Q#%% @NKLP=>HAW`^kY*Wp-Y7 ?NA=PAP=>HA`^k*p$E`EJPE@AJPEPU$-(-% (IuGauR=N?D=N$1,% (IuR]hqaR=N?D=N$1,%%7 ?NA=PAQJEMQA?HQOPANA@EJ@ATLG[p-KJ`^k*p-$WE`Y=O?%7 ?NA=PAQJEMQAJKJ?HQOPANA@EJ@ATET[PaopKJ`^k*p-$IuGau%7 CK OAHA?PPKL-,,,, E@AJPEPU$EJP(-(-%=Oj EJPKP]hhu BNKII]opan*`^k*Ouo?khqijoo_(I]opan*`^k*Ouo?khqijoo_.7 EJOANPEJPK`^k*p-$IuGau(IuR]hqa% OAHA?PPKL-,,,, #QjemqaGau#'?=OP$j=OR=N?D=N% (#@ao_nelpekj# BNKIP]hhu7 @NKLP=>HAP]hhu OAHA?PIuR]hqa BNKI`^k*pSDANAIuGau9#QjemqaGau///#7
335
336
C H A P T E R 1 1 N Q U E R Y D E S I G N A N A L Y S I S
OAHA?PIuR]hqa BNKI`^k*pSDANAIuGau9J#QjemqaGau///#7 After creating the table p-, creating a couple of indexes on it, and placing some data, two queries are defined. Both queries return the same result set. As you can see, both queries are identical except for the data type of the variable equated to the IuGau column. Since this column is R=N?D=N]ÊÌ
iÊvÀÃÌʵÕiÀÞÊ`iýÌÊÀiµÕÀiÊ>Ê«VÌÊ`>Ì>ÊÌÞ«iÊVÛiÀðÊ/
iÊÃiV`Ê query uses a different data type from that of the IuGau column, requiring an implicit data type VÛiÀÃÊ>`ÊÌ
iÀiLÞÊ>``}ÊÛiÀ
i>`ÊÌÊÌ
iʵÕiÀÞÊ«iÀvÀ>Vi°Ê}ÕÀiÊ£££ÊÃ
ÜÃÊÌ
iÊ execution plans for both queries.
Figure 11-19. Cost of a query with and without implicit data type conversion The complexity of the implicit data type conversion depends on the precedence of the data types involved in the comparison. The data type precedence rules of SQL Server specify Ü
V
Ê`>Ì>ÊÌÞ«iÊÃÊVÛiÀÌi`ÊÌÊÌ
iÊÌ
iÀ°Ê1ÃÕ>Þ]ÊÌ
iÊ`>Ì>ÊÌÞ«iÊvÊÜiÀÊ«ÀiVi`iViÊÃÊVverted to the data type of higher precedence. For example, the PEJUEJP data type has a lower precedence than the EJP data type. For a complete list of data type precedence in SQL Server Óään]Ê«i>ÃiÊÀiviÀÊÌÊÌ
iÊ- Ê>ÀÌViʺ >Ì>Ê/Þ«iÊ*ÀiVi`iVi»Êdppl6++io`j*ie_nkokbp*_ki+ aj)qo+he^n]nu+io-5,/,5*]olt). For further information about which data type can implicitly convert to which data type, refer to the MSDN article “Data Type Conversion” (dppl6++io`j* ie_nkokbp*_ki+aj)qo+he^n]nu+io-5-1/,*]olt). When SQL Server compares a column value with a certain data type and a variable (or constant) with a different data type, the data type of the variable (or constant) is always converted to the data type of the column. This is done because the column value is accessed based on the implicit conversion value of the variable (or constant). Therefore, in such cases, the implicit conversion is always applied on the variable (or constant). As you can see, implicit data type conversion adds overhead to the query performance LÌ
ÊÊÌiÀÃÊvÊ>Ê«ÀÊiÝiVÕÌÊ«>Ê>`ÊÊ>``i`Ê *1ÊVÃÌÊÌÊ>iÊÌ
iÊVÛiÀÃðÊ/
iÀifore, to improve performance, always use the same data type for both expressions.
C H A P T E R 1 1 N Q U E R Y D E S I G N A N A L Y S I S
Use EXISTS over COUNT(*) to Verify Data Existence A commonÊ`>Ì>L>ÃiÊÀiµÕÀiiÌÊÃÊÌÊÛiÀvÞÊÜ
iÌ
iÀÊ>ÊÃiÌÊvÊ`>Ì>ÊiÝÃÌðÊ1ÃÕ>ÞÊÞÕ½ÊÃiiÊÌ
ÃÊ implemented using a batch of SQL queries, as follows (_kqjp*omh in the download): @A?H=NA<jEJP OAHA?P<j9?KQJP$&% BNKIO]hao*O]haoKn`an@ap]eh=Ook` SDANAok`*Kn`anMpu9EB<j:, LNEJP#Na_kn`Ateopo# 1Ã}Ê?KQJP$&% to verify the existence of data is highly resource intensive, because ?KQJP$&% has to scan all the rows in a table. ATEOPO merely has to scan and stop at the first record that matches the ATEOPO criterion. To improve performance, use ATEOPO instead of the ?KQJP$&% approach: EBATEOPO$OAHA?Pok`*& BNKIO]hao*O]haoKn`an@ap]eh=Ook` SDANAok`*Kn`anMpu9-% LNEJP#Na_kn`Ateopo# The performance benefit of the ATEOPO technique over the ?KQJP$&% technique can be compared using the OP=PEOPE?OEK and PEIA output, as well as the execution plan in Figure 11-20, as you can see from the output of running these queries: P]^ha#O]haoKn`an@ap]eh#*O_]j_kqjp-(hkce_]hna]`o-.0, ?LQpeia9-2io(ah]loa`peia9-4io* P]^ha#O]haoKn`an@ap]eh#*O_]j_kqjp-(hkce_]hna]`o/ ?LQpeia9,io(ah]loa`peia9,io*
Figure 11-20. Difference between ?KQJP and ATEOPO
337
338
C H A P T E R 1 1 N Q U E R Y D E S I G N A N A L Y S I S
As you can see, the ATEOPO technique used only three logical reads compared to the 1,240 used by the ?KQJP$&% technique, and the execution time went from 16 ms to effectively 0. Therefore, to determine whether data exists, use the ATEOPO technique.
Use UNION ALL Instead of UNION You can concatenate the result set of multiple OAHA?P statements using the QJEKJ clause as follows: OAHA?P& BNKIO]hao*O]haoKn`anDa]`an=Ookd SDANAokd*O]haoKn`anJqi^anHEGA#!034,4# QJEKJ OAHA?P& BNKIO]hao*O]haoKn`anDa]`an=Ookd SDANAokd*O]haoKn`anJqi^anHEGA#!21304# The QJEKJ clause processes the result set from the two OAHA?P statements, removing duplicates from the final result set and effectively running @EOPEJ?P on each query. If the result sets of the OAHA?P statements participating in the QJEKJ clause are exclusive to each other or you are allowed to have duplicate rows in the final result set, then use QJEKJ=HH instead of QJEKJ. This avoids the overhead of detecting and removing any duplicates, improving performance as shown in Figure 11-21.
Figure 11-21. Cost of a query with the QJEKJ clause vs. the QJEKJ=HH clause
C H A P T E R 1 1 N Q U E R Y D E S I G N A N A LY S I S
As you can see, in the first case (using QJEKJ), the optimizer used a merge join to process the duplicates while concatenating the result set of the two OAHA?P statements. Since the result sets are exclusive to each other, you can use QJEKJ=HH instead of the QJEKJÊV>ÕÃi°Ê1Ã}ÊÌ
iÊ QJEKJ=HH clause avoids the overhead of detecting duplicates and thereby improves performance. The optimizer is smart enough to recognize when two queries will absolutely result in distinct lists and at those times can choose execution plans that are effectively a QJEKJ=HH operation.
Use Indexes for Aggregate and Sort Conditions Generally, aggregate functions such as IEJ and I=T benefit from indexes on the corresponding column. Without any index on the column, the optimizer has to scan the base table (or the clustered index), retrieve all the rows, and perform a stream aggregate on the group (containing all rows) to identify the IEJ/I=T value, as shown in the following example: OAHA?PIEJ$ok`*QjepLne_a% BNKIO]hao*O]haoKn`an@ap]eh=Ook` The OP=PEOPE?OEK and PEIA output of the OAHA?P statement using the IEJ aggregate function is as follows: P]^ha#O]haoKn`an@ap]eh#*O_]j_kqjp-(hkce_]hna]`o-.0, ?LQpeia902io(ah]loa`peia9-.-io* As shown in the OP=PEOPE?O output, the query performed more than 1,000 logical reads just to retrieve the row containing the minimum value for the QjepLne_a column. If you create an index on the QjepLne_a column, then the QjepLne_a values will be presorted by the index in the leaf pages: ?NA=PAEJ@ATET[PaopKJO]hao*O]haoKn`an@ap]eh$QjepLne_a=O?% The index on the QjepLne_a column improves the performance of the IEJ aggregate function significantly. The optimizer can retrieve the minimum QjepLne_aÊÛ>ÕiÊLÞÊÃii}ÊÌÊÌ
iÊ topmost row in the index. This reduces the number of logical reads for the query, as shown in the corresponding OP=PEOPE?O output: ]^ha#O]haoKn`an@ap]eh#*O_]j_kqjp-(hkce_]hna]`o. ?LQpeia9,io(ah]loa`peia9,io* Similarly, creating an index on the columns referred to in an KN@AN>U clause helps the optimizer organize the result set fast because the column values are prearranged in the index. The internal implementation of the CNKQL>U clause also sorts the column values first because ÃÀÌi`ÊVÕÊÛ>ÕiÃÊ>ÜÊÌ
iÊ>`>ViÌÊ>ÌV
}ÊÛ>ÕiÃÊÌÊLiÊ}ÀÕ«i`ʵÕVÞ°Ê/
iÀivÀi]Ê iÊÌ
iÊKN@AN>U clause, the CNKQL>U clause also benefits from having the values of the columns referred to in the CNKQL>U clause sorted in advance.
Avoid Local Variables in a Batch Query "vÌi]ÊÕÌ«iʵÕiÀiÃÊ>ÀiÊÃÕLÌÌi`ÊÌ}iÌ
iÀÊ>ÃÊ>ÊL>ÌV
]Ê>Û`}ÊÕÌ«iÊiÌÜÀÊ ÀÕ`ÌÀ«Ã°Ê̽ÃÊVÊÌÊÕÃiÊV>ÊÛ>À>LiÃÊÊ>ʵÕiÀÞÊL>ÌV
ÊÌÊ«>ÃÃÊ>ÊÛ>ÕiÊLiÌÜiiÊÌ
iÊ `Û`Õ>ʵÕiÀiðÊÜiÛiÀ]ÊÕÃ}ÊV>ÊÛ>À>LiÃÊÊÌ
iÊSDANA clause of a query in a batch `iýÌÊ>ÜÊÌ
iÊ«ÌâiÀÊÌÊ}iiÀ>ÌiÊ>ÊivvViÌÊiÝiVÕÌÊ«>°
339
340
C H A P T E R 1 1 N Q U E R Y D E S I G N A N A L Y S I S
To understand how the use of a local variable in the SDANA clause of a query in a batch can affect performance, consider the following batch query (^]p_d*omh): @A?H=NA<e`EJP9-7 OAHA?Plk`*& BNKILqn_d]oejc*Lqn_d]oaKn`an@ap]eh=Olk` FKEJLqn_d]oejc*Lqn_d]oaKn`anDa]`an=Olkd KJlkd*Lqn_d]oaKn`anE@9lk`*Lqn_d]oaKn`anE@ SDANAlkd*Lqn_d]oaKn`anE@:9<e`7 Figure 11-22 shows the execution plan of this OAHA?P statement.
Figure 11-22. Execution plan showing the effect of a local variable in a batch query As you can see, an Ej`atOaag operation is performed to access the rows from the Lqn_d]oejc*Lqn_d]oaKn`an@ap]eh table. If the OAHA?P statement is executed without using the local variable, by replacing the local variable value with an appropriate constant value as in the following query, then the optimizer >iÃÊ`vviÀiÌÊV
ViÃ\ OAHA?Plk`*& BNKILqn_d]oejc*Lqn_d]oaKn`an@ap]eh=Olk` FKEJLqn_d]oejc*Lqn_d]oaKn`anDa]`an=Olkd KJlkd*Lqn_d]oaKn`anE@9lk`*Lqn_d]oaKn`anE@ SDANAlkd*Lqn_d]oaKn`anE@:9-7 Figure 11-23 shows the result.
Figure 11-23. Execution plan for the query when the local variable is not used Ì
Õ}
ÊÌ
iÃiÊÌÜÊ>««À>V
iÃÊÊ`iÌV>]ÊÊVÃiÀÊiÝ>>Ì]ÊÌiÀiÃÌ}Ê`vviÀences begin to appear. Notice the estimated cost of some of the operations. For example, the IancaFkejÊÃÊ`vviÀiÌÊLiÌÜiiÊ}ÕÀiÊ££ÓÓÊ>`Ê}ÕÀiÊ££ÓÎÆÊ̽ÃÊÓÊ«iÀViÌÊÊÌ
iÊvÀÃÌÊ>`Ê ÓxÊ«iÀViÌÊÊÌ
iÊÃiV`°ÊvÊÞÕÊÊ>ÌÊOP=PEOPE?OEK and PEIA for each query, other differiViÃÊ>««i>À°ÊÀÃÌÊ
iÀi½ÃÊÌ
iÊvÀ>ÌÊvÀÊÌ
iÊÌ>ʵÕiÀÞ\
C H A P T E R 1 1 N Q U E R Y D E S I G N A N A L Y S I S
P]^ha#Lqn_d]oaKn`an@ap]eh#*O_]j_kqjp-(hkce_]hna]`o22 P]^ha#Lqn_d]oaKn`anDa]`an#*O_]j_kqjp-(hkce_]hna]`o00 ?LQpeia9,io(ah]loa`peia9050io* /
iÊ
iÀi½ÃÊÌ
iÊÃiV`ʵÕiÀÞ]ÊÜÌ
ÕÌÊÌ
iÊV>ÊÛ>À>Li\ P]^ha#Lqn_d]oaKn`an@ap]eh#*O_]j_kqjp-(hkce_]hna]`o22 P]^ha#Lqn_d]oaKn`anDa]`an#*O_]j_kqjp-(hkce_]hna]`o00 ?LQpeia902io(ah]loa`peia9/2,io* Notice that the scans and reads are the same, as might be expected of queries with near `iÌV>Ê«>ðÊ/
iÊ *1Ê>`Êi>«Ãi`ÊÌiÃÊ>ÀiÊ`vviÀiÌ]ÊÜÌ
ÊÌ
iÊÃiV`ʵÕiÀÞÊÌ
iÊiÊÜÌ
out the local variable) consistently being a little less. Based on these facts, you may assume that the execution plan of the first query will be somewhat more costly compared to the second query. But the reality is quite different, as shown in the execution plan cost comparison in Figure 11-24.
Figure 11-24. Relative cost of the query with and without the use of a local variable From the relative cost of theÊÌÜÊiÝiVÕÌÊ«>Ã]ÊÌÊ>««i>ÀÃÊÌ
>ÌÊÌ
iÊÃiV`ʵÕiÀÞÊýÌÊ V
i>«iÀÊÌ
>ÊÌ
iÊvÀÃÌʵÕiÀÞ°ÊÜiÛiÀ]ÊvÀÊÌ
iÊOP=PEOPE?Ocomparison, it appears that the second query should be cheaper than the first query. Which one should you believe: the comparison of OP=PEOPE?OÀÊÌ
iÊÀi>ÌÛiÊVÃÌÊvÊÌ
iÊiÝiVÕÌÊ«>¶Ê7
>̽ÃÊÌ
iÊÃÕÀViÊvÊÌ
ÃÊ >>Þ¶ /
iÊiÝiVÕÌÊ«>ÊÃÊ}iiÀ>Ìi`ÊL>Ãi`ÊÊÌ
iÊ«ÌâiÀ½ÃÊiÃÌ>ÌÊvÊÌ
iÊÕLiÀÊvÊÀÜÃÊ >vviVÌi`ÊvÀÊi>V
ÊiÝiVÕÌÊÃÌi«°ÊvÊÞÕÊÌ>iÊ>ÊÊ>ÌÊÌ
iÊ««Õ«ÃÊvÀÊÌ
iÊÛ>ÀÕÃÊ«iÀ>ÌÀÃÊÊ the initial execution plan for the query with the local variable (as shown in Figure 11-22), you >ÞÊÌViÊ>Ê`ë>ÀÌÞ°Ê/>iÊ>ÊÊ>ÌÊÌ
ÃÊÊ}ÕÀiÊ££Óx°
341
342
C H A P T E R 1 1 N Q U E R Y D E S I G N A N A L Y S I S
Figure 11-25. Clustered index seek details with a local variable /
iÊ`ë>ÀÌÞÊÞÕ½ÀiÊ}ÊvÀÊÃÊÌ
iÊ ÃÌ>Ìi`Ê ÕLiÀÊvÊ,ÜÃÊÛ>ÕiÊV«>Ài`ÊÌÊÌ
iÊ Actual Number of Rows value. In the properties shown in Figure 11-25, there are 1203.6 estimated rows, while the actual number is considerably higher at 4012. If you compare this to the same operator in the second query (the one without the local variable), you may notice someÌ
}ÊiÃi°Ê/>iÊ>ÊÊ>ÌÊ}ÕÀiÊ££ÓȰ
Figure 11-26. Clustered index seek details without a local variable
C H A P T E R 1 1 N Q U E R Y D E S I G N A N A LY S I S
iÀiÊÞÕ½ÊÃiiÊÌ
>ÌÊÌ
iÊVÌÕ>Ê ÕLiÀÊvÊ,ÜÃÊ>`Ê ÃÌ>Ìi`Ê ÕLiÀÊvÊ,ÜÃÊÛ>ÕiÃÊ are the same, 4012. From these two measures, you can see that the estimated rows for the execution steps of the first query (using a local variable in the SDANA clause) is way off the actual number of rows returned by the steps. Consequently, the execution plan cost for the first query, which is based on the estimated rows, is somewhat misleading. The incorrect estimation misguides the optimizer and somewhat causes some variations in how the query is executed. You can see this in the return times on the query, even though the number of rows returned is identical. Any time you find such an anomaly between the relative execution plan cost and the OP=PEOPE?O output for the queries under analysis, you should verify the basis of the estimation. If the underlying facts (estimated rows) of the execution plan itself are wrong, then it is quite iÞÊÌ
>ÌÊÌ
iÊVÃÌÊÀi«ÀiÃiÌi`ÊÊÌ
iÊiÝiVÕÌÊ«>ÊÜÊ>ÃÊLiÊÜÀ}°Ê ÕÌÊÃViÊÌ
iÊÕÌ«ÕÌÊ of the various OP=PEOPE?O measurements shows the actual number of logical reads and the real elapsed time required to perform the query without being affected by the initial estimation, you can rely on the OP=PEOPE?O output. ÜÊi̽ÃÊÀiÌÕÀÊÌÊÌ
iÊ>VÌÕ>Êperformance issue associated with using local variables in the SDANA clause. As shown in the preceding example, using the local variable as the filter criterion in the SDANAÊV>ÕÃiÊvÊ>ÊL>ÌV
ʵÕiÀÞÊ`iýÌÊ>ÜÊÌ
iÊ«ÌâiÀÊÌÊ`iÌiÀiÊÌ
iÊÀ}
ÌÊ indexing strategy. This happens because, during the optimization of the queries in the batch, Ì
iÊ«ÌâiÀÊ`iýÌÊÜÊÌ
iÊÛ>ÕiÊvÊÌ
iÊÛ>À>LiÊÕÃi`ÊÊÌ
iÊSDANAÊV>ÕÃiÊ>`ÊV>½ÌÊ`iÌiÀiÊÌ
iÊÀ}
ÌÊ>VViÃÃÊÃÌÀ>Ìi}ÞpÌÊÜÃÊÌ
iÊÛ>ÕiÊvÊÌ
iÊÛ>À>LiÊÞÊ`ÕÀ}ÊiÝiVÕ̰Ê9ÕÊ can further see this by noting that the second query in Figure 11-24 has a missing index alert, suggesting a possible way to improve the performance of the query, whereas the query with Ì
iÊV>ÊÛ>À>LiÊÃÊÕ>LiÊÌÊ>iÊÌ
>ÌÊ`iÌiÀ>̰ To avoid this performance problem, use one of the following approaches: Ê
UÊ ÊÌÊÕÃiÊ>ÊV>ÊÛ>À>LiÊ>ÃÊ>ÊvÌiÀÊVÀÌiÀÊÊ>ÊL>ÌV
°
Ê
UÊ Ài>ÌiÊ>ÊÃÌÀi`Ê«ÀVi`ÕÀiÊvÀÊÌ
iÊL>ÌV
]Ê>`ÊiÝiVÕÌiÊÌÊ>ÃÊvÜÃÊ^]p_d[olnk_*omh): ?NA=PALNK?A@QNAolLnk`q_p@ap]eho $ <e`EJP % =O OAHA?Plk`*& BNKILqn_d]oejc*Lqn_d]oaKn`an@ap]eh=Olk` FKEJLqn_d]oejc*Lqn_d]oaKn`anDa]`an=Olkd KJlkd*Lqn_d]oaKn`anE@9lk`*Lqn_d]oaKn`anE@ SDANAlkd*Lqn_d]oaKn`anE@:9<e` CK ATA?olLnk`q_p@ap]eho<e`9-
The optimizer generates the same execution plan as the query that does not use a local variable for the ideal case. Correspondingly, the execution time is also reduced. In the case of a stored procedure, the optimizer generates the execution plan during the first execution of the stored procedure and uses the parameter value supplied to determine the right processing strategy.
343
344
C H A P T E R 1 1 N Q U E R Y D E S I G N A N A L Y S I S
Be Careful Naming Stored Procedures The name of a stored procedure does matter. You should not name your procedures with a prefix of ol[. Developers often prefix their stored procedures with ol[ so that they can easily `iÌvÞÊÌ
iÊÃÌÀi`Ê«ÀVi`ÕÀiðÊÜiÛiÀ]Ê-+Ê-iÀÛiÀÊ>ÃÃÕiÃÊÌ
>ÌÊ>ÞÊÃÌÀi`Ê«ÀVi`ÕÀiÊÜÌ
Ê this exact prefix is probably a system stored procedure, whose home is in the i]opan database. When a stored procedure with an ol[Ê«ÀivÝÊÃÊÃÕLÌÌi`ÊvÀÊiÝiVÕÌ]Ê-+Ê-iÀÛiÀÊÃÊvÀÊ the stored procedure in the following places in the following order: Ê
UÊ ÊÌ
iÊi]opan database
Ê
UÊ ÊÌ
iÊVÕÀÀiÌÊ`>Ì>L>ÃiÊL>Ãi`ÊÊ>ÞʵÕ>viÀÃÊ«ÀÛ`i`Ê`>Ì>L>ÃiÊ>iÊÀÊÜiÀ®
Ê
UÊ ÊÌ
iÊVÕÀÀiÌÊ`>Ì>L>ÃiÊÕÃ}Ê`^k as the schema, if a schema is not specified
Therefore, although the user-created stored procedure prefixed with ol[ exists in the current database, the i]opanÊ`>Ì>L>ÃiÊÃÊV
iVi`ÊvÀÃ̰Ê/
ÃÊ
>««iÃÊiÛiÊÜ
iÊÌ
iÊÃÌÀi`Ê procedure is qualified with the database name. To understand the effect of prefixing ol[ to a stored procedure name, consider the following stored procedure (ol[`kjp*omh in the download): EBATEOPO$OAHA?P&BNKIouo*k^fa_poSDANAk^fa_p[e`9 K>FA?P[E@$J#W`^kY*Wol[@kjpY#%=J@pulaej$J#L#(J#L?#%% @NKLLNK?A@QNAW`^kY*Wol[@kjpY CK ?NA=PALNK?Wol[@kjpY =O LNEJP#@kja# CK ATA?=`rajpqnaSkngo.,,4*`^k*Wol[@kjpY7))=``lh]jkbol[l-pklnk_a`qna_]_da CK ATA?=`rajpqnaSkngo.,,4*`^k*Wol[@kjpY7))Qoapda]^kra_]_da`lh]jkbol[lCK The first execution of the stored procedure adds the execution plan of the stored procedure to the procedure cache. A subsequent execution of the stored procedure reuses the existing plan from the procedure cache unless a recompilation of the plan is required (the causes of stored procedure recompilation are explained in Chapter 10). Therefore, the second execution of the stored procedure ol[@kjp shown in Figure 11-27 should find a plan in the procedure cache. This is indicated by an OL6?]_daDepÊiÛiÌÊÊÌ
iÊVÀÀië`}Ê*ÀviÀÊ trace output.
Figure 11-27. Profiler trace output showing the effect of the ol[ prefix on a stored procedure name
C H A P T E R 1 1 N Q U E R Y D E S I G N A N A L Y S I S
Note that an OL6?]_daIeoo event is fired before SQL Server tries to locate the plan for the stored procedure in the procedure cache. The OL6?]_daIeoo event is caused by SQL Server }ÊÊÌ
iÊi]opan database for the stored procedure, even though the execution of the stored procedure is properly qualified with the user database name. This aspect of the ol[ prefix becomes more interesting when you create a stored procedure with the name of an existing system stored procedure (ol[]``iaoo]ca*omh in the download): ?NA=PALNK?Wol[]``iaoo]caY
Figure 11-28. Execution result for stored procedure showing the effect of the ol[ prefix on a stored procedure name 1vÀÌÕ>ÌiÞ]ÊÌÊÃÊÌÊ«ÃÃLiÊÌÊiÝiVÕÌiÊÌ
ÃÊÕÃiÀ`ivi`ÊÃÌÀi`Ê«ÀVi`ÕÀi°
NTip As a side note, please don’t try to execute the @NKLLNK?A@QNA statement on this stored procedure twice. On the second execution, the system stored procedure will be dropped from the i]opan database.
You can see now why youÊÃ
Õ`ÊÌÊ«ÀivÝÊ>ÊÕÃiÀ`ivi`ÊÃÌÀi`Ê«ÀVi`ÕÀi½ÃÊ>iÊÜÌ
Ê ol[°Ê1ÃiÊÃiÊÌ
iÀÊ>}ÊVÛḭ
Reducing the Number of Network Round-Trips Database applications often execute multiple queries to implement a database operation. Besides optimizing the performance of the individual query, it is important that you optimize Ì
iÊ«iÀvÀ>ViÊvÊÌ
iÊL>ÌV
°Ê/ÊÀi`ÕViÊÌ
iÊÛiÀ
i>`ÊvÊÕÌ«iÊiÌÜÀÊÀÕ`ÌÀ«Ã]ÊVsider the following techniques:
345
346
CHAPTER 11 N Q U ER Y DES IG N A NA L YS IS
Ê
UÊ ÝiVÕÌiÊÕÌ«iʵÕiÀiÃÊÌ}iÌ
iˡ
Ê
UÊ 1ÃiÊOAPJK?KQJP. i̽ÃÊÊ>ÌÊÌ
iÃiÊÌiV
µÕiÃÊÊ>ÊÌÌiÊÀiÊ`i«Ì
°
Execute Multiple Queries Together It is preferable to submit all the queries of a set together as a batch or a stored procedure. iÃ`iÃÊÀi`ÕV}ÊÌ
iÊiÌÜÀÊÀÕ`ÌÀ«ÃÊLiÌÜiiÊÌ
iÊ`>Ì>L>ÃiÊ>««V>ÌÊ>`ÊÌ
iÊÃiÀÛiÀ]Ê stored procedures also provide multiple performance and administrative benefits, as `iÃVÀLi`ÊÊ
>«ÌiÀʰÊ/
ÃÊi>ÃÊÌ
>ÌÊÌ
iÊV`iÊÊÌ
iÊ>««V>ÌÊÜÊii`ÊÌÊLiÊ>LiÊÌÊ`i>Ê with multiple result sets. It also means your T-SQL code may need to deal with XML data or other large sets of data, not single-row inserts or updates.
Use SET NOCOUNT You need to consider one more factor when executing a batch or a stored procedure. After every query in the batch or the stored procedure is executed, the server reports the number of rows affected: $8Jqi^an:nks$o%]bba_pa`% /
ÃÊvÀ>ÌÊÃÊÀiÌÕÀi`ÊÌÊÌ
iÊ`>Ì>L>ÃiÊ>««V>ÌÊ>`Ê>``ÃÊÌÊÌ
iÊiÌÜÀÊÛiÀ
i>`°Ê1ÃiÊÌ
iÊ/-+ÊÃÌ>ÌiiÌÊOAPJK?KQJP to avoid this overhead: OAPJK?KQJPKJ 8OMHmqaneao: OAPJK?KQJPKBB Note that the OAPJK?KQJPÊÃÌ>ÌiiÌÊ`iýÌÊV>ÕÃiÊ>ÞÊÀiV«>ÌÊÃÃÕiÊÜÌ
ÊÃÌÀi`Ê «ÀVi`ÕÀiÃ]ÊÕiÊÃiÊOAP statements as explained in Chapter 10.
Reducing the Transaction Cost Every action query in SQL Server is performed as an atomic action so that the state of a database table moves from one consistent state to another. SQL Server does this automatically, and it cannot be disabled. If the transition from one consistent state to another requires multiple database queries, then atomicity across the multiple queries should be maintained using explicitly defined database transactions. The old and new state of every atomic action is >Ì>i`ÊÊÌ
iÊÌÀ>Ã>VÌÊ}ÊÊÌ
iÊ`îÊÌÊiÃÕÀiÊdurability, which guarantees that the ÕÌViÊvÊ>Ê>ÌVÊ>VÌÊܽÌÊLiÊÃÌÊViÊÌÊV«iÌiÃÊÃÕVViÃÃvÕÞ°ÊÊ>ÌVÊ>VÌÊ during its execution is isolatedÊvÀÊÌ
iÀÊ`>Ì>L>ÃiÊ>VÌÃÊÕÃ}Ê`>Ì>L>ÃiÊVð Based on the characteristics of a transaction, here are two broad recommendations to reduce the cost of the transaction: Ê
UÊ ,i`ÕViÊ}}}ÊÛiÀ
i>`°
Ê
UÊ ,i`ÕViÊVÊÛiÀ
i>`°
C H A P T E R 1 1 N Q U E R Y D E S I G N A N A LY S I S
Reduce Logging Overhead A database query may consist of multiple data manipulation queries. If atomicity is mainÌ>i`ÊvÀÊi>V
ʵÕiÀÞÊÃi«>À>ÌiÞ]ÊÌ
iÊÌÊ>ÞÊ`ÃÊÜÀÌiÃÊ>ÀiÊ«iÀvÀi`ÊÊÌ
iÊÌÀ>Ã>VÌÊ }Ê`ÃÊÌÊ>Ì>ÊÌ
iÊ`ÕÀ>LÌÞÊvÊi>V
Ê>ÌVÊ>V̰Ê-ViÊ`ÃÊ>VÌÛÌÞÊÃÊiÝÌÀiiÞÊÃÜÊ V«>Ài`ÊÌÊiÀÞÊÀÊ *1Ê>VÌÛÌÞ]ÊÌ
iÊiÝViÃÃÛiÊ`ÃÊ>VÌÛÌiÃÊVÀi>ÃiÊÌ
iÊiÝiVÕÌÊÌiÊ of the database functionality. For example, consider the following batch query (hkccejc*omh in the download): ))?na]pa]paopp]^ha EB$OAHA?PK>FA?P[E@$#p-#% %EOJKPJQHH @NKLP=>HAp-7 CK ?NA=PAP=>HAp-$_-PEJUEJP%7 CK ))Ejoanp-,,,,nkso @A?H=NAACEJ EJOANPEJPKpR=HQAO$ÃÞÊÜ>ÞÊÌÊÀi`ÕViÊÌ
iÊÕLiÀÊvÊ}Ê`ÃÊÜÀÌiÃÊÃÊÌÊVÕ`iÊÌ
iÊ>VÌʵÕiÀiÃÊÜÌ
Ê an explicit transaction: ))Ejoanp-,,,,nkso @A?H=NAACEJPN=JO=?PEKJ SDEHAACEJ EJOANPEJPKpR=HQAO$ACEJPN=JO=?PEKJ and ?KIIEP pair of commands) expands the scope of atomicity to the multiple EJOANP statements included within the ÌÀ>Ã>V̰Ê/
ÃÊ`iVÀi>ÃiÃÊÌ
iÊÕLiÀÊvÊ}Ê`ÃÊÜÀÌiÃÊ>`Ê«ÀÛiÃÊÌ
iÊ«iÀvÀ>ViÊvÊÌ
iÊ database functionality. To test this theory, run the following T-SQL command before and after each of the SDEHA loops: @>??OMHLANB$HKCOL=?A%
347
348
CHAPTER 11 N Q U ER Y DES IG N A NA L YS IS
/
ÃÊÜÊÃ
ÜÊÞÕÊÌ
iÊ«iÀViÌ>}iÊvÊ}Êë>ViÊÕÃi`°Ê"ÊÀÕ}ÊÌ
iÊvÀÃÌÊÃiÌÊvÊÃiÀÌÃÊÊ ÞÊ`>Ì>L>Ãi]ÊÌ
iÊ}ÊÜiÌÊvÀÊÓ°ÈÊ«iÀViÌÊÕÃi`ÊÌÊÓÊ«iÀVḭÊ7
iÊÀÕ}ÊÌ
iÊÃiV`ÊÃiÌÊ of inserts, the log grew about 6 percent. /
iÊLiÃÌÊÜ>ÞÊÃÊÌÊÜÀÊÜÌ
ÊÃiÌÃÊvÊ`>Ì>ÊÀ>Ì
iÀÊÌ
>Ê`Û`Õ>ÊÀÜðÊÊSDEHA loop can be >Ê
iÀiÌÞÊVÃÌÞÊ«iÀ>Ì]ÊiÊ>ÊVÕÀÃÀÊÀiÊ`iÌ>ÃÊÊVÕÀÃÀÃÊÊ
>«ÌiÀÊ£{®°Ê-]ÊÀÕning a query that avoids the SDEHAÊ«Ê>`ÊÃÌi>`ÊÜÀÃÊvÀÊ>ÊÃiÌL>Ãi`Ê>««À>V
ÊÜÊLiÊ even better: OAHA?PPKL-,,,, E@AJPEPU$EJP(-(-%=Oj EJPKP]hhu BNKII]opan*`^k*Ouo?khqijoo_(I]opan*`^k*Ouo?khqijoo_. >ACEJPN=JO=?PEKJ EJOANPEJPK`^k*p-$_-% OAHA?P$j!.12% BNKIP]hhu7 ?KIIEPPN=JO=?PEKJ @NKLP=>HAP]hhu7 Running this query with the @>??OMHLANB$% function before and after showed less than 4 percent growth of the used space within the log, and it ran in 41 ms as compared to more than 2 seconds for the SDEHA loop. "iÊ>Ài>ÊvÊV>ÕÌ]Ê
ÜiÛiÀ]ÊÃÊÌ
>ÌÊLÞÊVÕ`}ÊÌÊ>ÞÊ`>Ì>Ê>«Õ>ÌʵÕiÀiÃÊ within a transaction, the duration of the transaction is increased. During that time, all other queries trying to access the resourcesÊÀiviÀÀi`ÊÌÊÊÌ
iÊÌÀ>Ã>VÌÊ>ÀiÊLVi`°
Reduce Lock Overhead By default, all four SQL statements (OAHA?P, EJOANP, QL@=PA, and @AHAPA) useÊ`>Ì>L>ÃiÊVÃÊÌÊ Ã>ÌiÊÌ
iÀÊÜÀÊvÀÊÌ
>ÌÊvÊÌ
iÀÊ-+ÊÃÌ>ÌiiÌðÊ/
ÃÊVÊ>>}iiÌÊ>``ÃÊ>Ê«iÀvÀmance overhead to the query. The performance of a query can be improved by requesting viÜiÀÊVÃ°Ê ÞÊiÝÌiÃ]ÊÌ
iÊ«iÀvÀ>ViÊvÊÌ
iÀʵÕiÀiÃÊ>ÀiÊ>ÃÊ«ÀÛi`ÊLiV>ÕÃiÊÌ
iÞÊ
>ÛiÊÌÊÜ>ÌÊ>ÊÃ
ÀÌiÀÊ«iÀ`ÊvÊÌiÊÌÊLÌ>ÊÌ
iÀÊÜÊVð By default, SQL Server can provide ÀÜiÛiÊV°ÊÀÊ>ʵÕiÀÞÊÜÀ}ÊÊ>Ê>À}iÊÕLiÀÊ vÊÀÜÃ]ÊÀiµÕiÃÌ}Ê>ÊÀÜÊVÊÊ>ÊÌ
iÊ`Û`Õ>ÊÀÜÃÊ>``ÃÊ>ÊÃ}vV>ÌÊÛiÀ
i>`ÊÌÊÌ
iÊ V>>}iiÌÊ«ÀViÃðÊ9ÕÊV>ÊÀi`ÕViÊÌ
ÃÊVÊÛiÀ
i>`ÊLÞÊ`iVÀi>Ã}ÊÌ
iÊVÊ}À>Õ>ÀÌÞ]ÊÃ>ÞÊÌÊÌ
iÊ«>}iÊiÛiÊÀÊÌ>LiÊiÛi°Ê-+Ê-iÀÛiÀÊ«iÀvÀÃÊÌ
iÊVÊiÃV>>ÌÊ`Þ>V>ÞÊ LÞÊÌ>}ÊÌÊVÃ`iÀ>ÌÊÌ
iÊVÊÛiÀ
i>`ðÊ/
iÀivÀi]Ê}iiÀ>Þ]ÊÌÊÃÊÌÊiViÃÃ>ÀÞÊÌÊ >Õ>ÞÊiÃV>>ÌiÊÌ
iÊVÊiÛi°Ê ÕÌ]ÊvÊÀiµÕÀi`]ÊÞÕÊV>ÊVÌÀÊÌ
iÊVVÕÀÀiVÞÊvÊ>ʵÕiÀÞÊ «À}À>>ÌV>ÞÊÕÃ}ÊVÊ
ÌÃÊ>ÃÊvÜÃ\ OAHA?P&BNKI8P]^haJ]ia:SEPD$L=CHK?G%))Qoal]caharahhk_g ->ÀÞ]ÊLÞÊ`iv>ÕÌ]Ê-+Ê-iÀÛiÀÊÕÃiÃÊVÃÊvÀÊOAHA?P statements besides those for EJOANP, QL@=PA, and @AHAPA statements. This allows the OAHA?PÊÃÌ>ÌiiÌÃÊÌÊÀi>`Ê`>Ì>ÊÌ
>ÌÊýÌÊLi}Ê `vi`°ÊÊÃiÊV>ÃiÃ]ÊÌ
iÊ`>Ì>Ê>ÞÊLiʵÕÌiÊÃÌ>ÌV]Ê>`ÊÌÊ`iýÌÊ}ÊÌ
ÀÕ}
ÊÕV
Ê`vV>̰ÊÊÃÕV
ÊV>ÃiÃ]ÊÞÕÊV>ÊÀi`ÕViÊÌ
iÊVÊÛiÀ
i>`ÊvÊÌ
iÊOAHA?P statements in one of the following ways:
C H A P T E R 1 1 N Q U E R Y D E S I G N A N A LY S I S
Ê
UÊ >ÀÊÌ
i database as NA=@[KJHU: =HPAN@=P=>=OA8@]p]^]oaJ]ia:OAPNA=@[KJHU This allows users to retrieve data from the database, but it prevents them from modify}ÊÌ
iÊ`>Ì>°Ê/
iÊÃiÌÌ}ÊÌ>iÃÊivviVÌÊi`>ÌiÞ°ÊvÊVV>Ã>Ê`vV>ÌÃÊÌÊÌ
iÊ database are required, then it may be temporarily converted to NA=@[SNEPA mode: =HPAN@=P=>=OA8@]p]^]oaJ]ia:OAPNA=@[SNEPA 8@]p]^]oaik`ebe_]pekjo: =HPAN@=P=>=OA8@]p]^]oaJ]ia:OAPNA=@[KJHU
Ê
UÊ *>ViÊÌ
iÊëiVvVÊÌ>LiÃÊÊ>Êvi}ÀÕ«]Ê>`Ê>ÀÊÌ
iÊfilegroup as NA=@KJHU: ))=``]jasbehacnkqlsepd]behapkpda`]p]^]oa =HPAN@=P=>=OA=`rajpqnaSkngo.,,4 =@@BEHACNKQLNa]`KjhuBehaCnkql =HPAN@=P=>=OA=`rajpqnaSkngo.,,4 =@@BEHA$J=IA9Na]`KjhuBeha(BEHAJ=IA9#?6X]`s[-*j`b#% PKBEHACNKQLNa]`KjhuBehaCnkql ))?na]paola_ebe_p]^ha$o%kjpdajasbehacnkql ?NA=PAP=>HAP-$?-EJP(?.EJP%KJNa]`KjhuBehaCnkql ?NA=PA?HQOPANA@EJ@ATE-KJP-$?-% EJOANPEJPKP-R=HQAO$-(-% ))Knikraateopejcp]^ha$o%pkpdajasbehacnkql ?NA=PA?HQOPANA@EJ@ATE-KJP-$?-% SEPD@NKL[ATEOPEJCKJNa]`KjhuBehaCnkql ))OappdabehacnkqllnklanpupkNA=@KJHU =HPAN@=P=>=OA=`rajpqnaSkngo.,,4 IK@EBUBEHACNKQLNa]`KjhuBehaCnkqlNA=@KJHU This allows you to limit the data access to only the tables residing on the specific filegroup to NA=@KJHUÊLÕÌÊii«ÊÌ
iÊ`>Ì>Ê>VViÃÃÊÌÊÌ>LiÃÊÊÌ
iÀÊvi}ÀÕ«ÃÊ>ÃÊNA=@SNEPA. /
ÃÊvi}ÀÕ«ÊÃiÌÌ}ÊÌ>iÃÊivviVÌÊi`>ÌiÞ°ÊvÊVV>Ã>Ê`vV>ÌÃÊÌÊÌ
iÊ specific tables are required, then the property of the corresponding filegroup may be temporarily converted to NA=@SNEPA mode: =HPAN@=P=>=OA=`rajpqnaSkngo.,,4 IK@EBUBEHACNKQLNa]`KjhuBehaCnkqlNA=@SNEPA 8@]p]^]oaik`ebe_]pekjo: =HPAN@=P=>=OA=`rajpqnaSkngo.,,4 IK@EBUBEHACNKQLNa]`KjhuBehaCnkqlNA=@KJHU
349
350
CHAPTER 11 N Q U ER Y DES IG N A NA L YS IS
Ê
UÊ *ÀiÛiÌÊOAHA?PÊÃÌ>ÌiiÌÃÊvÀÊÀiµÕiÃÌ}Ê>ÞÊV\ OAHA?P&BNKI8P]^haJ]ia:SEPD$JKHK?G% This prevents the OAHA?PÊÃÌ>ÌiiÌÊvÀÊÀiµÕiÃÌ}Ê>ÞÊV]Ê>`ÊÌÊÃÊ>««V>LiÊÌÊ OAHA?P statements only. Although the JKHK?G hint cannot be used directly on the tables referred to in the action queries (EJOANP, QL@=PA, and @AHAPA), it may be used on the data-retrieval part of the action queries, as shown here: @AHAPAO]hao*O]haoKn`an@ap]eh BNKIO]hao*O]haoKn`an@ap]ehok`SEPD$JKHK?G% FKEJLnk`q_pekj*Lnk`q_plSEPD$JKHK?G% KJok`*Lnk`q_pE@9l*Lnk`q_pE@ =J@l*Lnk`q_pE@9,
I discuss the different typesÊvÊVÊÀiµÕiÃÌÃÊ>`Ê
ÜÊÌÊ>>}iÊVÊÛiÀ
i>`ÊÊÌ
iÊiÝÌÊ chapter.
Summary As discussed in this chapter, to improve the performance of a database application, it is important to ensure that SQL queries are designed properly to benefit from performanceenhancement techniques such as indexes, stored procedures, database constraints, and so on.
ÃÕÀiÊÌ
>ÌʵÕiÀiÃÊ>ÀiÊÀiÃÕÀViÊvÀi`Þ]Ê>`Ê`½ÌÊ«ÀiÛiÌÊÌ
iÊÕÃiÊvÊ`iÝiÃ°Ê ÛiÊÌ
Õ}
]Ê in many cases, the optimizer has the ability to generate cost-effective execution plans irrespective of query structure, it is sti