มีวิธีเพิ่มประสิทธิภาพการเรียงลำดับตามคอลัมน์ของตารางที่เข้าร่วมหรือไม่


10

นี่คือแบบสอบถามที่ช้าของฉัน:

SELECT `products_counts`.`cid`
FROM
  `products_counts` `products_counts`

  LEFT OUTER JOIN `products` `products` ON (
  `products_counts`.`product_id` = `products`.`id`
  )
  LEFT OUTER JOIN `trademarks` `trademark` ON (
  `products`.`trademark_id` = `trademark`.`id`
  )
  LEFT OUTER JOIN `suppliers` `supplier` ON (
  `products_counts`.`supplier_id` = `supplier`.`id`
  )
WHERE
  `products_counts`.product_id IN
  (159, 572, 1075, 1102, 1145, 1162, 1660, 2355, 2356, 2357, 3236, 6471, 6472, 6473, 8779, 9043, 9095, 9336, 9337, 9338, 9445, 10198, 10966, 10967, 10974, 11124, 11168, 16387, 16689, 16827, 17689, 17920, 17938, 17946, 17957, 21341, 21352, 21420, 21421, 21429, 21544, 27944, 27988, 30194, 30196, 30230, 30278, 30699, 31306, 31340, 32625, 34021, 34047, 38043, 43743, 48639, 48720, 52453, 55667, 56847, 57478, 58034, 61477, 62301, 65983, 66013, 66181, 66197, 66204, 66407, 66844, 66879, 67308, 68637, 73944, 74037, 74060, 77502, 90963, 101630, 101900, 101977, 101985, 101987, 105906, 108112, 123839, 126316, 135156, 135184, 138903, 142755, 143046, 143193, 143247, 144054, 150164, 150406, 154001, 154546, 157998, 159896, 161695, 163367, 170173, 172257, 172732, 173581, 174001, 175126, 181900, 182168, 182342, 182858, 182976, 183706, 183902, 183936, 184939, 185744, 287831, 362832, 363923, 7083107, 7173092, 7342593, 7342594, 7342595, 7728766)
ORDER BY
  products_counts.inflow ASC,
  supplier.delivery_period ASC,
  trademark.sort DESC,
  trademark.name ASC
LIMIT
  0, 3;

เวลาสอบถามเฉลี่ยคือ 4.5s บนชุดข้อมูลของฉันและไม่สามารถยอมรับได้

โซลูชันที่ฉันเห็น:

เพิ่มคอลัมน์ทั้งหมดจากส่วนคำสั่งไปยังproducts_countsตาราง แต่ฉันมีแอพพลิเคชั่นประมาณ 10 ชนิดดังนั้นฉันควรสร้างคอลัมน์และดัชนีจำนวนมาก นอกจากproducts_countsนี้ยังมีการอัปเดต / แทรก / ลบอย่างเข้มข้นดังนั้นฉันต้องดำเนินการอัปเดตคอลัมน์ทั้งหมดที่เกี่ยวข้องกับคำสั่งซื้อทันที (โดยใช้ทริกเกอร์หรือไม่)

มีวิธีอื่นไหม

อธิบาย:

+----+-------------+-----------------+--------+---------------------------------------------+------------------------+---------+----------------------------------+------+----------------------------------------------+
| id | select_type | table           | type   | possible_keys                               | key                    | key_len | ref                              | rows | Extra                                        |
+----+-------------+-----------------+--------+---------------------------------------------+------------------------+---------+----------------------------------+------+----------------------------------------------+
|  1 | SIMPLE      | products_counts | range  | product_id_supplier_id,product_id,pid_count | product_id_supplier_id | 4       | NULL                             |  227 | Using where; Using temporary; Using filesort |
|  1 | SIMPLE      | products        | eq_ref | PRIMARY                                     | PRIMARY                | 4       | uaot.products_counts.product_id  |    1 |                                              |
|  1 | SIMPLE      | trademark       | eq_ref | PRIMARY                                     | PRIMARY                | 4       | uaot.products.trademark_id       |    1 |                                              |
|  1 | SIMPLE      | supplier        | eq_ref | PRIMARY                                     | PRIMARY                | 4       | uaot.products_counts.supplier_id |    1 |                                              |
+----+-------------+-----------------+--------+---------------------------------------------+------------------------+---------+----------------------------------+------+----------------------------------------------+

