سؤال

أنا مبتدئ نسبي عندما يتعلق الأمر بقواعد البيانات. نحن نستخدم MySQL وأحاول حاليًا تسريع عبارة SQL التي يبدو أنها تستغرق بعض الوقت. نظرت حولي على سؤال مماثل لكنني لم أجد سؤالًا.

الهدف من ذلك هو إزالة جميع الصفوف في الجدول A التي لها معرف مطابق في الجدول B.

أقوم حاليًا بما يلي:

DELETE FROM a WHERE EXISTS (SELECT b.id FROM b WHERE b.id = a.id);

هناك ما يقرب من 100 كيلو صف في الجدول أ وحوالي 22 كيلو صف في الجدول ب. العمود "معرف" هو PK لكلا الجدولين.

يستغرق هذا البيان حوالي 3 دقائق للتشغيل على مربع الاختبار الخاص بي - Pentium D ، XP SP3 ، 2GB RAM ، MySQL 5.0.67. هذا يبدو بطيئا بالنسبة لي. ربما ليس الأمر كذلك ، لكنني كنت آمل في تسريع الأمور. هل هناك طريقة أفضل/أسرع لإنجاز هذا؟


تعديل:

بعض المعلومات الإضافية التي قد تكون مفيدة. يحتوي الجدولان A و B على نفس الهيكل كما فعلت فيما يلي لإنشاء الجدول B:

CREATE TABLE b LIKE a;

يحتوي الجدول A (وبالتالي الجدول B) على عدد قليل من الفهارس للمساعدة في تسريع الاستعلامات التي يتم إجراؤها ضدها. مرة أخرى ، أنا مبتدئ نسبي في عمل DB وما زلت أتعلم. لا أعرف كم من التأثير ، إن وجد ، على الأشياء. أفترض أنه له تأثير حيث يجب تنظيف الفهارس أيضًا ، أليس كذلك؟ كنت أتساءل أيضًا عما إذا كانت هناك أي إعدادات DB أخرى قد تؤثر على السرعة.

أيضا ، أنا أستخدم Inno DB.


فيما يلي بعض المعلومات الإضافية التي قد تكون مفيدة لك.

يحتوي الجدول A على بنية مشابهة لهذا (لقد قمت بتطهير هذا قليلاً):

DROP TABLE IF EXISTS `frobozz`.`a`;
CREATE TABLE  `frobozz`.`a` (
  `id` bigint(20) unsigned NOT NULL auto_increment,
  `fk_g` varchar(30) NOT NULL,
  `h` int(10) unsigned default NULL,
  `i` longtext,
  `j` bigint(20) NOT NULL,
  `k` bigint(20) default NULL,
  `l` varchar(45) NOT NULL,
  `m` int(10) unsigned default NULL,
  `n` varchar(20) default NULL,
  `o` bigint(20) NOT NULL,
  `p` tinyint(1) NOT NULL,
  PRIMARY KEY  USING BTREE (`id`),
  KEY `idx_l` (`l`),
  KEY `idx_h` USING BTREE (`h`),
  KEY `idx_m` USING BTREE (`m`),
  KEY `idx_fk_g` USING BTREE (`fk_g`),
  KEY `fk_g_frobozz` (`id`,`fk_g`),
  CONSTRAINT `fk_g_frobozz` FOREIGN KEY (`fk_g`) REFERENCES `frotz` (`g`)
) ENGINE=InnoDB AUTO_INCREMENT=179369 DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC;

أظن أن جزءًا من القضية هو وجود عدد من الفهارس لهذا الجدول. يشبه الجدول B الجدول B ، على الرغم من أنه يحتوي فقط على الأعمدة id و h.

أيضا ، نتائج التنميط هي كما يلي:

starting 0.000018
checking query cache for query 0.000044
checking permissions 0.000005
Opening tables 0.000009
init 0.000019
optimizing 0.000004
executing 0.000043
end 0.000005
end 0.000002
query end 0.000003
freeing items 0.000007
logging slow query 0.000002
cleaning up 0.000002

تم حلها

