سؤال

لدينا جدولان يشبهان بنية تسجيل العلامات البسيطة كما يلي (في الواقع، الأمر أكثر تعقيدًا ولكن هذا هو جوهر المشكلة):

tag (A.a) | recordId (A.b)
1         | 1
2         | 1
2         | 2
3         | 2
....

و

recordId (B.b) | recordData (B.c)
1              | 123
2              | 666
3              | 1246

تكمن المشكلة في جلب السجلات المطلوبة بعلامة محددة.الطريقة الواضحة للقيام بذلك هي من خلال صلة بسيطة وفهارس على (PK)(A.a, A.b)، (A.b)، (PK)(B.b)، (B.b,B.c) على هذا النحو:

select A.a, A.b, B.c from A join B on A.b = B.b where a = 44 order by c;

ومع ذلك، فإن هذا يعطي نتيجة غير سارة لفرز الملفات:

+----+-------------+-------+------+---------------+---------+---------+-----------+------+----------------------------------------------+
| id | select_type | table | type | possible_keys | key     | key_len | ref       | rows | Extra                                        |
+----+-------------+-------+------+---------------+---------+---------+-----------+------+----------------------------------------------+
|  1 | SIMPLE      | A     | ref  | PRIMARY,b     | PRIMARY | 4       | const     |   94 | Using index; Using temporary; Using filesort | 
|  1 | SIMPLE      | B     | ref  | PRIMARY,b     | b       | 4       | booli.A.b |    1 | Using index                                  | 
+----+-------------+-------+------+---------------+---------+---------+-----------+------+----------------------------------------------+

باستخدام "عرض مادي" ضخم ومتكرر للغاية، يمكننا الحصول على أداء لائق جدًا ولكن هذا على حساب تعقيد منطق العمل، وهو أمر نود تجنبه، خاصة وأن الجداول A وB هي بالفعل MV:s (وهي اللازمة للاستعلامات الأخرى، وفي الواقع نفس الاستعلامات باستخدام UNION).

create temporary table C engine=innodb as (select A.a, A.b, B.c from A join B on A.b = B.b);
explain select a, b, c from C where a = 44 order by c;

ومما يزيد الوضع تعقيدًا حقيقة أن لدينا شروطًا شرطية في الجدول B، مثل مرشحات النطاق.

select A.a, A.b, B.c from A join B on A.b = B.b where a = 44 AND B.c > 678 order by c;

لكننا واثقون من قدرتنا على التعامل مع هذا الأمر إذا انتهت مشكلة فرز الملفات.

هل يعرف أحد لماذا لن يستخدم الانضمام البسيط إلى codeblock 3 أعلاه الفهرس للفرز وما إذا كان بإمكاننا التغلب على المشكلة بطريقة ما دون إنشاء فيديو موسيقي جديد؟

فيما يلي قائمة SQL الكاملة التي نستخدمها للاختبار.

DROP TABLE IF EXISTS A;
DROP TABLE IF EXISTS B;
DROP TABLE IF EXISTS C;
CREATE TEMPORARY TABLE A (a INT NOT NULL, b INT NOT NULL, PRIMARY KEY(a, b), INDEX idx_A_b (b)) ENGINE=INNODB;
CREATE TEMPORARY TABLE B (b INT NOT NULL, c INT NOT NULL, d VARCHAR(5000) NOT NULL DEFAULT '', PRIMARY KEY(b), INDEX idx_B_c (c), INDEX idx_B_b (b, c)) ENGINE=INNODB;

DELIMITER $$
CREATE PROCEDURE prc_filler(cnt INT)
BEGIN
        DECLARE _cnt INT;
        SET _cnt = 1;
        WHILE _cnt <= cnt DO
                INSERT IGNORE INTO A SELECT RAND()*100, RAND()*10000;
                INSERT IGNORE INTO B SELECT RAND()*10000, RAND()*1000, '';
                SET _cnt = _cnt + 1;
        END WHILE;
END
$$
DELIMITER ;

START TRANSACTION;
CALL prc_filler(100000);
COMMIT;
DROP PROCEDURE prc_filler;

CREATE TEMPORARY TABLE C ENGINE=INNODB AS (SELECT A.a, A.b, B.c FROM A JOIN B ON A.b = B.b);
ALTER TABLE C ADD (PRIMARY KEY(a, b), INDEX idx_C_a_c (a, c));

