আমার সিলেক্ট ডিসটিন্ট টপ এন কোয়েরি পুরো টেবিলটি স্ক্যান করে কেন?


28

আমি কয়েকটি SELECT DISTINCT TOP Nক্যোয়ারী চালিয়েছি যা এসকিউএল সার্ভারের ক্যোয়ারী অপ্টিমাইজারের দ্বারা দুর্বলতর হিসাবে প্রত্যাশিত। আসুন একটি তুচ্ছ উদাহরণ বিবেচনা করে শুরু করুন: দুটি বিকল্প মানের একটি মিলিয়ন সারি টেবিল। আমি ডেটা উত্পন্ন করতে getNums ফাংশনটি ব্যবহার করব :

DROP TABLE IF EXISTS X_2_DISTINCT_VALUES;

CREATE TABLE X_2_DISTINCT_VALUES (PK INT IDENTITY (1, 1), VAL INT NOT NULL);

INSERT INTO X_2_DISTINCT_VALUES WITH (TABLOCK) (VAL)
SELECT N % 2
FROM dbo.GetNums(1000000);

UPDATE STATISTICS X_2_DISTINCT_VALUES WITH FULLSCAN;

নিম্নলিখিত প্রশ্নের জন্য:

SELECT DISTINCT TOP 2 VAL
FROM X_2_DISTINCT_VALUES
OPTION (MAXDOP 1);

এসকিউএল সার্ভার কেবল সারণীর প্রথম ডেটা পৃষ্ঠা স্ক্যান করে দুটি স্বতন্ত্র মান খুঁজে পেতে পারে তবে এটি পরিবর্তে সমস্ত ডেটা স্ক্যান করে । এসকিউএল সার্ভার কেন কেবলমাত্র পৃথক মানের জন্য অনুরোধ করা নম্বর না পাওয়া পর্যন্ত স্ক্যান করে না?

এই প্রশ্নের জন্য দয়া করে নিম্নলিখিত পরীক্ষার ডেটা ব্যবহার করুন যা ব্লকগুলিতে উত্পন্ন 10 স্বতন্ত্র মানগুলির সাথে 10 মিলিয়ন সারি ধারণ করে:

DROP TABLE IF EXISTS X_10_DISTINCT_HEAP;

CREATE TABLE X_10_DISTINCT_HEAP (VAL VARCHAR(10) NOT NULL);

INSERT INTO X_10_DISTINCT_HEAP WITH (TABLOCK)
SELECT REPLICATE(CHAR(65 + (N / 100000 ) % 10 ), 10)
FROM dbo.GetNums(10000000);

UPDATE STATISTICS X_10_DISTINCT_HEAP WITH FULLSCAN;

একটি ক্লাস্টার্ড সূচক সহ একটি টেবিলের উত্তরগুলিও গ্রহণযোগ্য:

DROP TABLE IF EXISTS X_10_DISTINCT_CI;

CREATE TABLE X_10_DISTINCT_CI (PK INT IDENTITY (1, 1), VAL VARCHAR(10) NOT NULL, PRIMARY KEY (PK));

INSERT INTO X_10_DISTINCT_CI WITH (TABLOCK) (VAL)
SELECT REPLICATE(CHAR(65 + (N / 100000 ) % 10 ), 10)
FROM dbo.GetNums(10000000);

UPDATE STATISTICS X_10_DISTINCT_CI WITH FULLSCAN;

নিম্নলিখিত কোয়েরিটি সারণী থেকে সমস্ত 10 মিলিয়ন সারি স্ক্যান করে । আমি কীভাবে এমন কিছু পেতে পারি যা পুরো টেবিলটি স্ক্যান করে না? আমি এসকিউএল সার্ভার 2016 এসপি 1 ব্যবহার করছি।

SELECT DISTINCT TOP 10 VAL
FROM X_10_DISTINCT_HEAP
OPTION (MAXDOP 1);

উত্তর:


30

DISTINCTউপরোক্ত ক্যোয়ারিতে অপারেশন সম্পাদন করতে পারে এমন তিনটি পৃথক অপ্টিমাইজার বিধি রয়েছে look নিম্নলিখিত কোয়েরিতে একটি ত্রুটি ছুঁড়েছে যা পরামর্শ দেয় যে তালিকাটি সম্পূর্ণরূপে:

SELECT DISTINCT TOP 10 ID
FROM X_10_DISTINCT_HEAP
OPTION (MAXDOP 1, QUERYRULEOFF GbAggToSort, QUERYRULEOFF GbAggToHS, QUERYRULEOFF GbAggToStrm);

এমএসজি 8622, স্তর 16, রাজ্য 1, লাইন 1

এই কোয়েরিতে সংজ্ঞায়িত ইঙ্গিতগুলির কারণে ক্যোয়ারী প্রসেসর কোনও ক্যোয়ারী প্ল্যান তৈরি করতে পারেনি। কোনও ইঙ্গিত নির্দিষ্ট না করে এবং SET FORCEPLAN ব্যবহার না করে কোয়েরিটি পুনরায় জমা দিন।

GbAggToSortগোষ্ঠী দ্বারা সমষ্টিগত (স্বতন্ত্র) একটি স্বতন্ত্র সাজানোর হিসাবে প্রয়োগ করে। এটি একটি ব্লকিং অপারেটর যা কোনও সারি তৈরি করার আগে ইনপুট থেকে সমস্ত ডেটা পড়বে। GbAggToStrmগ্রুপ-বাই সমষ্টিটি স্ট্রিম সমষ্টি হিসাবে প্রয়োগ করে (যার জন্য এই উদাহরণে একটি ইনপুট সাজানোরও প্রয়োজন)। এটি একটি ব্লকিং অপারেটরও। GbAggToHSহ্যাশ ম্যাচ হিসাবে প্রয়োগ করে, যা আমরা প্রশ্ন থেকে খারাপ পরিকল্পনায় দেখেছি, তবে এটি হ্যাশ ম্যাচ (সমষ্টিগত) বা হ্যাশ ম্যাচ (প্রবাহ স্বতন্ত্র) হিসাবে প্রয়োগ করা যেতে পারে।

