Plan Cache در SQL Server چیست ؟

یکی از موضوعات مهم در بهینه سازی عملکرد SQL Server ، نحوه مدیریت و استفاده مجدد از Execution Plan ها است . هر زمان SQL Server یک Query را دریافت میکند ، برای اجرای بهینه آن باید یک Execution Plan ایجاد یا از Plan موجود استفاده کند . فرآیند Optimization و تولید Execution Plan می تواند هزینه پردازشی داشته باشد . به همین دلیل SQL Server از سازکاری به نام Plan Cache استفاده می کند تا Planهای ایجاد شده را نگهداری کند و در اجرای مجدد Query ، در صورت امکان از همان Plan استفاده کند .

به زبان ساده :

Plan Cache محلی برای نگهداری Execution Plan های قابل استفاده مجدد در SQL Server است . هدف اصلی این قابلیت ، کاهش سربار ناشی از Compile و Optimization مجدد Query ها و در نتیجه بهبود Performance سیستم است . محتوای فایل نیز تاکید می کند که اصل اساسی Plan Cache ، استفاده مجدد از Execution Plan برای کاهش سربار Compilation است .

چرا Plan Cache اهمیت دارد ؟

فرض کنید یک Query بارها در یک سیستم اجرا می شود و هر بار دقیقا همان ساختار را دارد، اما SQL Server مجبور باشد در هربار اجرا دوباره آن را Compile و Optimize کند . در چنین شرایطی منابعی مانند CPU برای کاری مصرف می شوند که قبلا انجام شده است .

Plan Cache با ذخیره Execution Plan به SQL Server اجازه می دهد در اجرای بعدی ، در صورت مناسب بودن شرایط ، Plan موجود را دوباره استفاده کند .

مهمترین مزیت های Plan Cache عبارتند از :

    1. کاهش Compile شدن مکرر Query ها .

    2.کاهش سربار Optimization .

    3. افزایش امکان Reuse Plan .

    4. کاهش مصرف CPU در برخی Workload ها .

    5. جلوگیری از ایجاد تعداد زیادی Plan مشابه در Cache .

    6. بهبود Performance در Query های پر تکرار .

البته استفاده از Plan Cache به این معنا نیست که SQL Server همیشه باید یک Plan را برای تمام اجراها استفاده کند . تفاوت مقادیر ورودی و شرایط مختلف می تواند باعث ایجاد Execution Plan های متفاوت شود .

Execution Plan چیست ؟

execution Plan برنامه ای است که SQL Server برای اجرای Query انتخاب می کند .

برای مثال :

 ; SELECT  *  FROM Production.Product  WHERE  ProductID=416 

SQL Server باید تصمیم بگیرد داده ها را چگونه پیدا کند . برای این کار ممکن است از Index ، Scan یا روش های دیگر استفاده کند . این تصمیم در قالب Execution Plan نمایش داده می شود .

نکته مهم این است که ایجاد Execution Plan یک فرآیند رایگان نیست . SQL Server باید Query را بررسی و Optimize کند و سپس مناسب ترین روش اجرای آن را انتخاب کند . اگر Query بارها اجرا شود ، استفاده مجدد از Plan می تواند باعث کاهش این هزینه شود .

Plan Reuse چیست ؟

Plan Reuse یا استفاده مجدد از Plan یعنی SQL Server بتواند Execution Plan ایجاد شده برای یک Query را در اجرای بعدی دوباره استفاده کند .

فرض کنید Query زیر چندین بار اجرا شود :

; SELECT  *  FROM  Production.Product  WHERE  ProductID=461

اگر Query به شکلی نوشته شده باشد که SQL Server بتواند Plan آن را مجددا استفاده کند ، لازم نیست برای هر اجرای مشابه ، فرآیند Compile و Optimization را از ابتدا انجام دهد . اما اگر Query ها به شکل های مختلف و با ساختارهای متفاوت ارسال شوند ، ممکن است SQL Server مجبور شود Plan های بیشتری ایجاد و در Cache نگهداری کند . بنابراین یکی از اهداف مهم در مدیریت SQL Server این است که Reuse Plan تا حد مناسبی افزایش پیدا کند .

