ฉันพยายามเพิ่มประสิทธิภาพส่วนหนึ่งของรหัสของฉันที่แทรกข้อมูลลงใน MySQL ฉันควรเชื่อมโยง INSERT เพื่อสร้าง INSERT หลายแถวขนาดใหญ่หนึ่งรายการหรือแทรก INSERT แยกหลายรายการเร็วขึ้นหรือไม่
ฉันพยายามเพิ่มประสิทธิภาพส่วนหนึ่งของรหัสของฉันที่แทรกข้อมูลลงใน MySQL ฉันควรเชื่อมโยง INSERT เพื่อสร้าง INSERT หลายแถวขนาดใหญ่หนึ่งรายการหรือแทรก INSERT แยกหลายรายการเร็วขึ้นหรือไม่
คำตอบ:
https://dev.mysql.com/doc/refman/8.0/en/insert-optimization.html
เวลาที่ใช้ในการแทรกแถวถูกกำหนดโดยปัจจัยต่อไปนี้โดยที่ตัวเลขแสดงสัดส่วนโดยประมาณ:
- เชื่อมต่อ: (3)
- การส่งข้อความค้นหาไปยังเซิร์ฟเวอร์: (2)
- การแยกวิเคราะห์แบบสอบถาม: (2)
- แทรกแถว: (1 ×ขนาดของแถว)
- การแทรกดัชนี: (1 ×จำนวนดัชนี)
- ปิด: (1)
จากนี้ควรจะเห็นได้ชัดว่าการส่งคำสั่งขนาดใหญ่หนึ่งคำจะช่วยให้คุณประหยัดค่าใช้จ่าย 7 คำสั่งต่อการแทรกซึ่งในการอ่านข้อความต่อไปก็กล่าวว่า:
หากคุณกำลังแทรกหลายแถวจากไคลเอนต์เดียวกันในเวลาเดียวกันให้ใช้คำสั่ง INSERT พร้อมกับรายการ VALUES หลายรายการเพื่อแทรกหลายแถวพร้อมกัน สิ่งนี้เร็วกว่ามาก (เร็วกว่าในบางกรณี) กว่าการใช้คำสั่ง INSERT แบบแถวเดี่ยวแยกกัน
ฉันรู้ว่าฉันตอบคำถามนี้เกือบสองปีครึ่งหลังจากที่มันถูกถาม แต่ฉันแค่อยากจะให้ข้อมูลบางอย่างหนักจากโครงการที่ผมทำงานในตอนนี้ที่แสดงให้เห็นว่าจริง ๆ แล้วการทำบล็อกมูลค่าหลายต่อแทรกมากเร็วกว่าคำสั่ง VALUE บล็อก INSERT เดี่ยวตามลำดับ
รหัสที่ฉันเขียนสำหรับเบนช์มาร์กนี้ใน C # ใช้ ODBC เพื่ออ่านข้อมูลในหน่วยความจำจากแหล่งข้อมูล MSSQL (ประมาณ 19,000 แถวทั้งหมดจะถูกอ่านก่อนที่จะเริ่มเขียน) และ MySql .NET (Mysql.Data. *) แทรกข้อมูลจากหน่วยความจำลงในตารางบนเซิร์ฟเวอร์ MySQL ผ่านคำสั่งที่เตรียมไว้ มันถูกเขียนในลักษณะที่อนุญาตให้ฉันปรับจำนวนบล็อกแบบไดนามิกต่อ INSERT ที่เตรียมไว้ (เช่นแทรก n แถวในแต่ละครั้งที่ฉันสามารถปรับค่าของ n ก่อนเรียกใช้) ฉันยังได้ทำการทดสอบ หลายครั้งสำหรับแต่ละ n
การทำบล็อก VALUE เดียว (เช่นครั้งละ 1 แถว) ใช้เวลา 5.7 - 5.9 วินาทีในการเรียกใช้ ค่าอื่น ๆ มีดังนี้:
2 แถวต่อครั้ง: 3.5 - 3.5 วินาที
5 แถวต่อครั้ง: 2.2 - 2.2 วินาที
10 แถวต่อครั้ง: 1.7 - 1.7 วินาที
50 แถวต่อครั้ง: 1.17 - 1.18 วินาที
100 ครั้งต่อแถว: 1.1 - 1.4 วินาที
500 แถวต่อครั้ง: 1.1 - 1.2 วินาที
ในแต่ละครั้ง 1,000 แถว: 1.17 - 1.17 วินาที
ดังนั้นใช่แม้กระทั่งการรวม 2 หรือ 3 การเขียนเข้าด้วยกันก็ช่วยเพิ่มความเร็วได้อย่างมาก (รันไทม์ตัดด้วยตัวคูณ n) จนกระทั่งคุณไปถึงที่ใดที่หนึ่งระหว่าง n = 5 และ n = 10 ซึ่งเป็นจุดที่การปรับปรุงลดลงอย่างชัดเจน และบางแห่งในช่วง n = 10 ถึง n = 50 การปรับปรุงมีน้อยมาก
หวังว่าจะช่วยให้ผู้ใช้ตัดสินใจ (a) ว่าจะใช้แนวคิดหลายรายการและ (b) จำนวน VALUE บล็อกที่จะสร้างต่อคำสั่ง (สมมติว่าคุณต้องการทำงานกับข้อมูลที่อาจมีขนาดใหญ่พอที่จะส่งแบบสอบถามผ่านขนาดแบบสอบถามสูงสุด สำหรับ MySQL ซึ่งฉันเชื่อว่าเป็น 16MB เป็นค่าเริ่มต้นในหลาย ๆ แห่งอาจมีขนาดใหญ่ขึ้นหรือเล็กลงขึ้นอยู่กับค่าของชุด max_allowed_packet บนเซิร์ฟเวอร์)
ปัจจัยสำคัญคือว่าคุณกำลังใช้เอ็นจินการทำธุรกรรมหรือไม่และคุณเปิดออโต้คอมมิชชันไว้หรือไม่
การเปิดใช้งาน Autocommit เป็นค่าเริ่มต้นและคุณอาจต้องการเปิดไว้ ดังนั้นการแทรกแต่ละครั้งที่คุณทำธุรกรรมของตัวเอง ซึ่งหมายความว่าหากคุณแทรกหนึ่งครั้งต่อแถวคุณจะต้องทำธุรกรรมสำหรับแต่ละแถว
สมมติว่าเป็นเธรดเดี่ยวซึ่งหมายความว่าเซิร์ฟเวอร์ต้องการซิงค์ข้อมูลบางอย่างลงในดิสก์สำหรับทุก ๆ แถว ต้องรอข้อมูลเพื่อไปยังที่เก็บข้อมูลถาวร (หวังว่า ram ที่สำรองแบตเตอรี่ในคอนโทรลเลอร์ RAID ของคุณ) สิ่งนี้ค่อนข้างช้าและอาจกลายเป็นปัจจัย จำกัด ในกรณีเหล่านี้
ฉันแน่นอนสมมติว่าคุณกำลังใช้เครื่องมือการทำธุรกรรม (โดยทั่วไปคือ innodb) และคุณไม่ได้ปรับแต่งการตั้งค่าเพื่อลดความทนทาน
ฉันยังสมมติว่าคุณกำลังใช้เธรดเดียวเพื่อแทรกสิ่งเหล่านี้ การใช้หลายเธรด muddies เป็นบิตเพราะ MySQL บางรุ่นมีการทำงานเป็นกลุ่มใน innodb - ซึ่งหมายความว่าหลายเธรดที่ทำคอมมิทเองสามารถแชร์การเขียนหนึ่งครั้งในบันทึกธุรกรรมซึ่งดีเพราะมันหมายถึงการซิงค์น้อยลงไปยังที่เก็บข้อมูลถาวร .
ในทางกลับกันผลที่สุดคือคุณต้องการใช้เม็ดมีดหลายแถวจริงๆ
มีข้อ จำกัด มากกว่าที่จะได้รับการต่อต้าน แต่ในกรณีส่วนใหญ่จะมีแถวอย่างน้อย 10,000 แถว ดังนั้นหากคุณแบทช์ถึง 1,000 แถวคุณอาจปลอดภัย
หากคุณกำลังใช้ MyISAM มีสิ่งอื่นอีกมากมาย แต่ฉันจะไม่เบื่อคุณ ความสงบ.
ส่งเม็ดมีดจำนวนมากข้ามเส้นลวดในครั้งเดียวให้มากที่สุด ความเร็วในการแทรกที่แท้จริงควรเท่ากัน แต่คุณจะเห็นประสิทธิภาพที่เพิ่มขึ้นจากการลดค่าใช้จ่ายของเครือข่าย
โดยทั่วไปจำนวนการโทรไปยังฐานข้อมูลน้อยลงจะดีกว่า (หมายถึงเร็วกว่าและมีประสิทธิภาพมากกว่า) ดังนั้นให้ลองใส่รหัสในลักษณะที่ช่วยลดการเข้าถึงฐานข้อมูล โปรดจำไว้ว่าหากคุณไม่ใช้พูลการเชื่อมต่อการเข้าถึงฐานข้อมูลแต่ละครั้งจะต้องสร้างการเชื่อมต่อดำเนินการ sql จากนั้นจึงยกเลิกการเชื่อมต่อ ค่าใช้จ่ายค่อนข้างน้อย!
คุณอาจต้องการ:
ขึ้นอยู่กับว่าเซิร์ฟเวอร์ของคุณปรับขนาดได้ดีเพียงใด (ทำตามขั้นPostgreSQl
ตอนOracle
และMSSQL
) ให้ทำสิ่งด้านบนด้วยหลายเธรดและการเชื่อมต่อที่หลากหลาย
โดยทั่วไปแล้วการแทรกหลาย ๆ ครั้งจะช้าลงเนื่องจากค่าใช้จ่ายในการเชื่อมต่อ การทำเม็ดมีดหลายใบในครั้งเดียวจะช่วยลดต้นทุนค่าใช้จ่ายต่อเม็ดมีด
ขึ้นอยู่กับภาษาที่คุณใช้คุณสามารถสร้างแบทช์ในภาษาการเขียนโปรแกรม / สคริปต์ก่อนที่จะไปที่ db และเพิ่มแต่ละส่วนแทรกลงในแบทช์ จากนั้นคุณจะสามารถดำเนินการแบทช์ขนาดใหญ่โดยใช้การดำเนินการเชื่อมต่อเดียว นี่คือตัวอย่างใน Java
MYSQL 5.5 คำสั่ง SQL หนึ่งคำสั่งใช้เวลาประมาณ 300 ถึง ~ 450ms ในขณะที่สถิติด้านล่างใช้สำหรับการแทรกแบบแทรกหลายส่วน
(25492 row(s) affected)
Execution Time : 00:00:03:343
Transfer Time : 00:00:00:000
Total Time : 00:00:03:343
ฉันจะบอกว่า inline เป็นวิธีไป :)
มันไร้สาระที่ Mysql และ MariaDB ที่ไม่ดีจะได้รับการปรับให้เหมาะสมเมื่อมีการแทรก ฉันทดสอบ mysql 5.7 และ mariadb 10.3 ไม่แตกต่างกันจริง
ฉันได้ทดสอบสิ่งนี้บนเซิร์ฟเวอร์ที่มีดิสก์ NVME, 70,000 IOPS, ปริมาณการรับส่งข้อมูล 1.1 GB / วินาทีและนั่นเป็นเพล็กซ์เต็มรูปแบบ (อ่านและเขียน)
เซิร์ฟเวอร์เป็นเซิร์ฟเวอร์ที่มีประสิทธิภาพสูงเช่นกัน
ให้หน่วยความจำ 20 GB
ฐานข้อมูลว่างเปล่าอย่างสมบูรณ์
ความเร็วที่ฉันได้รับคือ 5,000 เม็ดต่อวินาทีเมื่อทำเม็ดมีดแบบหลายแถว (ทดลองกับข้อมูลขนาด 1MB สูงสุด 10MB)
ตอนนี้เบาะแส:
ถ้าฉันเพิ่มเธรดอื่นและแทรกลงในตารางเดียวกันฉันก็มี 2x5000 / วินาที อีกหนึ่งเธรดและฉันมี 15,000 ทั้งหมด / วินาที
พิจารณาสิ่งนี้: เมื่อทำการแทรกหนึ่งเธรดหมายความว่าคุณสามารถเขียนลงดิสก์ตามลำดับ (โดยมีข้อยกเว้นในการทำดัชนี) เมื่อใช้เธรดคุณจะลดประสิทธิภาพที่เป็นไปได้เพราะตอนนี้มันต้องทำการเข้าถึงแบบสุ่มมากขึ้น แต่การตรวจสอบความเป็นจริงแสดงให้เห็นว่า mysql ได้รับการปรับให้เหมาะสมอย่างมากซึ่งเธรดช่วยได้มาก
ประสิทธิภาพที่แท้จริงที่เป็นไปได้สำหรับเซิร์ฟเวอร์นี้อาจเป็นล้านต่อวินาที, CPU ไม่ได้ใช้งานดิสก์ไม่ได้ใช้งาน
เหตุผลค่อนข้างชัดเจนว่า mariadb เช่นเดียวกับ mysql มีความล่าช้าภายใน
id
, parent_id
) VALUES (1, NULL) หนึ่งในชุดของค่าถัดไปจะลิงก์ไปยังแถวนั้น หากคุณแยกเป็นชิ้น ๆ และชุดนั้นเข้าสู่อีกอันหนึ่งมันอาจถูกประมวลผลก่อนอันแรกซึ่งทำให้กระบวนการทั้งหมดล้มเหลว มีความคิดอย่างไรที่จะจัดการกับสิ่งนั้น?
เม็ดมีดหลายใบเร็วขึ้น แต่มีลายนูน thrik อื่นกำลังปิดใช้งานการ จำกัด การตรวจสอบ temprorary ทำให้การแทรกเร็วขึ้นมาก มันไม่สำคัญว่าโต๊ะของคุณจะมีหรือไม่ ตัวอย่างเช่นการทดสอบการปิดใช้งานคีย์ต่างประเทศและเพลิดเพลินกับความเร็ว:
SET FOREIGN_KEY_CHECKS=0;
offcourse คุณควรเปิดใหม่หลังจากแทรกโดย:
SET FOREIGN_KEY_CHECKS=1;
นี่เป็นวิธีทั่วไปในการแทรกข้อมูลขนาดใหญ่ ข้อมูลจำนวนเต็มอาจทำลายได้ดังนั้นคุณจึงต้องระมัดระวังก่อนที่จะปิดใช้งานการตรวจสอบคีย์ต่างประเทศ