Có cách nào để tối ưu hóa việc sắp xếp theo các cột của các bảng đã tham gia không?


10

Đây là truy vấn chậm của tôi:

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;

Thời gian truy vấn trung bình là 4,5 giây trên tập dữ liệu của tôi và điều này không thể chấp nhận được.

Giải pháp tôi thấy:

Thêm tất cả các cột từ mệnh đề thứ tự vào products_countsbảng. Nhưng tôi có ~ 10 loại đơn đặt hàng trong ứng dụng, vì vậy tôi nên tạo ra nhiều cột và chỉ mục. Plus products_countscó các cập nhật / chèn / xóa rất mạnh mẽ, vì vậy tôi cần thực hiện cập nhật ngay lập tức tất cả các cột liên quan đến đơn hàng (sử dụng kích hoạt?).

Có giải pháp nào khác không?

Giải thích:

+----+-------------+-----------------+--------+---------------------------------------------+------------------------+---------+----------------------------------+------+----------------------------------------------+
| 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 |                                              |
+----+-------------+-----------------+--------+---------------------------------------------+------------------------+---------+----------------------------------+------+----------------------------------------------+

Cấu trúc bảng:

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;

Thông tin máy chủ MySQL:

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

3
Bạn có thể cung cấp SQL Fiddle với các chỉ mục, lược đồ bảng và dữ liệu thử nghiệm không? Ngoài ra thời gian mục tiêu của bạn là gì? Bạn đang tìm cách để nó hoàn thành trong 3 giây, 1 giây, 50 mili giây? Bạn có bao nhiêu hồ sơ trong các bảng khác nhau 1k, 100k, 100M?
Erik

Nếu các trường mà bạn sắp xếp theo không được lập chỉ mục và dữ liệu được đặt thì nó thực sự lớn, có lẽ bạn đang xem xét vấn đề sort_buffer_size? Bạn có thể thử sửa đổi giá trị của mình trên phiên và chạy truy vấn để xem nó có cải thiện không.
Brian Efting

Bạn đã thử thêm một chỉ mục trên (inflow, product_id)?
ypercubeᵀᴹ

Hãy chắc chắn rằng bạn có một phong nha innodb_buffer_pool_size. Thông thường khoảng 70% RAM có sẵn là tốt.
Rick James

Câu trả lời:


6

Xem xét các định nghĩa bảng của bạn cho thấy rằng bạn có các chỉ mục khớp với các bảng liên quan. Điều này sẽ khiến các phép nối xảy ra càng nhanh càng tốt trong giới hạn của MySQL'slogic nối.

Tuy nhiên, sắp xếp từ nhiều bảng phức tạp hơn.

Vào năm 2007, Serge Petrunia đã mô tả 3 MySQLthuật toán sắp xếp theo thứ tự tốc độ MySQLtại: http://s.petrunia.net/blog/?m=201407

  1. Sử dụng phương thức truy cập dựa trên chỉ mục tạo ra đầu ra có thứ tự
  2. Sử dụng filesort()trên bảng không đổi thứ 1
  3. Đặt kết quả tham gia vào một bảng tạm thời và sử dụng filesort()

Từ các định nghĩa và tham gia bảng được hiển thị ở trên, bạn có thể thấy rằng bạn sẽ không bao giờ có được sắp xếp nhanh nhất . Điều đó có nghĩa là bạn sẽ phụ thuộc vào filesort()các tiêu chí sắp xếp bạn đang sử dụng.

Tuy nhiên, nếu bạn thiết kế và sử dụng Chế độ xem Vật liệu, bạn sẽ có thể sử dụng thuật toán sắp xếp nhanh nhất.

Để xem chi tiết được xác định cho MySQL 5.5các phương pháp sắp xếp, hãy xem: http://dev.mysql.com/doc/refman/5.5/en/order-by-optimization.html

Để MySQL 5.5(trong ví dụ này) tăng ORDER BYtốc độ nếu bạn không thể MySQLsử dụng các chỉ mục thay vì một giai đoạn sắp xếp bổ sung, hãy thử các chiến lược sau:

• Tăng sort_buffer_sizegiá trị biến.

• Tăng read_rnd_buffer_sizegiá trị biến.