Query Hash و Query Plan Hash چیست ؟

یکی از ابزارهای مفید برای تحلیل Query های موجود در Plan Cache ، استفاده از مقادیر Hashe است .

دو مفهوم مهم در این زمینه عبارتند از :

    1. quey_hash

    2. quey_plan_hash

این دو مقدار می توانند در شناسایی Query های مشابه و Plan های مشابه کمک کنند .

Query Hash :

query_hash برای شناسایی ساختار منطقی Query کاربرد دارد .

نکته مهم این است که دو Query الزاما نباید از نظر متن کاملا یکسان باشند تا مقدار Hash مشابهی داشته باشند . برای مثال ، ممکن است دو Query تنها در بعضی قسمت ها تفاوت داشته باشند ، اما ساختار کلی آنها مشابه باشند .

Query Plan Hash :

query_paln_hash بیشتر برای شناسایی Execution Plan های مشابه کاربرد دارد . این قابلیت در زمان Performance Tuning بسیار ارزشمند است .

فرض کنید یک Query در چند نقطه مختلف برنامه استفاده شده و عملکرد آن ضعیف است . اگر چند Query مختلف دارای query_plan_hash یکسان باشند، می توان آنها را به عنوان مواردی که از یک Plan مشابه استفاده می کنند بررسی کرد . در نتیجه اگر یک راهکار برای بهبود Plan پیدا شود ، می توان سایر محل هایی را که همان Plan را استفاده می کنند نیز شناسایی کرد .

آیا Query Hash همیشه به معنی Plan یکسان است ؟

خیر .

یکی از نکات مهم در تحلیل Plan Cache این است که ممکن است Query Hash یکسان باشد اما Query Plan Hash متفاوت باشد .

فایل نمونه ای از دو Query تقریبا مشابه را نشان می دهد که فقط مقدار ProductID آنها متفاوت است :

,SELECT p.Name

     , tha.TransactionDate     

, tha.TransactionType     

 ,tha.Quantity     

       tha.ActualCost     

FROM Production.TransactionHistoryArchive AS tha

JOIN Production.Product AS p

    ON tha.ProductID = p.ProductID     

;WHERE p.ProductID = 461

و :

  , SELECT p.Name

  , tha.TransactionDate     

, tha.TransactionType     

, tha.Quantity     

       tha.ActualCost     

FROM Production.TransactionHistoryArchive AS tha

JOIN Production.Product AS p

    ON tha.ProductID = p.ProductID     

 ;WHERE p.ProductID = 712

در این مثال ، تفاوت های جزئی متن Query برای تغییر مقدار Query Hash کافی نیستند ، اما مقادیر متفاوت ارسال شده در WHERE می توانند باعث ایجاد Execution Plan های متفاوت شوند . این موضوع نشان می دهد که هنگام Performance Tuning نباید تنها به متن Query نگاه کنیم .

یکی از مشکلات مهم Plan Cache :

یکی از موضوعات مهم در مدیریت Plan Cache ها ، Ad Hoc Query ها هستند .

Ad Hoc Query ها Query هایی هستند که به شکل مستقیم و بدون سازکار مناسب برای استفاده مجدد از Plan ارسال می شوند . وجود مقدار مشخصی Ad Hoc Query در بسیاری از سیستم ها اجتناب ناپذیر است ، اما فزایش بیش از حد آنها می تواند برای Plan Cache مشکل ایجاد کند .Ad Hoc Query ها در بسیاری از موارد از Reuse Plan بهره مناسبی نمی برند و می توانند باعث افزایش سربار Compile و همچنین ایجاد Cache Bloat شوند .

Cache Bloat چیست ؟

Cache Bloat زمانی رخ می دهد که Plan Cache با تعداد زیادی Plan پر شود ، به خصوص زمانی که تعداد زیادی Query مشابه اما از نظر متن متفاوت وارد Cache شوند .

برای مثال تصور کنید برنامه ای Query زیر را ارسال کند :

SELECT *

FROM Products

WHERE ProductID = 10;

سپس :

SELECT *

FROM Products

WHERE ProductID = 20;

و سپس :

SELECT *

FROM Products

WHERE ProductID = 30;

