পরিবর্তন লগের উপর ভিত্তি করে স্টক পরিমাণ গণনা করা


10

কল্পনা করুন যে আপনার নীচের সারণির কাঠামো রয়েছে:

LogId | ProductId | FromPositionId | ToPositionId | Date                 | Quantity
-----------------------------------------------------------------------------------
1     | 123       | 0              | 10002        | 2018-01-01 08:10:22  | 5
2     | 123       | 0              | 10003        | 2018-01-03 15:15:10  | 9
3     | 123       | 10002          | 10004        | 2018-01-07 21:08:56  | 3
4     | 123       | 10004          | 0            | 2018-02-09 10:03:23  | 1

FromPositionIdএবং ToPositionIdস্টক পজিশন হয়। কিছু অবস্থানের আইডি: এর বিশেষ অর্থ রয়েছে, উদাহরণস্বরূপ 0। বা এর থেকে একটি ইভেন্টের 0অর্থ হ'ল স্টকটি তৈরি বা সরানো হয়েছিল। থেকে 0এবং একটি ডেলিভারি থেকে স্টক হতে পারে জন্য 0একটি জাহাজে অর্ডার হতে পারে।

এই সারণীতে বর্তমানে প্রায় 5.5 মিলিয়ন সারি রয়েছে। আমরা এই জাতীয় কিছু দেখতে এমন কোয়েরি ব্যবহার করে একটি তফসিলের জন্য প্রতিটি পণ্য এবং অবস্থানের জন্য ক্যাশে টেবিলের জন্য স্টক মান গণনা করি:

WITH t AS
(
    SELECT ToPositionId AS PositionId, SUM(Quantity) AS Quantity, ProductId 
    FROM ProductPositionLog
    GROUP BY ToPositionId, ProductId
    UNION
    SELECT FromPositionId AS PositionId, -SUM(Quantity) AS Quantity, ProductId 
    FROM ProductPositionLog
    GROUP BY FromPositionId, ProductId
)

SELECT t.ProductId, t.PositionId, SUM(t.Quantity) AS Quantity
FROM t
WHERE NOT t.PositionId = 0
GROUP BY t.ProductId, t.PositionId
HAVING SUM(t.Quantity) > 0

যদিও এটি একটি যুক্তিসঙ্গত পরিমাণে প্রায় (প্রায় 20 সেকেন্ড) মধ্যে পূর্ণ হয়, আমি মনে করি এটি স্টক মানগুলি গণনা করার জন্য বেশ অদক্ষ উপায়। INSERTএই টেবিলে আমরা খুব কমই কিছু করতে পারি : তবে কখনও কখনও আমরা এই সারিগুলি তৈরি করে লোকজনের ভুলের কারণে আমরা ভিতরে andুকে পরিমাণটি সামঞ্জস্য করি বা একটি সারি ম্যানুয়ালি সরিয়ে ফেলি।

আমার একটি পৃথক সারণীতে "চেকপয়েন্ট" তৈরি করার ধারণা ছিল, সময় নির্দিষ্ট সময় পর্যন্ত মান গণনা করা এবং আমাদের স্টক পরিমাণের ক্যাশে টেবিল তৈরি করার সময় এটি একটি শুরুর মান হিসাবে ব্যবহার:

ProductId | PositionId | Date                | Quantity
-------------------------------------------------------
123       | 10002      | 2018-01-07 21:08:56 | 2

আমরা মাঝে মাঝে সারিগুলি পরিবর্তন করি তা এই সমস্যার কারণ হয়ে দাঁড়ায়, সেই ক্ষেত্রে আমাদের অবশ্যই লগ সারি পরিবর্তিত হওয়ার পরে তৈরি হওয়া কোনও চৌকোপোটি সরাতে হবে। এখন অবধি চেকপয়েন্টগুলি গণনা না করে এটি সমাধান করা যেতে পারে তবে এখন থেকে শেষ চৌকপয়েন্টের মধ্যে এক মাস রেখে দিন (আমরা খুব কমই খুব সহজেই এর পরিবর্তন করেছি)।

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

লগ টেবিলটি হ'ল, আপনি কল্পনা করতে পারেন, বেশ দ্রুত বাড়ছে এবং গণনার সময় কেবল সময়ের সাথে বাড়বে।

