สิ่งใดเร็วกว่า: INSERT เดียวหลายรายการหรือ INSERT หลายแถว


184

ฉันพยายามเพิ่มประสิทธิภาพส่วนหนึ่งของรหัสของฉันที่แทรกข้อมูลลงใน MySQL ฉันควรเชื่อมโยง INSERT เพื่อสร้าง INSERT หลายแถวขนาดใหญ่หนึ่งรายการหรือแทรก INSERT แยกหลายรายการเร็วขึ้นหรือไม่

คำตอบ:


287

https://dev.mysql.com/doc/refman/8.0/en/insert-optimization.html

เวลาที่ใช้ในการแทรกแถวถูกกำหนดโดยปัจจัยต่อไปนี้โดยที่ตัวเลขแสดงสัดส่วนโดยประมาณ:

  • เชื่อมต่อ: (3)
  • การส่งข้อความค้นหาไปยังเซิร์ฟเวอร์: (2)
  • การแยกวิเคราะห์แบบสอบถาม: (2)
  • แทรกแถว: (1 ×ขนาดของแถว)
  • การแทรกดัชนี: (1 ×จำนวนดัชนี)
  • ปิด: (1)

จากนี้ควรจะเห็นได้ชัดว่าการส่งคำสั่งขนาดใหญ่หนึ่งคำจะช่วยให้คุณประหยัดค่าใช้จ่าย 7 คำสั่งต่อการแทรกซึ่งในการอ่านข้อความต่อไปก็กล่าวว่า:

หากคุณกำลังแทรกหลายแถวจากไคลเอนต์เดียวกันในเวลาเดียวกันให้ใช้คำสั่ง INSERT พร้อมกับรายการ VALUES หลายรายการเพื่อแทรกหลายแถวพร้อมกัน สิ่งนี้เร็วกว่ามาก (เร็วกว่าในบางกรณี) กว่าการใช้คำสั่ง INSERT แบบแถวเดี่ยวแยกกัน


27
คำตอบนี้จะนำไปใช้ได้อย่างไรถ้า INSERT หลาย ๆ อันอยู่ในรายการฐานข้อมูลเดียวกัน
หยิก

2
จำนวนครั้งที่ฉันสามารถแทรกในแต่ละครั้งโดยใช้คำสั่งแทรกเดียว อนุญาตให้ฉันใส่ 10,000 แถวต่อครั้งหรือไม่
Naresh Ramoliya

10
@Pinch การใช้ธุรกรรมในขณะที่กำลังทำ ~ 1.5k upserts (แทรก / ปรับปรุง) ลดเวลาที่การดำเนินการใช้จาก ~ 1.5 วินาทีถึง ~ 0.2 วินาที หรือกล่าวอีกอย่างหนึ่งคือทำให้เร็วขึ้น 86% เมื่อเทียบกับเม็ดมีดแถวเดียว ประณาม.
fgblomqvist

1
หมายเหตุ: ดูเหมือนจะแตกต่างกันมากใน MSSQL: stackoverflow.com/questions/8635818/…
marsze

วิธีการเกี่ยวกับการใช้งบที่เตรียมไว้สำหรับการแทรกเม็ดมีดซ้ำหลาย ๆ อันซ้ำแล้วซ้ำอีก?
priyabagus

151

ฉันรู้ว่าฉันตอบคำถามนี้เกือบสองปีครึ่งหลังจากที่มันถูกถาม แต่ฉันแค่อยากจะให้ข้อมูลบางอย่างหนักจากโครงการที่ผมทำงานในตอนนี้ที่แสดงให้เห็นว่าจริง ๆ แล้วการทำบล็อกมูลค่าหลายต่อแทรกมากเร็วกว่าคำสั่ง 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 บนเซิร์ฟเวอร์)


1
คำขอให้ชี้แจง: คือเวลาของคุณ "วินาทีต่อแถว" หรือ "ยอดรวมวินาที"
EngrStudent

