MySQL - วิธีที่เร็วที่สุดในการเปลี่ยนตารางสำหรับ InnoDB


12

ฉันมีตาราง InnoDB ที่ฉันต้องการแก้ไข ตารางมีแถว ~ 80M และออกจากไม่กี่ดัชนี

ฉันต้องการเปลี่ยนชื่อของคอลัมน์ใดคอลัมน์หนึ่งและเพิ่มดัชนีอีกสองสามรายการ

  • วิธีที่เร็วที่สุดที่จะทำคืออะไร (สมมติว่าฉันสามารถประสบแม้กระทั่งการหยุดทำงาน - เซิร์ฟเวอร์เป็นทาสที่ไม่ได้ใช้)?
  • คำว่า "ธรรมดา" alter tableคือทางออกที่เร็วที่สุดหรือไม่?

ในเวลานี้สิ่งที่ฉันสนใจคือความเร็ว :)


โปรดSHOW CREATE TABLE tblname\Gแสดงคอลัมน์ที่จำเป็นต้องเปลี่ยนประเภทข้อมูลของคอลัมน์และชื่อใหม่สำหรับคอลัมน์
RolandoMySQLDBA

นี่คือ: pastie.org/3078349คอลัมน์ที่ต้องเปลี่ยนชื่อsent_atและเพิ่มดัชนีอีกสองสามข้อ
Ran

sent_at ต้องเปลี่ยนชื่อเป็นอะไร
RolandoMySQLDBA

สมมติว่า: new_sent_at
Ran

คำตอบ:


14

วิธีหนึ่งที่แน่นอนในการเพิ่มความเร็วใน ALTER TABLE คือการลบดัชนีที่ไม่จำเป็นออก

นี่คือขั้นตอนเริ่มต้นในการโหลดเวอร์ชันใหม่ของตาราง

CREATE TABLE s_relations_new LIKE s_relations;
#
# Drop Duplicate Indexes
#
ALTER TABLE s_relations_new
    DROP INDEX source_persona_index,
    DROP INDEX target_persona_index,
    DROP INDEX target_persona_relation_type_index
;

โปรดทราบสิ่งต่อไปนี้:

  • ฉันปล่อย source_persona_index เนื่องจากเป็นคอลัมน์แรกในดัชนีอื่น ๆ 4 รายการ

    • unique_target_persona
    • unique_target_object
    • source_and_target_object_index
    • source_target_persona_index
  • ฉันทำ target_persona_index ลดลงเพราะเป็นคอลัมน์แรกในดัชนีอีก 2 รายการ

    • target_persona_relation_type_index
    • target_persona_relation_type_message_id_index
  • ฉันลดเป้าหมาย target_persona_relation_type_index เพราะ 2 คอลัมน์แรกยังอยู่ใน target_persona_relation_type_message_id_index

ตกลงนั่นคือการดูแลดัชนีที่ไม่จำเป็น มีดัชนีใดบ้างที่มีภาวะหัวใจต่ำ นี่คือวิธีการตรวจสอบว่า:

เรียกใช้แบบสอบถามต่อไปนี้:

SELECT COUNT(DISTINCT sent_at)               FROM s_relations;
SELECT COUNT(DISTINCT message_id)            FROM s_relations;
SELECT COUNT(DISTINCT target_object_id)      FROM s_relations;

ตามคำถามของคุณมีแถวประมาณ 80,000,000 แถว โดยทั่วไปแล้ว MySQL Query Optimizer จะไม่ใช้ดัชนีหากความสำคัญของคอลัมน์ที่เลือกมีค่ามากกว่า 5% ของจำนวนแถวของตาราง ในกรณีนี้จะเท่ากับ 4,000,000

  • ถ้าCOUNT(DISTINCT sent_at)> 4,000,000
    • แล้วก็ ALTER TABLE s_relations_new DROP INDEX sent_at_index;
  • ถ้าCOUNT(DISTINCT message_id)> 4,000,000
    • แล้วก็ ALTER TABLE s_relations_new DROP INDEX message_id_index;
  • ถ้าCOUNT(DISTINCT target_object_id)> 4,000,000
    • แล้วก็ ALTER TABLE s_relations_new DROP INDEX target_object_index;

เมื่อพิจารณาถึงประโยชน์หรือความไร้ประโยชน์ของดัชนีเหล่านั้นแล้วคุณสามารถโหลดข้อมูลใหม่ได้

