Birleştirilen tabloların sütunlarına göre sıralamayı optimize etmenin bir yolu var mı?


10

Bu benim yavaş sorgu:

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;

Veri kümemde ortalama sorgu süresi 4,5 saniyedir ve bu kabul edilemez.

Gördüğüm çözümler:

Sipariş yan tümcesinden products_countstabloya tüm sütunları ekleyin . Ama ben uygulamada ~ 10 sipariş türleri var, bu yüzden sütun ve dizin bir sürü oluşturmak gerekir. Artı products_countsçok yoğun güncellemeler / ekler / siler var, bu yüzden hemen sipariş ile ilgili tüm sütunları (tetikleyicileri kullanarak?) Güncellemek gerekir.

Başka çözüm var mı?

Açıklamak:

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

Tablolar yapısı:

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 sunucu bilgileri:

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

3
Dizinler, tablo şeması ve test verileri içeren bir SQL Fiddle sağlayabilir misiniz? Ayrıca hedef süreniz nedir? 3 saniye, 1 saniye, 50 milisaniyede tamamlamasını mı istiyorsunuz? 1k, 100k, 100M çeşitli tablolarda kaç tane kaydınız var?
Erik

Sıraladığınız alanlar dizine eklenmemişse ve veri kümesi gerçekten büyükse bir sort_buffer_size sorununa bakabilirsiniz. Oturumunuzdaki değeri değiştirmeyi deneyebilir ve iyileşip iyileşmediğini görmek için sorguyu çalıştırabilirsiniz.
Brian Efting

Tarihinde bir dizin eklemeyi denediniz (inflow, product_id)mi?
ypercubeᵀᴹ

İyi bir şey olduğundan emin olun innodb_buffer_pool_size. Tipik olarak mevcut RAM'in yaklaşık% 70'i iyidir.
Rick James

Yanıtlar:


6

Tablo tanımlarınızı incelemek, ilgili tablolarda eşleşen dizinlerinizin olduğunu gösterir. Bu, MySQL'sbirleştirme mantığı sınırları dahilinde birleşimlerin mümkün olduğunca çabuk gerçekleşmesine neden olmalıdır .

Ancak, birden çok tablodan sıralama daha karmaşıktır.

2007 yılında Sergey Petrunia 3 MySQLsıralama algoritmasını hız sırasına göre açıkladı MySQL: http://s.petrunia.net/blog/?m=201407

  1. Sıralı çıktı üreten dizin tabanlı erişim yöntemini kullanın
  2. Kullanım filesort()1 Sabit olmayan masada
  3. Geçici bir tabloya sonucu katılmak ve kullanmak koyun filesort()üzerinde

Yukarıda gösterilen tablo tanımlarından ve birleştirmelerden, asla en hızlı sıralamayı alamayacağınızı görebilirsiniz . Bu filesort(), kullandığınız sıralama ölçütlerine bağlı olacağınız anlamına gelir .

Bununla birlikte, bir Materyalleştirilmiş Görünüm tasarlar ve kullanırsanız, en hızlı sıralama algoritmasını kullanabilirsiniz.

MySQL 5.5Sıralama yöntemleri için tanımlanan ayrıntıları görmek için bakınız: http://dev.mysql.com/doc/refman/5.5/en/order-by-optimization.html

İçin MySQL 5.5(bu örnekte) artırmak için ORDER BYalamayacağınız eğer hız MySQLekstra sıralama fazı yerine dizinleri kullanmayı, aşağıdaki stratejileri deneyin:

sort_buffer_sizeDeğişken değerini artırın .

read_rnd_buffer_sizeDeğişken değerini artırın .

• Sütunları yalnızca depolanacak gerçek değerler için gerektiği kadar büyük bildirerek satır başına daha az RAM kullanın. [Ör. Varchar'ı (256) varchar'a düşürün (ActualLongestString)]

tmpdirSistem değişkenini, büyük miktarda boş alana sahip özel bir dosya sistemine işaret edecek şekilde değiştirin . (Diğer ayrıntılar yukarıdaki bağlantıda sunulmaktadır.)

MySQL 5.7Belgede ORDERhızı arttırmak için , bazıları biraz yükseltilmiş davranışlar olabilen daha fazla ayrıntı vardır :

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

Gerçekleştirilmiş Görünümler - Birleştirilen Tabloları Sıralamada Farklı Bir Yaklaşım

Sen ima Maddileştirilmiş Görünümler soru tetikleyicileri kullanarak atıfta ile. MySQL'in Materyalleştirilmiş Görünüm oluşturmak için yerleşik bir işlevi yoktur, ancak gereken araçlara sahipsiniz. Yükü yaymak için tetikleyiciler kullanarak Materyalleştirilmiş Görünümü şu ana kadar koruyabilirsiniz .

Maddileştirilmiş Görünüm aslında bir olduğunu tablosu doldurulur prosedürel kod üzerinden sürüme veya yeniden Maddileştirilmiş Görünüm ve tetikleyiciler tarafından tutulan yukarı güncel veri tutmak için.

Dizin içeren bir tablo oluşturduğunuz için sorgulandığında Materyalize Görünüm en hızlı sıralama yöntemini kullanabilir : Sıralı çıktı üreten dizin tabanlı erişim yöntemini kullanın

Yana MySQL 5.5kullanır tetikleyiciler bir sürdürmek için Maddileştirilmiş Görünümü , ayrıca ilk inşa etmek için bir süreç, senaryo, veya yordamını gerekecektir Maddileştirilmiş Görünümü .

