ในขณะที่ฉันเห็นด้วยกับผู้แสดงความคิดเห็นคนอื่นว่านี่เป็นปัญหาที่มีราคาแพง แต่ฉันคิดว่ามีพื้นที่สำหรับการปรับปรุงมากมายโดยการปรับแต่ง SQL ที่คุณใช้อยู่ เพื่อแสดงให้เห็นว่าฉันสร้างชุดข้อมูลปลอมที่มีชื่อ 15 มม. และวลี 3K วิ่งตามแนวทางเก่าและวิ่งเข้าหาแนวทางใหม่
สคริปต์แบบเต็มเพื่อสร้างชุดข้อมูลปลอมและลองใช้วิธีการใหม่
TL; DR
บนเครื่องของฉันและชุดข้อมูลปลอมนี้วิธีดั้งเดิมใช้เวลาประมาณ 4 ชั่วโมงในการทำงาน วิธีการใหม่ที่นำเสนอนี้ใช้เวลาประมาณ 10 นาทีซึ่งเป็นการปรับปรุงที่สำคัญ นี่เป็นบทสรุปโดยย่อของวิธีการที่นำเสนอ:
- สำหรับแต่ละชื่อสร้างซับสตริงเริ่มต้นที่แต่ละอักขระออฟเซ็ต (และต่อยอดที่ความยาวของวลีที่ไม่ดีที่สุดที่ยาวที่สุดเป็นการปรับให้เหมาะสม)
- สร้างดัชนีคลัสเตอร์บนสตริงย่อยเหล่านี้
- สำหรับวลีที่ไม่ดีแต่ละข้อให้ค้นหาในสตริงย่อยเหล่านี้เพื่อระบุการจับคู่ใด ๆ
- สำหรับแต่ละสตริงต้นฉบับให้คำนวณจำนวนวลีที่ไม่เหมาะสมที่ตรงกับสตริงย่อยหนึ่งรายการหรือมากกว่านั้น
วิธีการดั้งเดิม: การวิเคราะห์อัลกอริทึม
จากแผนของUPDATE
คำแถลงเดิมเราจะเห็นได้ว่าปริมาณงานเป็นสัดส่วนเชิงเส้นตรงทั้งจำนวนชื่อ (15 มม.) และจำนวนวลี (3K) ดังนั้นถ้าเราคูณทั้งจำนวนชื่อและวลีด้วย 10 เวลารันไทม์โดยรวมจะช้ากว่า ~ 100 เท่า
แบบสอบถามเป็นสัดส่วนจริงกับความยาวของname
เช่นกัน ในขณะที่นี่เป็นบิตที่ซ่อนอยู่ในแผนแบบสอบถามมันมาใน "จำนวนการประหารชีวิต" สำหรับการค้นหาลงในตารางเก็บพัก ในแผนจริงเราจะเห็นว่านี้เกิดขึ้นไม่ได้เป็นเพียงหนึ่งครั้งต่อหนึ่งแต่จริงๆแล้วหนึ่งครั้งต่อตัวละครชดเชยภายในname
name
ดังนั้นวิธีนี้คือ O ( # names
* # phrases
* name length
) ในความซับซ้อนของเวลาทำงาน
วิธีการใหม่: รหัส
รหัสนี้ยังมีอยู่ในpastebinแบบเต็มแต่ฉันได้คัดลอกไว้ที่นี่เพื่อความสะดวก Pastebin ยังมีคำจำกัดความขั้นตอนแบบเต็มซึ่งรวมถึง@minId
และ@maxId
ตัวแปรที่คุณเห็นด้านล่างเพื่อกำหนดขอบเขตของชุดปัจจุบัน
-- For each name, generate the string at each offset
DECLARE @maxBadPhraseLen INT = (SELECT MAX(LEN(phrase)) FROM Bad_Phrase)
SELECT s.id, sub.sub_name
INTO #SubNames
FROM (SELECT * FROM SourceTable WHERE id BETWEEN @minId AND @maxId) s
CROSS APPLY (
-- Create a row for each substring of the name, starting at each character
-- offset within that string. For example, if the name is "abcd", this CROSS APPLY
-- will generate 4 rows, with values ("abcd"), ("bcd"), ("cd"), and ("d"). In order
-- for the name to be LIKE the bad phrase, the bad phrase must match the leading X
-- characters (where X is the length of the bad phrase) of at least one of these
-- substrings. This can be efficiently computed after indexing the substrings.
-- As an optimization, we only store @maxBadPhraseLen characters rather than
-- storing the full remainder of the name from each offset; all other characters are
-- simply extra space that isn't needed to determine whether a bad phrase matches.
SELECT TOP(LEN(s.name)) SUBSTRING(s.name, n.n, @maxBadPhraseLen) AS sub_name
FROM Numbers n
ORDER BY n.n
) sub
-- Create an index so that bad phrases can be quickly compared for a match
CREATE CLUSTERED INDEX IX_SubNames ON #SubNames (sub_name)
-- For each name, compute the number of distinct bad phrases that match
-- By "match", we mean that the a substring starting from one or more
-- character offsets of the overall name starts with the bad phrase
SELECT s.id, COUNT(DISTINCT b.phrase) AS bad_count
INTO #tempBadCounts
FROM dbo.Bad_Phrase b
JOIN #SubNames s
ON s.sub_name LIKE b.phrase + '%'
GROUP BY s.id
-- Perform the actual update into a "bad_count_new" field
-- For validation, we'll compare bad_count_new with the originally computed bad_count
UPDATE s
SET s.bad_count_new = COALESCE(b.bad_count, 0)
FROM dbo.SourceTable s
LEFT JOIN #tempBadCounts b
ON b.id = s.id
WHERE s.id BETWEEN @minId AND @maxId
วิธีการใหม่: แผนแบบสอบถาม
ก่อนอื่นเราสร้างซับสตริงเริ่มต้นที่ตัวละครแต่ละตัว
จากนั้นสร้างดัชนีคลัสเตอร์บนสตริงย่อยเหล่านี้
ทีนี้สำหรับวลีที่ไม่ดีแต่ละครั้งที่เราค้นหาในสตริงย่อยเหล่านี้เพื่อระบุการแข่งขันใด ๆ จากนั้นเราจะคำนวณจำนวนวลีที่ไม่เหมาะสมที่ตรงกับสตริงย่อยหนึ่งรายการหรือมากกว่านั้น นี่เป็นขั้นตอนสำคัญจริงๆ เนื่องจากวิธีการที่เราจัดทำดัชนีสารตั้งต้นเราไม่จำเป็นต้องตรวจสอบผลิตภัณฑ์ที่มีวลีและชื่อที่ไม่เหมาะสมทั้งหมด ขั้นตอนนี้ซึ่งทำการคำนวณจริงคิดเป็นเพียงประมาณ 10% ของเวลาทำงานจริง (ที่เหลือคือการประมวลผลล่วงหน้าของสตริงย่อย)
ท้ายสุดให้ดำเนินการตามคำสั่งการอัพเดทจริงโดยใช้ a LEFT OUTER JOIN
เพื่อกำหนดจำนวน 0 ให้กับชื่อใด ๆ ที่เราไม่พบวลีที่ไม่ดี
วิธีการใหม่: การวิเคราะห์อัลกอริทึม
วิธีการใหม่สามารถแบ่งออกเป็นสองขั้นตอนก่อนการประมวลผลและการจับคู่ ลองกำหนดตัวแปรต่อไปนี้:
N
= # ของชื่อ
B
= # ของวลีที่ไม่ดี
L
= ความยาวชื่อเฉลี่ยเป็นตัวอักษร
ขั้นตอนการประมวลผลล่วงหน้าคือO(N*L * LOG(N*L))
การสร้างN*L
สตริงย่อยจากนั้นเรียงลำดับ
การจับคู่ที่แท้จริงคือO(B * LOG(N*L))
การค้นหาวัสดุพิมพ์สำหรับแต่ละวลีที่ไม่ดี
ด้วยวิธีนี้เราได้สร้างอัลกอริทึมที่ไม่ได้ปรับขนาดเชิงเส้นตามจำนวนวลีที่ไม่ดีปลดล็อคประสิทธิภาพที่สำคัญเมื่อเราปรับขนาดเป็น 3K วลีและอื่น ๆ กล่าวอีกวิธีหนึ่งการใช้งานดั้งเดิมนั้นจะใช้เวลาประมาณ 10 เท่าหากเราเปลี่ยนจากวลีที่ไม่ดี 300 วลีเป็นวลีไม่ดี 3K ในทำนองเดียวกันมันจะใช้เวลา 10 เท่าอีกต่อไปหากเราต้องเปลี่ยนจากวลี 3K ไปเป็น 30K อย่างไรก็ตามการใช้งานใหม่จะเพิ่มขนาดย่อยเชิงเส้นและในความเป็นจริงนั้นใช้เวลาน้อยกว่า 2x ในการวัดวลีที่ไม่ดี 3K เมื่อปรับขนาดวลีที่ไม่ดี 30K
สมมติฐาน / Caveats
- ฉันแบ่งงานโดยรวมออกเป็นแบทช์ที่มีขนาดพอประมาณ นี่อาจเป็นความคิดที่ดีสำหรับแนวทางใดวิธีหนึ่ง แต่เป็นสิ่งสำคัญอย่างยิ่งสำหรับวิธีการใหม่เพื่อให้การตั้งค่า
SORT
บนวัสดุพิมพ์แยกกันสำหรับแต่ละชุดและง่ายในหน่วยความจำ คุณสามารถจัดการขนาดแบทช์ได้ตามต้องการ แต่ไม่ควรลองแถว 15 มม. ทั้งหมดในแบทช์เดียว
- ฉันใช้ SQL 2014 ไม่ใช่ SQL 2005 เนื่องจากฉันไม่มีสิทธิ์เข้าถึงเครื่อง SQL 2005 ฉันระมัดระวังที่จะไม่ใช้ไวยากรณ์ใด ๆ ที่ไม่พร้อมใช้งานใน SQL 2005 แต่ฉันยังอาจได้รับประโยชน์จากคุณลักษณะการเขียนแบบช้าของ tempdbใน SQL 2012+ และคุณลักษณะSELECT INTO แบบขนานใน SQL 2014
- ความยาวของทั้งชื่อและวลีนั้นมีความสำคัญต่อวิธีการใหม่ ฉันสมมติว่าวลีที่ไม่ดีมักจะค่อนข้างสั้นเนื่องจากน่าจะตรงกับกรณีการใช้งานจริง ชื่อมีความยาวมากกว่าวลีที่ไม่ดี แต่ค่อนข้างจะสันนิษฐานว่าไม่ใช่ตัวละครนับพัน ฉันคิดว่านี่เป็นสมมติฐานที่ยุติธรรมและการตั้งชื่อให้นานขึ้นจะทำให้วิธีการเดิมของคุณช้าลงเช่นกัน
- การปรับปรุงบางส่วน (แต่ไม่มีที่ใกล้เคียงทั้งหมด) เนื่องจากความจริงที่ว่าวิธีการใหม่สามารถยกระดับความขนานได้อย่างมีประสิทธิภาพมากกว่าวิธีการแบบเก่า (ซึ่งทำงานแบบเธรดเดียว) ฉันใช้แล็ปท็อปแบบ quad core ดังนั้นจึงเป็นเรื่องดีที่มีวิธีการที่สามารถทำให้แกนประมวลผลเหล่านี้ใช้งานได้
โพสต์บล็อกที่เกี่ยวข้อง
แอรอนเบอร์ทรานด์สำรวจประเภทของการแก้ปัญหานี้ในรายละเอียดมากขึ้นในการโพสต์บล็อกของเขาวิธีการหนึ่งที่จะได้รับดัชนีแสวงหาตัวแทนชั้นนำ%