شكرا لجميع الردود والتعليقات. بالتأكيد جعلوني أفكر في المشكلة. مجد ل dotjoe لحملني على الابتعاد عن المشكلة من خلال طرح السؤال البسيط "هل هناك أي جداول أخرى مرجع A.ID؟"

كانت المشكلة أن هناك حذفًا على الجدول A الذي يسمى إجراءً مخزنًا لتحديث جدولين آخرين ، كان C و D. الجدول C يعودون إلى A.ID وبعد القيام ببعض الأشياء المتعلقة بهذا المعرف في الإجراء المخزن ، كان لديه البيان ،

DELETE FROM c WHERE c.id = theId;

نظرت إلى البيان الشرح وأعيد كتابة هذا على أنه ،

EXPLAIN SELECT * FROM c WHERE c.other_id = 12345;

لذلك ، استطعت أن أرى ما الذي كان يفعله هذا وأعطاني المعلومات التالية:

id            1
select_type   SIMPLE
table         c
type          ALL
possible_keys NULL
key           NULL
key_len       NULL
ref           NULL
rows          2633
Extra         using where

أخبرني هذا أنها كانت عملية مؤلمة لإجراءها ولأنها ستحصل على 22500 مرة (لمجموعة البيانات المحددة التي يتم حذفها) ، كانت هذه هي المشكلة. بمجرد أن أنشأت فهرسًا على هذا العمود الآخر و Reran الشرح ، حصلت على:

id            1
select_type   SIMPLE
table         c
type          ref
possible_keys Index_1
key           Index_1
key_len       8
ref           const
rows          1
Extra         

أفضل بكثير ، في الواقع رائع حقا.

أضفت أن index_1 وأوقات الحذف الخاصة بي تتماشى مع الأوقات التي أبلغ عنها Mattkemp. كان هذا خطأً خفيًا حقًا من جانبي بسبب تسخين الأحذية بعض الوظائف الإضافية في اللحظة الأخيرة. اتضح أن معظم عبارات الحذف/الاختيار المقترحة ، مثل دانيال ذكر ، انتهى الأمر بأخذ نفس القدر من الوقت Soulmerge المذكور ، كان البيان هو أفضل ما كنت سأتمكن من بناءه بناءً على ما يجب أن أفعله. بمجرد أن قدمت فهرسًا لهذا الجدول C الآخر ، كانت عمليات الحذف الخاصة بي سريعة.

ما بعد الوفاة:
خرج درسان من هذا التمرين. أولاً ، من الواضح أنني لم أستفيد من قوة البيان الشرح للحصول على فكرة أفضل عن تأثير استفسارات SQL الخاصة بي. هذا خطأ صاعد ، لذلك لن أتغلب على ذلك. سأتعلم من هذا الخطأ. ثانياً ، كان الرمز المخالف نتيجة لعقلية "الحصول عليها بسرعة" وعدم كفاية التصميم/الاختبار أدت إلى عدم ظهور هذه المشكلة عاجلاً. لو قمت بإنشاء العديد من مجموعات بيانات الاختبار الكبيرة لاستخدامها كمدخل اختبار لهذه الوظيفة الجديدة ، لم أكن قد أضيع وقتي ولا لك. كان الاختبار الخاص بي على جانب DB يفتقر إلى العمق الذي يوجد به جانب التطبيق الخاص بي. الآن لدي الفرصة لتحسين ذلك.

المرجع: شرح البيان

هل كانت مفيدة؟

المحلول

يعد حذف البيانات من InnoDB أغلى عملية يمكنك طلبها. كما اكتشفت بالفعل أن الاستعلام نفسه ليس هو المشكلة - سيتم تحسين معظمها في خطة التنفيذ نفسها على أي حال.

على الرغم من أنه قد يكون من الصعب فهم سبب كون حذف جميع الحالات هو الأبطأ ، إلا أن هناك تفسيرًا بسيطًا إلى حد ما. InnoDB هو محرك تخزين المعاملات. هذا يعني أنه إذا تم إحباط استعلامك في منتصف الطريق ، فستظل جميع السجلات في مكانها كما لو لم يحدث شيء. بمجرد اكتماله ، سيتم اختفاء كل شيء في نفس اللحظة. أثناء حذف العملاء الآخرين الذين يتصلون بالخادم ، سيشاهد السجلات حتى يكتمل حذفك.