اگر این Query ها به شکلی ارسال شوند که SQL Server نتواند Plan را به درستی Reuse کند ، ممکن است Plan های متعددی در Cache ایجاد شوند . در سیستم های بزرگ ، این مسئله می تواند باعث مصرف غیر ضروری منابع Cache شود .

Parameterizetion چیست ؟

یکی از بهترین راهکارها برای افزایش Plan Reuse می باشد . در Parameterization ، قسمت های ثابت Query از مقادیری که در هر اجرا تغییر می کنند جدا می شوند و بجای اینکه Query برای هر مقدار جدید دوباره به شکل متفاوت ارسال شود ، می توان از parameter استفاده کرد .

به صورت مفهومی :

SELECT *

FROM Production.Product

WHERE ProductID = @ProductID;

در این حالت ساختار اصلی Query ثابت باقی می ماند و تنها مقدار Parameter تغییر می کند . توصیه می شود مقادیر موجود در Query به صورت صریح Parameterize شوند ؛ زیرا این روش می تواند میزان Reuse Plan را افزایش داده و تعداد Plan های موجود در Cache را کاهش دهد .

Forced Parameterization  و Simple Parameterization :

SQL Server روش هایی برای Parameterization دارد که از جمله آنها می توان به :

    1. Simple Parameterization 

    2. Forced Parameterization

اشاره کرد .

با این حال ، این روش ها محدودیت هایی دارند و همیشه پاسخ مناسب برای تمام Workload ها نیستند . به همین دلیل در شرایطی که کنترل بیشتری روی Query دارید ، Explicit Parameterization می تواند گزینه مناسبی باشد . 

Parameterization صریح می تواند در Workload ها به استفاده مجدد از Plan کمک کرده و تعداد Plan های موجود در Cache را کاهش دهد .

Stored Procedure و Plan Cache :

یکی از روش های مهم برای مدیریت Query ها و افزایش Reuse Plan ، استفاده از Stored Procedure است .

Stored Procedure می تواند Query و منطق مربوط به اجرای آن را در SQL Server نگهداری کند و پارامترهای لازم هنگام اجرا ارسال شوند .

به صورت ساده :

CREATE PROCEDURE GetProduct

    @ProductID INT

AS

BEGIN

    SELECT *

    FROM Production.Product

    WHERE ProductID = @ProductID;

END;

سپس :

EXEC GetProduct @ProductID = 461;

و در اجرای بعدی :

EXEC GetProduct @ProductID = 712;

ساختار کلی Query ثابت باقی می ماند و مقدار Parameter تغییر می کند .

مزایایی Store Procedure :

استفاده از Store Procedure در شرایط مناسب می تواند مزایایی داشته باشد :

    1. افزایش امکان Reuse Plan .

    2. کاهش حجم اطلاعات ارسالی در شبکه .

    3. نگهدار منطق Query در Database .

    4. کاهش نیاز به ارسال مکرر متن کامل Query .

در Store Procedure ، علاوه بر نام Procedure  ، فقط پارامترها باید ارسال شوند ؛ بنابر این در مقایسه با Ad Hoc Query می توان ترافیک شبکه کمتری داشت . همچنین Stored Proucedure ها می توانند از Plan Cache مجددا استفاده کند .

در شرایطی که Stored Procedure گزینه مناسبی نیست ، یکی از ابزارهای مهم SQL Server برای اجرای Query های SP_ executesql ، Parameterized است .

این روش به شما اجازه می دهد Query را همراه با Parameterها اجرا کنید .

یک نمونه ساده :

DECLARE @SQL NVARCHAR(MAX);

SET @SQL = N'

SELECT *

FROM Production.Product

WHERE ProductID = @ProductID';

EXEC sp_executesql

    @SQL,

    N'@ProductID INT',

    @ProductID = 461;

در اجرای بعدی می توان مقدار دیگری ارسال کرد :

EXEC sp_executesql

    @SQL,

    N'@ProductID INT',

    @ProductID = 712

مزیت مهم این روش ، امکان Parameterize کردن Query است .

چرا استفاده از sp_executesql بهتر از EXECUTE برای Dynamic SQL است ؟

در Dynamic SQL ممکن است برنامه Query را به صورت یک String بسازد .

