مطالب
پروسیجرها و شنود پارامترها در SQL Server - قسمت دوم
در ادامه مطلب قبلی، آشنایی با شنود پارامترها در اس کیو ال سرور، جهت بهبود زمان پاسخگویی پروسیجرها یا کوئری‌های پارامتری الزامی است.
اس کیو ال سرور بوسیله ایجاد پلن‌های اجرایی کامپایل شده، سعی در بهینه سازی پروسیجرها دارد. هنگامیکه اس کیو ال سرور یک پروسیجر را کامپایل می‌نماید، به پارامترهای ارسال شده توجه دارد و یک پلن اجرایی را بر اساس پارامترهای ارسالی ایجاد می‌کند. به فرآیند تماشا یا توجه به پارامترهای ارسالی به پروسیجر، شنود پارامترها گفته می‌شود. شنود پارامترها می‌تواند بعضی از اوقات به کاهش کارآیی پلن اجرایی منجر شود؛ مخصوصا زمانیکه پارامترهایی با کاردینالیتی متفاوت، فراخوانی شوند.

شنود پارامتر چیست؟

شنود پارامتر، قابلیتی است که اس کیو ال سرور توسط آن یک پلن مناسب و بهینه اجرایی را برای پروسیجری با پارامترهای ارسالی، در اولین مرتبه اجرا، تولید خواهد کرد. در ادامه هر فراخوانی پروسیجر با پارامترهای مشابه، منجر به استفاده از پلن اجرایی ذخیره شده خواهد شد. شاید در اولین نگاه این استفاده مجدد، مناسب به نظر برسد. ولی در صورتیکه پروسیجر با پارامترهای متفاوتی فراخوانی شود، ممکن است پلن اجرایی تولید شده بر اساس آن پارامترهای اولیه، برای پارامترهای جدید بهینه نباشد.

پلن‌های اجرایی بر اساس چیزهایی که از SQL Server خواسته می‌شوند، بهینه سازی می‌شوند. اس کیو ال سرور کوئری را بررسی کرده و یک استراتژی بهینه را برای اجرای آن مشخص می‌کند. به کارهایی که کوئری قرار است انجام دهد نگاه می‌کند؛ از مقادیر پارامترها برای استفاده از اطلاعات آماری استفاده کرده و محاسباتی را انجام خواهد داد.

مثالی از مصرف شدید I/O بدلیل شنود پارامتر

در ادامه، برای درک بهتر شنود پارامتر، با مثالی خواهید دید که پروسیجر ذیل، باعث مصرف بالای منابع، بر اساس پارامترهای ارسالی خواهد شد. در این مثال دو دسته متفاوت پارامتر برای اجرای پروسیجر ارسال خواهند شد و خواهید دید که فراخوانی دوم، منابع I/O بیشتری را نسبت به فراخوانی اول، مصرف خواهد کرد. در ادامه کدهای جدولی را که پروسیجر قرار است بر روی آن فراخوانی اطلاعات را انجام دهد، می‌بینید.

SET NOCOUNT ON;
DROP TABLE BillingInfo;
CREATE TABLE BillingInfo(
ID INT IDENTITY,
BillingDate DATETIME,
BillingAmt MONEY,
BillingDesc varchar(500));
 
DECLARE @I INT;
DECLARE @BD INT;
SET @I = 0;
WHILE @I < 1000000 
BEGIN
  SET @I = @I + 1;
  SET @BD=CAST(RAND()*10000 AS INT)%3650;
  INSERT BillingInfo (BillingDate, BillingAmt) 
  VALUES (DATEADD(DD,@BD,
    CAST('1999/01/01' AS DATETIME)),
    RAND()*5000);
END 
 
ALTER TABLE BillingInfo 
  ADD  CONSTRAINT [PK_BillingInfo_ID] 
  PRIMARY KEY CLUSTERED (ID);
 
CREATE NONCLUSTERED INDEX IX_BillingDate
  ON dbo.BillingInfo(BillingDate);

در جدول BilingInfo بالا، یک میلیون رکورد با مقادیر BilingDate و BilingAmt به صورت تصادفی ایجاد شده است. بر روی ستون ID، ایندکس خوشه‌ای و ستون ایندکس غیر خوشه‌ای بر روی ستون BilingDate ایجاد شده‌است.