• Sử dụng ít RAM hơn trên mỗi hàng bằng cách khai báo các cột chỉ lớn khi cần cho các giá trị thực được lưu trữ. [Ví dụ: Giảm một varchar (256) thành varchar (ActualLongestString)]

• Thay đổi tmpdirbiến hệ thống để trỏ đến một hệ thống tệp chuyên dụng với số lượng lớn không gian trống. (Các chi tiết khác được cung cấp trong liên kết ở trên.)

Có nhiều chi tiết được cung cấp trong MySQL 5.7tài liệu để tăng ORDERtốc độ, một số trong đó có thể là các hành vi được nâng cấp nhẹ :

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

Các khung nhìn cụ thể hóa - Một cách tiếp cận khác nhau để sắp xếp các bảng đã tham gia

Bạn đã ám chỉ đến Chế độ xem cụ thể hóa với câu hỏi của bạn đề cập đến việc sử dụng kích hoạt. MySQL không có chức năng tích hợp để tạo Chế độ xem cụ thể nhưng bạn có các công cụ cần thiết. Bằng cách sử dụng các kích hoạt để phân tán tải, bạn có thể duy trì Chế độ xem Vật chất cho đến thời điểm này.

Các thể hóa Xem thực sự là một bảng mà được phổ biến thông qua mã thủ tục để xây dựng hoặc xây dựng lại các thể hóa Xemduy trì bởi trigger để giữ cho dữ liệu up-to-date.

Vì bạn đang xây dựng một bảng sẽ có một chỉ mục , nên Chế độ xem được vật chất hóa khi được truy vấn có thể sử dụng phương pháp sắp xếp nhanh nhất : Sử dụng phương thức truy cập dựa trên chỉ mục để tạo đầu ra theo thứ tự

MySQL 5.5sử dụng các kích hoạt để duy trì Chế độ xem được vật chất hóa , bạn cũng sẽ cần một quy trình, tập lệnh hoặc quy trình được lưu trữ để xây dựng Chế độ xem cụ thể hóa ban đầu .

Nhưng đó rõ ràng là một quá trình quá nặng để chạy sau mỗi lần cập nhật vào các bảng cơ sở nơi bạn quản lý dữ liệu. Đó là nơi các trình kích hoạt phát huy tác dụng để giữ cho dữ liệu được cập nhật khi các thay đổi được thực hiện. Bằng cách này insert, mỗi cách update, và deletesẽ truyền bá các thay đổi của chúng, bằng cách sử dụng trình kích hoạt của bạn, vào Chế độ xem được Vật chất hóa .

Tổ chức FROMDUAL tại http://www.fromdual.com/ có mã mẫu để duy trì Chế độ xem cụ thể hóa . Vì vậy, thay vì viết các mẫu của riêng tôi, tôi sẽ chỉ cho bạn các mẫu của họ:

http://www.fromdual.com/mysql-m vật liệu-view

Ví dụ 1: Xây dựng một khung nhìn cụ thể hóa

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;

Điều này cung cấp cho bạn Chế độ xem cụ thể tại thời điểm làm mới. Tuy nhiên, vì bạn có cơ sở dữ liệu di chuyển nhanh, bạn cũng muốn giữ chế độ xem này cập nhật nhất có thể.

Do đó, các bảng dữ liệu cơ sở bị ảnh hưởng cần phải có các kích hoạt để truyền các thay đổi từ bảng cơ sở sang bảng Chế độ xem . Như một ví dụ:

Ví dụ 2: Chèn dữ liệu mới vào chế độ xem cụ thể hóa

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 ;

Tất nhiên, bạn cũng sẽ cần các trình kích hoạt để duy trì Xóa dữ liệu khỏi Chế độ xem cụ thể hóaCập nhật dữ liệu trong Chế độ xem cụ thể hóa . Các mẫu có sẵn cho các kích hoạt là tốt.

TẠI CUỐI: Làm thế nào mà sắp xếp các bảng tham gia nhanh hơn?