3
รวมวินาที - ดังนั้นวินาทีต่อแถวคือหารด้วย ~ 19,000 แถว แม้ว่าจะเป็นจำนวนน้อยดังนั้นบางทีแถว / วินาทีอาจเป็นตัวชี้วัดที่ดีกว่าหากคุณกำลังมองหาตัวเลขที่เปรียบเทียบได้ง่าย
Jon Kloske

บังเอิญมีบางตัวอย่างรหัส. NET สำหรับวิธีที่ฉันอธิบายข้างต้นในคำตอบที่เกี่ยวข้องของฉันนี้: stackoverflow.com/questions/25377357/…
Jon Kloske

18

ปัจจัยสำคัญคือว่าคุณกำลังใช้เอ็นจินการทำธุรกรรมหรือไม่และคุณเปิดออโต้คอมมิชชันไว้หรือไม่

การเปิดใช้งาน Autocommit เป็นค่าเริ่มต้นและคุณอาจต้องการเปิดไว้ ดังนั้นการแทรกแต่ละครั้งที่คุณทำธุรกรรมของตัวเอง ซึ่งหมายความว่าหากคุณแทรกหนึ่งครั้งต่อแถวคุณจะต้องทำธุรกรรมสำหรับแต่ละแถว

สมมติว่าเป็นเธรดเดี่ยวซึ่งหมายความว่าเซิร์ฟเวอร์ต้องการซิงค์ข้อมูลบางอย่างลงในดิสก์สำหรับทุก ๆ แถว ต้องรอข้อมูลเพื่อไปยังที่เก็บข้อมูลถาวร (หวังว่า ram ที่สำรองแบตเตอรี่ในคอนโทรลเลอร์ RAID ของคุณ) สิ่งนี้ค่อนข้างช้าและอาจกลายเป็นปัจจัย จำกัด ในกรณีเหล่านี้

ฉันแน่นอนสมมติว่าคุณกำลังใช้เครื่องมือการทำธุรกรรม (โดยทั่วไปคือ innodb) และคุณไม่ได้ปรับแต่งการตั้งค่าเพื่อลดความทนทาน

ฉันยังสมมติว่าคุณกำลังใช้เธรดเดียวเพื่อแทรกสิ่งเหล่านี้ การใช้หลายเธรด muddies เป็นบิตเพราะ MySQL บางรุ่นมีการทำงานเป็นกลุ่มใน innodb - ซึ่งหมายความว่าหลายเธรดที่ทำคอมมิทเองสามารถแชร์การเขียนหนึ่งครั้งในบันทึกธุรกรรมซึ่งดีเพราะมันหมายถึงการซิงค์น้อยลงไปยังที่เก็บข้อมูลถาวร .

ในทางกลับกันผลที่สุดคือคุณต้องการใช้เม็ดมีดหลายแถวจริงๆ

มีข้อ จำกัด มากกว่าที่จะได้รับการต่อต้าน แต่ในกรณีส่วนใหญ่จะมีแถวอย่างน้อย 10,000 แถว ดังนั้นหากคุณแบทช์ถึง 1,000 แถวคุณอาจปลอดภัย

หากคุณกำลังใช้ MyISAM มีสิ่งอื่นอีกมากมาย แต่ฉันจะไม่เบื่อคุณ ความสงบ.


1
มีเหตุผลใดบ้างที่จะได้รับการตอบโต้ที่เกิดขึ้นหลังจากจุดหนึ่ง? ฉันเคยเห็นมันเกิดขึ้นมาก่อน แต่ก็ไม่แน่ใจว่าทำไม
Dhruv Gairola

1
คุณทราบว่ามีจุดใดที่ทุกคนใน batching แทรก MySQL เมื่อใช้ธุรกรรม ฉันแค่สงสัยว่าถ้าฉันสามารถช่วยตัวเองปัญหาของการสร้างคำสั่ง SQL ที่มีมูลค่าหลายค่าถ้าห้องสมุดต้นแบบของฉัน (Java JDBC - mysql-connector-java-5.1.30) ไม่ได้กระทำจริงจนกว่าฉันจะบอก
RTF

