ベテランのプロダクションDBAが知っているように、データベースのパフォーマンスの問題をできるだけ早く診断して軽減するという大きなプレッシャーにさらされることがよくあります。ワークロードとインフラストラクチャに応じて、データベースのパフォーマンスに非常に顕著なプラスの影響を与えるために利用できる可能性のある3つのことを次に示します。
基本的な行ストアインデックスの調整
私のキャリアで遭遇したほとんどのSQLServerインスタンスには、比較的簡単な行ストアインデックスの調整の機会がありました。行ストアインデックスの調整の良い点の1つは、特に開発者やサードパーティベンダーの管理下にあるクエリやストアドプロシージャの調整と比較して、DBAとして直接管理できることが多いことです。
一部のDBAは、データベースまたはアプリケーションに対する何かを壊したり、ベンダーのサポートを危険にさらしたりすることを心配しているため、(特にサードパーティのデータベースで)インデックスの調整を行うことを躊躇しています。明らかに、サードパーティのデータベースにはもっと注意を払う必要があり、インデックスを自分で変更する前にベンダーに連絡する必要がありますが、状況によっては、他の実行可能な代替手段がない場合があります(問題でより高速なハードウェアとストレージを投入する以外に) 。
インスタンスまたはデータベースで簡単なインデックス調整の機会があるかどうかを知るために、SQLServer診断情報クエリからいくつかの重要なクエリを実行できます。欠落しているインデックス要求、欠落しているインデックス警告、十分に使用されていない、または使用されていない非クラスター化インデックス、およびデータ圧縮の可能性に注意する必要があります。
適切なインデックス調整を行うには、ある程度の経験、適切な判断、およびワークロードに関する知識が必要です。適切な分析を行わずに多くのインデックス変更を急いで行うことによって、人々が誤ったインデックス調整を行うのを見るのは非常に一般的です。
データベースレベルで使用したいクエリは次のとおりです。
-- Missing Indexes for current database by Index Advantage (Query 1) (Missing Indexes) SELECT DISTINCT CONVERT(decimal(18,2), user_seeks * avg_total_user_cost * (avg_user_impact * 0.01)) AS [index_advantage], migs.last_user_seek, mid.[statement] AS [Database.Schema.Table], mid.equality_columns, mid.inequality_columns, mid.included_columns, migs.unique_compiles, migs.user_seeks, migs.avg_total_user_cost, migs.avg_user_impact, OBJECT_NAME(mid.[object_id]) AS [Table Name], p.rows AS [Table Rows] FROM sys.dm_db_missing_index_group_stats AS migs WITH (NOLOCK) INNER JOIN sys.dm_db_missing_index_groups AS mig WITH (NOLOCK) ON migs.group_handle = mig.index_group_handle INNER JOIN sys.dm_db_missing_index_details AS mid WITH (NOLOCK) ON mig.index_handle = mid.index_handle INNER JOIN sys.partitions AS p WITH (NOLOCK) ON p.[object_id] = mid.[object_id] WHERE mid.database_id = DB_ID() AND p.index_id < 2 ORDER BY index_advantage DESC OPTION (RECOMPILE); ------ -- Look at index advantage, last user seek time, number of user seeks to help determine source and importance -- SQL Server is overly eager to add included columns, so beware -- Do not just blindly add indexes that show up from this query!!! -- Find missing index warnings for cached plans in the current database (Query 2) (Missing Index Warnings) -- Note: This query could take some time on a busy instance SELECT TOP(25) OBJECT_NAME(objectid) AS [ObjectName], cp.objtype, cp.usecounts, cp.size_in_bytes, query_plan FROM sys.dm_exec_cached_plans AS cp WITH (NOLOCK) CROSS APPLY sys.dm_exec_query_plan(cp.plan_handle) AS qp WHERE CAST(query_plan AS NVARCHAR(MAX)) LIKE N'%MissingIndex%' AND dbid = DB_ID() ORDER BY cp.usecounts DESC OPTION (RECOMPILE); ------ -- Helps you connect missing indexes to specific stored procedures or queries -- This can help you decide whether to add them or not -- Possible Bad NC Indexes (writes >= reads) (Query 3) (Bad NC Indexes) SELECT OBJECT_NAME(s.[object_id]) AS [Table Name], i.name AS [Index Name], i.index_id, i.is_disabled, i.is_hypothetical, i.has_filter, i.fill_factor, s.user_updates AS [Total Writes], s.user_seeks + s.user_scans + s.user_lookups AS [Total Reads], s.user_updates - (s.user_seeks + s.user_scans + s.user_lookups) AS [Difference] FROM sys.dm_db_index_usage_stats AS s WITH (NOLOCK) INNER JOIN sys.indexes AS i WITH (NOLOCK) ON s.[object_id] = i.[object_id] AND i.index_id = s.index_id WHERE OBJECTPROPERTY(s.[object_id],'IsUserTable') = 1 AND s.database_id = DB_ID() AND s.user_updates > (s.user_seeks + s.user_scans + s.user_lookups) AND i.index_id > 1 AND i.[type_desc] = N'NONCLUSTERED' AND i.is_primary_key = 0 AND i.is_unique_constraint = 0 AND i.is_unique = 0 ORDER BY [Difference] DESC, [Total Writes] DESC, [Total Reads] ASC OPTION (RECOMPILE); ------ -- Look for indexes with high numbers of writes and zero or very low numbers of reads -- Consider your complete workload, and how long your instance has been running -- Investigate further before dropping an index! -- Breaks down buffers used by current database by object (table, index) in the buffer cache (Query 4) (Buffer Usage) -- Note: This query could take some time on a busy instance SELECT OBJECT_NAME(p.[object_id]) AS [Object Name], p.index_id, CAST(COUNT(*)/128.0 AS DECIMAL(10, 2)) AS [Buffer size(MB)], COUNT(*) AS [BufferCount], p.[Rows] AS [Row Count], p.data_compression_desc AS [Compression Type] FROM sys.allocation_units AS a WITH (NOLOCK) INNER JOIN sys.dm_os_buffer_descriptors AS b WITH (NOLOCK) ON a.allocation_unit_id = b.allocation_unit_id INNER JOIN sys.partitions AS p WITH (NOLOCK) ON a.container_id = p.hobt_id WHERE b.database_id = CONVERT(int, DB_ID()) AND p.[object_id] > 100 AND OBJECT_NAME(p.[object_id]) NOT LIKE N'plan_%' AND OBJECT_NAME(p.[object_id]) NOT LIKE N'sys%' AND OBJECT_NAME(p.[object_id]) NOT LIKE N'xml_index_nodes%' GROUP BY p.[object_id], p.index_id, p.data_compression_desc, p.[Rows] ORDER BY [BufferCount] DESC OPTION (RECOMPILE); ------ -- Tells you what tables and indexes are using the most memory in the buffer cache -- It can help identify possible candidates for data compression
遅延耐久性の使用
遅延耐久性機能はSQLServer2014の製品に追加されたため、かなり前から利用可能になっています。遅延した永続的なトランザクションコミットは非同期であり、トランザクションコミットが成功したことを前に報告します。 トランザクションのログレコードは、実際にはストレージサブシステムに書き込まれます。遅延した永続的なトランザクションは、トランザクションログエントリがディスクにフラッシュされるまで実際には永続的になりません。
この機能は、SQLServerのすべてのエディションで使用できます。それにもかかわらず、クライアントデータベースを見ると、それが使用されていることはめったにありません。耐久性の遅延は、最悪のシナリオ(ここでPaul Randalによって説明されている)ではログバッファ全体まで、ある程度のデータ損失の可能性を開きます。したがって、データ損失がまったく許容されないRPOシナリオには絶対に適していません。
遅延耐久性は、ログIOが終了してクライアントに制御を戻すのを待たないため、トランザクションの待ち時間を短縮します。また、同時トランザクションのロックとディスクの競合も軽減します。これらの2つの利点は、多くの場合、書き込みが非常に多い適切なワークロードを使用して、クエリとアプリケーションのパフォーマンスに非常に良い影響を与える可能性があります。
耐久性の遅延は、トランザクションログファイルのsys.dm_io_virtual_file_statsからのファイルレベルの書き込みレイテンシが高い、またはsysからのWRITELOG待機が長い、非常に頻繁で小さな書き込みトランザクションを伴う重いOLTPタイプのワークロードに最もよく役立ちます。 dm_os_wait_stats。
次のコマンドを実行することで、SQL Server 2014以降ですべてのトランザクションに遅延耐久性を(コードを変更せずに)使用するように簡単に強制できます。
ALTER DATABASE AdventureWorks2014 SET DELAYED_DURABILITY = FORCED;
1日のさまざまな時間(スケジュールされたETLやメンテナンスアクティビティ中など)に遅延耐久性のオンとオフをプログラムで切り替えるクライアントがいます。また、適切なワークロードとデータ損失のリスク許容度があるため、常に遅延耐久性を使用するクライアントがいます。
最後に、遅延耐久性の使用を決して検討しない、または単にワークロードでそれを必要としないクライアントがいます。ワークロードが可能性があると思われる場合 遅延耐久性を使用することでメリットが得られますが、データ損失の可能性が懸念される場合は、他にも検討できる選択肢があります。
1つの代替手段は、SQL Server 2016 SP1の永続ログバッファー機能です。この機能では、NV-DIMM永続メモリデバイスでホストされているダイレクトアクセスモード(DAX)ストレージボリュームに2番目の20MBトランザクションログファイルを作成できます。この追加のトランザクションログファイルは、ログの末尾をキャッシュするために使用され、従来のブロックレベルのストレージスタックをバイパスするバイトレベルのアクセスがあります。
永続ログバッファー機能を使用することでワークロードにメリットがあると思われる場合は、遅延耐久性を一時的に使用して、NV-DIMM永続メモリにお金をかける前にワークロードに実際のパフォーマンス上のメリットがあるかどうかを確認できます。永続ログバッファ機能を使用する必要があります。
tempdbをIntelOptaneDCP4800Xストレージに移動する
tempdbデータベースファイルを他のタイプのストレージから、2枚のIntel Optane DC P4800X PCIe NVMeストレージカード(ソフトウェアRAID 1アレイ内)でバックアップされた論理ドライブに移動した最近のクライアントで大成功を収めました。
これらのストレージカードは、375 GB、750 GB、および1.5 TBの容量で利用できます(ただし、1.5 TBの容量は新品であり、まだ見つけるのは困難です)。レイテンシーが非常に低く(どのタイプのNANDフラッシュストレージよりもはるかに低い)、キューの深さが浅い場合に優れたランダムI / Oパフォーマンスを発揮し(NANDフラッシュストレージよりもはるかに優れています)、非常に重い書き込みワークロードで一貫した読み取り応答時間を実現します。
また、「書き込みが集中する」エンタープライズNANDフラッシュストレージよりも書き込み耐久性が高く、フルに近いためパフォーマンスが低下することはありません。これらの特性により、これらのカードは多くの重いtempdbワークロード、特に重いOLTPワークロードやユーザーデータベースでRCSIを使用している状況(結果のバージョンストアワークロードをtempdbに置く)に非常に適しています。
また、sys.dm_io_virtual_file_stats DMVからのtempdbデータファイルでファイルレベルの書き込みレイテンシが高くなることもよくあります。そのため、tempdbデータファイルをOptaneストレージに移動することは、この問題に直接対処する1つの方法であり、従来よりも迅速かつ簡単になります。ワークロードの調整。
Optaneストレージカードのもう1つの可能な用途は、トランザクションログファイルのホームとしての使用です。 OptaneストレージをレガシーバージョンのSQLServerで使用することもできます(OSとハードウェアがそれをサポートしている場合)。遅延耐久性(SQL Server 2014が必要)または永続ログバッファー機能(SQL Server 2016 SP1が必要)を使用する代わりに使用できます。
結論
SQLServerでパフォーマンスをすばやく向上させるための3つの手法について説明しました。
- 従来の行ストアインデックスの調整は、SQL Serverのすべてのバージョンに適用可能であり、兵器庫で最高のツールの1つです。
- 遅延耐久性はSQLServer2014以降で利用可能であり、一部のワークロードタイプ(およびRPO要件)で非常に有益な場合があります。永続ログバッファーはSQLServer2016 SP1で使用可能であり、データ損失の危険性なしに、遅延耐久性と同様の利点を提供します。
- 特定の種類のデータベースファイルをIntelOptaneストレージに移動すると、tempdbまたはユーザーデータベーストランザクションログファイルのパフォーマンスの問題を軽減できます。 OptaneストレージはレガシーバージョンのSQLServerで使用でき、コードや構成の変更は必要ありません。