হ্যাশ ম্যাচ ( প্রবাহ পৃথক ) অপারেটর এই সমস্যাটি সমাধানের এক উপায় কারণ এটি অবরুদ্ধ নয়। এসকিউএল সার্ভারটি পর্যাপ্ত স্বতন্ত্র মানগুলি খুঁজে পেয়ে স্ক্যানটি থামাতে সক্ষম হওয়া উচিত।

ফ্লো ডিস্টিন্ট লজিকাল অপারেটরটি ডুপ্লিকেটগুলি সরিয়ে ইনপুটটিকে স্ক্যান করে। যেহেতু ডিস্টিন্ট অপারেটর কোনও আউটপুট উত্পাদনের আগে সমস্ত ইনপুট গ্রাস করে, ফ্লো ডিসটিনিট অপারেটর প্রতিটি সারিটি ইনপুট থেকে প্রাপ্ত হওয়ার সাথে সাথে ফেরত দেয় (যদি না যে সারিটি সদৃশ হয়, তবে এটি বাতিল করা হয়)।

কেন প্রশ্নটিতে প্রশ্নটি হ্যাশ ম্যাচের পরিবর্তে হ্যাশ মিলাকে (সমষ্টিগত) ব্যবহার করে (পৃথক প্রবাহকে পৃথক করে)? সারণীতে স্বতন্ত্র মানের সংখ্যা পরিবর্তনের সাথে সাথে আমি হ্যাশ ম্যাচের ব্যয় (প্রবাহ স্বতন্ত্র) কোয়েরি কমতে আশা করব কারণ এটির টেবিলে স্ক্যান করার জন্য সারিগুলির সংখ্যার অনুমান হ্রাস হওয়া উচিত। আমি আশা করব হ্যাশ ম্যাচের (সমষ্টিগত) পরিকল্পনার ব্যয় আরও বাড়বে কারণ হ্যাশ টেবিলটি যেটি তৈরি করতে হবে তা আরও বড় হবে। এটি তদন্ত করার একটি উপায় হ'ল পরিকল্পনা গাইড তৈরি করা । যদি আমি ডেটার দুটি অনুলিপি তৈরি করি তবে সেগুলির একটিতে একটি পরিকল্পনা গাইড প্রয়োগ করি তবে একই তথ্যের বিপরীতে আমি হ্যাশ ম্যাচ (সমষ্টি) সাথে হ্যাশ ম্যাচ (স্বতন্ত্র) পাশাপাশি তুলনা করতে সক্ষম হব। মনে রাখবেন যে কোয়েরি অপ্টিমাইজার নিয়মগুলি অক্ষম করে আমি এটি করতে পারি না কারণ একই নিয়ম উভয় পরিকল্পনার ( GbAggToHS) উভয় ক্ষেত্রেই প্রযোজ্য ।

পরিকল্পনার গাইড পাওয়ার একটি উপায় যা আমি পরে করছি:

DROP TABLE IF EXISTS X_PLAN_GUIDE_TARGET;

CREATE TABLE X_PLAN_GUIDE_TARGET (VAL VARCHAR(10) NOT NULL);

INSERT INTO X_PLAN_GUIDE_TARGET WITH (TABLOCK)
SELECT CAST(N % 10000 AS VARCHAR(10))
FROM dbo.GetNums(10000000);

UPDATE STATISTICS X_PLAN_GUIDE_TARGET WITH FULLSCAN;

-- run this query
SELECT DISTINCT TOP 10 VAL  FROM X_PLAN_GUIDE_TARGET  OPTION (MAXDOP 1)

পরিকল্পনার হ্যান্ডেল পান এবং একটি পরিকল্পনা গাইড তৈরি করতে এটি ব্যবহার করুন:

-- plan handle is 0x060007009014BC025097E88F6C01000001000000000000000000000000000000000000000000000000000000
SELECT qs.plan_handle, st.text FROM 
sys.dm_exec_query_stats AS qs   
CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) AS st  
WHERE st.text LIKE '%X[_]PLAN[_]GUIDE[_]TARGET%'
ORDER BY last_execution_time DESC;

EXEC sp_create_plan_guide_from_handle 
'EVIL_PLAN_GUIDE', 
0x060007009014BC025097E88F6C01000001000000000000000000000000000000000000000000000000000000;

পরিকল্পনার গাইডগুলি কেবল সঠিক ক্যোয়ারী পাঠ্যে কাজ করে, তাই আসুন পরিকল্পনার গাইড থেকে এটি আবার অনুলিপি করুন:

SELECT query_text
FROM sys.plan_guides
WHERE name = 'EVIL_PLAN_GUIDE';

ডেটা পুনরায় সেট করুন:

TRUNCATE TABLE X_PLAN_GUIDE_TARGET;

INSERT INTO X_PLAN_GUIDE_TARGET WITH (TABLOCK)
SELECT REPLICATE(CHAR(65 + (N / 100000 ) % 10 ), 10)
FROM dbo.GetNums(10000000);

প্রয়োগ করা পরিকল্পনার গাইডটির সাহায্যে ক্যোয়ারির জন্য একটি ক্যোয়ারী পরিকল্পনা পান :

SELECT DISTINCT TOP 10 VAL  FROM X_PLAN_GUIDE_TARGET  OPTION (MAXDOP 1)