بوسیله پروسیجر زیر هم قرار است اطلاعات درخواستی فراهم شود:

CREATE PROC [dbo].[DisplayBillingInfo]
  @BeginDate DATETIME,
  @EndDate DATETIME
AS
SELECT BillingDate, BillingAmt
  FROM BillingInfo
  WHERE BillingDate between @BeginDate AND @EndDate;
سپس پروسیجر را 2 بار، با مقادیر پارامترهای متفاوتی اجرا می‌کنیم:
SET STATISTICS IO ON;
DBCC FREEPROCCACHE;
EXEC dbo.DisplayBillingInfo 
  @BeginDate = '1999-01-01',  
  @EndDate  = '1999-12-31';  
  
EXEC dbo.DisplayBillingInfo 
  @BeginDate = '2005-01-01',  
  @EndDate  = '2005-01-03';
اطلاعات آماری I/O روشن است و اطلاعات I/O در هر بار اجرا، نمایش داده می‌شود. در خط دوم توسط DBCC FREEPROCCACHE، پلن کش خالی خواهد شد؛ جهت اطمینان از عدم وجود پلن اجرایی مشابهی.
در فراخوانی اول، اطلاعات در بازه یک سال و در فراخوانی دوم، در بازه چند روز، درخواست شده‌اند. همانطور که گفته شد، پلن اجرایی بر اساس فراخوانی اول ایجاد خواهد شد و فراخوانی دوم نیز براساس همین پلن اجرایی ایجاد شده، اجرا می‌شود.


همانطور که مشاهده می‌کنید عملیات Clustered Index Scan، اجرا شده و اطلاعات I/O نیز بشرح زیر است (خط اول فراخوانی اول، خط دوم فراخوانی دوم):
Table 'BillingInfo'. Scan count 1, logical reads 3593, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
 
Table 'BillingInfo'. Scan count 1, logical reads 3593, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
حال ترتیب فراخوانی‌ها را به‌شرح زیر جابجا می‌کنیم:
SET STATISTICS IO ON;
DBCC FREEPROCCACHE;
EXEC dbo.DisplayBillingInfo 
  @BeginDate = '2005-01-01',  
  @EndDate  = '2005-01-03';
  
EXEC dbo.DisplayBillingInfo 
  @BeginDate = '1999-01-01',  
  @EndDate  = '1999-12-31';
در کد بالا ابتدا فراخوانی که بازه کوچکتری دارد اجرا و پلن اجرایی بر اساس آن ایجاد خواهد شد و فراخوانی دوم از پلن کش شده استفاده می‌کند.

اکنون عملیات Index Seek را بجای Index Scan مشاهده می‌کنید. اطلاعات I/O هم بشرح زیر است:

Table 'BillingInfo'. Scan count 1, logical reads 2965, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
 
Table 'BillingInfo'. Scan count 1, logical reads 337040, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
اکنون فراخوانی اول بهینه‌تر اجرا شده و بجای Index Scan از Index Seek استفاده کرده‌است و logical reads آن کاهش یافته‌است. در حالیکه فراخوانی دوم که در بازه یکسال اجرا شده‌است، با عملیات هزینه بر I/O بیشتری نسبت به آزمایش (حدود 100 برابر) انجام شده‌است. انجام این 2 آزمایش، پلن اجرایی متفاوتی را بر اساس پارامترهای ورودی ایجاد کرد و هزینه‌های I/O آن را مشاهده کردید. اکنون درک خوبی را نسبت به این قابلیت و اثرات آن خواهید داشت و در قسمت بعدی به راه حل‌هایی جهت کاهش اثرات منفی آن اشاره خواهد شد.
اشتراک‌ها
تغییرات SQL Server 2022

SQL Server 2022 is on its way and there are a lot of new features that could be of interest. Check out this summary of new features and things to investigate. 

تغییرات SQL Server 2022
اشتراک‌ها
مروری بر SQL Server 2016

Scott Klein and Joey D'Antoni provide an overview of the SQL Server 2016 features and enhancements, teasing up many of the features discussed in the other videos as well as covered in the training kit.
 

مروری بر SQL Server 2016
مطالب
پیاده سازی عملیات صفحه بندی (paging) در sql server