EXPLAIN EXTENDED SELECT A.a, A.b, B.c FROM A JOIN B ON A.b = B.b WHERE A.a = 44;
EXPLAIN EXTENDED SELECT A.a, A.b, B.c FROM A JOIN B ON A.b = B.b WHERE 1 ORDER BY B.c;
EXPLAIN EXTENDED SELECT A.a, A.b, B.c FROM A JOIN B ON A.b = B.b where A.a = 44 ORDER BY B.c;
EXPLAIN EXTENDED SELECT a, b, c FROM C WHERE a = 44 ORDER BY c;
-- Added after Quassnois comments
EXPLAIN EXTENDED SELECT A.a, A.b, B.c FROM  B FORCE INDEX (idx_B_c) JOIN A ON A.b = B.b WHERE A.a = 44 ORDER BY B.c;
EXPLAIN EXTENDED SELECT A.a, A.b, B.c FROM A JOIN B ON A.b = B.b WHERE A.a = 44 ORDER BY B.c LIMIT 10;
EXPLAIN EXTENDED SELECT A.a, A.b, B.c FROM  B FORCE INDEX (idx_B_c) JOIN A ON A.b = B.b WHERE A.a = 44 ORDER BY B.c LIMIT 10;
هل كانت مفيدة؟

المحلول

عندما أحاول إعادة إنتاج هذا الاستعلام باستخدام البرامج النصية الخاصة بك:

SELECT  A.a, A.b, B.c
FROM    A
JOIN    B
ON      A.b = B.b
WHERE   a = 44
ORDER BY
        c

, ، يكتمل في 0.0043 seconds (في الحال)، يعود 930 الصفوف وتنتج هذه الخطة:

1, 'SIMPLE', 'A', 'ref', 'PRIMARY', 'PRIMARY', '4', 'const', 1610, 'Using index; Using temporary; Using filesort'
1, 'SIMPLE', 'B', 'eq_ref', 'PRIMARY', 'PRIMARY', '4', 'test.A.b', 1, ''

إنها فعالة جدًا لمثل هذا الاستعلام.

بالنسبة لمثل هذا الاستعلام، لا يمكنك استخدام فهرس واحد للتصفية والفرز.

راجع هذه المقالة في مدونتي للحصول على شرح أكثر تفصيلاً:

إذا كنت تتوقع أن يُرجع استعلامك عددًا قليلاً من السجلات، فيجب عليك استخدام الفهرس في A للتصفية ثم الفرز باستخدام فرز الملفات (كما يفعل الاستعلام أعلاه).

إذا كنت تتوقع أن تقوم بإرجاع العديد من السجلات (و LIMIT لهم)، تحتاج إلى استخدام الفهرس للفرز ثم التصفية:

CREATE INDEX ix_a_b ON a (b);
CREATE INDEX ix_b_c ON b (c)

SELECT  *
FROM    B FORCE INDEX (ix_b_c)
JOIN    A
ON      A.b = B.b
ORDER BY
        b.c
LIMIT 10;

1, 'SIMPLE', 'B', 'index', '', 'ix_b_c', '4', '', 2, 'Using index'
1, 'SIMPLE', 'A', 'ref', 'ix_a_b', 'ix_a_b', '4', 'test.B.b', 4, 'Using index'

نصائح أخرى

select A.a, A.b, B.c from A join B on A.b = B.b where a = 44 order by c;

إذا قمت بتسمية الأعمدة، فهل يساعد ذلك؟مثال:

 SELECT 
 T1.a AS colA, 
 T2.b AS colB, 
 T2.c AS colC 
 FROM A AS T1 
 JOIN B AS T2 
 ON (T1.b = T2.b) 
 WHERE 
 T1.a = 44 
 ORDER BY colC;

التغييرات الوحيدة التي قمت بها هي:

  • أضع شروط الانضمام بين قوسين
  • شروط الانضمام وحيث تستند الشروط إلى أعمدة الجدول
  • يستند شرط ORDER BY إلى عمود الجدول الناتج
  • لقد قمت بإنشاء اسم مستعار لأعمدة جدول النتائج والجداول التي تم الاستعلام عنها (على أمل) لتوضيح الأمر أكثر عندما كنت أستخدم أحدهما أو الآخر (وأكثر وضوحًا للخادم.لقد أهملت الإشارة إلى أعمدتك في مكانين في استعلامك الأصلي).

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

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