View ایندکس شده (Indexed View) در SQL Server: راهکاری برای افزایش چشمگیر عملکرد پایگاه داده
View ایندکس شده (Indexed View) یکی از قابلیتهای قدرتمند و اغلب نادیده گرفته شده در SQL Server است که میتواند تأثیر شگرفی بر بهینهسازی عملکرد کوئریها و کاهش بار پردازشی سرور داشته باشد. این Viewها با ذخیرهسازی فیزیکی نتایج کوئری در پایگاه داده، مانند یک جدول معمولی، به شما این امکان را میدهند تا به سرعت به دادههای پیچیده یا تجمیع شده دسترسی پیدا کنید. اگر به دنبال راهی برای افزایش سرعت گزارشگیری، بهبود پاسخدهی برنامهها و کاهش منابع مصرفی در محیطهای پربار هستید، درک و پیادهسازی Views ایندکس شده ضروری است.
View ایندکس شده چیست؟
یک View استاندارد (Standard View) در SQL Server در واقع یک کوئری ذخیره شده است که هنگام فراخوانی، هر بار مجدداً اجرا میشود. اما یک View ایندکس شده (Indexed View) (که گاهی اوقات به آن View مادی شده یا Materialized View نیز گفته میشود)، فراتر از این است. این View دادههای برگشتی از کوئری خود را به صورت فیزیکی روی دیسک ذخیره میکند، دقیقاً مانند یک جدول. این ذخیرهسازی فیزیکی به این معنی است که SQL Server میتواند یک ایندکس کلاستر (Clustered Index) و سپس ایندکسهای غیرکلاستر (Non-Clustered Indexes) را روی آن ایجاد کند. هدف اصلی از ایجاد یک View ایندکس شده، کاهش زمان اجرای کوئریها برای محاسبات پیچیده و تجمیع دادهها است. هنگامی که یک ایندکس کلاستر منحصر به فرد (Unique Clustered Index) بر روی یک View ایجاد میشود، نتایج آن View محاسبه شده و در پایگاه داده ذخیره میگردند. هر زمان که دادههای جداول پایه تغییر میکنند، این View ایندکس شده نیز به صورت خودکار بهروزرسانی میشود تا همیشه با دادههای اصلی سازگار باشد.
مزایای استفاده از View ایندکس شده
استفاده از View ایندکس شده مزایای قابل توجهی را به همراه دارد که میتواند به طور چشمگیری کارایی پایگاه داده شما را افزایش دهد:
- بهبود عملکرد کوئری: مهمترین مزیت، افزایش سرعت اجرای کوئریها است، به خصوص برای گزارشهایی که شامل تجمیعهای پیچیده یا جوینهای متعدد هستند. به جای محاسبه مجدد دادهها در هر بار اجرا، سیستم از نسخه از پیش محاسبه شده و ایندکس شده View استفاده میکند.
- کاهش ورودی/خروجی (I/O): با دسترسی به دادههای از پیش محاسبه شده، نیاز به خواندن حجم زیادی از دادهها از جداول پایه و انجام محاسبات تکراری کاهش مییابد که به نوبه خود بار I/O دیسک را کاهش میدهد.
- بهینهسازی کوئریهای پیچیده: برای کوئریهایی که شامل توابع تجمیعی (SUM, COUNT_BIG, AVG) و گروهبندی (GROUP BY) هستند، Views ایندکس شده میتوانند عملکرد را به طرز چشمگیری بهبود بخشند.
- یکپارچگی و سازگاری دادهها: دادههای موجود در View ایندکس شده همیشه با دادههای جداول پایه سازگار و بهروز هستند، زیرا هرگونه تغییر در جداول پایه به صورت خودکار در View اعمال میشود.
پیشنیازهای ایجاد View ایندکس شده
برای ایجاد یک View ایندکس شده در SQL Server، مجموعهای از الزامات سختگیرانه وجود دارد که باید رعایت شوند. عدم رعایت هر یک از این موارد میتواند منجر به خطا در ایجاد ایندکس روی View شود. در ادامه به مهمترین این پیشنیازها اشاره میکنیم:
- SCHEMA_BINDING: View شما باید با گزینه
WITH SCHEMABINDINGایجاد شود. این گزینه تضمین میکند که تعاریف جداول پایه استفاده شده در View، بدون تغییر در View قابل تغییر نیستند. این ویژگی ثبات و یکپارچگی ساختار View را تضمین میکند. - توابع و عملیات قطعی (Deterministic): تمام توابع (مانند UDF) و عملیات استفاده شده در View باید قطعی (Deterministic) باشند. به این معنی که با همان ورودی، همیشه خروجی یکسانی را تولید کنند. توابع غیرقطعی مانند
GETDATE()یاNEWID()مجاز نیستند. - توابع تجمیعی: در صورت استفاده از توابع تجمیعی، باید از
COUNT_BIGبه جایCOUNT(*)استفاده شود. همچنینSUMفقط برای عباراتی مجاز است که امکانNULLشدن نداشته باشند.AVG،MAX،MIN،STDEV،VAR،APPROX_COUNT_DISTINCT، وGROUPINGنیز با محدودیتهای خاصی مجازند. - عبارت JOIN: فقط
INNER JOINمجاز است.LEFT OUTER JOIN،RIGHT OUTER JOIN،FULL OUTER JOIN، وCROSS JOINمجاز نیستند. - عبارت FROM: فقط میتواند شامل جداول پایه باشد و نمیتواند شامل Views دیگر (حتی View ایندکس شده دیگر)، توابع جدولی (Table-Valued Functions) یا
OPENROWSETباشد. - عبارت GROUP BY: اگر از توابع تجمیعی استفاده میکنید، عبارت
GROUP BYالزامی است. اگر فقطCOUNT_BIG(*)استفاده شود و هیچ ستون دیگری درSELECTوGROUP BYنباشد،GROUP BYنیز لازم نیست. - عدم وجود عبارات خاص: عباراتی مانند
OUTER JOIN،UNION،TOP،ORDER BY،DISTINCT،COMPUTE/COMPUTE BY،ROWSET،TABLE HINTS،subqueries،full-text predicates،CONTAINS/FREETEXT،MIN/MAX(در صورت استفاده از نوع دادهfloatیاreal) و انواع دادهTEXT،NTEXT،IMAGE،XML،VARBINARY(MAX)،VARCHAR(MAX)،NVARCHAR(MAX)درSELECT،GROUP BY، یاWHERE، مجاز نیستند. همچنینtimestamp،datetime،smalldatetime،datetimeoffset،time،real،floatوsql_variantنمیتوانند به عنوان ستونهای کلیدی در ایندکس کلاستر منحصر به فرد استفاده شوند. - تنظیمات نشست (Session Options): این تنظیمات برای همه نشستهایی که View و ایندکس آن را ایجاد میکنند، باید به صورت زیر باشد:
SET ANSI_NULLS ON; SET ANSI_PADDING ON; SET ANSI_WARNINGS ON; SET ARITHABORT ON; SET CONCAT_NULL_YIELDS_NULL ON; SET QUOTED_IDENTIFIER ON;این تنظیمات تضمین میکنند که نتایج کوئری همیشه به صورت قطعی و قابل پیشبینی محاسبه شوند.
- ایندکس کلاستر منحصر به فرد: اولین ایندکسی که روی View ایندکس شده ایجاد میشود، حتماً باید یک ایندکس کلاستر منحصر به فرد (Unique Clustered Index) باشد. این ایندکس است که باعث ذخیره فیزیکی دادههای View میشود.
- نسخه SQL Server: استفاده خودکار از Views ایندکس شده توسط بهینهساز کوئری (Query Optimizer) نیازمند نسخه Enterprise یا بالاتر SQL Server است. در نسخههای Standard یا Web، برای استفاده از Views ایندکس شده، باید از هینت
WITH (NOEXPAND)در کوئری خود استفاده کنید.
ایجاد یک View ایندکس شده
برای درک بهتر نحوه ایجاد یک View ایندکس شده، گام به گام یک مثال را دنبال میکنیم. این مثال شامل ایجاد جداول پایه، تعریف View و سپس ساخت ایندکس روی آن است.
ابتدا، تنظیمات نشست را همانطور که گفته شد، برقرار میکنیم:
SET ANSI_NULLS ON;
SET QUOTED_IDENTIFIER ON;
GO
سپس، اگر View مورد نظر یا جداول پایه از قبل وجود دارند، آنها را حذف میکنیم تا از تداخل جلوگیری شود:
IF OBJECT_ID('dbo.v_SalesSummary', 'V') IS NOT NULL
DROP VIEW dbo.v_SalesSummary;
GO
IF OBJECT_ID('dbo.Sales', 'U') IS NOT NULL
DROP TABLE dbo.Sales;
GO
IF OBJECT_ID('dbo.Products', 'U') IS NOT NULL
DROP TABLE dbo.Products;
GO
حالا جداول پایه Products و Sales را ایجاد میکنیم:
CREATE TABLE dbo.Products
(
ProductID INT PRIMARY KEY,
ProductName NVARCHAR(100),
Category NVARCHAR(50)
);
GO
CREATE TABLE dbo.Sales
(
SaleID INT PRIMARY KEY,
ProductID INT,
SaleDate DATE,
Quantity INT,
Price DECIMAL(10, 2)
);
GO
در مرحله بعد، کمی داده نمونه را به جداول Products و Sales اضافه میکنیم:
INSERT INTO dbo.Products (ProductID, ProductName, Category) VALUES
(1, 'Laptop', 'Electronics'),
(2, 'Mouse', 'Electronics'),
(3, 'Keyboard', 'Electronics'),
(4, 'Desk', 'Furniture'),
(5, 'Chair', 'Furniture');
GO
INSERT INTO dbo.Sales (SaleID, ProductID, SaleDate, Quantity, Price) VALUES
(1, 1, '2023-01-01', 2, 1200.00),
(2, 2, '2023-01-01', 5, 25.00),
(3, 1, '2023-01-02', 1, 1150.00),
(4, 3, '2023-01-02', 3, 75.00),
(5, 4, '2023-01-03', 1, 300.00),
(6, 2, '2023-01-03', 10, 22.00);
GO
اکنون، View v_SalesSummary را با استفاده از WITH SCHEMABINDING ایجاد میکنیم. این View مجموع فروش هر محصول در هر دسته را محاسبه میکند:
CREATE VIEW dbo.v_SalesSummary
WITH SCHEMABINDING
AS
SELECT
P.Category,
P.ProductName,
SUM(S.Quantity * S.Price) AS TotalSales,
COUNT_BIG(*) AS NumberOfSales
FROM
dbo.Sales AS S
INNER JOIN
dbo.Products AS P ON S.ProductID = P.ProductID
GROUP BY
P.Category,
P.ProductName;
GO
و در نهایت، مهمترین مرحله: ایجاد یک ایندکس کلاستر منحصر به فرد بر روی View. این کار باعث میشود که View ما به یک View ایندکس شده تبدیل شود و دادهها به صورت فیزیکی ذخیره شوند:
CREATE UNIQUE CLUSTERED INDEX IX_v_SalesSummary_Category_ProductName
ON dbo.v_SalesSummary (Category, ProductName);
GO
شما میتوانید ایندکسهای غیرکلاستر اضافی را نیز روی View ایندکس شده خود ایجاد کنید تا عملکرد کوئریهای خاصی را بیشتر بهبود بخشید. به عنوان مثال:
-- Optional: Create a non-clustered index on TotalSales
CREATE NONCLUSTERED INDEX IX_v_SalesSummary_TotalSales
ON dbo.v_SalesSummary (TotalSales DESC);
GO
تایید View ایندکس شده
برای اطمینان از اینکه View شما به درستی ایندکس شده است، میتوانید از چندین روش استفاده کنید:
1. بررسی در sys.views و sys.indexes:
شما میتوانید با کوئری گرفتن از Views سیستمی، وجود ایندکس و ویژگیهای آن را تایید کنید. ستون is_indexed در sys.views نشان میدهد که آیا View ایندکس شده است یا خیر:
SELECT
v.name AS ViewName,
v.object_id,
v.is_indexed
FROM
sys.views AS v
WHERE
v.name = 'v_SalesSummary';
SELECT
i.name AS IndexName,
i.type_desc AS IndexType,
OBJECT_NAME(i.object_id) AS ObjectName
FROM
sys.indexes AS i
WHERE
OBJECT_NAME(i.object_id) = 'v_SalesSummary'
AND i.index_id > 0;
GO
2. بررسی پلن اجرای کوئری (Execution Plan):
مهمترین راه برای اطمینان از استفاده بهینهساز کوئری از View ایندکس شده، بررسی پلن اجرای کوئری است. یک کوئری ساده بر روی View:
SELECT Category, ProductName, TotalSales
FROM dbo.v_SalesSummary
WHERE TotalSales > 1000;
GO
در نسخههای Enterprise، بهینهساز ممکن است به صورت خودکار از View ایندکس شده استفاده کند، حتی اگر شما به طور مستقیم از آن کوئری نگیرید یا جداول پایه را فراخوانی کنید. اما برای اطمینان و در نسخههای Standard یا Web، باید از هینت WITH (NOEXPAND) استفاده کنید تا صراحتاً به SQL Server بگویید که از View ایندکس شده استفاده کند:
SELECT Category, ProductName, TotalSales
FROM dbo.v_SalesSummary WITH (NOEXPAND)
WHERE TotalSales > 1000;
GO
در پلن اجرا، باید یک عملگر Clustered Index Scan یا Clustered Index Seek بر روی ایندکس View ایندکس شده (مثلاً IX_v_SalesSummary_Category_ProductName) مشاهده کنید، به جای اینکه SQL Server به جداول پایه دسترسی پیدا کند.
حذف یک View ایندکس شده
برای حذف یک View ایندکس شده، ابتدا باید تمام ایندکسهای موجود بر روی آن را حذف کنید و سپس خود View را حذف کنید. این ترتیب ضروری است زیرا تا زمانی که ایندکسها وجود دارند، View نمیتواند حذف شود:
IF OBJECT_ID('dbo.v_SalesSummary', 'V') IS NOT NULL
BEGIN
-- Drop non-clustered indexes first, if any
IF EXISTS (SELECT 1 FROM sys.indexes WHERE name = 'IX_v_SalesSummary_TotalSales' AND object_id = OBJECT_ID('dbo.v_SalesSummary'))
DROP INDEX IX_v_SalesSummary_TotalSales ON dbo.v_SalesSummary;
-- Drop the unique clustered index
IF EXISTS (SELECT 1 FROM sys.indexes WHERE name = 'IX_v_SalesSummary_Category_ProductName' AND object_id = OBJECT_ID('dbo.v_SalesSummary'))
DROP INDEX IX_v_SalesSummary_Category_ProductName ON dbo.v_SalesSummary;
-- Then drop the view itself
DROP VIEW dbo.v_SalesSummary;
END
GO
ملاحظات و بهترین شیوهها
در حالی که Views ایندکس شده مزایای عملکردی فوقالعادهای دارند، استفاده از آنها بدون در نظر گرفتن ملاحظات خاص میتواند چالشبرانگیز باشد. رعایت بهترین شیوهها برای بهرهبرداری حداکثری از این قابلیت حیاتی است:
- سربار نگهداری (Maintenance Overhead): هر زمان که دادههای جداول پایه تغییر میکنند (
INSERT،UPDATE،DELETE)، View ایندکس شده نیز باید بهروزرسانی شود. این عملیات سرباری را به تراکنشها اضافه میکند. بنابراین، Views ایندکس شده برای جداولی که تغییرات دادهای کمی دارند یا جداولی که عملیات خواندن (Read) بسیار بیشتر از نوشتن (Write) است، مناسبتر هستند. - تاثیر بر عملکرد نوشتن: اگر جداول پایه حجم بالایی از عملیات
INSERT،UPDATEیاDELETEرا تجربه میکنند، سربار نگهداری View ایندکس شده میتواند عملکرد عملیات نوشتن را کاهش دهد. این یک معاوضه (Trade-off) است: بهبود سرعت خواندن در مقابل کاهش سرعت نوشتن. - استفاده از
WITH (NOEXPAND): همانطور که قبلاً ذکر شد، در نسخههای Standard و Web، بهینهساز کوئری به صورت خودکار از View ایندکس شده استفاده نمیکند. بنابراین، توسعهدهندگان باید به صراحت از هینتWITH (NOEXPAND)در کوئریهای خود استفاده کنند تا از مزایای آن بهرهمند شوند. در نسخههای Enterprise، بهینهساز هوشمندتر است و ممکن است بدون این هینت نیز از View استفاده کند. - کوئرینویسی بر روی View یا جداول پایه: شما میتوانید کوئریهای خود را به طور مستقیم روی View ایندکس شده اجرا کنید یا حتی کوئریهایی را بنویسید که مستقیماً به جداول پایه اشاره دارند اما شامل همان منطق تجمیع و جوین موجود در View باشند. در حالت دوم، بهینهساز میتواند تشخیص دهد که View ایندکس شده میتواند برای پاسخ به آن کوئری مفید باشد.
- طراحی دقیق: به دلیل الزامات سختگیرانه، طراحی View ایندکس شده نیازمند دقت و برنامهریزی است. مطمئن شوید که تمام پیشنیازها را قبل از ایجاد رعایت کردهاید.
نتیجهگیری
View ایندکس شده یک ابزار بسیار قدرتمند در جعبه ابزار DBA و توسعهدهندگان SQL Server است که میتواند به طور چشمگیری عملکرد کوئریها را بهبود بخشد و مصرف منابع را کاهش دهد. با این حال، استفاده از آن مستلزم درک عمیق از پیشنیازها و ملاحظات عملکردی است. با برنامهریزی دقیق و در نظر گرفتن سربار نگهداری، میتوانید از Views ایندکس شده برای بهینهسازی سیستمهای خود به بهترین شکل استفاده کنید، به خصوص در سناریوهایی که گزارشگیریهای پیچیده و دسترسی مکرر به دادههای تجمیع شده نیاز به سرعت بالا دارند. با استفاده صحیح از این قابلیت، میتوانید تجربه کاربری را ارتقا داده و کارایی کلی پایگاه داده خود را به سطوح جدیدی برسانید.