মাস্টার / বিশদ টেবিলের মধ্যে হ্যাশ জোড় খুব কম কার্ডিনালিটির অনুমান করে


9

কোনও বিস্তৃত সারণিতে একটি মাস্টার টেবিলে যোগদান করার সময়, আমি কীভাবে এসকিউএল সার্ভারকে সংযুক্ত আউটপুটের কার্ডিনালিটির প্রাক্কলন হিসাবে বৃহত্তর (বিশদ) সারণীর কার্ডিনালিটি অনুমানটি ব্যবহার করতে উত্সাহিত করতে পারি?

উদাহরণস্বরূপ, 10 কে মাস্টার সারিগুলিকে 100 কে বিশদ সারিগুলিতে যোগদান করার সময়, আমি এসকিউএল সার্ভারকে 100K সারিগুলিতে যোগদানের প্রাক্কলন করতে চাই - বিশদ সারিগুলির আনুমানিক সংখ্যার সমান। এসকিউএল সার্ভারের অনুমানকারীকে প্রতিটি বিশদ সারিতে সর্বদা অনুরূপ মাস্টার সারি থাকে তা উপকারে আসার জন্য আমার কীভাবে আমার প্রশ্নগুলি এবং / অথবা টেবিলগুলি এবং / অথবা সূচিগুলি গঠন করতে হবে? (এর অর্থ এই যে তাদের মধ্যে যোগদানের ফলে কার্ডিনালিটির প্রাক্কলনটি কখনই হ্রাস করা উচিত নয়))

আরও বিশদ এখানে। আমাদের ডাটাবেসটিতে সারণীর একটি মাস্টার / বিস্তারিত জোড় রয়েছে: VisitTargetপ্রতিটি বিক্রয় লেনদেনের VisitSaleজন্য একটি সারি থাকে এবং প্রতিটি লেনদেনে প্রতিটি পণ্যের জন্য একটি সারি থাকে। এটি এক থেকে একাধিক সম্পর্ক: গড়ে 10 টি ভিজিটসেল সারিগুলির জন্য একটি ভিজিটর্যাজেট সারি।

টেবিলগুলি দেখতে দেখতে: (আমি এই প্রশ্নের জন্য কেবলমাত্র প্রাসঙ্গিক কলামগুলিতেই সহজ করছি)

-- "master" table
CREATE TABLE VisitTarget
(
  VisitTargetId int IDENTITY(1,1) NOT NULL PRIMARY KEY CLUSTERED,
  SaleDate date NOT NULL,
  StoreId int NOT NULL
  -- other columns omitted for clarity  
);
-- covering index for date-scoped queries
CREATE NONCLUSTERED INDEX IX_VisitTarget_SaleDate 
    ON VisitTarget (SaleDate) INCLUDE (StoreId /*, ...more columns */);

-- "detail" table
CREATE TABLE VisitSale
(
  VisitSaleId int IDENTITY(1,1) NOT NULL PRIMARY KEY CLUSTERED,
  VisitTargetId int NOT NULL,
  SaleDate date NOT NULL, -- denormalized; copied from VisitTarget
  StoreId int NOT NULL, -- denormalized; copied from VisitTarget
  ItemId int NOT NULL,
  SaleQty int NOT NULL,
  SalePrice decimal(9,2) NOT NULL
  -- other columns omitted for clarity  
);
-- covering index for date-scoped queries
CREATE NONCLUSTERED INDEX IX_VisitSale_SaleDate 
  ON VisitSale (SaleDate)
  INCLUDE (VisitTargetId, StoreId, ItemId, SaleQty, TotalSalePrice decimal(9,2) /*, ...more columns */
);
ALTER TABLE VisitSale 
  WITH CHECK ADD CONSTRAINT FK_VisitSale_VisitTargetId 
  FOREIGN KEY (VisitTargetId)
  REFERENCES VisitTarget (VisitTargetId);
ALTER TABLE VisitSale
  CHECK CONSTRAINT FK_VisitSale_VisitTargetId;

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

আমরা যখন এই দুটি টেবিলগুলিতে যোগদান করি তখন ক্যোয়ারীগুলি এর মতো দেখায়:

SELECT vt.StoreId, vt.SomeOtherColumn, Sales = sum(vs.SalePrice*vs.SaleQty)
FROM VisitTarget vt 
    JOIN VisitSale vs on vt.VisitTargetId = vs.VisitTargetId
WHERE
    vs.SaleDate BETWEEN '20170101' and '20171231'
    and vt.SaleDate BETWEEN '20170101' and '20171231'
    -- more filtering goes here, e.g. by store, by product, etc. 

বিশদ টেবিলের তারিখের ফিল্টার ( VisitSale) অপ্রয়োজনীয়। এটি একটি তারিখের ব্যাপ্তি অনুসারে ফিল্টার করা প্রশ্নের জন্য বিশদ বিবরণ সারণিতে ক্রমানুসারে I / O (ওরফে সূচক সিক অপারেটর) সক্ষম করতে পারে।

এই ধরণের প্রশ্নের জন্য পরিকল্পনাটি এরকম দেখাচ্ছে:

এখানে চিত্র বর্ণনা লিখুন

একই সমস্যার সাথে একটি ক্যোয়ারির আসল পরিকল্পনা এখানে পাওয়া যাবে

আপনি দেখতে পাচ্ছেন, যোগদানের জন্য কার্ডিনালিটির অনুমান (ছবিতে নীচের বামে থাকা সরঞ্জামটিপ) খুব কম 4x এরও বেশি: 2.1 এম প্রকৃত বনাম 0.5 মি। এটি কর্মক্ষমতা সংক্রান্ত সমস্যার কারণ হয়ে দাঁড়ায় (যেমন টেম্পডিবির কাছে স্পিলিং), বিশেষত যখন এই ক্যোয়ারীটি এমন একটি সাব-কোয়ারী যা আরও জটিল কোয়েরিতে ব্যবহৃত হয়।