এতে হ্যাশ ম্যাচ (প্রবাহ স্বতন্ত্র) অপারেটর রয়েছে যা আমরা আমাদের পরীক্ষার ডেটা দিয়ে চেয়েছিলাম। নোট করুন যে এসকিউএল সার্ভারটি সারণী থেকে সমস্ত সারি পড়ার প্রত্যাশা করে এবং হ্যাশ ম্যাচের (সমষ্টিগত) পরিকল্পনার জন্য আনুমানিক ব্যয় হুবহু একই। আমি যে পরীক্ষার পরামর্শ দিয়েছিলাম যে দুটি পরিকল্পনার জন্য ব্যয় একইরকম হবে যখন এসকিউএল সার্ভার টেবিল থেকে প্রত্যাশিত স্বতন্ত্র মানের সংখ্যার চেয়ে বড় বা সমান হয় তখন এই পরিকল্পনার জন্য ব্যয়গুলি অভিন্ন হয় this পরিসংখ্যান। দুর্ভাগ্যক্রমে (আমাদের ক্যোয়ারির জন্য) যখন খরচগুলি একই হয় তখন অপটিমাইজার হ্যাশ ম্যাচটিকে (সমষ্টিগত) ওভার হ্যাশ ম্যাচটিকে (প্রবাহ স্বতন্ত্র) বাছাই করে। সুতরাং আমরা 0.0000001 যাদু অপ্টিমাইজার ইউনিট যে পরিকল্পনাটি আমরা চাই তা থেকে দূরে।

এই সমস্যার আক্রমণ করার একটি উপায় হ'ল সারি লক্ষ্য হ্রাস করা। অপ্টিমাইজারের দৃষ্টিকোণ থেকে সারি লক্ষ্য যদি সারিগুলির স্বতন্ত্র গণনা থেকে কম হয় তবে আমরা সম্ভবত হ্যাশ ম্যাচটি পেয়ে যাব (স্বতন্ত্র প্রবাহের)। এটি OPTIMIZE FORক্যোয়ারী ইঙ্গিত দিয়ে সম্পন্ন করা যেতে পারে :

DECLARE @j INT = 10;

SELECT DISTINCT TOP (@j) VAL
FROM X_10_DISTINCT_HEAP
OPTION (MAXDOP 1, OPTIMIZE FOR (@j = 1));

এই ক্যোয়ারির জন্য অপ্টিমাইজার একটি পরিকল্পনা তৈরি করে যেন কোয়েরিতে কেবল প্রথম সারির প্রয়োজন হয় তবে যখন কোয়েরিটি কার্যকর করা হয় তখন এটি প্রথম 10 টি সারি ফিরে পায়। আমার মেশিনে এই কোয়েরিটি 892800 টি সারিগুলি স্ক্যান করে X_10_DISTINCT_HEAPএবং 299 এমএসে সিপিইউয়ের 250 এমএস এবং 2537 লজিক্যাল রিড সহ সম্পূর্ণ হয়।

নোট করুন যে পরিসংখ্যানগুলি কেবলমাত্র একটি স্বতন্ত্র মান রিপোর্ট করে, যা স্কিউড ডেটার বিরুদ্ধে নমুনাযুক্ত পরিসংখ্যানগুলির জন্য ঘটতে পারে যদি এই কৌশলটি কাজ করবে না। যাইহোক, সেক্ষেত্রে আপনার ডেটা এই জাতীয় কৌশলগুলি ব্যবহার করে ন্যায়সঙ্গত করার জন্য যথেষ্ট পরিমাণে প্যাক করা সম্ভব নয়। আপনি টেবিলের সমস্ত ডেটা স্ক্যান করে খুব বেশি হারাতে পারেন না, বিশেষত যদি এটি সমান্তরালে করা যায়।

এই সমস্যাটিতে আক্রমণের আরেকটি উপায় হ'ল এসকিউএল সার্ভার বেস টেবিল থেকে প্রত্যাশিত স্বতন্ত্র মানগুলির সংখ্যার স্ফীত করে। এটি প্রত্যাশার চেয়ে কঠিন ছিল। একটি নির্বিচারক ফাংশন প্রয়োগ করা ফলাফলের স্বতন্ত্র গণনা সম্ভবত বাড়িয়ে দিতে পারে না। যদি ক্যোয়ারী অপ্টিমাইজার সেই গাণিতিক সত্য সম্পর্কে সচেতন থাকে (কিছু পরীক্ষার পরামর্শ দেয় এটি কমপক্ষে আমাদের উদ্দেশ্যগুলির জন্য) তবে ডিটারমিনিস্টিক ফাংশনগুলি প্রয়োগ করা ( এতে সমস্ত স্ট্রিং ফাংশন অন্তর্ভুক্ত থাকে ) স্বতন্ত্র সারিগুলির আনুমানিক সংখ্যা বাড়বে না।

Nondeterministic ফাংশন অনেক পারেন কাজ না করে, এর সুস্পষ্ট পছন্দ সহ NEWID()এবং RAND()। যাইহোক, LAG()এই ক্যোয়ারির জন্য কৌশলটি করে। ক্যোয়ারী অপ্টিমাইজারটি LAGএক্সপ্রেশনটির বিরুদ্ধে 10 মিলিয়ন স্বতন্ত্র মান প্রত্যাশা করে যা একটি হ্যাশ মিলাকে (প্রবাহের স্বতন্ত্র) পরিকল্পনাকে উত্সাহিত করবে :

SELECT DISTINCT TOP 10 LAG(VAL, 0) OVER (ORDER BY (SELECT NULL)) AS ID
FROM X_10_DISTINCT_HEAP
OPTION (MAXDOP 1);