روش نا مناسب می تواند چیزی شبیه این یاشد :

 

EXEC(

    'SELECT *

     FROM Production.Product

     WHERE ProductID = ' + @ProductID

);

این روش علاوه بر مشکلات مربوط به Reuse Plan ، می تواند ریسک های امنیتی ایجاد کند ؛ بخصوص اگر داده ورودی بدون اعتبار سنجی و Parameterization مناسب وارد Query شود . در مقابل ، می توان از sp_executesql همراه با Parameter استفاده کرد .

توصیه می شود برای Dynamic Query ها بجای EXECUTE از sp_executesql استفاده شود و اشاره می کند که Parameterization در این روش احتمال SQL Injection را کاهش می دهد .

SQL Injection و Dynamic Query :

SQL Injection یکی از خطرات مهم در برنامه هایی است که Query را به صورت Dynamic و با اتصال مستقیم ورودی کاربر تولید می کنند .

برای مثال ، ترکیب مستقیم ورودی کاربر با String مربوط به Query می تواند خطرناک باشد . به همین دلیل بهتر است از Parameter استفاده شود .

EXEC sp_executesql

    N'SELECT *

      FROM Production.Product

      WHERE ProductID = @ID',

    N'@ID INT',

    @ID = @ProductID

در این روش مقدار داده از ساختار Query جدا می شود .

با این حال باید توجه داشت که Parameterization یک راهکار مهم است ، اما امنیت یک سیستم Database فقط به این موضوع محدود نمی شود .

Execute/Prepare چیست ؟

روش دیگری که برای مدیریت Plan ها معرفی می شود ، مدل Execute/Prepare است .

اگر برنامه ای Query های Dynamic ایجاد کند و آنها را از طریق sp_executesql روی شبکه ارسال کند ، در برخی شرایط می توان از Execute/Prepare استفاده کرد . در این مدل Query کامل می تواند یک بار ارسال شود و آماده شود و اجرای بعدی با استفاده از Plan آماده انجام شود . یکی از مزایای این مدل این است که تنها یکبار لازم است رشته کامل Query از طریق شبکه ارسال شود . همچنین با داشتن Plan Handle ، بیش از یک Connection User می تواند از Prepared Plan استفاده کند .

Optimize for Ad Hoc Workloads چیست ؟

همان طور که گفتیم ، Ad Hoc Query ها در برخی سیستم ها اجتناب ناپذیر هستند .اما اگر تعداد زیادی Ad Hoc Query داشته باشیم ، ممکن است Plan های زیادی وارد Cache شوند .

SQL Server گزینه ای به نام Optimize for Ad Hoc Workloads در اختیار مدیر Database قرار می دهد . با فعال کردن این گزینه ، Plan تنها زمانی به شکل کامل وارد Cache می شود که Query بیش از یک بار اجرا شده باشد . به بیان ساده ، این قابلیت می تواند برای محیط هایی که تعداد زیادی Ad Hoc Query دارند ، به کاهش مصرف غیر ضروری Plan Cache کمک کند .

بهترین روش ها برای مدیریت Plan Cache :

برای داشتن Plan Cache سالم تر و افزایش Reuse Plan ، چند توصیه مهم وجود دارد :

1. Query ها را Parameterize کنید .

تا حد امکان مقادیر متغیر را به Parameter تبدیل کنید ، این کار باعث می شود ساختار Query ثابت تر بماند و امکان Reuse Plan  افزایش پیدا می کند .

2. از Stored Procedure در شرایط مناسب استفاده کنید .

Stored Procedure یکی از روش های مناسب برای ایجاد Workload هایی است که قابلیت استفاده مجدد از Plan دارند . اما نباید همه منطق برنامه را بدون دلیل داخل Database منتقل کرد .

3. برای Dynamic SQL از sp_executesql استفاده کنید .

اگر Dynamin SQL لازم است ، استفاده از sp_executesql همراه با Parameter ها گزینه مناسب تری نسیت به ساختن رشته های نا امن با EXECUTE است .

4. تعداد Ad Hoc Query را کاهش دهید .