Các thể hóa Xem đang được xây dựng liên tục như các bản cập nhật được thực hiện với nó. Do đó, bạn có thể xác định Chỉ mục (hoặc Chỉ mục ) mà bạn muốn sử dụng để sắp xếp dữ liệu trong Dạng xem hoặc Bảng cụ thể hóa .

Nếu chi phí duy trì dữ liệu không quá nặng, thì bạn đang sử dụng một số tài nguyên (CPU / IO / vv) cho mỗi thay đổi dữ liệu có liên quan để giữ Chế độ xem được vật chất hóa và do đó dữ liệu chỉ mục được cập nhật và sẵn sàng. Do đó, lựa chọn sẽ nhanh hơn, vì bạn:

  1. Đã dành CPU và IO gia tăng để có được dữ liệu sẵn sàng cho CHỌN của bạn.
  2. Chỉ mục trên Chế độ xem được vật chất hóa có thể sử dụng phương pháp sắp xếp nhanh nhất có sẵn cho MySQL, cụ thể là Sử dụng phương thức truy cập dựa trên chỉ mục để tạo đầu ra theo thứ tự .

Tùy thuộc vào hoàn cảnh của bạn và cách bạn cảm nhận về quy trình tổng thể, bạn có thể muốn xây dựng lại Chế độ xem được Vật chất hóa mỗi đêm trong thời gian chậm.

Lưu ý: Trong Microsoft SQL Server Chế độ xem được vật chất hóa được tham chiếu đến Chế độ xem được lập chỉ mục và được cập nhật tự động dựa trên siêu dữ liệu của Chế độ xem được lập chỉ mục .


6

Không có quá nhiều thứ để tiếp tục ở đây, nhưng tôi đoán vấn đề chính là bạn đang tạo một bảng tạm thời khá lớn và sắp xếp tệp trên đĩa mỗi lần. Lý do là:

  1. Bạn đang sử dụng UTF8
  2. Bạn đang sử dụng một số trường varchar lớn (255) để sắp xếp

Điều này có nghĩa là tệp bảng và sắp xếp tạm thời của bạn có thể khá lớn, vì khi tạo bảng tạm thời, các trường được tạo ở độ dài MAX và khi sắp xếp các bản ghi đều có độ dài MAX (và UTF8 là 3 byte cho mỗi ký tự). Đây cũng có khả năng ngăn chặn việc sử dụng bảng tạm thời trong bộ nhớ. Để biết thêm thông tin, xem chi tiết bảng tạm thời nội bộ .

GIỚI HẠN cũng không tốt cho chúng tôi ở đây, vì chúng tôi cần cụ thể hóa và đặt hàng toàn bộ kết quả trước khi chúng tôi biết 3 hàng đầu tiên là gì.

Bạn đã thử chuyển tmpdir của mình sang hệ thống tập tin tmpfs chưa? Nếu / tmp chưa sử dụng tmpfs (MySQL sử dụng tmpdir=/tmptheo mặc định trên * nix), thì bạn có thể sử dụng / dev / shm trực tiếp. Trong tập tin my.cnf của bạn:

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

Sau đó, bạn sẽ cần phải khởi động lại mysqld.

Điều đó có thể tạo ra một sự khác biệt rất lớn . Nếu bạn có khả năng chịu áp lực bộ nhớ trên hệ thống, có lẽ bạn muốn giới hạn kích thước mặc dù (thường là linux distro cap tmpfs ở mức 50% tổng RAM theo mặc định) để tránh hoán đổi các phân đoạn bộ nhớ ra đĩa hoặc thậm chí tệ hơn là một tình huống OOM . Bạn có thể làm điều đó bằng cách chỉnh sửa dòng trong /etc/fstab:

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

Bạn cũng có thể thay đổi kích thước "trực tuyến". Ví dụ:

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

Bạn cũng có thể nâng cấp lên MySQL 5.6 - có các truy vấn con và bảng dẫn xuất - và chơi xung quanh với truy vấn thêm một chút. Tôi không nghĩ rằng chúng ta sẽ thấy những chiến thắng lớn đi theo con đường đó, từ những gì tôi thấy.

Chúc may mắn!


Cảm ơn câu trả lời của bạn. Di chuyển tmpdir sang tmpfs đã mang lại hiệu quả tốt.
Stanislav Gamayunov
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.