@RTF ฉันคิดว่าคุณจะต้องทำการทดสอบขนาดเล็กเพื่อกำหนดพฤติกรรมนั้นในสถานการณ์ของคุณเนื่องจากมันมีการใช้งานที่เฉพาะเจาะจง แต่ในหลาย ๆ กรณีใช่ว่าการทำธุรกรรมควรให้ผลการดำเนินงานที่คล้ายกัน
จัสมิน Hegman

9

ส่งเม็ดมีดจำนวนมากข้ามเส้นลวดในครั้งเดียวให้มากที่สุด ความเร็วในการแทรกที่แท้จริงควรเท่ากัน แต่คุณจะเห็นประสิทธิภาพที่เพิ่มขึ้นจากการลดค่าใช้จ่ายของเครือข่าย


7

โดยทั่วไปจำนวนการโทรไปยังฐานข้อมูลน้อยลงจะดีกว่า (หมายถึงเร็วกว่าและมีประสิทธิภาพมากกว่า) ดังนั้นให้ลองใส่รหัสในลักษณะที่ช่วยลดการเข้าถึงฐานข้อมูล โปรดจำไว้ว่าหากคุณไม่ใช้พูลการเชื่อมต่อการเข้าถึงฐานข้อมูลแต่ละครั้งจะต้องสร้างการเชื่อมต่อดำเนินการ sql จากนั้นจึงยกเลิกการเชื่อมต่อ ค่าใช้จ่ายค่อนข้างน้อย!


จะทำอย่างไรถ้ามีการใช้การเชื่อมต่อแบบถาวร
dusoft

6
ยังคงมีค่าใช้จ่าย เวลาในการขนส่งเพียงอย่างเดียว (ไปและกลับจากการแทรกแต่ละครั้ง) จะสังเกตเห็นได้อย่างรวดเร็วหากคุณกำลังทำเม็ดมีดหลายพันเม็ด
RC

4

คุณอาจต้องการ:

  • ตรวจสอบว่าการยอมรับอัตโนมัติปิดอยู่
  • เปิดการเชื่อมต่อ
  • ส่งใบมีดหลายชุดในการทำรายการเดียว (ขนาดประมาณ 4000-10000 แถว? คุณเห็น)
  • ปิดการเชื่อมต่อ

ขึ้นอยู่กับว่าเซิร์ฟเวอร์ของคุณปรับขนาดได้ดีเพียงใด (ทำตามขั้นPostgreSQlตอนOracleและMSSQL) ให้ทำสิ่งด้านบนด้วยหลายเธรดและการเชื่อมต่อที่หลากหลาย


3

โดยทั่วไปแล้วการแทรกหลาย ๆ ครั้งจะช้าลงเนื่องจากค่าใช้จ่ายในการเชื่อมต่อ การทำเม็ดมีดหลายใบในครั้งเดียวจะช่วยลดต้นทุนค่าใช้จ่ายต่อเม็ดมีด

ขึ้นอยู่กับภาษาที่คุณใช้คุณสามารถสร้างแบทช์ในภาษาการเขียนโปรแกรม / สคริปต์ก่อนที่จะไปที่ db และเพิ่มแต่ละส่วนแทรกลงในแบทช์ จากนั้นคุณจะสามารถดำเนินการแบทช์ขนาดใหญ่โดยใช้การดำเนินการเชื่อมต่อเดียว นี่คือตัวอย่างใน Java


3

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 เป็นวิธีไป :)


0

มันไร้สาระที่ 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 มีความล่าช้าภายใน


@Craftables คุณต้องการการพัฒนาจากภายนอกไม่สามารถทำได้ภายใน mysql เธรดหมายความว่าคุณใช้การเชื่อมต่อหลายครั้งไปยังเซิร์ฟเวอร์คุณแบ่งแบบสอบถามเป็นหลายชิ้น (ตัวอย่างเช่นโดยแยกออกเป็นส่วน ๆ โดยใช้คีย์หลัก) ฉันจัดการเพื่อให้ได้มากถึง 10,000 เท่าของประสิทธิภาพโดยใช้วิธีนี้ในตารางที่มีขนาดใหญ่มาก ข้อความค้นหาที่ใช้เวลา 40,000 วินาทีสามารถเสร็จใน 2-3 นาทีถ้าคุณใช้หลายเธรดและ mysql ของคุณก็ปรับให้เหมาะสม
จอห์น