সুতরাং আমার প্রশ্ন, আপনি কিভাবে এই সমাধান করবে? বর্তমান স্টক মান গণনার আরও কার্যকর উপায় আছে কি? চেকপয়েন্টগুলি সম্পর্কে আমার ধারণাটি কি ভাল?

আমরা এসকিউএল সার্ভার 2014 ওয়েব চালিয়ে যাচ্ছি (12.0.5511)

সম্পাদন পরিকল্পনা: https://www.brentozar.com/pastetheplan/?id=Bk8gyc68Q

আমি আসলে উপরেরটিকে ভুল প্রয়োগের সময় দিয়েছিলাম, 20-এর দশকে ক্যাশের সম্পূর্ণ আপডেটের সময় ছিল। এই কোয়েরিটি চালাতে কোথাও প্রায় 6-10 সেকেন্ড সময় নেয় (যখন আমি এই কোয়েরি পরিকল্পনাটি তৈরি করেছি 8 সেকেন্ড) এই প্রশ্নের মধ্যে একটি যোগদানও রয়েছে যা মূল প্রশ্নে ছিল না।

উত্তর:


6

কখনও কখনও আপনি নিজের পুরো ক্যোয়ারী পরিবর্তন না করে কিছুটা টিউনিং করে কোয়েরি পারফরম্যান্সকে উন্নত করতে পারেন। আমি আপনার প্রকৃত ক্যোয়ারী পরিকল্পনায় লক্ষ্য করেছি যে আপনার কোয়েরিটি তিন জায়গায় টেম্পডবিতে ছড়িয়ে পড়ে। একটি উদাহরণ এখানে:

টেম্পডিবি স্পিল

এই টেম্পডিবি স্পিলগুলি সমাধান করা কার্যকারিতা উন্নত করতে পারে। যদি Quantityসর্বদা অ নেতিবাচক তারপর আপনি প্রতিস্থাপন করতে পারেন UNIONসঙ্গে UNION ALLযা সম্ভবত যে অন্য কিছু স্মৃতি অনুদান প্রয়োজন হয় না করতে হ্যাশ ইউনিয়ন অপারেটর পরিবর্তন করতে হবে। আপনার অন্যান্য টেম্পডিবি স্পিলগুলি কার্ডিনালিটির অনুমানের সাথে সমস্যার কারণে ঘটে। আপনি এসকিউএল সার্ভার ২০১৪ এ এবং নতুন সিই ব্যবহার করছেন যাতে কার্ডিনালিটির অনুমানগুলি উন্নত করা কঠিন হতে পারে কারণ কোয়েরি অপ্টিমাইজারটি মাল্টি-কলামের পরিসংখ্যান ব্যবহার করবে না। দ্রুত সমাধান হিসাবে, এসকিউএল সার্ভার 2014 এসপি 2 এMIN_MEMORY_GRANT উপলব্ধ ক্যোয়ারী ইঙ্গিতটি ব্যবহার করে বিবেচনা করুন। আপনার ক্যোয়ারীর মেমরি অনুদানটি কেবল 49104 কেবি এবং সর্বাধিক উপলব্ধ অনুদান 5054840 কেবি তাই আশা করি এটিকে ঘায়েল করা খুব বেশি পরিমাণে প্রভাব ফেলবে না। 10% হ'ল একটি যুক্তিসঙ্গত সূচনা অনুমান তবে আপনার এটিকে সামঞ্জস্য করতে এবং আপনার হার্ডওয়্যার এবং ডেটার উপর নির্ভর করে আপনার প্রয়োজন হতে পারে। সব মিলিয়ে রাখলে আপনার প্রশ্নের এইরকম দেখতে পাওয়া যায়:

WITH t AS
(
    SELECT ToPositionId AS PositionId, SUM(Quantity) AS Quantity, ProductId 
    FROM ProductPositionLog
    GROUP BY ToPositionId, ProductId
    UNION ALL
    SELECT FromPositionId AS PositionId, -SUM(Quantity) AS Quantity, ProductId 
    FROM ProductPositionLog
    GROUP BY FromPositionId, ProductId
)

SELECT t.ProductId, t.PositionId, SUM(t.Quantity) AS Quantity
FROM t
WHERE NOT t.PositionId = 0
GROUP BY t.ProductId, t.PositionId
HAVING SUM(t.Quantity) > 0
OPTION (MIN_GRANT_PERCENT = 10);

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

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