আমার মেশিনে এই ক্যোয়ারীটি সিপিইউয়ের X_10_DISTINCT_HEAP1109 এমএস এবং 2537 লজিক্যাল রিডের সাথে 1165 এমএস থেকে 892800 টি সারি স্ক্যান করে এবং সমাপ্ত LAG()ওভারহেডের বেশ কিছুটা যোগ করে adds @ পল হোয়াইট এই প্রশ্নের জন্য ব্যাচ মোড প্রসেসিংয়ের চেষ্টা করার পরামর্শ দিয়েছেন suggested এসকিউএল সার্ভার ২০১ 2016-তে আমরা এমনকি সাথে ব্যাচ মোড প্রসেসিংও পেতে পারি MAXDOP 1। রোস্ট স্টোর টেবিলের জন্য ব্যাচ মোড প্রসেসিংয়ের একটি উপায় হ'ল নিম্নরূপ একটি খালি সিসিআইতে যোগদান করা:

CREATE TABLE #X_DUMMY_CCI (ID INT NOT NULL);

CREATE CLUSTERED COLUMNSTORE INDEX X_DUMMY_CCI ON #X_DUMMY_CCI;

SELECT DISTINCT TOP 10 VAL
FROM
(
    SELECT LAG(VAL, 1) OVER (ORDER BY (SELECT NULL)) AS VAL
    FROM X_10_DISTINCT_HEAP
    LEFT OUTER JOIN #X_DUMMY_CCI ON 1 = 0
) t
WHERE t.VAL IS NOT NULL
OPTION (MAXDOP 1);

কোডটি এই ক্যোয়ারী পরিকল্পনায় ফলাফল দেয় ।

পল উল্লেখ করেছিলেন যে আমাকে জিজ্ঞাসাটি ব্যবহার করতে হবে LAG(..., 1)কারণ LAG(..., 0)উইন্ডো সমষ্টিগত অপ্টিমাইজেশনের জন্য উপযুক্ত বলে মনে হচ্ছে না। এই পরিবর্তনটি অতিবাহিত সময়কে 520 এমএস এবং সিপিইউ সময়কে 454 এমএসে হ্রাস করেছে।

নোট করুন যে LAG()পদ্ধতিটি সবচেয়ে স্থিতিশীল নয়। মাইক্রোসফ্ট যদি ফাংশনের বিরুদ্ধে স্বতন্ত্র ধারণা অনুমান করে তবে এটি আর কাজ করতে পারে না। সিই উত্তরাধিকার নিয়ে এটির একটি আলাদা অনুমান রয়েছে। এছাড়াও এই স্তরের বিরুদ্ধে এই ধরণের অপ্টিমাইজেশান একটি ভাল ধারণা প্রয়োজনীয় নয়। যদি টেবিলটি পুনর্নির্মাণ করা হয় তবে সবচেয়ে খারাপ পরিস্থিতিতে এটি শেষ হওয়া সম্ভব যেখানে প্রায় সারিগুলির সমস্ত সারণী থেকে পড়তে হবে।

একটি অনন্য কলামযুক্ত টেবিলের বিপরীতে (যেমন প্রশ্নে ক্লাস্টারড সূচক উদাহরণ) আমাদের আরও ভাল বিকল্প রয়েছে। উদাহরণস্বরূপ আমরা SUBSTRINGসর্বদা একটি খালি স্ট্রিং ফিরিয়ে দেয় এমন একটি এক্সপ্রেশন ব্যবহার করে অপ্টিমাইজারটিকে চালিত করতে পারি । এসকিউএল সার্ভার মনে করে না যে SUBSTRINGস্বতন্ত্র মানগুলির সংখ্যার পরিবর্তন হবে তাই আমরা যদি এটি কোনও অনন্য কলামে পিকে হিসাবে প্রয়োগ করি তবে স্বতন্ত্র সারির আনুমানিক সংখ্যা 10 মিলিয়ন। এই নিম্নলিখিত কোয়েরিতে হ্যাশ ম্যাচটি (প্রবাহের স্বতন্ত্র) অপারেটরটি পান:

SELECT DISTINCT TOP 10 VAL + SUBSTRING(CAST(PK AS VARCHAR(10)), 11, 1)
FROM X_10_DISTINCT_CI
OPTION (MAXDOP 1);

আমার মেশিনে এই ক্যোয়ারী 900000 সারিগুলি স্ক্যান করে এবং সিপিইউয়ের X_10_DISTINCT_CI297 এমএস এবং 3011 লজিক্যাল রিড সহ 333 এমএসে সম্পূর্ণ হয়।

সংক্ষেপে, ক্যোয়ারী অপ্টিমাইজারটি ধরে নিয়েছে যে সমস্ত সারিগুলি SELECT DISTINCT TOP Nপ্রশ্নের জন্য টেবিল থেকে পড়বে যখন N> = সারণী থেকে অনুমানযুক্ত স্বতন্ত্র সারিগুলির সংখ্যা। হ্যাশ ম্যাচ (সমষ্টিগত) অপারেটরের হ্যাশ ম্যাচ (প্রবাহের স্বতন্ত্র) অপারেটরের মতো একই দাম থাকতে পারে তবে অপটিমাইজার সর্বদা সমষ্টিগত অপারেটরটিকে বাছাই করে। টেবিল স্ক্যান শুরুর নিকটে পর্যাপ্ত স্বতন্ত্র মানগুলি অবস্থিত হলে এটি অপ্রয়োজনীয় লজিক্যাল রিডের দিকে পরিচালিত করতে পারে। হ্যাশ ম্যাচটি (প্রবাহের স্বতন্ত্র) অপারেটরটি ব্যবহার করে অপ্টিমাইজারকে ঠকানোর দুটি উপায় OPTIMIZE FORহিন্ট ব্যবহার করে সারি লক্ষ্যটি হ্রাস করা LAG()বা স্বতন্ত্র সারিগুলির আনুমানিক সংখ্যাটি ব্যবহার করে বা SUBSTRINGঅনন্য কলামে ব্যবহার করা ।


12

আপনি ইতিমধ্যে আপনার নিজের প্রশ্নের উত্তর দিয়েছেন।