لتحقيق ذلك ، يستخدم InnoDB تقنية تسمى MVCC (التحكم في التزامن متعدد الإصدار). ما تفعله أساسًا هو إعطاء كل اتصال عرض لقطة لقاعدة البيانات بأكملها كما كانت عندما بدأت البيان الأول للمعاملة. لتحقيق ذلك ، يمكن أن يكون لكل سجل في InnoDB داخليًا قيمًا متعددة - واحدة لكل لقطة. هذا هو السبب أيضًا في أن الاعتماد على Innodb يستغرق بعض الوقت - يعتمد ذلك على حالة اللقطة التي تراها في ذلك الوقت.

بالنسبة لمعاملة الحذف الخاصة بك ، يتم وضع علامة على كل سجل تم تحديده وفقًا لظروف الاستعلام الخاصة بك. نظرًا لأن العملاء الآخرين قد يصلون إلى البيانات في نفس الوقت ، فلا يمكن إزالتها على الفور من الجدول ، لأنه يتعين عليهم رؤية لقطة كل منها لضمان ذرية الحذف.

بمجرد تمييز جميع السجلات للحذف ، يتم ارتكاب المعاملة بنجاح. وحتى مع ذلك ، لا يمكن إزالتها على الفور من صفحات البيانات الفعلية ، قبل أن تكون جميع المعاملات الأخرى التي عملت مع قيمة لقطة قبل معاملة الحذف الخاصة بك ، قد انتهت كذلك.

لذلك في الواقع ، لا تكون هذه الدقائق الثلاث بطيئة حقًا ، مع الأخذ في الاعتبار حقيقة أنه يجب تعديل جميع السجلات من أجل إعدادها للإزالة بطريقة آمنة للمعاملة. ربما ستسمع "القرص الثابت الخاص بك يعمل أثناء تشغيل البيان. هذا ناتج عن الوصول إلى جميع الصفوف. لتحسين الأداء ، يمكنك محاولة زيادة حجم تجمع Buffer INNODB للخادم الخاص بك ومحاولة الحد من الوصول الآخر إلى قاعدة البيانات أثناء حذفك ، وبالتالي تقليل عدد الإصدارات التاريخية التي يجب على Innodb الحفاظ عليها لكل سجل. مع الذاكرة الإضافية ، قد يكون InnoDB قادرًا على قراءة الجدول (في الغالب) في الذاكرة وتجنب بعض الوقت الذي يبحث فيه عن القرص.

نصائح أخرى

يبدو أن وقتك لمدة ثلاث دقائق بطيئة حقًا. أظن أن عمود المعرف لا يجري فهرسته بشكل صحيح. إذا تمكنت من توفير تعريف الجدول الدقيق الذي تستخدمه ، فسيكون ذلك مفيدًا.

لقد قمت بإنشاء برنامج نصي Python بسيط لإنتاج بيانات الاختبار وتشغيل إصدارات مختلفة متعددة من استعلام الحذف مقابل نفس مجموعة البيانات. ها هي تعريفات الجدول الخاصة بي:

drop table if exists a;
create table a
 (id bigint unsigned  not null primary key,
  data varchar(255) not null) engine=InnoDB;

drop table if exists b;
create table b like a;

ثم أدرجت 100 ألف صف في صفوف A و 25K في B (22.5k منها كانت أيضًا في A). فيما يلي نتائج أوامر الحذف المختلفة. لقد أسقطت وأعدت الجدول بين الركض بالمناسبة.

mysql> DELETE FROM a WHERE EXISTS (SELECT b.id FROM b WHERE a.id=b.id);
Query OK, 22500 rows affected (1.14 sec)

mysql> DELETE FROM a USING a LEFT JOIN b ON a.id=b.id WHERE b.id IS NOT NULL;
Query OK, 22500 rows affected (0.81 sec)

