خانه SQL Server آشنایی با IQP در SQL Server SQL Server افزایش سرعت SQL Server نوشته شده توسط: مهدی قپانوری تاریخ انتشار: ۱۹ بهمن ۱۳۹۸ آخرین بروزرسانی: ۱۹ بهمن ۱۴۰۰ زمان مطالعه: 19 دقیقه ۴.۳ (۳) مقدمه Intelligent Query Processing یا به اختصار IQP مجموعهایی از ویژگیهایی است که تاثیر گستردهایی بر بهبود عملکرد حجم کاری موجود، با کمترین میزان پیاده سازی را دارد. با تنظیم سطح سازگاری مناسب به ازای یک Database می توان حجم کاری را به صورت اتوماتیک واجد شرایط برای IQP نمود. به عنوان نمونه دستور زیر سطح سازگاری پایگاه داده AdventureWorks را به ۱۵۰ تنظیم مینماید: Alter Database AdventureWorks Set Compatibility_Level = 150 تصویر زیر مجموعهایی از ویژگیهای مرتبط با IQP را نمایش میدهد: بررسی یک نمونه از ویژگیهای مرتبط با IQP Scalar UDF Inlining UDF ها یک راه استفاده مجدد از کد در میان کوئریهای مختلف هستند، همچنین برخی از محاسبات مانند (Business Ruleهای پیچیده) ضروری است که به شکل UDF نوشته شوند. UDFها نوشتن Logicهای پیچیده را آسان میسازند، با وجود این مزایا، UDFها Performance مربوط به کوئریها را کاهش میدهند و همین موضوع باعث کاهش استفاده از آنها میگردد. هدف از Scalar UDF Inlining بهبود Performance مربوط به کوئریهایی است که در آنها UDF فراخوانی میشود و اجرای UDF مشکل اصلی Performance کوئری است. در ادامه تاثیر Scalar UDF Inlining بر Performance کوئریها را با ارائه یک مثال بررسی مینماییم: در این مثال از دیتابیس AdventureWorks استفاده نمودهایم و ابتدا Compatibility Level دیتابیس را برابر ۱۴۰ قرار میدهیم. Use master GO Alter Database AdventureWorks Set Compatibility_Level = 140 GO تصویر زیر Compatibility Level مربوط به دیتابیس AdventureWorks را نمایش میدهد. سپس یک Scalar Function ایجاد مینماییم که ID مربوط به هر سفارش را از ورودی دریافت نموده و بعد از انجام یک عملیات ریاضی نتیجه را برمیگرداند. Use AdventureWorks GO Create Or Alter Function dbo.GetChangeValue (@SalesOrderDetailID SmallInt) Returns Int AS Begin Declare @Value As Int Select @Value = (OrderQty * UnitPrice) + 13 From Sales.SalesOrderDetail Where SalesOrderDetailID = @SalesOrderDetailID Return @Value END کوئری زیر بدون فراخوانی Scalar Function در کسری از ثانیه اجرا میشود و ۲۴۴۵ رکورد را برمیگرداند. Select * from Sales.SalesOrderDetail Where ModifiedDate Between '2012-01-01 00:00:00.000' And '2012-03-10 00:00:00.000' جهت افزایش کیفیت تصویر همه ستونها در تصویر نمایش داده نشدهاند. اگر Scalar Function ایجاد شده را همانند یک ستون در کوئری قرار دهیم زمان اجرای آن با توجه به سخت افزار استفاده شده ۱۴ ثانیه میباشد. Select *, dbo.GetChangeValue (SalesOrderDetailID ) As NewColumn from Sales.SalesOrderDetail Where ModifiedDate Between '2012-01-01 00:00:00.000' And '2012-03-10 00:00:00.000' تا اینجا مشخص میشود که Bottleneck مربوط به Performance کوئری، استفاده از Scalar Function است. همان گونه که گفته شد IQP باعث بهبود عملکرد با کمترین میزان پیاده سازی میشود، در ادامه خواهیم دید که Performance مربوط به کوئری مورد بحث تنها با تغییر Compatibility Level مربوط به دیتابیس AdventureWorks به صورت چشمگیری افزایش خواهد یافت و در کسری از ثانیه اجرا میشود (مدت زمان اجرای قبلی ۱۴ ثانیه بوده است). با استفاده از دستور زیر دادههایی را که در Cache مربوط به SQL Server قرار گرفتهاند، بدون نیاز به Shut down و Restart سرور Clean مینماییم: DBCC DropCleanBuffers GO با استفاده از دستور زیر سطح سازگاری پایگاه داده AdventureWorks را به ۱۵۰ تنظیم می نماییم: Alter Database AdventureWorks Set Compatibility_Level = 150 سپس کوئری را مجدد اجرا مینماییم. Select *, dbo.GetChangeValue (SalesOrderDetailID ) As NewColumn from Sales.SalesOrderDetail Where ModifiedDate Between '2012-01-01 00:00:00.000' And '2012-03-10 00:00:00.000' همان گونه که در تصویر زیر مشاهده میشود، کوئری در کسری از ثانیه اجرا شده است: بررسی مباحث Internals جهت بهتر نشان دادن علت مشکل Performance مربوط به استفاده از Scalar Function در کوئریها، دو جدول ساده ایجاد میکنیم و آن ها را با تعداد کمی رکورد پر مینماییم. برای این کار یک Database با نام NewDB ایجاد می¬کنیم و در ابتدا Compatibility Level آن را برابر با ۱۴۰ قرار میدهیم: Use master GO If DB_ID (N'NewDB') Is Not Null Begin Alter Database NewDB Set Single_User With Rollback Immediate Drop Database NewDB End Create DataBase NewDB On Primary (Name = N'NewDB', FileName = N'D:\Databases\Data\NewDB.mdf', Size = 128 mb, FileGrowth = 32 mb, Maxsize = Unlimited ) Log On (Name = N'NewDBLog', FileName = N'D:\Databases\Data\NewDB.ldf', Size = 128 mb, FileGrowth = 32 mb, Maxsize = Unlimited ) GO Alter Database NewDB Set Compatibility_Level = 140 GO سپس دو جدول با نام Employees و Languages ایجاد مینماییم: Use NewDB GO Drop Table If Exists dbo.Employees Drop Table If Exists dbo.Languages Create Table dbo.Languages (LanguageID Int Not Null Primary Key, LanguageName Nvarchar(50) Not Null) Create Table dbo.Employees (EmployeeID Int Not Null Primary Key, EmployeeName Nvarchar(100)Not Null, LanguageID Int Not Null, Constraint FK_Employees Foreign Key(LanguageID) References dbo.Languages(LanguageID) ) GO دستورات زیر دادهها را به جداول ایجاد شده Insert مینمایند: Insert Into dbo.Languages (LanguageID, LanguageName) Values (1000, N'Persian'), (2000, N'English') Insert Into Employees Select [OBJECT_ID], [Name], CASE ABS([object_id]%2) WHEN 1 THEN 1000 ELSE 2000 END As LanguageID From sys.all_objects GO و بعد یک Scalar Function بسیار ساده ایجاد مینماییم که LanguageID را از ورودی دریافت نموده و LanguageName را برمیگرداند: CREATE OR Alter FUNCTION dbo.GetLanguage(@LanguageID int) RETURNS sysname AS BEGIN RETURN (SELECT LanguageName FROM dbo.Languages WHERE LanguageID = @LanguageID); END GO کوئری زیر با استفاده از Scalar Function ایجاد شده LanguageName مربوط به هر Employee را نمایش میدهد. SELECT TOP (8) EmployeeID, [Language] = dbo.GetLanguage(LanguageID) FROM dbo.Employees; بدیهی است که به جای استفاده از UDF در این کوئری میتوان از Join استفاده نمود. (ما جهت پیاده سازی تست از UDF استفاده نمودهایم). به Actual Execution Paln کوئری توجه نمایید:جدول Languages در Plan مشاهده نمیشود، در حقیقت این Plan معادل آن است که ستون LanguageName مانند یک مقادر ثابت یا یک Variable در نظر گرفته شده است. SQL Server چگونه به جدول Languages دست یافته است؟ برای Trace کوئری فوق از View سیستمی sys.dm_exec_function_stats استفاده مینماییم. کوئری زیر نشان میدهد که فانکشن GetLanguage به ازای اجرای کوئری اصلی هشت بار فراخوانی شده است (به ازای هر Row یک بار). اما Plan مربوط به آن توسط SQL Server برگردانده نشده است SELECT OBJECT_NAME([object_id]) as FunctionName, execution_count FROM sys.dm_exec_function_stats WHERE object_name(object_id) IS NOT NULL; نمایش داده نشدن Operatorهای مربوط به کد درون Scalar UDF در Plan گرافیکی عیب یابی را دشوار میسازد زیرا نه تنها Operatorها در Plan نشان داده نمیشوند بلکه Cost مربوطه نیز نمایش داده نمیشود، در واقع در طی مرحله Optimization فقط به ازای Relational Operators هزینه برآورد میشود و به ازای Scalar Operators برآورد هزینه صورت نمیگیرد (از نظر میزان CPU هزینه اندکی در نظر گرفته میشود) به این موضوع اصطلاحا Lack of costing گفته میشود. SQL Server 2019 اپراتورها را در Execution Plan کوئریهایی که در آنها UDF فراخوانی میشود نمایش میدهد و این موضوع عیب یابی را آسان مینماید. نکته مهم این است که اکنون Cardinality Estimates بر پایه Statistics استراتژی Join را فراهم مینماید، به عبارت دیگر در Execution Plan از اپراتور join استفاده میشود. استفاده از Strategies Join. هنگامی که در کوئری، Function به ازی هر ردیف فراخوانی میشود به سادگی امکان پذیر نیست. در ادامه Compatibility Level مربوط به دیتابیس NewDB را به ۱۵۰ تنظیم مینماییم: Use master GO Alter Database NewDB Set Compatibility_Level = 150 GO سپس کوئری زیر را اجرا مینماییم: Use NewDB GO SELECT TOP (8) EmployeeID, [Language] = dbo.GetLanguage(LanguageID) FROM dbo.Employees; GO در اینجا Optimizer اپراتور nested loops join را انتخاب نموده است اما بسته به شرایط مختلف ممکن است استراتژی join متفاوتی را انتخاب نماید. همچنین Scalar Function به ازای هر ردیف فراخوانی نشده است بلکه عمل Clustered Index Seek در واقع ۸ بار اتفاق افتاده است (با توجه به استفاده از Nested loops Join). اجرا به صورت Statement-by-Statement و تکرار فراخوانی باعث ایجاد کندی میگردد. در نظر گرفتن اجرای کوئری به صورت Parallelism: به Plan کوئریهای مثال اول که در ادامه خواهد آمد توجه نمایید: Plan مربوط به اجرای کوئری با سطح سازگاری ۱۴۰ به ازای دیتابیس AdventureWorks:Plan مربوط به اجرای همان کوئری با سطح سازگاری ۱۵۰ به ازای دیتابیس AdventureWorks: همان طور که مشاهده مینمایید Plan دوم به صورت Parallel اجرا شده است و چندین CPU Core عمل پردازش دادهها را انجام دادهاند بررسی چند نکته ۱. Scalar UDF Inlining از SQL Server 2019 در دسترس میباشد. 2. همه Scalar Function ها Inlineable نیستند، حتی ممکن است Inlineable باشند اما نیاز به Inlined بودن آنها در هر Scenario نباشد. Inlineable بودن Scalar Function را میتوان با استفاده از کوئری زیر چک نمود. SELECT OBJECT_NAME([object_id]), definition, is_inlineable FROM sys.sql_modules; ۳. جهت کنترل نمودن Inline در بدنه Function میتوان از WITH INLINE = OFF استفاده نمود. 4. با استفاده از دستور زیر می توان این ویژگی را در سطح دیتابیس و جدا از Compatibility Level تنظیم نمود: ALTER DATABASE SCOPED CONFIGURATION SET TSQL_SCALAR_UDF_INLINING = OFF; ۵. ارائه Scalar UDF Inlining در SQL Server 2019 بدین معنا نیست که هر Logic را به صورت Scalar UDF نوشته و انتظار داشته باشیم SQL Server عملکرد بهینه به ازای آن انجام دهد، اگر دیتابیسی وجود دارد که Scalar Function زیاد در آن استفاده شده است Backup آن را روی SQL Server 2019 ریستور نموده و با استفاده از DMV وضعیت Inlineable بودن را بررسی نمایید. در مقالههای بعدی به بررسی بیشتر IQP در SQL Server خواهیم پرداخت چه رتبه ای میدهید؟ میانگین ۴.۳ / ۵. از مجموع ۳ اولین نفر باش معرفی نویسنده مقالات 15 مقاله توسط این نویسنده محصولات 1 دوره توسط این نویسنده مهدی قپانوری مهدی قپانوری بیش از 6 سال است که در زمینههای نرم افزار و بانک اطلاعاتی فعالیت مینماید. تخصص اصلی او در بانک اطلاعاتی SQL Server بوده و دارای تجربه در حوزههایPerformance Tuning، Database Administration، Database Development و طراحی سیستمهای OLTP میباشد. مهدی علاقهمند به R&D در حوزههای نوین SQL Server است. معرفی محصول مسعود طاهری آموزش ۳ در ۱ Performance Tuning در SQL Server 6.700.000 تومان مقالات مرتبط ۰۲ آبان SQL Server ابزار Database Engine Tuning Advisor؛ مزایا، کاربردها و روش استفاده تیم فنی نیک آموز ۱۵ مهر SQL Server معرفی Performance Monitor ابزار مانیتورینگ SQL Server تیم فنی نیک آموز ۱۱ مهر SQL Server راهنمای جامع مانیتورینگ بکاپ ها در SQL Server تیم فنی نیک آموز ۰۸ مهر SQL Server Resource Governor چیست؟ آشنایی با نحوه پیکربندی و اهمیت های آن تیم فنی نیک آموز دیدگاه کاربران لغو پاسخ دیدگاه نام و نام خانوادگی ایمیل ذخیره نام، ایمیل و وبسایت من در مرورگر برای زمانی که دوباره دیدگاهی مینویسم. موبایل برای اطلاع از پاسخ لطفاً مرا با خبر کن ثبت دیدگاه Δ مهدی عدالتی ۰۵ / ۱۰ / ۹۹ - ۰۸:۱۹ ممنون پاسخ به دیدگاه