আমি কেবল একটি পর্যবেক্ষণ যোগ করতে চাই যে সবচেয়ে কার্যকর উপায়টি হ'ল পুরো টেবিলটি স্ক্যান করা - যদি এটি একটি কলাম স্টোর 'হিপ' হিসাবে সংগঠিত করা যায় :

CREATE CLUSTERED COLUMNSTORE INDEX CCSI 
ON dbo.X_10_DISTINCT_HEAP;

সাধারণ প্রশ্ন:

SELECT DISTINCT TOP (10)
    XDH.VAL 
FROM dbo.X_10_DISTINCT_HEAP AS XDH
OPTION (MAXDOP 1);

তারপরে:

হত্যা পরিকল্পনা

সারণী 'X_10_DISTINCT_HEAP'। স্ক্যান কাউন্ট 1,
 লজিকাল 0, রিডিকাল 0, রিড-ফরোয়ার্ড 0, 
 লব লজিকাল reads 66 , লব ফিজিকাল 0, লব রিড-ফরোয়ার্ড 0 পড়ছে reads
সারণী 'X_10_DISTINCT_HEAP'। বিভাগটি 13 টি পড়ছে, বিভাগটি 0 এড়িয়ে গেছে।

 এসকিউএল সার্ভার এক্সিকিউশন টাইমস:
   সিপিইউ সময় = 0 এমএস, অতিবাহিত সময় = 11 এমএস।

হ্যাশ ম্যাচ (ফ্লো ডিস্ট্রিন্ট) বর্তমানে ব্যাচ মোডে কার্যকর করতে পারে না। ব্যাচ থেকে সারি প্রক্রিয়াকরণে ব্যয়বহুল রূপান্তর (অদৃশ্য) হওয়ার কারণে এটি যে পদ্ধতিগুলি ব্যবহার করে তা অনেক ধীর। উদাহরণ স্বরূপ:

SET ROWCOUNT 10;

SELECT DISTINCT 
    XDH.VAL
FROM dbo.X_10_DISTINCT_HEAP AS XDH
OPTION (FAST 1);

SET ROWCOUNT 0;

দেয়:

ফ্লো স্বাতন্ত্র্য সম্পাদন পরিকল্পনা

সারণী 'X_10_DISTINCT_HEAP'। স্ক্যান কাউন্ট 1,
 লজিকাল 0, রিডিকাল 0, রিড-ফরোয়ার্ড 0, 
 লব লজিকাল 20 পড়ে , লব ফিজিকাল 0, লব রিড-ফরোয়ার্ড 0 পড়ছে।
সারণী 'X_10_DISTINCT_HEAP'। বিভাগটি 4 টি পড়ছে , বিভাগটি 0 এড়িয়ে গেছে।

 এসকিউএল সার্ভার এক্সিকিউশন টাইমস:
   সিপিইউ সময় = 640 এমএস, অতিবাহিত সময় = 680 এমএস।

টেবিলটি রো-স্টোর হিপ হিসাবে সজ্জিত হওয়ার চেয়ে ধীর।


5

পুনরাবৃত্ত সিটিই ব্যবহার করে পুনরাবৃত্তি হওয়া আংশিক স্ক্যানের (একই সাথে স্কিপ স্ক্যানের মতো নয়) অনুকরণ করার চেষ্টা করা হল। উদ্দেশ্য - যেহেতু আমাদের কোনও সূচি নেই (id)- হ'ল টেবিলে বিভিন্ন ধরণের এবং একাধিক স্ক্যান এড়ানো।

এটি কিছু পুনরাবৃত্ত সিটিই বিধিনিষেধ বাইপাস করার জন্য কয়েকটি কৌশল করে:

  • TOPপুনরাবৃত্তির অংশে অনুমতি নেই । আমরা একটি subquery এবং ROW_NUMBER()পরিবর্তে ব্যবহার করুন।
  • আমাদের ধ্রুবক অংশে একাধিক উল্লেখ থাকতে পারে না বা পুনরাবৃত্ত অংশ থেকে ব্যবহার LEFT JOINবা ব্যবহার করতে পারি NOT IN (SELECT id FROM cte)। বাইপাস করার জন্য, আমরা একটি বিল্ড VARCHARস্ট্রিংটি সব accumulates idমূল্যবোধ, অনুরূপ STRING_AGGবা hierarchyID এবং তারপর সঙ্গে তুলনা LIKE

রেক্সটেসটার.কম- এ একটি গাদা (কলামটির নাম ধরে নেওয়া id) পরীক্ষা -১ এর জন্য

এটি - যেমন পরীক্ষাগুলি দেখিয়েছে - একাধিক স্ক্যান এড়িয়ে চলবে না তবে প্রথম কয়েকটি পৃষ্ঠায় বিভিন্ন মান পাওয়া গেলে ঠিক আছে। মানগুলি যদি সমানভাবে বিতরণ না করা হয় তবে এটি টেবিলের বড় অংশগুলিতে একাধিক স্ক্যান করতে পারে - যা পিএফ কোর্সের ফলাফল খারাপ ফলাফলের জন্য।

WITH ct (id, found, list) AS
  ( SELECT TOP (1) id, 1, CAST('/' + id + '/' AS VARCHAR(MAX))
    FROM x_large_table_2
  UNION ALL
    SELECT y.ID, ct.found + 1, CAST(ct.list + y.id + '/' AS VARCHAR(MAX))
    FROM ct
      CROSS APPLY 
      ( SELECT x.id, 
               rn = ROW_NUMBER() OVER (ORDER BY (SELECT NULL))
        FROM x_large_table_2 AS x
        WHERE ct.list NOT LIKE '%/' + id + '/%'
      ) AS y
    WHERE ct.found < 3         -- the TOP (n) parameter here
      AND y.rn = 1
  )
SELECT id FROM ct ;

এবং যখন টেবিলটি ক্লাস্টার্ড হয় (সিআই চালু থাকে unique_key), তখন রেক্সটেসটার.কম-এ পরীক্ষা -২