Ad Hoc Query ها در بسیاری از سیستم ها قابل حذف کامل نیستند ، اما بهتر است تعداد آنها تا حد امکان کنترل شود . وجود تعداد زیادی Plan کم استفاده می تواند باعث Cache Bloat شود .

5. Optimize for Ad Hoc Workload را بررسی کنید .

در سیستم هایی که تعداد زیادی Ad Hoc Query دارند ، فعال کردن این گزینه می تواند به مدیریت بهتر Cache کمک کند .البته فعال کردن آن باید بر اساس Workoload واقعی سیستم انجام شود .

6. Query Hash و Plan Hash را بررسی کنید .

هنگام Performance Tuning فقط به متن Query نگاه نکنید . با بررسی query_hash و query_plan_hash می توانید Query های مشابه و Plan های مشابه را بهتر شناسایی کنید . این موضوع برای پیدا کردن Query هایی که از یک Plan مشترک استفاده می کنند بسیار کاربردی است .

آیا یک Query همیشه یک Execution Plan دارد ؟

خیر !

یکی از نکات مهم در SQL Server این است که یک Query می تواند در شرایط مختلف Execution Plan های متفاوتی داشته باشد . برای مثال ، مقدار parameter یا داده های موجود در جدول ممکن است بر Plan انتخاب شده تاثیر بگذارد . 

Query های تقریبا یکسان با تغییر مقدار WHERE پلن های متفاوتی ایجاد می کند . بنابر این در زمان بررسی Performance علاوه بر متن Query باید Execution Plan واقعی را نیز بررسی کرد .

چگونه Plan Cache به بهینه سازی کمک میکند ؟

فرآیند کلی را می توان به شکل زیر خلاصه کرد :

Query ---> Compile/Optimize ---> Execution Plan ---> Plan Cache ---> اجرای مجدد ---> Reuse Plan

اگر SQL Server بتواند Plan موجود را مجددا استفاده کند ، نیاز به Compile و Optimization مجدد کاهش پیدا می کند ، در نتیجه منابع سیستم بهتر مصرف می شوند . اما اگر Query ها دائما با ساختارهای متفاوت ارسال شوند ، ممکن است تعداد زیادی Plan ایجاد شود ، بنابر این طراحی صحیح Query و نحوه ارسال آن از سمت Application اهمیت زیادی دارد .

یک سناریوی واقعی برای درک Reuse Plan :

فرض کنید یک فروشگاه اینترنتی دارید و برنامه باید اطلاعات محصولات مختلف را دریافت کند .

روش اول :

SELECT *

FROM Products

WHERE ProductID = 1001; 

سپس :

SELECT *

FROM Products

WHERE ProductID = 1002;

و :

SELECT *

FROM Products

WHERE ProductID = 1003;

در صورتی که این Query ها به شکل Ad Hoc و بدون parameterization مناسب مدیریت شوند ، احتمال ایجاد Plan های متعدد افزایش پیدا می کند .

اما می توان ساختار Query را Parameterized کرد :

SELECT *

FROM Products

WHERE ProductID = @ProductID;

در این حالت ساختار Query ثابت است و فقط مقدار Parameter تغییر می کند ، این دقیقا همان ایده ای است که Parameterization برای افزایش Reuse Plan دنبال می کند .

چه زمانی از Store Procedure استفاده کنیم ؟

Stored Procedure زمانی گزینه مناسبی است که :

    1. Query یا منطق database به شکل مشخص و قابل تکرار دارید .

    2. می خواهید پارامترها را به صورت ساختار یافته دریافت کنید .

    3. Reuse Plan برای Workload اهمیت دارد .

    4. می خواهید متن Query دائما از Application به Database ارسال نشود .

اکا Stored Procedure راه حل مطلق برای همه مسائل نیست . بعضی Business Process ها بهتر است در Database باشند و برخی دیگر نباید در Database پیاده سازی شوند .

چه زمانی sp_executesql انتخاب بهتری است ؟

sp_executesql زمانی اهمیت بیشتری پیدا می کند که Query شما Dynamic باشد . برای مثال ممکن است Application بر اساس شرایط مختلف ، بخش هایی از Query را تغییر دهد . در این حالت بجای ساختن Query با Concatenation نا امن ، می توان قسمت های متغیر را تا حد امکان Parameterize کرد .