@ John ที่น่าสนใจและอาจมีแอปพลิเคชั่นที่ดีจริง ๆ ... แต่ ... หากคุณแยกแบบสอบถามออกเป็นหลายชิ้นคุณจะจัดการธุรกรรมได้อย่างไร และพิจารณาสถานการณ์สมมติต่อไปนี้: ตาราง x มีคอลัมน์ 'parent_id' ที่เกี่ยวข้องกับตาราง 'id' เดียวกัน อยู่ภายในข้อมูลของคุณคุณมี INSERT INTO x ( id, parent_id) VALUES (1, NULL) หนึ่งในชุดของค่าถัดไปจะลิงก์ไปยังแถวนั้น หากคุณแยกเป็นชิ้น ๆ และชุดนั้นเข้าสู่อีกอันหนึ่งมันอาจถูกประมวลผลก่อนอันแรกซึ่งทำให้กระบวนการทั้งหมดล้มเหลว มีความคิดอย่างไรที่จะจัดการกับสิ่งนั้น?
zozo

@zozo สิ่งนี้มีประโยชน์สำหรับการแทรกจำนวนมากและการสืบค้นจำนวนมาก ธุรกรรมจะทำลายประสิทธิภาพการทำงานเนื่องจากมีการบัฟเฟอร์ข้อมูลจำนวนมาก แต่คุณสามารถใช้ธุรกรรมในส่วนแทรกแบบหลายเธรดหรือแบบสอบถาม
John

-2

เม็ดมีดหลายใบเร็วขึ้น แต่มีลายนูน thrik อื่นกำลังปิดใช้งานการ จำกัด การตรวจสอบ temprorary ทำให้การแทรกเร็วขึ้นมาก มันไม่สำคัญว่าโต๊ะของคุณจะมีหรือไม่ ตัวอย่างเช่นการทดสอบการปิดใช้งานคีย์ต่างประเทศและเพลิดเพลินกับความเร็ว:

SET FOREIGN_KEY_CHECKS=0;

offcourse คุณควรเปิดใหม่หลังจากแทรกโดย:

SET FOREIGN_KEY_CHECKS=1;

นี่เป็นวิธีทั่วไปในการแทรกข้อมูลขนาดใหญ่ ข้อมูลจำนวนเต็มอาจทำลายได้ดังนั้นคุณจึงต้องระมัดระวังก่อนที่จะปิดใช้งานการตรวจสอบคีย์ต่างประเทศ


1
ไม่มีความคิดว่าทำไม ppl สนับสนุนสิ่งนี้ด้วยเหตุผลสองประการ: 1. มันไม่มีอะไรเกี่ยวข้องกับคำถาม 2. มันเป็นความคิดที่แย่จริงๆ (มีข้อยกเว้นเล็กน้อย - เช่นการทุ่มตลาดหรือการเปลี่ยนแปลงอุณหภูมิเชิงโครงสร้าง การตรวจสอบมีเหตุผล: มีการตรวจสอบเพื่อให้แน่ใจความสอดคล้องของข้อมูล สิ่งที่ทำให้ช้าลงนั้นเป็นเพราะพวกเขาให้แน่ใจว่าคุณไม่ได้แทรกหรือเปลี่ยนแปลงข้อมูลที่คุณไม่ควรทำ ลองปรับการสืบค้นให้ถูกวิธี ในสภาพแวดล้อมทางธุรกิจที่สำคัญนี่อาจหมายถึงการเสียชีวิตของแอพเนื่องจากไม่ว่าคุณจะระมัดระวังในเรื่องใดบ้างจะล้มเหลวในบางจุด
zozo

1
อาจ แต่ตัวเลือกนี้มีประสิทธิภาพอย่างมากในการนำเข้าตารางขนาดใหญ่และใช้งานได้จริงมากและอาจทำให้บางคนมีความคิดว่าพวกเขาสามารถทำให้การแทรกข้อมูลเร็วขึ้นได้อย่างไร
MSS
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.