WHERE x.unique_key > ct.unique_keyএকাধিক স্ক্যান এড়ানোর জন্য এটি ক্লাস্টারড ইনডেক্স ( ) ব্যবহার করে :

WITH ct (unique_key, id, found, list) AS
  ( SELECT TOP (1) unique_key, id, 1, CAST(CONCAT('/',id, '/') AS VARCHAR(MAX))
    FROM x_large_table_2
  UNION ALL
    SELECT y.unique_key, y.ID, ct.found + 1, 
        CAST(CONCAT(ct.list, y.id, '/') AS VARCHAR(MAX))
    FROM ct
      CROSS APPLY 
      ( SELECT x.unique_key, x.id, 
               rn = ROW_NUMBER() OVER (ORDER BY (SELECT NULL))
        FROM x_large_table_2 AS x
        WHERE x.unique_key > ct.unique_key
          AND ct.list NOT LIKE '%/' + id + '/%'
      ) AS y
    WHERE ct.found < 5       -- the TOP (n) parameter here
      AND y.rn = 1
  )
-- SELECT * FROM ct ;        -- for debugging
SELECT id FROM ct ;

এই সমাধানটির সাথে মোটামুটি সূক্ষ্ম পারফরম্যান্স সমস্যা রয়েছে। এটি টেবিলে একটি অতিরিক্ত অন্বেষণ শেষ করে এটি নবম মানের সন্ধান করার পরে। সুতরাং যদি শীর্ষ 10 এর জন্য 10 স্বতন্ত্র মান থাকে তবে এটি একটি 11 তম মানের সন্ধান করবে যা সেখানে নেই। আপনি একটি অতিরিক্ত পূর্ণ স্ক্যান এবং 10 মিলিয়ন ROW_NUMBER () গণনা সত্যিই যুক্ত করে শেষ করেছেন। আমার এখানে একটি কার্যবিবরণী আছে যা আমার মেশিনে কোয়েরি 20X দ্রুত করে। আপনি কি মনে করেন? brentozar.com/pastetheplan/?id=SkDhAmFKe
জো ওবিশ

2

সম্পূর্ণতার জন্য অন্য উপায় এই সমস্যা কাছে ব্যবহার করা বাহ্য প্রযোজ্য । আমাদের OUTER APPLYপ্রতিটি স্বতন্ত্র মানের জন্য অপারেটর যুক্ত করতে পারি যা আমাদের সন্ধান করতে হবে। এটি ইপারকিউবের পুনরাবৃত্তির পদ্ধতির মত ধারণার অনুরূপ, তবে কার্যকরভাবে পুনরাবৃত্তিটি হাতে লেখা রয়েছে। এর একটি সুবিধা হ'ল আমরা কর্মক্ষেত্রের TOPপরিবর্তে উত্সযুক্ত টেবিলগুলিতে ব্যবহার করতে সক্ষম ROW_NUMBER()। একটি বড় অসুবিধা হ'ল ক্যোরির পাঠ্য Nবাড়ার সাথে সাথে দীর্ঘ হয় ।

স্তূপের বিরুদ্ধে ক্যোয়ারির জন্য এখানে একটি প্রয়োগকরণ রয়েছে:

SELECT VAL
FROM (
    SELECT t1.VAL VAL1, t2.VAL VAL2, t3.VAL VAL3, t4.VAL VAL4, t5.VAL VAL5, t6.VAL VAL6, t7.VAL VAL7, t8.VAL VAL8, t9.VAL VAL9, t10.VAL VAL10
    FROM 
    ( 
    SELECT TOP 1 VAL FROM X_10_DISTINCT_HEAP 
    ) t1
    OUTER APPLY
    ( 
    SELECT TOP 1 VAL FROM X_10_DISTINCT_HEAP t2 WHERE t2.VAL NOT IN (t1.VAL)
    ) t2
    OUTER APPLY
    ( 
    SELECT TOP 1 VAL FROM X_10_DISTINCT_HEAP t3 WHERE t3.VAL NOT IN (t1.VAL, t2.VAL)
    ) t3
    OUTER APPLY
    ( 
    SELECT TOP 1 VAL FROM X_10_DISTINCT_HEAP t4 WHERE t4.VAL NOT IN (t1.VAL, t2.VAL, t3.VAL)
    ) t4
    OUTER APPLY
    ( 
    SELECT TOP 1 VAL FROM X_10_DISTINCT_HEAP t5 WHERE t5.VAL NOT IN (t1.VAL, t2.VAL, t3.VAL, t4.VAL)
    ) t5
    OUTER APPLY
    ( 
    SELECT TOP 1 VAL FROM X_10_DISTINCT_HEAP t6 WHERE t6.VAL NOT IN (t1.VAL, t2.VAL, t3.VAL, t4.VAL, t5.VAL)
    ) t6
    OUTER APPLY
    ( 
    SELECT TOP 1 VAL FROM X_10_DISTINCT_HEAP t7 WHERE t7.VAL NOT IN (t1.VAL, t2.VAL, t3.VAL, t4.VAL, t5.VAL, t6.VAL)
    ) t7
    OUTER APPLY
    ( 
    SELECT TOP 1 VAL FROM X_10_DISTINCT_HEAP t8 WHERE t8.VAL NOT IN (t1.VAL, t2.VAL, t3.VAL, t4.VAL, t5.VAL, t6.VAL, t7.VAL)
    ) t8
    OUTER APPLY
    ( 
    SELECT TOP 1 VAL FROM X_10_DISTINCT_HEAP t9 WHERE t9.VAL NOT IN (t1.VAL, t2.VAL, t3.VAL, t4.VAL, t5.VAL, t6.VAL, t7.VAL, t8.VAL)
    ) t9
    OUTER APPLY
    ( 
    SELECT TOP 1 VAL FROM X_10_DISTINCT_HEAP t10 WHERE t10.VAL NOT IN (t1.VAL, t2.VAL, t3.VAL, t4.VAL, t5.VAL, t6.VAL, t7.VAL, t8.VAL, t9.VAL)
    ) t10
) t
UNPIVOT 
(
  VAL FOR VALS IN (VAL1, VAL2, VAL3, VAL4, VAL5, VAL6, VAL7, VAL8, VAL9, VAL10)
) AS upvt;