mysql> DELETE a FROM a INNER JOIN b on a.id=b.id;
Query OK, 22500 rows affected (0.97 sec)

mysql> DELETE QUICK a.* FROM a,b WHERE a.id=b.id;
Query OK, 22500 rows affected (0.81 sec)

تم إجراء جميع الاختبارات على intel core2 رباعي النواة 2.5 جيجا هرتز ، وذاكرة وصول عشوائي 2 جيجابايت مع Ubuntu 8.10 و MySQL 5.0. لاحظ أن تنفيذ عبارة SQL لا يزال واحدًا ملولبًا.


تحديث:

لقد قمت بتحديث اختباراتي لاستخدام مخطط ITSMATT. لقد قمت بتعديله قليلاً عن طريق إزالة الزيادة التلقائية (أقوم بإنشاء بيانات اصطناعية) وترميز مجموعة الأحرف (لم يكن يعمل - لم يحدث ذلك).

ها هي تعريفات الجدول الجديدة الخاصة بي:

drop table if exists a;
drop table if exists b;
drop table if exists c;

create table c (id varchar(30) not null primary key) engine=InnoDB;

create table a (
  id bigint(20) unsigned not null primary key,
  c_id varchar(30) not null,
  h int(10) unsigned default null,
  i longtext,
  j bigint(20) not null,
  k bigint(20) default null,
  l varchar(45) not null,
  m int(10) unsigned default null,
  n varchar(20) default null,
  o bigint(20) not null,
  p tinyint(1) not null,
  key l_idx (l),
  key h_idx (h),
  key m_idx (m),
  key c_id_idx (id, c_id),
  key c_id_fk (c_id),
  constraint c_id_fk foreign key (c_id) references c(id)
) engine=InnoDB row_format=dynamic;

create table b like a;

ثم أقوم بإعادة الاختبارات نفسها مع 100 ألف صف في صفوف A و 25K في B (وإعادة تدويرها بين الركض).

mysql> DELETE FROM a WHERE EXISTS (SELECT b.id FROM b WHERE a.id=b.id);
Query OK, 22500 rows affected (11.90 sec)

mysql> DELETE FROM a USING a LEFT JOIN b ON a.id=b.id WHERE b.id IS NOT NULL;
Query OK, 22500 rows affected (11.48 sec)

mysql> DELETE a FROM a INNER JOIN b on a.id=b.id;
Query OK, 22500 rows affected (12.21 sec)

mysql> DELETE QUICK a.* FROM a,b WHERE a.id=b.id;
Query OK, 22500 rows affected (12.33 sec)

كما ترون أن هذا أبطأ قليلاً من ذي قبل ، وربما بسبب الفهارس المتعددة. ومع ذلك ، فهو ليس في أي مكان بالقرب من علامة ثلاث دقائق.

شيء آخر قد ترغب في النظر إليه هو تحريك حقل النص الطويل إلى نهاية المخطط. يبدو أنني أتذكر أن MySQL يؤدي بشكل أفضل إذا كانت جميع الحقول المقيدة في الحجم هي الأولى والنص ، والنص ، وما إلى ذلك في النهاية.

جرب هذا:

DELETE a
FROM a
INNER JOIN b
 on a.id = b.id

تميل استخدام الفخات الفرعية إلى أن تكون أبطأ ثم ينضم إليها حيث يتم تشغيلها لكل سجل في الاستعلام الخارجي.

هذا ما أقوم به دائمًا ، عندما يتعين علي العمل مع بيانات كبيرة فائقة (هنا: جدول اختبار عينة مع 150000 صف):

drop table if exists employees_bak;
create table employees_bak like employees;
insert into employees_bak 
    select * from employees
    where emp_no > 100000;

rename table employees to employees_todelete;
rename table employees_bak to employees;

في هذه الحالة ، يقوم SQL بتصفية 50000 صف في جدول النسخ الاحتياطي. يتسلل الاستعلام على جهازتي البطيئة في 5 ثوان. يمكنك استبدال الإدراج في Select by الخاص بك الاستعلام الخاص بك.

هذه هي الحيلة لإجراء حذف الكتلة على قواعد البيانات الكبيرة! ؛ =)

