خانه / مقالات / بانک های اطلاعاتی / وقتی بهینه ساز بهینه سازی نمی کند

وقتی بهینه ساز بهینه سازی نمی کند

Index Seek و Index (Table) Scan دو اپراتور بسیار متداول هستند که شما در طرح های اجرایی پرس و جو می بینید. چیزی که معمولا به اشتباه فهمیده می شود این است که اسکن بد است و جستجو خوب. واقعیت این است که هر کدام از این ها تحت شرایط مختلف بهترین هستند. در برخی موارد، بهینه ساز براساس موقعیت موجود و اینکه کدام یک از این دو می توانند مفید واقع شوند، یکی را انتخاب می کند. در اینگونه موارد، غیر از قدردانی از بهینه ساز در انتخاب طرح مطلوب واقعی، دیگر نیاز نیست کار خاصی انجام دهیم. بهرحال، نکته جالب این جاست که افراد تنظیم کننده پرس و جوها باید موارد یا الگوهایی را شناسایی کنند که بهینه ساز در خصوص آنها گزینه مطلوب ویا راه حلی ندارد.
این مقاله با نشان دادن عناصری آغاز می شود که بهینه ساز قادر است براساس خصوصیات داده ها و قابلیت دسترسی به شاخص، برای آن ها استراتژی مطلوبی را انتخاب نماید. سپس در ادامه عناصری را نشان می دهیم که با جایگزین های محدودتر بهینه سازی می شوند و شما باید مطمئن باشید که از راهکاری استفاده می کنید که طرح بهینه و مطلوب را ارائه می دهد.
داده های نمونه
از کد موجود در لیست ۱ برای ساخت داده های نمونه این مقاله استفاده نمائید.

Listing 1: Code to create sample data
SET NOCOUNT ON;
USE tempdb;
GO

IF OBJECT_ID(N›dbo.Orders›   , N›U› ) IS NOT NULL DROP TABLE dbo.Orders;
IF OBJECT_ID(N›dbo.Customers›, N›U› ) IS NOT NULL DROP TABLE dbo.Customers;
IF OBJECT_ID(N›dbo.Employees›, N›U› ) IS NOT NULL DROP TABLE dbo.Employees;
IF OBJECT_ID(N›dbo.GetNums›  , N›IF›) IS NOT NULL DROP FUNCTION dbo.GetNums;
GO

— Definition of GetNums function
CREATE FUNCTION dbo.GetNums(@low AS BIGINT, @high AS BIGINT) RETURNS TABLE
AS
RETURN
WITH
L0   AS (SELECT c FROM (VALUES(1),(1)) AS D(c)),
L1   AS (SELECT 1 AS c FROM L0 AS A CROSS JOIN L0 AS B),
L2   AS (SELECT 1 AS c FROM L1 AS A CROSS JOIN L1 AS B),
L3   AS (SELECT 1 AS c FROM L2 AS A CROSS JOIN L2 AS B),
L4   AS (SELECT 1 AS c FROM L3 AS A CROSS JOIN L3 AS B),
L5   AS (SELECT 1 AS c FROM L4 AS A CROSS JOIN L4 AS B),
Nums AS (SELECT ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) AS rownum
FROM L5)
SELECT TOP(@high – @low + 1) @low + rownum – 1 AS n
FROM Nums
ORDER BY rownum;
GO

— Data distribution settings for orders
DECLARE
@numorders   AS INT      =   ۱۰۰۰۰۰۰,
@numcusts    AS INT      =     ۲۰۰۰۰,
@numemps     AS INT      =       ۱۰۰,
@numshippers AS INT      =         ۵,
@numyears    AS INT      =         ۴,
@startdate   AS DATE     = ‹۲۰۱۱۰۱۰۱›;

— Creating and populating the Customers table
CREATE TABLE dbo.Customers
(
custid   CHAR(11)     NOT NULL,
custname NVARCHAR(50) NOT NULL
);

INSERT INTO dbo.Customers(custid, custname)
SELECT
‹C› + RIGHT(‹۰۰۰۰۰۰۰۰۰› + CAST(T.n * @numcusts + N.n AS VARCHAR(10)), 10) AS custid,
N›Cust_› + CAST(N.n AS VARCHAR(10)) AS custname
FROM dbo.GetNums(1, @numcusts) AS N
CROSS JOIN ( VALUES(0),(1) ) AS T(n);

ALTER TABLE dbo.Customers ADD
CONSTRAINT PK_Customers PRIMARY KEY(custid);