#
# Change the Column Name
# Load the Table
#
ALTER TABLE s_relations_new CHANGE sent_at sent_at_new int(11) DEFAULT NULL;
INSERT INTO s_relations_new SELECT * FROM s_relations;

ใช่ไหม ไม่มี !!!

หากเว็บไซต์ของคุณใช้งานไม่ได้ตลอดเวลาอาจมีการแทรก INSERTs กับ s_relations ระหว่างการโหลด s_relations_new คุณจะเรียกคืนแถวที่หายไปเหล่านั้นได้อย่างไร

ไปหา id สูงสุดใน s_relations_new และต่อท้ายทุกอย่างหลังจาก ID นั้นจาก s_relations เพื่อให้มั่นใจว่าตารางถูกตรึงและใช้สำหรับการอัปเดตนี้เท่านั้นคุณต้องมีเวลาหยุดทำงานเล็กน้อยเพื่อรับแถวสุดท้ายที่แทรกเข้าไปใน s_relation_new นี่คือสิ่งที่คุณทำ:

ในระบบปฏิบัติการรีสตาร์ท mysql เพื่อให้ไม่มีใครสามารถเข้าสู่ระบบได้ แต่รูท @ localhost (ปิดใช้งาน TCP / IP):

$ service mysql restart --skip-networking

ถัดไปเข้าสู่ mysql และโหลดแถวสุดท้าย:

mysql> SELECT MAX(id) INTO @maxidnew FROM s_relations_new;
mysql> INSERT INTO s_relations_new SELECT * FROM s_relations WHERE id > @maxidnew;
mysql> ALTER TABLE s_relations RENAME s_relations_old;
mysql> ALTER TABLE s_relations_new RENAME s_relations;

จากนั้นเริ่ม mysql ใหม่ตามปกติ

$ service mysql restart

ตอนนี้ถ้าคุณไม่สามารถลบ mysql ได้คุณจะต้องทำการเปลี่ยนเหยื่อและเปลี่ยนเป็น s_relations เพียงเข้าสู่ mysql และทำสิ่งต่อไปนี้:

mysql> ALTER TABLE s_relations RENAME s_relations_old;
mysql> SELECT MAX(id) INTO @maxidnew FROM s_relations_new;
mysql> INSERT INTO s_relations_new SELECT * FROM s_relations_old WHERE id > @maxidnew;
mysql> ALTER TABLE s_relations_new RENAME s_relations;

ให้มันลอง !!!

ถ้ำ: เมื่อคุณพอใจกับการดำเนินการนี้แล้วคุณสามารถวางโต๊ะเก่าตามความสะดวกได้เร็วที่สุด:

mysql> DROP TABLE s_relations_old;

12

คำตอบที่ถูกต้องขึ้นอยู่กับรุ่นของเอ็นจิน MySQL ที่คุณใช้

หากใช้ 5.6+ การเปลี่ยนชื่อและเพิ่ม / ลบดัชนีจะดำเนินการออนไลน์เช่นโดยไม่ต้องคัดลอกข้อมูลทั้งหมดของตาราง

เพียงใช้ALTER TABLEตามปกติมันจะเป็นส่วนใหญ่ทันทีสำหรับการเปลี่ยนชื่อและดัชนีลดลงและเร็วพอสมควรสำหรับการเพิ่มดัชนี (เร็วเท่ากับการอ่านตารางทั้งหมดครั้งเดียว)

หากใช้ 5.1+ และเปิดใช้งานปลั๊กอิน InnoDB การเพิ่ม / ลบดัชนีจะออนไลน์เช่นกัน ไม่แน่ใจเกี่ยวกับการเปลี่ยนชื่อ

หากใช้เวอร์ชั่นเก่ากว่าALTER TABLEก็ยังเร็วที่สุด - แต่อาจช้าลงอย่างน่ากลัวเพราะข้อมูลทั้งหมดของคุณจะถูกแทรกเข้าไปในตารางชั่วคราวภายใต้ประทุน

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

โดยทั่วไปแล้ว MySQL Query Optimizer จะไม่ใช้ดัชนีหากความสำคัญของคอลัมน์ที่เลือกมีค่ามากกว่า 5% ของจำนวนแถวของตาราง

มันเป็นจริงรอบวิธีอื่น