তবে যোগদানের প্রতিটি শাখার জন্য সারি গণনা অনুমানগুলি আসল সারি গণনার কাছাকাছি। যোগদানের উপরের অর্ধেকটি 100K আসল বনাম 164K আনুমানিক। যোগদানের নীচের অর্ধেকটি 2.1M সারি প্রকৃত বনাম 3.7 এম এর অনুমান। হ্যাশ বালতির বিতরণও ভাল দেখাচ্ছে। এই পর্যবেক্ষণগুলি আমার কাছে পরামর্শ দেয় যে পরিসংখ্যানগুলি প্রতিটি টেবিলের জন্য ঠিক আছে, এবং সমস্যাটি জয়েন্ট কার্ডিনালিটির অনুমান।

প্রথমে আমি ভেবেছিলাম যে সমস্যাটি এসকিউএল সার্ভারের প্রত্যাশা ছিল যে প্রতিটি টেবিলের সেলডেট কলামগুলি স্বতন্ত্র, যদিও সত্যই সেগুলি অভিন্ন। সুতরাং আমি বিক্রির তারিখগুলির সাথে যোগদানের শর্তে বা WHERE ধারাটিতে সমতা তুলনা যুক্ত করার চেষ্টা করেছি eg

ON vt.VisitTargetId = vs.VisitTargetId and vt.SaleDate = vs.SaleDate

অথবা

WHERE vt.SaleDate = vs.SaleDate

এটি কাজ করে না। এমনকি এটি কার্ডিনালিটির প্রাক্কলনগুলি আরও খারাপ করেছে! সুতরাং হয় এসকিউএল সার্ভার সেই সাম্যতার ইঙ্গিতটি ব্যবহার করছে না বা অন্য কিছু হ'ল সমস্যার মূল কারণ।

কীভাবে সমস্যা সমাধান করবেন এবং আশা রাখবেন যে এই কার্ডিনালিটি অনুমানের সমস্যাটি সমাধান করবেন? আমার লক্ষ্য হ'ল মাস্টার / ডিটেইল জয়েনের কার্ডিনালিটির জন্য যোগটির বৃহত্তর ("বিস্তৃত টেবিল") ইনপুটটির জন্য অনুমান হিসাবে একই অনুমান করা যায়।

যদি এটি গুরুত্বপূর্ণ হয় তবে আমরা উইন্ডোজ সার্ভারে এসকিউএল সার্ভার 2014 এন্টারপ্রাইজ এসপি 2 সিই 8 বিল্ড 12.0.5557.0 চালাচ্ছি। কোনও ট্রেস পতাকা সক্ষম নেই। ডেটাবেস সামঞ্জস্যতা স্তরটি এসকিউএল সার্ভার 2014। আমরা একাধিক বিভিন্ন এসকিউএল সার্ভারে একই আচরণ দেখতে পাই, তাই এটি কোনও সার্ভার-নির্দিষ্ট সমস্যা বলে মনে হয় না।

এসকিউএল সার্ভার ২০১৪ কার্ডিনালাইটি এসটিমেটরে একটি অপ্টিমাইজেশন রয়েছে যা আমি ঠিক আচরণ করে যাচ্ছি:

নতুন সিই, তবে একটি সহজ অ্যালগরিদম ব্যবহার করে যা ধরে নিয়েছে যে একটি বৃহত টেবিল এবং একটি ছোট টেবিলের মধ্যে একের মধ্যে অনেকে যোগ দেয়। এটি ধরে নেওয়া হয় যে বড় টেবিলের প্রতিটি সারি ছোট টেবিলের ঠিক এক সারিতে মেলে। এই অ্যালগরিদম যোগ কার্ডিনালটির হিসাবে বৃহত্তর ইনপুটটির আনুমানিক আকারটি দেয়।

আদর্শভাবে আমি এই আচরণটি পেতে পারি, যেখানে যোগদানের জন্য কার্ডিনালিটির প্রাক্কলনটি বড় টেবিলের জন্য অনুমানের সমান হবে, যদিও আমার "ছোট" টেবিলটি এখনও 100 কে সারি ধরে ফিরে আসবে!

উত্তর:


6

Assuming যে কোনো অগ্রগতি পরিসংখ্যান কিছু করছেন বা উত্তরাধিকার সিই ব্যবহার করে অর্জন করা যাবে, তারপর আপনার সমস্যা কাছাকাছি সবচেয়ে সহজবোধ্য ভাবে আপনার পরিবর্তন হয় INNER JOINএকটি থেকে LEFT OUTER JOIN:

SELECT vt.StoreId, vt.SomeOtherColumn, Sales = sum(vs.SalePrice*vs.SaleQty)
FROM VisitSale vs
    LEFT OUTER JOIN VisitTarget vt on vt.VisitTargetId = vs.VisitTargetId
            AND vt.SaleDate BETWEEN '20170101' and '20171231'
WHERE vs.SaleDate BETWEEN '20170101' and '20171231'

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

যোগদানের পরে কোয়েরিতে আর কী ঘটে তার উপর নির্ভর করে এই ফর্মটিতে ক্যোয়ারী লেখা ভালভাবে কাজ করবে না এটি সম্ভব। আপনি সবচেয়ে গুরুত্বপূর্ণ কার্ডিনালিটির প্রাক্কলন সহ ফলাফল সেটটির মধ্যবর্তী ফলাফলগুলি ধরে রাখতে একটি টেম্প টেবিল ব্যবহার করে চেষ্টা করতে পারেন।

আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.