โครงสร้างตาราง:

CREATE TABLE `products_counts` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `product_id` int(11) unsigned NOT NULL,
  `supplier_id` int(11) unsigned NOT NULL,
  `count` int(11) unsigned NOT NULL,
  `cid` varchar(64) NOT NULL,
  `inflow` varchar(10) NOT NULL,
  `for_delete` tinyint(1) unsigned NOT NULL DEFAULT '0',
  PRIMARY KEY (`id`),
  UNIQUE KEY `cid` (`cid`),
  UNIQUE KEY `product_id_supplier_id` (`product_id`,`supplier_id`),
  KEY `product_id` (`product_id`),
  KEY `count` (`count`),
  KEY `pid_count` (`product_id`,`count`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

CREATE TABLE `products` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `external_id` varchar(36) NOT NULL,
  `name` varchar(255) NOT NULL,
  `category_id` int(11) unsigned NOT NULL,
  `trademark_id` int(11) unsigned NOT NULL,
  `photo` varchar(255) NOT NULL,
  `sort` int(11) unsigned NOT NULL,
  `otech` tinyint(1) unsigned NOT NULL,
  `not_liquid` tinyint(1) unsigned NOT NULL DEFAULT '0',
  `applicable` varchar(255) NOT NULL,
  `code_main` varchar(64) NOT NULL,
  `code_searchable` varchar(128) NOT NULL,
  `total` int(11) unsigned NOT NULL,
  `slider` int(11) unsigned NOT NULL,
  `slider_title` varchar(255) NOT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `external_id` (`external_id`),
  KEY `category_id` (`category_id`),
  KEY `trademark_id` (`trademark_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

CREATE TABLE `trademarks` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `external_id` varchar(36) NOT NULL,
  `name` varchar(255) NOT NULL,
  `country_id` int(11) NOT NULL,
  `sort` int(11) unsigned NOT NULL DEFAULT '0',
  `sort_list` int(10) unsigned NOT NULL DEFAULT '0',
  `is_featured` tinyint(1) unsigned NOT NULL,
  `is_direct` tinyint(1) unsigned NOT NULL DEFAULT '0',
  PRIMARY KEY (`id`),
  UNIQUE KEY `external_id` (`external_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

CREATE TABLE `suppliers` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `external_id` varchar(36) NOT NULL,
  `code` varchar(64) NOT NULL,
  `name` varchar(255) NOT NULL,
  `delivery_period` tinyint(1) unsigned NOT NULL,
  `is_default` tinyint(1) unsigned NOT NULL,
  PRIMARY KEY (`id`),
  KEY `external_id` (`external_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

ข้อมูลเซิร์ฟเวอร์ MySQL:

mysqld  Ver 5.5.45-1+deb.sury.org~trusty+1 for debian-linux-gnu on i686 ((Ubuntu))

3
คุณสามารถจัดทำดัชนี Fiddle ของ SQL schema ของตารางและทดสอบข้อมูลได้หรือไม่ นอกจากนี้เวลาเป้าหมายของคุณคืออะไร? คุณต้องการทำให้มันเสร็จสมบูรณ์ภายใน 3 วินาที 1 วินาที 50 มิลลิวินาทีหรือไม่? คุณมีกี่ระเบียนในตารางต่าง ๆ 1k, 100k, 100M
Erik

หากเขตข้อมูลเหล่านั้นที่คุณเรียงลำดับโดยไม่ได้รับการจัดทำดัชนีและชุดข้อมูลมีขนาดใหญ่มากคุณอาจมองปัญหา sort_buffer_size หรือไม่ คุณสามารถลองปรับเปลี่ยนค่าของคุณในเซสชั่นของคุณและเรียกใช้แบบสอบถามเพื่อดูว่ามันจะดีขึ้น
Brian Efting

คุณได้ลองเพิ่มดัชนี(inflow, product_id)หรือไม่?
ypercubeᵀᴹ

innodb_buffer_pool_sizeตรวจสอบให้แน่ใจว่าคุณมีดี โดยทั่วไปประมาณ 70% ของ RAM ที่ใช้งานได้ดี
Rick James

คำตอบ:


6

การตรวจสอบคำจำกัดความตารางของคุณแสดงว่าคุณมีดัชนีที่ตรงกันในตารางที่เกี่ยวข้อง สิ่งนี้จะทำให้การรวมเกิดขึ้นเร็วที่สุดเท่าที่จะทำได้ภายในขีด จำกัด ของMySQL'sตรรกะการรวม

อย่างไรก็ตามการเรียงลำดับจากหลายตารางนั้นซับซ้อนกว่า

ในปี 2007 Sergey Petrunia อธิบายMySQLขั้นตอนวิธีการเรียงลำดับ3 แบบตามลำดับความเร็วMySQLที่: http://s.petrunia.net/blog/?m=201407

  1. ใช้วิธีการเข้าถึงแบบอิงดัชนีที่สร้างเอาท์พุทที่สั่ง
  2. ใช้filesort()บนตารางที่ไม่คงที่ 1
  3. ใส่ผลการแข่งขันลงในตารางชั่วคราวและใช้filesort()มัน

จากคำจำกัดความของตารางและร่วมแสดงข้างต้นจะเห็นได้ว่าคุณจะไม่เคยได้รับการจัดเรียงที่เร็วที่สุด ซึ่งหมายความว่าคุณจะต้องขึ้นอยู่กับfilesort()เกณฑ์การจัดเรียงที่คุณใช้

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

หากต้องการดูรายละเอียดที่กำหนดไว้สำหรับMySQL 5.5วิธีการเรียงลำดับโปรดดู: http://dev.mysql.com/doc/refman/5.5/en/order-by-optimization.html

เพื่อMySQL 5.5(ในตัวอย่างนี้) เพื่อเพิ่มORDER BYความเร็วหากคุณไม่สามารถMySQLใช้ดัชนีแทนระยะการเรียงพิเศษให้ลองใช้กลยุทธ์ต่อไปนี้:

•เพิ่มsort_buffer_sizeค่าตัวแปร

•เพิ่มread_rnd_buffer_sizeค่าตัวแปร

ใช้ RAM น้อยลงต่อแถวด้วยการประกาศคอลัมน์ที่มีขนาดใหญ่เท่าที่จำเป็นสำหรับค่าจริงที่จะจัดเก็บ [เช่นลด varchar (256) เป็น varchar (ActualLongestString)]

•เปลี่ยนtmpdirตัวแปรระบบให้ชี้ไปที่ระบบไฟล์เฉพาะที่มีพื้นที่ว่างจำนวนมาก (รายละเอียดอื่น ๆ มีอยู่ในลิงค์ด้านบน)

มีรายละเอียดเพิ่มเติมในMySQL 5.7เอกสารประกอบเพื่อเพิ่มORDERความเร็วซึ่งบางพฤติกรรมอาจได้รับการอัพเกรดเล็กน้อย :

http://dev.mysql.com/doc/refman/5.7/en/order-by-optimization.html

มุมมองที่ปรากฏขึ้น - แนวทางที่แตกต่างในการจัดเรียงตารางที่เข้าร่วม

คุณพูดถึงมุมมองที่ปรากฏขึ้นพร้อมกับคำถามของคุณที่อ้างถึงการใช้ทริกเกอร์ MySQL ไม่มีฟังก์ชั่นในการสร้างMaterialized Viewแต่คุณมีเครื่องมือที่จำเป็น ด้วยการใช้ทริกเกอร์เพื่อกระจายภาระให้คุณสามารถรักษามุมมองวัสดุให้ทันสมัยได้ตลอดเวลา

materialized ดูเป็นจริงตารางซึ่งเป็นประชากรรหัสผ่านขั้นตอนการสร้างหรือสร้างmaterialized ดูและบำรุงรักษาโดยการเรียกเก็บข้อมูล up-to-date

เนื่องจากคุณกำลังสร้างตารางซึ่งจะมีดัชนีดังนั้นMaterialized Viewเมื่อเคียวรีสามารถใช้วิธีการเรียงลำดับที่เร็วที่สุด : ใช้วิธีการเข้าถึงแบบอิงดัชนีที่สร้างผลลัพธ์ที่สั่งซื้อ

ตั้งแต่MySQL 5.5การใช้ทริกเกอร์ที่จะรักษาmaterialized ดูคุณยังจะต้องมีกระบวนการสคริปต์หรือขั้นตอนการเก็บการสร้างครั้งแรกmaterialized ดู

แต่นั่นเป็นกระบวนการที่หนักเกินไปที่จะเรียกใช้หลังจากการอัพเดทแต่ละครั้งไปยังตารางฐานที่คุณจัดการข้อมูล นั่นคือจุดที่ทริกเกอร์เข้ามาเล่นเพื่อให้ข้อมูลทันสมัยเมื่อมีการเปลี่ยนแปลง วิธีนี้แต่ละinsert, updateและdeleteจะเผยแพร่การเปลี่ยนแปลงของพวกเขาโดยใช้ทริกเกอร์ของคุณเพื่อดู Materialized

องค์กร FROMDUAL ที่http://www.fromdual.com/มีรหัสตัวอย่างสำหรับการรักษามุมมองที่ปรากฏขึ้น ดังนั้นแทนที่จะเขียนตัวอย่างของฉันเองฉันจะชี้ให้คุณไปที่ตัวอย่าง:

http://www.fromdual.com/mysql-materialized-views

ตัวอย่างที่ 1: การสร้างมุมมองที่ปรากฏขึ้น

DROP TABLE sales_mv;
CREATE TABLE sales_mv (
    product_name VARCHAR(128)  NOT NULL
  , price_sum    DECIMAL(10,2) NOT NULL
  , amount_sum   INT           NOT NULL
  , price_avg    FLOAT         NOT NULL
  , amount_avg   FLOAT         NOT NULL
  , sales_cnt    INT           NOT NULL
  , UNIQUE INDEX product (product_name)
);

INSERT INTO sales_mv
SELECT product_name
    , SUM(product_price), SUM(product_amount)
    , AVG(product_price), AVG(product_amount)
    , COUNT(*)
  FROM sales
GROUP BY product_name;

สิ่งนี้จะช่วยให้คุณเห็นMaterializedในขณะที่รีเฟรช อย่างไรก็ตามเนื่องจากคุณมีฐานข้อมูลที่เคลื่อนไหวรวดเร็วคุณจึงต้องการรักษามุมมองนี้ให้ทันสมัยที่สุดเท่าที่จะทำได้

ดังนั้นตารางฐานข้อมูลได้รับผลกระทบต้องมีทริกเกอร์เพื่อเผยแพร่การเปลี่ยนแปลงจากตารางฐานในการที่materialized ดูตาราง เป็นตัวอย่างหนึ่ง:

ตัวอย่างที่ 2: การแทรกข้อมูลใหม่เข้าไปในมุมมองที่ปรากฏขึ้น

DELIMITER $$

CREATE TRIGGER sales_ins
AFTER INSERT ON sales
FOR EACH ROW
BEGIN

  SET @old_price_sum = 0;
  SET @old_amount_sum = 0;
  SET @old_price_avg = 0;
  SET @old_amount_avg = 0;
  SET @old_sales_cnt = 0;

  SELECT IFNULL(price_sum, 0), IFNULL(amount_sum, 0), IFNULL(price_avg, 0)
       , IFNULL(amount_avg, 0), IFNULL(sales_cnt, 0)
    FROM sales_mv
   WHERE product_name = NEW.product_name
    INTO @old_price_sum, @old_amount_sum, @old_price_avg
       , @old_amount_avg, @old_sales_cnt
  ;

  SET @new_price_sum = @old_price_sum + NEW.product_price;
  SET @new_amount_sum = @old_amount_sum + NEW.product_amount;
  SET @new_sales_cnt = @old_sales_cnt + 1;
  SET @new_price_avg = @new_price_sum / @new_sales_cnt;
  SET @new_amount_avg = @new_amount_sum / @new_sales_cnt;

  REPLACE INTO sales_mv
  VALUES(NEW.product_name, @new_price_sum, @new_amount_sum, @new_price_avg
       , @new_amount_avg, @new_sales_cnt)
  ;

END;
$$
DELIMITER ;

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

เมื่อเร็ว ๆ นี้: นั่นทำให้ตารางการเข้าร่วมเรียงลำดับเร็วขึ้นได้อย่างไร

materialized ดูจะถูกสร้างขึ้นอย่างต่อเนื่องจนปรับปรุงจะทำกับมัน ดังนั้นคุณสามารถกำหนดดัชนี (หรือดัชนี ) ที่คุณต้องการที่จะใช้สำหรับการจัดเรียงข้อมูลในmaterialized ดูหรือตาราง

หากค่าใช้จ่ายในการบำรุงรักษาข้อมูลไม่หนักเกินไปคุณต้องใช้ทรัพยากรบางส่วน (CPU / IO / ฯลฯ ) สำหรับการเปลี่ยนแปลงข้อมูลที่เกี่ยวข้องแต่ละครั้งเพื่อให้มุมมอง Materializedและทำให้ข้อมูลดัชนีเป็นปัจจุบันและพร้อมใช้งาน ดังนั้นการเลือกจะเร็วขึ้นเนื่องจากคุณ:

  1. ใช้ CPU ที่เพิ่มขึ้นและ IO ไปแล้วเพื่อเตรียมข้อมูลให้พร้อมสำหรับการเลือกของคุณ
  2. ดัชนีในมุมมอง Materializedสามารถใช้วิธีการเรียงลำดับที่เร็วที่สุดที่มีให้กับ MySQL คือใช้วิธีการเข้าถึงตามดัชนีที่สร้างผลลัพธ์ที่สั่งไว้

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

หมายเหตุ:ในMicrosoft SQL Server Materialized Viewsจะอ้างถึงมุมมองที่จัดทำดัชนีและอัปเดตโดยอัตโนมัติตามข้อมูลเมตาของมุมมองที่จัดทำดัชนี


6

มีไม่มากที่จะไปที่นี่ แต่ฉันเดาว่าปัญหาหลักคือคุณกำลังสร้างตารางชั่วคราวขนาดใหญ่และจัดเรียงไฟล์บนดิสก์ทุกครั้ง เหตุผล:

  1. คุณกำลังใช้ UTF8
  2. คุณกำลังใช้ varchar (255) ฟิลด์ขนาดใหญ่สำหรับการจัดเรียง

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

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

คุณลองย้ายtmpdirไปยังระบบไฟล์tmpfsหรือไม่? หาก / tmp ยังไม่ได้ใช้ tmpfs (MySQL ใช้tmpdir=/tmpตามค่าเริ่มต้นใน * nix) คุณสามารถใช้ / dev / shm ได้โดยตรง ในไฟล์ my.cnf ของคุณ:

[mysqld]
...
tmpdir=/dev/shm  

จากนั้นคุณจะต้องเริ่ม mysqld ใหม่

นั่นอาจสร้างความแตกต่างอย่างมาก หากคุณมีแนวโน้มที่จะตกอยู่ภายใต้ความกดดันของหน่วยความจำในระบบคุณอาจต้องการเพิ่มขนาด (โดยทั่วไป linux distros cap tmpfs ที่ 50% ของ RAM ทั้งหมดตามค่าเริ่มต้น) เพื่อหลีกเลี่ยงการแลกเปลี่ยนเซ็กเมนต์หน่วยความจำลงดิสก์หรือแม้แต่ ที่เลวร้ายยิ่งสถานการณ์ OOM คุณสามารถทำได้โดยแก้ไขบรรทัดใน/etc/fstab:

tmpfs                   /dev/shm                tmpfs   rw,size=2G,noexec,nodev,noatime,nodiratime        0 0

คุณสามารถปรับขนาดเป็น "ออนไลน์" ได้เช่นกัน ตัวอย่างเช่น:

mount -o remount,size=2G,noexec,nodev,noatime,nodiratime /dev/shm

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

โชคดี!


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