CREATE TABLE dbo.ProductPositionLog (
    LogId BIGINT NOT NULL,
    ProductId BIGINT NOT NULL,
    FromPositionId BIGINT NOT NULL,
    ToPositionId BIGINT NOT NULL,
    Quantity INT NOT NULL,
    FILLER VARCHAR(20),
    PRIMARY KEY (LogId)
);

INSERT INTO dbo.ProductPositionLog WITH (TABLOCK)
SELECT RN, RN % 100, RN % 3999, 3998 - (RN % 3999), RN % 10, REPLICATE('Z', 20)
FROM (
    SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) RN
    FROM master..spt_values t1
    CROSS JOIN master..spt_values t2
) q;

CREATE INDEX NCI1 ON dbo.ProductPositionLog (ToPositionId, ProductId) INCLUDE (Quantity);
CREATE INDEX NCI2 ON dbo.ProductPositionLog (FromPositionId, ProductId) INCLUDE (Quantity);

GO    

CREATE VIEW ProductPositionLog_1
WITH SCHEMABINDING  
AS  
   SELECT ToPositionId AS PositionId, SUM(Quantity) AS Quantity, ProductId, COUNT_BIG(*) CNT
    FROM dbo.ProductPositionLog
    WHERE ToPositionId <> 0
    GROUP BY ToPositionId, ProductId
GO  

CREATE UNIQUE CLUSTERED INDEX IDX_V1   
    ON ProductPositionLog_1 (PositionId, ProductId);  
GO  

CREATE VIEW ProductPositionLog_2
WITH SCHEMABINDING  
AS  
   SELECT FromPositionId AS PositionId, SUM(Quantity) AS Quantity, ProductId, COUNT_BIG(*) CNT
    FROM dbo.ProductPositionLog
    WHERE FromPositionId <> 0
    GROUP BY FromPositionId, ProductId
GO  

CREATE UNIQUE CLUSTERED INDEX IDX_V2   
    ON ProductPositionLog_2 (PositionId, ProductId);  
GO  

সূচিযুক্ত দর্শন ব্যতীত ক্যোরিটি আমার মেশিনে শেষ হতে প্রায় 2.7 সেকেন্ড সময় নেয়। সিরিয়ালটিতে আমার চালনা বাদে আমি আপনার কাছে অনুরূপ পরিকল্পনা পেয়েছি:

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

আমি বিশ্বাস করি যে আপনাকে সূচিত ভিউগুলি NOEXPANDইঙ্গিতটির সাথে জিজ্ঞাসা করতে হবে কারণ আপনি এন্টারপ্রাইজ সংস্করণে নেই। এটি করার একটি উপায় এখানে:

WITH t AS
(
    SELECT PositionId, Quantity, ProductId 
    FROM ProductPositionLog_1 WITH (NOEXPAND)
    UNION ALL
    SELECT PositionId, Quantity, ProductId 
    FROM ProductPositionLog_2 WITH (NOEXPAND)
)
SELECT t.ProductId, t.PositionId, SUM(t.Quantity) AS Quantity
FROM t
GROUP BY t.ProductId, t.PositionId
HAVING SUM(t.Quantity) > 0;

এই ক্যোয়ারির একটি সহজ পরিকল্পনা রয়েছে এবং আমার মেশিনে 400 এমএসের নীচে শেষ:

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

সর্বোত্তম অংশটি হ'ল আপনাকে এমন কোনও অ্যাপ্লিকেশন কোড পরিবর্তন করতে হবে না যা ProductPositionLogটেবিলে ডেটা লোড করে । আপনাকে কেবল যাচাই করতে হবে যে সূচীকরণের দর্শনটির জুটির DML ওভারহেড গ্রহণযোগ্য।


2

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

IF NOT EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[ProductPositionLog]') AND type in (N'U'))
BEGIN
CREATE TABLE [dbo].[ProductPositionLog] (
[LogId] int IDENTITY(1, 1) NOT NULL PRIMARY KEY,
[ProductId] int NULL,
[FromPositionId] int NULL,
[ToPositionId] int NULL,
[Date] datetime NULL,
[Quantity] int NULL
)
END;
GO

SET IDENTITY_INSERT [ProductPositionLog] ON