أنت تقوم باستعدادك الفرعي على "B" لكل صف في "A".

محاولة:

DELETE FROM a USING a LEFT JOIN b ON a.id = b.id WHERE b.id IS NOT NULL;

جرب هذا:

DELETE QUICK A.* FROM A,B WHERE A.ID=B.ID

إنه أسرع بكثير من الاستفسارات العادية.

الرجوع إلى بناء الجملة: http://dev.mysql.com/doc/refman/5.0/en/delete.html

أعلم أن هذا السؤال قد تم حله إلى حد كبير بسبب إغفال الفهرسة من OP ولكني أود تقديم هذه النصيحة الإضافية ، وهي صالحة لحالة أكثر عامة لهذه المشكلة.

لقد تعاملت شخصيا مع الاضطرار إلى حذف العديد من الصفوف من طاولة موجودة في آخر وفي تجربتي ، من الأفضل القيام بما يلي ، خاصة إذا كنت تتوقع حذف الكثير من الصفوف. الأهم من ذلك أن هذه التقنية ستؤدي إلى تحسين تأخر عبودية النسخ المتماثل ، حيث كلما طالما يتم تشغيل كل استعلام متحولة ، كلما كان التأخير أسوأ (النسخ المتماثل واحد ملولب).

حتى هنا هو عليه: قم بتحديد أولاً ، كاستعلام منفصل, ، تذكر المعرفات التي تم إرجاعها في البرنامج النصي/التطبيق الخاص بك ، ثم تابع الحذف على دفعات (على سبيل المثال ، 50000 صف في وقت واحد). هذا سيحقق ما يلي:

  • لن يقوم كل واحد من عبارات الحذف بإغلاق الجدول لفترة طويلة جدًا ، وبالتالي عدم السماح للتأخر في التكرار للخروج عن السيطرة. من المهم بشكل خاص إذا كنت تعتمد على النسخ المتماثل لتزويدك بالبيانات الحديثة نسبيًا. تتمثل فائدة استخدام الدُفعات في أنه إذا وجدت أن كل استعلام حذف لا يزال يستغرق وقتًا طويلاً ، فيمكنك ضبطه ليكون أصغر دون لمس أي هياكل DB.
  • فائدة أخرى لاستخدام اختيار منفصل هي ذلك قد يستغرق Select نفسه وقتًا طويلاً للتشغيل, ، خاصة إذا لم يتمكن لأي سبب من الأسباب استخدم أفضل فهارس DB. إذا كان Select داخليًا إلى حذف ، عندما ينتقل البيان بأكمله إلى العبيد ، فسيتعين عليه القيام بالتحديد مرة أخرى ، مما يحتمل أن يتخلف عن العبيد لأنه يتعين عليه القيام بالاختيار الطويل مرة أخرى. تأخر الرقيق ، مرة أخرى ، يعاني بشكل سيء. إذا كنت تستخدم استعلام SELECT منفصل ، فإن هذه المشكلة تختفي ، لأن كل ما تمر به هو قائمة معرفات.

اسمحوا لي أن أعرف إذا كان هناك خطأ في منطقتي في مكان ما.

لمزيد من النقاش حول تأخر النسخ المتماثل وطرق محاربه ، على غرار هذا ، انظر أوضح MySQL Slave Lag (تأخير) و 7 طرق لمحاربة ذلك

PS شيء واحد يجب توخيه هو ، بطبيعة الحال ، التعديلات المحتملة على الجدول بين الأوقات التي تشطيباتها وبدء حذفها. سأسمح لك بالتعامل مع هذه التفاصيل باستخدام المعاملات و/أو المنطق ذي الصلة بتطبيقك.

DELETE FROM a WHERE id IN (SELECT id FROM b)

ربما يجب عليك إعادة بناء المؤشرات قبل تشغيل هذا الاستعلام هيو. حسنًا ، يجب عليك إعادة بناءها بشكل دوري.

REPAIR TABLE a QUICK;
REPAIR TABLE b QUICK;

ثم قم بتشغيل أي من الاستفسارات أعلاه (أي)