ดัชนีมีประโยชน์ในการเลือกแถวไม่กี่แถวดังนั้นสิ่งสำคัญคือดัชนีเหล่านี้มีความสำคัญเชิงสูงซึ่งหมายถึงค่าที่แตกต่างจำนวนมากและไม่กี่แถวทางสถิติด้วยค่าเดียวกัน


ลิงก์ไปยังเอกสารประกอบปลั๊กอินของ InnoDB (ไม่สามารถวางเนื่องจากข้อ จำกัด ตัวแทน)
mezis

2
ใน MySQL 5.5 ฉันพบRENAME TABLEทันที (ตามที่คาดไว้) แต่การCHANGE COLUMNเปลี่ยนชื่อคีย์หลักนั้นได้ทำสำเนาเต็มรูปแบบ ... 7 ชั่วโมง! อาจเป็นเพราะเป็นคีย์หลักเท่านั้น ไม่ดี.
KCD

2

ฉันมีปัญหาเดียวกันกับ Maria DB 10.1.12 จากนั้นหลังจากอ่านเอกสารฉันพบว่ามีตัวเลือกในการดำเนินการ "แบบแทนที่" ซึ่งกำจัดสำเนาของตาราง ด้วยตัวเลือกนี้ตารางการเปลี่ยนแปลงนั้นเร็วมาก ในกรณีของฉันมันเป็น:

alter table user add column (resettoken varchar(256),
  resettoken_date date, resettoken_count int), algorithm=inplace;

เร็วมาก หากไม่มีตัวเลือกอัลกอริทึมมันจะไม่ยุติ

https://mariadb.com/kb/en/mariadb/alter-table/


0

สำหรับการเปลี่ยนชื่อคอลัมน์

ALTER TABLE tablename CHANGE columnname newcolumnname datatype;

ควรจะดีและไม่หยุดทำงานใด ๆ

สำหรับดัชนีคำสั่ง CREATE INDEX จะล็อคตาราง หากเป็นทาสที่ไม่ได้ใช้ดังที่กล่าวไว้นั่นไม่ใช่ปัญหา

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

BEGIN TRAN;
ALTER TABLE RENAME tablename tablenameold;
ALTER TABLE RENAME newtablename tablename;
DROP TABLE tablenameold;
COMMIT TRAN;

วิธีนี้จะช่วยลดการหยุดทำงานด้วยค่าใช้จ่ายชั่วคราวโดยใช้พื้นที่เป็นสองเท่า


1
DDL ใน MySQL ไม่ใช่ธุรกรรม แต่ละคำสั่ง DDL เรียกใช้ COMMIT ฉันเขียนเกี่ยวกับสิ่งนี้: dba.stackexchange.com/a/36799/877
RolandoMySQLDBA

0

ฉันมีปัญหานี้ด้วยและฉันใช้ SQL นี้:

/*on créé la table COPY SANS les nouveaux champs et SANS les FKs */
CREATE TABLE IF NOT EXISTS prestations_copy LIKE prestations;

/* on supprime les FKs de la table actuelle */
ALTER TABLE `prestations`
DROP FOREIGN KEY `fk_prestations_pres_promos`,
DROP FOREIGN KEY `fk_prestations_activites`;

/* on remet les FKs sur la table copy */
ALTER TABLE prestations_copy 
    ADD CONSTRAINT `fk_prestations_activites` FOREIGN KEY (`act_id`) REFERENCES `activites` (`id`) ON UPDATE NO ACTION ON DELETE NO ACTION,
    ADD CONSTRAINT `fk_prestations_pres_promos` FOREIGN KEY (`presp_id`) REFERENCES `pres_promos` (`id`) ON UPDATE NO ACTION ON DELETE NO ACTION;

/* On fait le transfert des données de la table actuelle vers la copy, ATTENTION: il faut le même nombre de colonnes */
INSERT INTO prestations_copy
SELECT * FROM prestations;

/* On modifie notre table copy de la façon que l'on souhaite */
ALTER TABLE `prestations_copy`
    ADD COLUMN `seo_mot_clef` VARCHAR(50) NULL;

/* on supprime la table actuelle et renome la copy avec le bon nom de table */
SET FOREIGN_KEY_CHECKS=0;
DROP TABLE prestations;
RENAME TABLE prestations_copy TO prestations;
SET FOREIGN_KEY_CHECKS=1;   

ฉันหวังว่ามันจะช่วยให้ใครบางคน

ความนับถือ,

จะ

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