— Creating and populating the Employees table
CREATE TABLE dbo.Employees
(
empid     INT          NOT NULL,
firstname NVARCHAR(25) NOT NULL,
lastname  NVARCHAR(25) NOT NULL
);

INSERT INTO dbo.Employees(empid, firstname, lastname)
SELECT T.n * @numemps + N.n AS empid,
N›Fname_› + CAST(N.n AS NVARCHAR(10)) AS firstname,
N›Lname_› + CAST(N.n AS NVARCHAR(10)) AS lastname
FROM dbo.GetNums(1, @numemps) AS N
CROSS JOIN ( VALUES(0),(1) ) AS T(n);

ALTER TABLE dbo.Employees ADD
CONSTRAINT PK_Employees PRIMARY KEY(empid);

— Creating and populating the Orders table
CREATE TABLE dbo.Orders
(
orderid   INT        NOT NULL,
custid    CHAR(11)   NOT NULL,
empid     INT        NOT NULL,
shipperid VARCHAR(5) NOT NULL,
orderdate DATE       NOT NULL,
filler    CHAR(160)  NOT NULL DEFAULT(‹a›)
);

INSERT INTO dbo.Orders(orderid, custid, empid, shipperid, orderdate)
SELECT n AS orderid,
‹C› + RIGHT(‹۰۰۰۰۰۰۰۰۰›
+ CAST(
۱ + ABS(CHECKSUM(NEWID())) % @numcusts
AS VARCHAR(10)), 10) AS custid,
۱ + ABS(CHECKSUM(NEWID())) % @numemps AS empid,
CHAR(ASCII(‹A›) – ۲
+ ۲ * (۱ + ABS(CHECKSUM(NEWID())) % @numshippers)) AS shipperid,
DATEADD(day, n / (@numorders / (@numyears * 365.25))
— late arrival with earlier date
– CASE WHEN n % 10 = 0
THEN 1 + ABS(CHECKSUM(NEWID())) % 30
ELSE 0
END, @startdate)
AS orderdate
FROM dbo.GetNums(1, @numorders)
ORDER BY CHECKSUM(NEWID())
OPTION(MAXDOP 1);

ALTER TABLE dbo.Orders ADD
CONSTRAINT PK_Orders PRIMARY KEY(orderid);

CREATE INDEX idx_eid_oid_i_od_cid
ON dbo.Orders(empid, orderid DESC)
INCLUDE(orderdate, custid);

CREATE INDEX idx_cid_oid_i_od_eid
ON dbo.Orders(custid, orderid DESC)
INCLUDE(orderdate, empid);
GO

این کد جدولی به نام Orders با ۰۰۰/۰۰۰/۱ سفارش، یک جدول به نام Employees با ۲۰۰ کارمند می سازد که از میان آن ها ۱۰۰ نفر سفارشات را کنترل می کنند و همچنین یک جدول به نام Customers با ۰۰۰/۴۰ مشتری که ۰۰۰/۲۰ نفر از آن ها سفارش می گذارند. این کد چند شاخص در جدول Orders می سازد تا گزینه های بهینه سازی را نشان دهد.

اسکن یا جستجو
تسک هایی وجود دارند که شامل استفاده از منطق در هر گروه هستند و این منطق آزمایش یک یا چند سطر از هر گروه را الزامی می داند. نمونه این نوع تسک ها زیادند. از میان این نمونه ها :
– پیوندهای Semi (وجود) و Anti-Semi (عدم وجود): مثلا، کارمندانی را بازگردان که سفارشات را کنترل می کنند / کنترل نمی کنند، مشتریانی را بازگردان که سفارش می گذارند/ نمی گذارند.
– Grouped Min/Max : مثلا، حداکثر آیدی سفارش را بازای هر کارمند / مشتری برگردان.
– TOP N per group: مثلا، سفارشی که حداکثر آیدی سفارش را  دارد بازای هر کارمند/ مشتری بازگردان.
در راهکارهایی که برای این نوع تسک¬ها استفاده می شوند می توان از چیزی به نام شاخص POC در جدول بزرگ (در مورد مربوط به ما، Orders) بهره مند شد. POC بیانگر عناصر درگیر در پرس و جو است که باید در تعریف شاخص ظاهر شوند. این عناصر Partitioning، Ordering و Coverage هستند. مثلاً در مورد تسک « سفارش (orderid، orderdate، custid، empid) را با حداکثر آیدی سفارش بازای هر کارمند بازگردان»  P=empid، O=ordered DESC و C=oderdate, custid است. عناصر P و O باید لیست اصلی شاخص را بسازند و عنصر C باید لیست INCLUDE را بسازد.  یکی از شاخص هایی که لیست ۱ می سازد تا بهینه سازی یک چنین تسکی را با شاخص POC نشان دهد از این قرار است:

CREATE INDEX idx_eid_oid_i_od_cid
ON dbo.Orders(empid, orderid DESC)
INCLUDE(orderdate, custid);

جالب اینجاست که بسته به چگالی عنصر group/partitioning، استراتژی های مختلف بهینه سازی می توانند برای این کار مناسب باشند.
اگر عنصر Partitioning یا پارتیشن بندی چگال تر باشد (تعداد اندکی از مقادیر مشخص، که هر کدام بارها ظاهر می شوند)، بهترین استراتژی استفاده از Seek یا جستجو در شاخص بازای هر مقدار مشخص است. مثلاً ستون empid در جدول Orders دارای چگالی بالایی است. بنابراین استراتژی مطلوب برای بازگرداندن سفارش با حداکثر آیدی سفارش بازای هر کارمند، اسکن کردن جدول Employees و استفاده از یک حلقه seek یا جستجو در شاخص POC در Orders بازای هر کارمند است.
با وجود ۲۰۰ کارمند در جدول Employee شما ۲۰۰ جستجو خواهید داشت که در کل به چند صدخواندن می رسد.
برعکس، ستون custid چگالی کمی در جدول Orders دارد (۰۰۰/۲۰ مقدار مشخص آیدی مشتری). با وجود ۰۰۰/۴۰ مشتری در جدول customers، استراتژی با روش جستجو می تواند به صد هزار خواندن ختم شود، در حالی که یک اسکن فقط چند هزار خواندن برای شما هزینه برمی دارد.
بطور خلاصه، برای تسک هایی که شامل کار در هر گروه هستند و مستلزم بازدید چند سطری از هرگروه می باشند، برای چگالی کم، اسکن مناسب است و برای چگالی بالا جستجو.
حال با توجه به نمونه های عنوان شده ببینیم که چه زمان بهینه ساز خودش به یک استراتژی مطلوب می رسد و چه زمان به کمی کمک نیاز دارد.
پیوندهای Anti-Semi / Semi
وقتی بهینه ساز پرس و جوها را با پریدیکیت های EXISTS (و متدهای دیگر) بهینه سازی می کند تا پیوندهای Semi و Anti-Semi را کنترل نماید، پس یقیناً قادر است طرح های داوطلب از جمله طرح با یک اسکن و طرح با حلقه ای از جستجوها را در مقابل شاخص در جدول بزرگتر، بررسی نماید.
در حقیقت، استفاده از اسکن بهتر از حلقه جستجوها جواب می دهد. اسکن هر سه الگوریتم پیوند را در خود دارد (حلقه های تودرتو، ادغام و هش). اما نکته این جاست که بهینه ساز بطور بالقوه می تواند استراتژی بهینه و مطلوب را براساس ویژگی ها و مشخصات داده ها انتخاب نماید.
به عنوان مثال دو پرس و جوی Semi Join زیر را در نظر بگیرید:

— High density – seeks, logical reads 4 + 670
SELECT E.empid
FROM dbo.Employees AS E
WHERE EXISTS
(SELECT *
FROM dbo.Orders AS O
WHERE O.empid = E.empid);

— Low density – scan, logical reads 110 + 3481
SELECT C.custid
FROM dbo.Customers AS C
WHERE EXISTS
(SELECT *
FROM dbo.Orders AS O
WHERE O.custid = C.custid);
طرح مربوط به این دو پرس و جو را در شکل ۱ می بینید.
طرح مربوط به اولین پرس و جو با چگالی بالای عنصر پارتیشن بندی، حلقه جستجوها را در مقابل شاخص در Orders به اجرا می گذارد. طرح مربوط به پرس و جوی ثانویه با چگالی کمتر عنصر پارتیشن بندی، یک اسکن را در برابر شاخص در Orders اجرا می کند.
شما همان گزینه پویای بهینه ساز با پیوندهای anti-semi را می بینید، همانند دو پرس و جو زیر:

— High density – seeks, logical reads 4 + 670
SELECT E.empid
FROM dbo.Employees AS E
WHERE NOT EXISTS
(SELECT *
FROM dbo.Orders AS O
WHERE O.empid = E.empid);