উপরের ক্যোয়ারির জন্য প্রকৃত ক্যোয়ারী পরিকল্পনা এখানে । আমার মেশিনে এই কোয়েরিটি সিপিইউয়ের 625 এমএস এবং 12605 লজিকাল রিডের সাথে 713 এমএসে সম্পূর্ণ হয়। আমরা প্রতি 100 কে সারিতে একটি নতুন স্বতন্ত্র মান পাই তাই আমি এই কোয়েরিটি প্রায় 900000 * 10 * 0.5 = 4500000 সারি স্ক্যান করে আশা করব। তাত্ত্বিকভাবে এই কোয়েরিটি অন্য উত্তর থেকে এই কোয়েরিটির পাঁচবার যৌক্তিক পাঠ করা উচিত:

DECLARE @j INT = 10;

SELECT DISTINCT TOP (@j) VAL
FROM X_10_DISTINCT_HEAP
OPTION (MAXDOP 1, OPTIMIZE FOR (@j = 1));

এই ক্যোয়ারী 2537 লজিক্যাল রিড করেছে। 2537 * 5 = 12685 যা প্রায় 12605 এর কাছাকাছি।

ক্লাস্টারড ইনডেক্স সহ টেবিলের জন্য আমরা আরও ভাল করতে পারি। এটি হ'ল কারণ একই সারিগুলিতে দু'বার স্ক্যান করা এড়াতে আমরা শেষ ক্লাস্টারযুক্ত মানটি উত্পন্ন টেবিলের মধ্যে দিয়ে যেতে পারি। একটি বাস্তবায়ন:

SELECT VAL
FROM (
    SELECT t1.VAL VAL1, t2.VAL VAL2, t3.VAL VAL3, t4.VAL VAL4, t5.VAL VAL5, t6.VAL VAL6, t7.VAL VAL7, t8.VAL VAL8, t9.VAL VAL9, t10.VAL VAL10
    FROM 
    ( 
    SELECT TOP 1 PK, VAL FROM X_10_DISTINCT_CI 
    ) t1
    OUTER APPLY
    ( 
    SELECT TOP 1 PK, VAL FROM X_10_DISTINCT_CI t2 WHERE PK > t1.PK AND t2.VAL NOT IN (t1.VAL)
    ) t2
    OUTER APPLY
    ( 
    SELECT TOP 1 PK, VAL FROM X_10_DISTINCT_CI t3 WHERE PK > t2.PK AND t3.VAL NOT IN (t1.VAL, t2.VAL)
    ) t3
    OUTER APPLY
    ( 
    SELECT TOP 1 PK, VAL FROM X_10_DISTINCT_CI t4 WHERE PK > t3.PK AND t4.VAL NOT IN (t1.VAL, t2.VAL, t3.VAL)
    ) t4
    OUTER APPLY
    ( 
    SELECT TOP 1 PK, VAL FROM X_10_DISTINCT_CI t5 WHERE PK > t4.PK AND t5.VAL NOT IN (t1.VAL, t2.VAL, t3.VAL, t4.VAL)
    ) t5
    OUTER APPLY
    ( 
    SELECT TOP 1 PK, VAL FROM X_10_DISTINCT_CI t6 WHERE PK > t5.PK AND t6.VAL NOT IN (t1.VAL, t2.VAL, t3.VAL, t4.VAL, t5.VAL)
    ) t6
    OUTER APPLY
    ( 
    SELECT TOP 1 PK, VAL FROM X_10_DISTINCT_CI t7 WHERE PK > t6.PK AND t7.VAL NOT IN (t1.VAL, t2.VAL, t3.VAL, t4.VAL, t5.VAL, t6.VAL)
    ) t7
    OUTER APPLY
    ( 
    SELECT TOP 1 PK, VAL FROM X_10_DISTINCT_CI t8 WHERE PK > t7.PK AND t8.VAL NOT IN (t1.VAL, t2.VAL, t3.VAL, t4.VAL, t5.VAL, t6.VAL, t7.VAL)
    ) t8
    OUTER APPLY
    ( 
    SELECT TOP 1 PK, VAL FROM X_10_DISTINCT_CI t9 WHERE PK > t8.PK AND t9.VAL NOT IN (t1.VAL, t2.VAL, t3.VAL, t4.VAL, t5.VAL, t6.VAL, t7.VAL, t8.VAL)
    ) t9
    OUTER APPLY
    ( 
    SELECT TOP 1 PK, VAL FROM X_10_DISTINCT_CI t10 WHERE PK > t9.PK AND t10.VAL NOT IN (t1.VAL, t2.VAL, t3.VAL, t4.VAL, t5.VAL, t6.VAL, t7.VAL, t8.VAL, t9.VAL)
    ) t10
) t
UNPIVOT 
(
  VAL FOR VALS IN (VAL1, VAL2, VAL3, VAL4, VAL5, VAL6, VAL7, VAL8, VAL9, VAL10)
) AS upvt;