مزایای مهم :

    1. مناسب برای Dynamic SQL .

    2. پشتیبانی از Parameter .

    3. کاهش ریسک SQL Injection در مقایسه با Concatenation مستقیم .

    4. کمک به Reuse Plan .

فایل نیز استفاده از sp_executesql را به عنوان جایگزینی برای Stored Procedure در شرایط مناسب مطرح می کند .

اشتباهات رایج در مدیریت Plan Cache 

استفاده بیش از حد از Ad Hoc Query

یکی از اشتباهات رایج، تولید تعداد زیادی Query با متن متفاوت است .

Concatenate کردن ورودی کاربر در Query

این روش می تواند هم از نظر امنیتی و هم از نظر مدیریت Plan مشکل ایجاد کند .

تصور اینکه یک Plan همیشه بهترین Plan است 

یک Execution Plan ممکن است برای یک مقدار parameter مناسب باشد ، اما برای مقدار دیگر عملکر خوبی نداشته باشد .

استفاده افراطی از Stored Procedure 

Stored Procedure ابزار قدرتمندی است ، اما نباید صرفا با هدف استفاده از Cache تمام ، منطق Business  را داخل Database قرار داد .

بی توجهی به Query Plan Hash 

گاهی چند Quey ظاهرا متفاوت هستند اما Plan یکسانی دارند .

برعکس ، Query هایی که بسیار شبیه به نظر می رسند ممکن است Planهای متفاوت ایجاد کنند .

استفاده از Hash ها می تواند به تحلیل این وضعیت کمک کند .

چک لیست بهینه سازی Plan Cache در SQL Server

اگر مسئول Performance یک SQL Server هستید، این موارد را بررسی کنید :

    1. آیا Query های پر تکرار Parameterize شده اند ؟

    2 آیا تعداد زیادی Ad Hoc Query در سیستم وجود دارد ؟

    3. آیا Plan های مشابه زیادی در Cache ایجاد شده اند ؟

    4.  آیا Query های Dynamic با sp_executesql اجرا می شوند ؟

    5. آیا استفاده از Store Procedure در بخش های مناسب بررسی شده است ؟

    6. آیا Query Hash ها برای پیدا کردن Query های مشابه بررسی شده اند ؟

    7. آیا Query Plan Hash ها برای پیدا کردن Plan های مشابه بررسی شده اند ؟

    8. آیا Cache Bloat در سیستم مشاهده می شود ؟

    9. آیا Optimize for Ad Hoc Workload برای Workload مورد نظر مناسب است ؟

    10 . آیا تفاوت Execution Plan ها برای parameter های مختلف بررسی شده است ؟

جمع بندی :

Plan Cache یکی از بخش های مهم SQL Server برای بهبود Performance است . ایده اصلی آن ساده است : اگر یک Query قبلا Comile و Optimize شده باشد و Execution Plan ، آن قابل استفاده مجدد باشد SQL Server می تواند به جای انجام دوباره این فرآیند ، از Plan موجود استفاده کند . 

برای افزایش احتمال Reuse Plan روش هایی مانند Prepare/Execute , sp_executesql , Store Procedure , Parameterization اهمیت دارند . همچنین بهتر است تعداد Ad Hoc Query ها کنترل شود و در محیط هایی که این Query ها اجتناب ناپذیر هستند ، قابلیت Optimize for Ad Hoc Workload مورد بررسی قرار گیرد .

از طرف دیگر ، نباید صرفا به دنبال بیشترین میزان Plan Reuse باشیم . همانطور که ، مثال ها نشان می دهند Query هایی با ساختار تقریبا مشابه می توانند در شرایط مختلف Execution Plan های متفاوتی ایجاد کنند . بنابر این Performance Tuning باید بر اساس Workload واقعی و Execution Plan های واقعی انجام شود .

با رعایت این اصول ، می توان مدیریت بهتری روی Plan Cache داشت و از منابع SQL Server به شکل موثرتری استفاده کرد .

💬 نظرات (0)

هنوز نظری ثبت نشده است. اولین نفری باشید که نظر می‌دهید!

📝 ثبت نظر جدید