— Low density – scan, logical reads 218 + 3481
SELECT C.custid
FROM dbo.Customers AS C
WHERE NOT EXISTS
(SELECT *
FROM dbo.Orders AS O
WHERE O.custid = C.custid);

طرح های مربوط به این پرس و جوها در شکل ۲ آمده است.
مجدداً، در مواردی که چگالی بالاست، طرح از اسکن و در مواردی که چگالی کمتر است از جستجوها استفاده می نماید.
MIN/MAX Grouped
یک تسک با Min/Max گروهبندی شده موردی است که شما در آن بدنبال حداقل یا حداکثر مقدار در هر گروه هستید. طبیعی ترین روش برای مخاطب قرار دادن چنین تسکی، یک پرس و جوی گروهبندی شده ساده است. با داشتن شاخص PO در محل (C در اینجا نامربوط است) بهینه ساز از نظر تئوری می تواند چگالی ستون گروه را ارزیابی کند و استراتژی اسکن را در برابر حلقه های جستجو انتخاب نماید. بهرحال هیچ منطقی برای بهینه ساز جهت اجرای حلقه جستجوها برای یک پرس و جوی گروهبندی شده وجود ندارد. بنابراین صرف نظر از چگالی، این طرح شاخص را اسکن خواهد کرد. اگر چگالی کمی داشته باشید(مثلا اگر به وسیله custid گروهبندی می کنید) این روش خوب است اما اگر چگالی بالایی دارید (مثلاً اگر به وسیله empid گروهبندی می کنید) روش خوبی نیست.
برای نشان دادن یک مورد نه چندان مطلوب با چگالی بالا، پرس و جوی زیر را در نظر بگیرید:
— logical reads 3474
SELECT empid, MAX(orderid) AS maxoid
FROM dbo.Orders
GROUP BY empid;
طرح مربوط به این پرس و جو به صورت اولین طرح در شکل ۳ ظاهر می شود.
اگر می خواهید طرحی را دریافت کنید که برای هر کارمند از یک جستجو استفاده می کند، باید راهکار خود را بازنویسی کنید. یک راه برای انجام چنین کاری، استفاده از اپراتور CROSS Apply است، مانند این:

— logical reads 4 + 670
SELECT E.empid, O.orderid
FROM dbo.Employees AS E
CROSS APPLY (SELECT TOP (1) O.orderid
FROM dbo.Orders AS O
WHERE O.empid = E.empid) AS O;
طرح مربوط به این پرس و جو به صورت طرح ثانویه در شکل ۳ نشان داده شده است.
همانطور که می بینید این طرح مطلوب است. اگر تعجب کرده اید که چرا از اپراتور CROSS Apply استفاده شده و نه فقط از یک subquery وابسته اسکالر، به خاطر داشته باشید که CROSS Apply سطرهای سمت چپ را که فاقد هماهنگی هستند حذف می کند و در این مورد، رفتار مطلوبی محسوب می شود.

TOP N Per Group
تسک TOP N per group(مثلاً آخرین سفارش هر مشتری/کارمند) یکی دیگر از تسک هایی است که در آن راهکارهای معمولی، یک طرح نسبتاً ایستا دریافت می کنند؛ حداقل در موردی که اسکن در مقابل حلقه جستجوها قرار می گیرد. دو راهکار معمولی شامل این ها هستند:
۱-محاسبه تعداد سطرها و فیلتر کردن سطرها در جاییکه تعداد سطرها برابر ۱ است. این راهکار همیشه یک اسکن دریافت می کند.
۲- پرس و جوی جدولی که گروه ها / پارتیشن بندی ها را نشان می دهد و استفاده از اپراتور CROSS Apply جهت استفاده از پرس و جوی TOP(1) در مقابل جدول بزرگتر. این راهکار حلقه جستجوها را دریافت می کند.
بنابراین برای بازگرداندن آخرین سفارش هر مشتری(چگالی کم) باید از این راهکار با تابع Row_NUMBER استفاده کنید. برای بازگرداندن آخرین سفارش هر کارمند(چگالی بالا) باید از این راهکار با اپراتور CROSS Apply استفاده نمائید. هر دو مثال اینجا هستند:

— To get a scan use ROW_NUMBER (low density), logical reads 3481
WITH C AS
(
SELECT ROW_NUMBER() OVER(PARTITION BY custid ORDER BY orderid DESC) AS n,
*
FROM dbo.Orders
)
SELECT custid, orderdate, orderid, empid
FROM C
WHERE n <= 1;