উপরের ক্যোয়ারির জন্য প্রকৃত ক্যোয়ারী পরিকল্পনা এখানে । আমার মেশিনে এই কোয়েরিটি সিপিইউয়ের 140 এমএস এবং 3203 লজিক্যাল রিডের সাথে 154 এমএসে সম্পূর্ণ হয়। এটি OPTIMIZE FORক্লাস্টারড ইনডেক্স টেবিলের বিপরীতে ক্যোয়ারির চেয়ে কিছুটা দ্রুত চলবে বলে মনে হয়েছিল । আমি আশা করছিলাম না তাই আমি আরও যত্ন সহকারে পরিমাপ করার চেষ্টা করেছি। আমার পদ্ধতি ফলাফলের সেট ছাড়া প্রতিটি প্রশ্নের সাথে দশ বার চালানোর থেকে সমষ্টিগত সংখ্যা তাকান ছিল sys.dm_exec_sessionsএবং sys.dm_exec_session_wait_stats। সেশন 56 এর APPLYক্যোয়ারী ছিল এবং সেশন 63 এর OPTIMIZE FORক্যোয়ারী ছিল ।

এর আউটপুট sys.dm_exec_sessions:

╔════════════╦══════════╦════════════════════╦═══════════════╗
 session_id  cpu_time  total_elapsed_time  logical_reads 
╠════════════╬══════════╬════════════════════╬═══════════════╣
         56      1360                1373          32030 
         63      2094                2091          30400 
╚════════════╩══════════╩════════════════════╩═══════════════╝

ক্যোরির জন্য সিপু_টাইম এবং অতিবাহিত_টাইমের একটি সুস্পষ্ট সুবিধা বলে মনে হচ্ছে APPLY

এর আউটপুট sys.dm_exec_session_wait_stats:

╔════════════╦════════════════════════════════╦═════════════════════╦══════════════╦══════════════════╦═════════════════════╗
 session_id            wait_type             waiting_tasks_count  wait_time_ms  max_wait_time_ms  signal_wait_time_ms 
╠════════════╬════════════════════════════════╬═════════════════════╬══════════════╬══════════════════╬═════════════════════╣
         56  SOS_SCHEDULER_YIELD                             340             0                 0                    0 
         56  MEMORY_ALLOCATION_EXT                            38             0                 0                    0 
         63  SOS_SCHEDULER_YIELD                             518             0                 0                    0 
         63  MEMORY_ALLOCATION_EXT                            98             0                 0                    0 
         63  RESERVED_MEMORY_ALLOCATION_EXT                  400             0                 0                    0 
╚════════════╩════════════════════════════════╩═════════════════════╩══════════════╩══════════════════╩═════════════════════╝

OPTIMIZE FORক্যোয়ারী একটি অতিরিক্ত অপেক্ষার টাইপ হয়েছে RESERVED_MEMORY_ALLOCATION_EXT । আমি এর সঠিক অর্থটি জানি না। এটি হ্যাশ ম্যাচ (প্রবাহ স্বতন্ত্র) অপারেটরে কেবল ওভারহেডের একটি পরিমাপ হতে পারে। যে কোনও ক্ষেত্রে, সম্ভবত এটি সিপিইউ সময়ে 70 এমএসের পার্থক্য সম্পর্কে উদ্বেগজনক নয়।


1

আমি মনে করি
এটির সমাধানের উপায় কেন এটি সম্পর্কে আপনার উত্তর রয়েছে
আমি জানি এটি অগোছালো বলে মনে হচ্ছে তবে কার্যকর করার পরিকল্পনা বলেছে স্বতন্ত্র শীর্ষ 2 ব্যয়ের ৮৮% ছিল

SELECT distinct top (2)  [enumID]
FROM [ENRONbbb].[dbo].[docSVenum1]

declare @table table (enumID tinyint);
declare @enumID tinyint;
set @enumID = (select top (1) [enumID] from [docSVenum1]);
insert into @table values (@enumID);
set @enumID = (select top (1) [enumID] from [docSVenum1] where [enumID] not in (select enumID from @table));
insert into @table values (@enumID);
set @enumID = (select top (1) [enumID] from [docSVenum1] where [enumID] not in (select enumID from @table));
insert into @table values (@enumID);
set @enumID = (select top (1) [enumID] from [docSVenum1] where [enumID] not in (select enumID from @table));
insert into @table values (@enumID);
set @enumID = (select top (1) [enumID] from [docSVenum1] where [enumID] not in (select enumID from @table));
insert into @table values (@enumID);
set @enumID = (select top (1) [enumID] from [docSVenum1] where [enumID] not in (select enumID from @table));
insert into @table values (@enumID);
set @enumID = (select top (1) [enumID] from [docSVenum1] where [enumID] not in (select enumID from @table));
insert into @table values (@enumID);
set @enumID = (select top (1) [enumID] from [docSVenum1] where [enumID] not in (select enumID from @table));
insert into @table values (@enumID);
set @enumID = (select top (1) [enumID] from [docSVenum1] where [enumID] not in (select enumID from @table));
insert into @table values (@enumID);
set @enumID = (select top (1) [enumID] from [docSVenum1] where [enumID] not in (select enumID from @table));
insert into @table values (@enumID);
select enumID from @table;

এই কোডটি আমার মেশিনে 5 সেকেন্ড সময় নিয়েছে। দেখে মনে হচ্ছে টেবিল ভেরিয়েবলের সাথে যোগ দেয় বেশ কিছুটা ওভারহেড যুক্ত। চূড়ান্ত ক্যোয়ারিতে টেবিলের ভেরিয়েবলটি 892800 বার স্ক্যান করা হয়েছিল। এই ক্যোয়ারিতে সিপিইউ সময় 1359 এমএস এবং অতিবাহিত সময়ের 1374 এমএস সময় নিয়েছে। আমি প্রত্যাশার চেয়ে অবশ্যই বেশি। সারণী ভেরিয়েবলের একটি প্রাথমিক কী যুক্ত করা সাহায্য করবে বলে মনে হচ্ছে, যদিও আমি তা নিশ্চিত নই। অন্যান্য সম্ভাব্য অপ্টিমাইজেশন হতে পারে।
জো ওবিশ
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.