DELETE FROM a WHERE id IN (SELECT id FROM b)

الاستعلام نفسه هو بالفعل في شكل مثالي ، فإن تحديث الفهارس يؤدي إلى استغرق العملية بأكملها هذا الوقت. يمكنك تعطيل المفاتيح على هذا الجدول قبل العملية ، يجب أن تسرع الأمور. يمكنك إعادة تشغيلها في وقت لاحق ، إذا لم تكن بحاجة إليها على الفور.

هناك نهج آخر هو إضافة أ deleted عمود العلم إلى طاولتك وضبط استفسارات أخرى حتى يأخذوا هذه القيمة في الاعتبار. أسرع نوع منطقي في MySQL هو CHAR(0) NULL (true = '' ، false = null). ستكون هذه عملية سريعة ، يمكنك حذف القيم بعد ذلك.

نفس الأفكار المعبر عنها في عبارات SQL:

ALTER TABLE a ADD COLUMN deleted CHAR(0) NULL DEFAULT NULL;

-- The following query should be faster than the delete statement:
UPDATE a INNER JOIN b SET a.deleted = '';

-- This is the catch, you need to alter the rest
-- of your queries to take the new column into account:
SELECT * FROM a WHERE deleted IS NULL;

-- You can then issue the following queries in a cronjob
-- to clean up the tables:
DELETE FROM a WHERE deleted IS NOT NULL;

إذا كان ذلك أيضًا ليس ما تريده ، فيمكنك إلقاء نظرة على ما تقوله مستندات MySQL عن سرعة حذف عبارات.

راجع للشغل ، بعد نشر ما سبق على مدونتي ، بارون شوارتز من بيركونا لفت انتباهي إلى أن Maatkit لديه بالفعل أداة لهذا الغرض فقط - MK -Archiver. http://www.maatkit.org/doc/mk-archiver.html.

من المرجح أن يكون أفضل أداة لديك لهذا المنصب.

Obviously the SELECT query that builds the foundation of your DELETE operation is quite fast so I'd think that either the foreign key constraint or the indexes are the reasons for your extremely slow query.

Try

SET foreign_key_checks = 0;
/* ... your query ... */
SET foreign_key_checks = 1;

This would disable the checks on the foreign key. Unfortunately you cannot disable (at least I don't know how) the key-updates with an InnoDB table. With a MyISAM table you could do something like

ALTER TABLE a DISABLE KEYS
/* ... your query ... */
ALTER TABLE a ENABLE KEYS 

I actually did not test if these settings would affect the query duration. But it's worth a try.

Connect datebase using terminal and execute command below, look at the result time each of them, you'll find that times of delete 10, 100, 1000, 10000, 100000 records are not Multiplied.

  DELETE FROM #{$table_name} WHERE id < 10;
  DELETE FROM #{$table_name} WHERE id < 100;
  DELETE FROM #{$table_name} WHERE id < 1000;
  DELETE FROM #{$table_name} WHERE id < 10000;
  DELETE FROM #{$table_name} WHERE id < 100000;

The time of deleting 10 thousand records is not 10 times as much as deleting 100 thousand records. Then, except for finding a way delete records more faster, there are some indirect methods.

1, We can rename the table_name to table_name_bak, and then select records from table_name_bak to table_name.

2, To delete 10000 records, we can delete 1000 records 10 times. There is an example ruby script to do it.

#!/usr/bin/env ruby
require 'mysql2'


$client = Mysql2::Client.new(
  :as => :array,
  :host => '10.0.0.250',
  :username => 'mysql',
  :password => '123456',
  :database => 'test'
)


$ids = (1..1000000).to_a
$table_name = "test"

until $ids.empty?
  ids = $ids.shift(1000).join(", ")
  puts "delete =================="
  $client.query("
                DELETE FROM #{$table_name}
                WHERE id IN ( #{ids} )
                ")
end

The basic technique for deleting multiple Row form MySQL in single table through the id field

DELETE FROM tbl_name WHERE id <= 100 AND id >=200; This query is responsible for deleting the matched condition between 100 AND 200 from the certain table

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top