— To get seeks use APPLY (high density), logical reads 4 + 670
SELECT E.empid, O.orderdate, O.orderid, O.custid
FROM dbo.Employees AS E
CROSS APPLY (SELECT TOP (1) *
FROM dbo.Orders AS O
WHERE O.empid = E.empid
ORDER BY orderid DESC) AS O;
طرح مربوط به این پرس و جوها در شکل ۴ آمده است.

Proxy CROSS APPLY
مورد مربوط به آخرین مثال توسط SQL Server MVP Erland Sommarskog در یک گروه خصوصی معرفی شد. فرض کنید که لازم است به لیست SELECT آخرین پرس و جو، ستون¬هایی را از جدول Orders بیافزائید که توسط شاخص پوشش داده نمی شوند. مثلاً پرس و جویی زیر ستونی به نام filler را به لیست SELECT می افزاید:
— logical reads 2445833
SELECT E.empid, O.orderdate, O.orderid, O.custid, O.filler
FROM dbo.Employees AS E
CROSS APPLY (SELECT TOP (1) *
FROM dbo.Orders AS O
WHERE O.empid = E.empid
ORDER BY orderid DESC) AS O;

این تغییر کوچک اثرات جدی بر اجرای پرس و جو دارد. بدون ستون اضافی، پرس و جو تنها چند صد خواندن را انجام داده و در ۷۷ هزارم ثانیه در سیستم به پایان می رسد.
با ستون اضافه، پرس و جو حدود ۵/۲ میلیون خواندن را انجام می دهد و ۸ ثانیه طول می کشد تا کامل شود. احتمالاً فکر می کنید که طرح مطلوب برای پرس و جوی جدید باید این کارها را بازای هر کارمند انجام دهد:
۱- یک جستجو در شاخص کلاستر نشده idx_eid_oid_i_od_cide جهت جمع آوری کلید کلاسترینگ.
۲- استفاده از یک جستجوی کلیدی(جستجو در شاخص کلاستر شده) جهت جمع آوری ستون filler از سطر داده ای مربوطه.
اما اگر طرح مربوط به پرس و جوی شکل ۵ را آزمایش کنید، می بینید که بهینه ساز استراتژی متفاوتی را بر می گزیند.
طرح به جای اینکه روش دو- جستجو(جستجو در میان کلاستر نشده ها و سپس جستجو در کلاستر شده ها) را بازای هر کارمند انجام دهد، (برای هر کارمند) یک اسکن سفارش شده از شاخص کلاستر شده انجام می دهد که توسط اپراتور TOP به محض اینکه سطر پیدا شد، انجام می گیرد. بخاطر داشته باشید که کلید شاخص کلاسترشده، ستون orderid است. به محض اینکه اولین سطر با مقدار empid خارجی یافت می شود، اسکن متوقف شده و عناصر سفارش از آن سطر بازگردانده می شوند. این استراتژی شاید عجیب بنظر برسد اما بهتر یا بدتر(در مورد ما بدتر) بستگی به بهینه سازی و مدل تخمین کاردینالیتی دارد، تا حدی هم منطق پشت این روش نهفته است.
مدل بهینه سازی از فرضی به نام Containment  و inclusion استفاده می کند. Containment فرض را بر این می گذارد که آنچه را که بدنبال آن هستید، واقعا وجود دارد. در یک مورد پیوندسازی (equijoin) فرض بر این است که مقادیر مشخص ستون پیوند در یک طرف، در طرف دیگر هم وجود دارند. فرض inclusion هم به همین صورت است.به پریدیکیت residual در اپراتور Clustered ¬Index Scan توجه کنید: O.empid =E.empid . براساس فرضیه های این مدل اگر آیدی کارمندی که اسکن بدنبال آن است حقیقتاً وجود داشته باشد، از نظر آماری، در هر صد سطر، یک سطر سطرمتناظر خواهد بود (چگالی Orders.empid ، ۰۱/۰ است). پس، از نظر آماری پیدا شدن سطر متناظر، فقط باندازه خواندن چند صفحه طول می کشد. اگر فرضیه های این مدل صحیح باشند، این استراتژی بهتر از استراتژی دوبار جستجو خواهد بود. بهرحال در مورد ما، فرضیه های مدل با شکست مواجه شد، چرا که ما ۲۰۰ کارمند در جدول Employees داریم که ۱۰۰ نفر از آن ها سفارش های متناظر ندارند. پس بعد از ۱۰۰ بار اسکن تمام شد. در نتیجه، عملکرد این راهکار بسیار ضعیف است.
در اینجا راهکارهای دیگری هم وجود دارند. اگر بتوان شاخص را گسترش داد تا شامل ستون های از دست رفته (در مورد مربوط به ما filler) باشد این گزینه بهترین گزینه خواهد بود. شما طرحی شبیه طرح دوم در شکل ۴ دریافت می کنید. اگر این گزینه را نداشتید، راهکار دیگر مشخص کردن یک hint شاخص در برابر جدول Orders است:
WITH (INDEX(idx_eid_oid_i_od_cid). شما استراتژی double-Seek یا دو جستجو را دریافت می کنید. اگر ترجیح می دهید از hint استفاده نکنید، گزینه دیگری که وجود دارد، استفاده از چیزی است که ما آن را روش proxy CROSS APPLY می نامیم. با این روش شما از دو اپراتور CROSS APPLY استفاده می کنید. اپراتور میانی فقط به این دلیل وجود دارد تا صلاحیت آیدی سفارش را برای کارمند فعلی با پرس و جوی TOP(1) محاسبه کند، اما هیچ کدام از عناصر آن را به پرس و جوی خارجی باز نمی گرداند. اپراتور داخلی CROSS APPLY را فرا می خواند وآیدی سفارش تائید صلاحیت شده را به پرس و جوی داخلی می فرستد و سپس پرس و جوی داخلی سطر مربوطه را از جدول Orders باز می گرداند. پرس و جوی proxy CROSS APPLY سطر را از پرس و جوی داخلی CROSS APPLY به پرس و جوی خارجی باز می گرداند و پرس و جوی خارجی در عوض عناصر مطلوب سفارش را باز می گرداند. اگر خیلی متوجه این توضیحات نشدید، به پرس و جوی زیر توجه کنید و دوباره توضیحات را بخوانید:

— Proxy CROSS APPLY, logical reads 4 + 922
SELECT E.empid, P.orderdate, P.orderid, P.custid, P.filler
FROM dbo.Employees AS E
CROSS APPLY (SELECT TOP (1) O.*
FROM dbo.Orders AS P
CROSS APPLY
(SELECT O.*
FROM dbo.Orders AS O
WHERE O.orderid = P.orderid) AS O
WHERE P.empid = E.empid
ORDER BY orderid DESC) AS P;

طرح مربوط به این پرس و جو در مشکل ۶ آمده است.
همانطورکه می بینید این بار طراح از استراتژی double-seek یا دو جستجو به جای اسکن استفاده می کند و تنها چند صد خواندن را به کار می گیرد و در ۹۰هزارم ثانیه پایان می پذیرد.
در آخر، کد زیر را برای پاک کردن اجرا کنید:

USE tempdb;
IF OBJECT_ID(N›dbo.Orders›   , N›U› ) IS NOT NULL DROP TABLE dbo.Orders;
IF OBJECT_ID(N›dbo.Customers›, N›U› ) IS NOT NULL DROP TABLE dbo.Customers;
IF OBJECT_ID(N›dbo.Employees›, N›U› ) IS NOT NULL DROP TABLE dbo.Employees;
IF OBJECT_ID(N›dbo.GetNums›  , N›IF›) IS NOT NULL DROP FUNCTION dbo.GetNums;

نتیجه
جستجو یا اسکن مسئله این است. این مقاله نشان می دهد که در برخی موارد بهینه ساز خودش گزینه مطلوب را می فهمد. مثلاً وقتی از پریدیکیت Exists برای کنترل پیوندهای semi و anti-semi استفاده می کند. بهرحال، در برخی موارد مانند مشکلات top N per group و grouped MIN/MAX aggregate ، شما باید یک پرس و جوی مخصوص بنویسید تا طراحی مناسب را دریافت نمائید. در برخی مورد بهینه ساز ممکن است در نهایت یک استراتژی نه چندان مطلوب را انتخاب کند و کاری که شما میتوانید انجام دهید آن است که مشکل را مخاطب قرار داده و به آن بپردازید.

دیدگاهتان را ثبت کنید

آدرس ایمیل شما منتشر نخواهد شدعلامتدارها لازمند *

*

x

شاید بپسندید

ماهنامه رایانه SQL

انتقال داده بین SQL Server 2014 و دیتابیس‌های اوراکل ۱۱g

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