در خیلی مواقع ملاحظه میشود که برای نمایش تعدادی از رکوردهای یک جدول در پایگاه داده، کل مقادیر موجود درآن توسط یک دستور select به دست می‌آید و صفحه‌بندی خروجی، به کنترلهای موجود سپرده میشود. اگر پایگاه داده ما دارای تعداد زیادی رکورد باشد، آن موقع است که دچار مشکل می‌شویم. فرض کنید به طور همزمان ۵ نفر (که تعداد زیادی نیستند) از برنامه ما که شامل ۱۰۰۰۰۰ سطر داده میباشد استفاده کنند و در هر صفحه، ۱۰ رکورد نمایش داده شود و صفحه‌بندی ما از نوع معقولی نباشد. در این صورت به جای اینکه با ۵×۱۰ رکورد داده را بارگزاری کنیم، ۵×۱۰۰۰۰۰ رکورد یعنی ۵۰۰۰۰۰ رکورد را برای به دست آوردن ۵۰ رکورد بارگزاری میکنیم. در زیر روشی شرح داده میشود که توسط آن، این سربار اضافه از روی برنامه و سرورهای مربوطه حذف شود. به stored procedure و توضیحات مربوط به آن توجه فرمایید :

CREATE PROCEDURE sp_PagedItems
(
 @Page int,
 @RecsPerPage int
)
AS

-- We don't want to return the # of rows inserted
-- into our temporary table, so turn NOCOUNT ON
SET NOCOUNT ON


--Create a temporary table
CREATE TABLE #TempItems
(
ID int IDENTITY,
Name varchar(50),
Price currency
)


-- Insert the rows from tblItems into the temp. table
INSERT INTO #TempItems (Name, Price)
SELECT Name,Price FROM tblItem ORDER BY Price

-- Find out the first and last record we want
DECLARE @FirstRec int, @LastRec int
SELECT @FirstRec = (@Page - 1) * @RecsPerPage
SELECT @LastRec = (@Page * @RecsPerPage + 1)

-- Now, return the set of paged records, plus, an indiciation of we
-- have more records or not!
SELECT *,
MoreRecords =
(
 SELECT COUNT(*)
 FROM #TempItems TI
 WHERE TI.ID >= @LastRec
)
FROM #TempItems
WHERE ID > @FirstRec AND ID < @LastRec


-- Turn NOCOUNT back OFF
SET NOCOUNT OFF
در این کد دو پارامتر از نوع integer تعریف میکنیم. اول پارامتر @Page که مربوط به شماره صفحه‌ای می‌باشد که قصد دارید آن‌را بارگزاری نمایید. دومین پارامتر با نام @RecsPerPage تعداد رکوردهایی است که هر بار میخواهید بارگزاری شوند. مثلا اگر میخواهید هر بار ۱۵ عدد از رکوردها را نمایش دهید، این مقدار را باید برابر ۱۵ قرار دهیم. در مرحله بعد یک جدول موقت با نام #TempItems ساخته شده است که به طور موقت مقادیری را در حافظه نگه میدارد. نکته کلیدی که جلوتر از آن استفاده شده، ستون با نام ID است که از نوع auto-increment بوده و روی جدول موقت تعریف شده است. این ستون شناسه هر سطر را در خود نگه میدارد که به صورت اتوماتیک بالا میرود و جزء لاینفکی از این نوع paging میباشد. پس از آن جدول موقت را توسط رکوردهای جدول واقعی با نام tblItem توسط دستور select پر میکنیم.

در مرحله بعد شماره اولین و آخرین سطر مورد نظر را بر اساس پارامترهای ورودی محاسبه کرده و در متغیرهای @FirstRec و @LastRec می‌ریزیم.
برای استفاده از این کد فقط کافیست که پارامترهای ورودی را مقداردهی نمایید. مثلا اگر میخواهید در یک کنترل Grid از آن استفاده کنید باید ابتدا یک کوئری داشته باشید که تعداد کل سطرها را به شما بدهد و بر اساس این مقدار تعداد صفحات مورد نظر را به دست آورید. پس از آن با کلیک روی هر کدام از شماره صفحات آن را به عنوان مقدار به پارامتر مورد نظر بفرستید و از آن لذت ببرید.