Ancak bu, verileri yönettiğiniz temel tablolardaki her güncellemeden sonra çalıştırılamayacak kadar ağır bir işlemdir. Değişiklikler yapıldıkça verileri güncel tutmak için tetikleyiciler devreye girer. Bu şekilde her insert, updateve deleteonların değişiklikleri yaymak için, senin tetikleyicileri kullanarak olacaktır Maddileştirilmiş Görünüm .

Http://www.fromdual.com/ adresindeki FROMDUAL kuruluşunun Materyalleştirilmiş Görünümü korumak için örnek kodu vardır . Yani, kendi örneklerimi yazmak yerine sizi örneklerine yönlendireceğim:

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

Örnek 1: Gerçekleştirilmiş Bir Görünüm Oluşturma

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;

Bu size yenileme anında Malzeme Görünümü verir . Ancak, hızlı hareket eden bir veritabanınız olduğundan, bu görünümü olabildiğince güncel tutmak istersiniz.

Bu nedenle, etkilenen temel veri tablolarının, değişiklikleri bir temel tablodan Materyalleştirilmiş Görünüm tablosuna yaymak için tetikleyicilere sahip olması gerekir . Örnek olarak:

Örnek 2: Maddi Bir Görünüme Yeni Veriler Ekleme

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 ;

Elbette, Materyalleştirilmiş Görünümden Veri Silme ve Materyalleştirilmiş Görünümde Verileri Güncelleme'yi korumak için tetikleyicilere de ihtiyacınız olacaktır . Bu tetikleyiciler için de örnekler mevcuttur.

SONDA: Bu birleştirilmiş tabloları sıralama daha hızlı nasıl yapar?

Maddileştirilmiş Görünüm güncellemeler kendisine yapıldıkça sürekli inşa ediliyor. Bu nedenle , Materyalleştirilmiş Görünüm veya Tablodaki verileri sıralamak için kullanmak istediğiniz Dizini (veya Dizinleri ) tanımlayabilirsiniz .

Verileri koruma yükü çok ağır değilse, Materyalleştirilmiş Görünümü korumak için ilgili her veri değişikliği için bazı kaynaklar (CPU / IO / vb.) Harcıyorsunuz ve bu nedenle endeks verileri güncel ve kolayca kullanılabilir durumda. Bu nedenle, seçim daha hızlı olacaktır, çünkü siz:

  1. Zaten verileri SELECT'iniz için hazır hale getirmek için artımlı CPU ve IO harcadı.
  2. Materyalleştirilmiş Görünüm'deki dizin, MySQL tarafından kullanılabilen en hızlı sıralama yöntemini kullanabilir, yani Sıralı çıktı üreten dizin tabanlı erişim yöntemini kullanın .

Koşullarınıza ve genel süreç hakkında nasıl hissettiğinize bağlı olarak, yavaş bir süre boyunca her gece Materyalleştirilmiş Görünümleri yeniden oluşturmak isteyebilirsiniz .

Not: In Microsoft SQL Server Maddileştirilmiş Görüntüleme adlandırılır Endeksli Görünümler ve otomatik dayalı olarak güncellenir Endeksli View'in meta verileri.


6

Burada devam etmek için çok fazla bir şey yok, ancak birincil sorun, her seferinde diskte oldukça büyük bir geçici tablo ve sıralama dosyası oluşturduğunuzdır. Nedeni:

  1. UTF8 kullanıyorsunuz
  2. Sıralama için bazı büyük varchar (255) alanları kullanıyorsunuz

Bu, geçici tablo ve sıralama dosyanızın oldukça büyük olabileceği anlamına gelir, geçici tablo oluşturulurken alanlar MAX uzunluğunda oluşturulur ve kayıtlar sıralanırken MAX uzunluğundadır (ve UTF8 karakter başına 3 bayttır). Bunlar ayrıca bir bellek içi geçici tablonun kullanılmasını da engeller. Daha fazla bilgi için dahili geçici tablo ayrıntılarına bakın .

LIMIT burada da bize iyi gelmiyor, çünkü ilk 3 satırın ne olduğunu bilmeden önce tüm sonuç setini gerçekleştirmemiz ve sipariş etmemiz gerekiyor.

Tmpdir'inizi bir tmpfs dosya sistemine taşımayı denediniz mi? / Tmp zaten tmpfs kullanmıyorsa (MySQL tmpdir=/tmpvarsayılan olarak * nix üzerinde kullanır ), / dev / shm komutunu doğrudan kullanabilirsiniz. My.cnf dosyanızda:

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

Sonra mysqld yeniden başlatmanız gerekir.

Bu büyük bir fark yaratabilir. Bellek bölümlerini diske takmaktan kaçınmak için muhtemelen sistemde bellek baskısı altındaysanız, muhtemelen boyutu (genellikle varsayılan RAM'in% 50'sinde linux distros cap tmpfs) veya hatta daha kötü bir OOM durumu . Bunu, satırı aşağıdaki gibi düzenleyerek yapabilirsiniz /etc/fstab:

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

Siz de "çevrimiçi" olarak yeniden boyutlandırabilirsiniz. Örneğin:

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

Ayrıca, performans alt sorguları ve türetilmiş tabloları olan MySQL 5.6'ya yükseltebilir ve sorgu ile biraz daha oynayabilirsiniz. Gördüğüm kadarıyla bu yolda büyük kazanımlar göreceğimizi sanmıyorum.

İyi şanslar!


Cevabınız için teşekkürler. Tmpdir'i tmpfs'ye taşımak iyi bir performans kazancı sağladı.
Stanislav Gamayunov
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.