INSERT INTO [ProductPositionLog] ([LogId], [ProductId], [FromPositionId], [ToPositionId], [Date], [Quantity])
VALUES (1, 123, 0, 1, '2018-01-01 08:10:22', 5)
INSERT INTO [ProductPositionLog] ([LogId], [ProductId], [FromPositionId], [ToPositionId], [Date], [Quantity])
VALUES (2, 123, 0, 2, '2018-01-03 15:15:10', 9)
INSERT INTO [ProductPositionLog] ([LogId], [ProductId], [FromPositionId], [ToPositionId], [Date], [Quantity])
VALUES (3, 123, 1, 3, '2018-01-07 21:08:56', 3)
INSERT INTO [ProductPositionLog] ([LogId], [ProductId], [FromPositionId], [ToPositionId], [Date], [Quantity])
VALUES (4, 123, 3, 0, '2018-02-09 10:03:23', 2)
INSERT INTO [ProductPositionLog] ([LogId], [ProductId], [FromPositionId], [ToPositionId], [Date], [Quantity])
VALUES (5, 123, 2, 3, '2018-02-09 10:03:23', 4)
SET IDENTITY_INSERT [ProductPositionLog] OFF

GO

INSERT INTO ProductPositionLog
SELECT ProductId + 1,
  FromPositionId + CASE WHEN FromPositionId = 0 THEN 0 ELSE 1 END,
  ToPositionId + CASE WHEN ToPositionId = 0 THEN 0 ELSE 1 END,
  [Date], Quantity
FROM ProductPositionLog
GO 20

-- Henrik's original solution.
WITH t AS
(
    SELECT ToPositionId AS PositionId, SUM(Quantity) AS Quantity, ProductId 
    FROM ProductPositionLog
    GROUP BY ToPositionId, ProductId
    UNION
    SELECT FromPositionId AS PositionId, -SUM(Quantity) AS Quantity, ProductId 
    FROM ProductPositionLog
    GROUP BY FromPositionId, ProductId
)
SELECT t.ProductId, t.PositionId, SUM(t.Quantity) AS Quantity
FROM t
WHERE NOT t.PositionId = 0
GROUP BY t.ProductId, t.PositionId
HAVING SUM(t.Quantity) > 0
GO

-- Same results via unpivot
SELECT ProductId, PositionId,
  SUM(CAST(TransferType AS INT) * Quantity) AS Quantity
FROM   
   (SELECT ProductId, Quantity, FromPositionId AS [-1], ToPositionId AS [1]
   FROM ProductPositionLog) p  
  UNPIVOT  
     (PositionId FOR TransferType IN 
        ([-1], [1])
  ) AS unpvt
WHERE PositionId <> 0
GROUP BY ProductId, PositionId

চেকপয়েন্টগুলি যতদূর যায় এটি আমার কাছে যুক্তিসঙ্গত ধারণা বলে মনে হয়। যেহেতু আপনি বলেছেন যে আপডেটগুলি এবং মুছে ফেলাগুলি সত্যই বিরল, তাই আমি ProductPositionLogআপডেট এবং মুছে ফেলার সেই আগুনের উপর একটি ট্রিগার যুক্ত করব এবং এটি চেকপয়েন্ট টেবিলটি যথাযথভাবে সামঞ্জস্য করে। এবং কেবল অতিরিক্ত নিশ্চিত হওয়ার জন্য, আমি মাঝে মাঝে স্ক্র্যাচ থেকে চেকপয়েন্ট এবং ক্যাশে টেবিলগুলি পুনরায় গণনা করব।


আপনার পরীক্ষার জন্য আপনাকে ধন্যবাদ! আমি আমার প্রশ্নের উপরে যেমন মন্তব্য করেছি আমি আমার প্রশ্নে এই ফাঁসির সময় লিখেছি (এই নির্দিষ্ট প্রশ্নের জন্য) এটি প্রায় 10 সেকেন্ডের কাছাকাছি। তবুও, এটি আপনার পরীক্ষাগুলির তুলনায় কিছুটা বেশি I আমি অনুমান করি এটি ব্লক করার কারণে বা এর মতো কিছু হতে পারে। আমার চেকপয়েন্ট সিস্টেমটির কারণ হ'ল সার্ভারে লোড হ্রাস করা এবং লগ বাড়ার সাথে সাথে পারফরম্যান্সটি ভাল থাকে তা নিশ্চিত করার একটি উপায় এটি। আপনি যদি দেখতে চান তবে আমি উপরে একটি কোয়েরি প্ল্যান জমা দিয়েছি। ধন্যবাদ।
হেনরিক
আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.