Sorguların zaman zaman yavaş olmasının nedenleri?


16

MySQL 5.1'i Windows Server 2008 R2 üzerinde çalıştırıyoruz.

Geç veritabanımızda bazı teşhisler yapıyoruz ve açıklayamadığımız bazı rahatsız edici bulgular bulduk . Uzun süren sorgular olduğunda (> 2000ms) günlüğe kaydetmek için bazı kodlar ekledik. Sonuçlar şaşırtıcıydı (ve muhtemelen kilitlenmelerimiz için bir açıklama).

Normalde çok az zaman alan (<10ms) sorgular 4 ila 13 saniye arasında sürer. Açık olmak gerekirse, bunlar sürekli (saniyede birkaç kez) çalışan ve bu sorgu süresi artışlarından muzdarip olmayan sorgulardır.

Belirgin hatalar arayan dizinlerimizden geçtik ve çok şansımız olmadı.

Güncelleme

Kişiler tablosu:

| people | CREATE TABLE `people` (
`people_id` bigint(20) NOT NULL AUTO_INCREMENT,
`company_id` bigint(20) NOT NULL,
`name` varchar(255) DEFAULT NULL,
`password` varchar(255) DEFAULT NULL,
`temp_password` varchar(10) DEFAULT NULL,
`reset_password_hash` varchar(255) DEFAULT NULL,
`email` varchar(255) DEFAULT NULL,
`phone` varchar(32) DEFAULT NULL,
`mobile` varchar(32) DEFAULT NULL,
`iphone_device_id` varchar(160) DEFAULT NULL,
`iphone_device_time` datetime DEFAULT NULL,
`last_checkin` datetime DEFAULT NULL,
`location_lat` double DEFAULT NULL,
`location_long` double DEFAULT NULL,
`gps_strength` smallint(6) DEFAULT NULL,
`picture_blob_id` bigint(20) DEFAULT NULL,
`authority` int(11) NOT NULL DEFAULT '0',
`active` tinyint(1) NOT NULL DEFAULT '1',
`date_created` datetime NOT NULL,
`last_login` datetime NOT NULL,
`panic_mode` tinyint(1) NOT NULL DEFAULT '0',
`battery_level` double DEFAULT NULL,
`battery_state` varchar(32) DEFAULT NULL,
PRIMARY KEY (`people_id`),
KEY `email` (`email`),
KEY `company_id` (`company_id`),
KEY `iphone_device_id` (`iphone_device_id`),
KEY `picture_blob_id` (`picture_blob_id`),
CONSTRAINT `people_ibfk_1` FOREIGN KEY (`company_id`) REFERENCES `companies` (`company_id`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `people_ibfk_2` FOREIGN KEY (`picture_blob_id`) REFERENCES `blobs` (`blob_id`) ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=4658 DEFAULT CHARSET=utf8 |

Endeksler:

+--------+------------+------------------+--------------+------------------+-----------+-------------+----------+--------+------+------------+---------+
| Table  | Non_unique | Key_name         | Seq_in_index | Column_name      | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment |
+--------+------------+------------------+--------------+------------------+-----------+-------------+----------+--------+------+------------+---------+
| people |          0 | PRIMARY          |            1 | people_id        | A         |        3502 |     NULL | NULL   |      | BTREE      |         |
| people |          1 | email            |            1 | email            | A         |        3502 |     NULL | NULL   | YES  | BTREE      |         |
| people |          1 | company_id       |            1 | company_id       | A         |        3502 |     NULL | NULL   |      | BTREE      |         |
| people |          1 | iphone_device_id |            1 | iphone_device_id | A         |        3502 |     NULL | NULL   | YES  | BTREE      |         |
| people |          1 | picture_blob_id  |            1 | picture_blob_id  | A         |        3502 |     NULL | NULL   | YES  | BTREE      |         |
+--------+------------+------------------+--------------+------------------+-----------+-------------+----------+--------+------+------------+---------+

sunucudaki tabloda ~ 5000 satır var, bu da bize sorun çıkarıyor.


1
Önceki iki soruda henüz göstermediğiniz bir şey var. Lütfen bu soruya üç (3) hırsız ekleyin: 1) TABLO insanları OLUŞTURUNU GÖSTER \ G 2) İnsanlardan ENDEKSLERİ GÖSTER; 3) COUNT (1) kişiden SEÇ;
RolandoMySQLDBA

@RolandoMySQLDBA Yarın işe başlar başlamaz bunu yapacağım. Şerefe :)
RedBlueThing

Cevabımı güncelledim. Lütfen oku !!!
RolandoMySQLDBA

@RolandoMySQLDBA Teşekkürler :). Hâlâ bu şeyleri ayrıştırma. Nasıl gittiğimizi sana bildireceğim.
01:20

Yanıtlar:


14

Önceki iki sorunuzdaki ( Soru1 , Soru2 ) GÜNCELLEŞTİRME sorguları, PRIMARY KEY tarafından 'seviye' tablosuna satır düzeyinde kilitleme ile vuruyor. Ben 6 Haziran 2011 10:03 AM Soru 1'de geri söylediğim budur

Tüm işlemler PRIMARY anahtarından geçiyor. PRIMARY, InnoDB'de kümelenmiş bir dizin olduğundan, PRIMARY anahtarı ve satırın kendisi bir aradadır. Böylece, bir satır ve birincil anahtar geçiş bir ve aynıdır. Bu nedenle, PRIMARY KEY üzerindeki herhangi bir dizin kilidi de satır düzeyinde bir kilittir.

Henüz indekslere yavaşlık atfedebilecek başka bir şey düşünülmemiştir: InnoDB'de UNIQUE olmayan indekslerin kullanımı. Benzersiz olmayan dizinler kullanan InnoDB'deki her dizinli aramada, benzersiz olmayan anahtara eklenmiş her satırın rowID değeri de bulunur. RowID temel olarak Kümelenmiş Dizin'den ayrılır . Benzersiz olmayan dizinlerin güncellenmesi, TABLO BİR PRİMER ANAHTAR YOKSA, DAİMA kümelenmiş dizinle etkileşim halinde OLMALIDIR.

Düşünülmesi gereken bir diğer şey de, bir dizindeki BTREE düğümlerini yönetme sürecidir. Bazen, düğümlerin sayfa bölünmesini gerektirir. Benzersiz olmayan dizinlerin BTREE düğümündeki tüm girdiler, kümelenmiş dizin içindeki rowID öğesini benzersiz olmayan PLUS satırları içerir. Veri bütünlüğünü bozmadan bu tür BTREE sayfalarının bölünmesini düzgün bir şekilde azaltmak için, rowID ile ilişkili satırın dahili olarak bir satır düzeyinde kilitle karşılaşması gerekir.

'Kişiler' tablosunda benzersiz olmayan çok sayıda dizin varsa, tablo alanında çok sayıda dizin sayfasına sahip olmanın yanı sıra küçük küçük satır kilitlerinin size zaman zaman gizlice girmeye hazırlanın.

Açıkça görülmeyen başka bir faktör daha var: Anahtar Nüfus

Bazen bir dizin doldurulduğunda, dizinleri oluşturan anahtar değerler zamanla atlanabilir ve MySQL Sorgu Optimize Edici'nin anahtarlı aramalardan dizin taramalarına ve son olarak da tam tablo taramalarına geçmesine neden olabilir. Kayıp ot tuşlarını telafi etmek için tabloyu yeni dizinlerle yeniden tasarlamadığınız sürece kontrol edemezsiniz. Lütfen 'insanlar' tablosu için tablo yapısını, 'insanlar' tablosunun sayısını ve 'insanlar' tablosu için dizinleri göster .

Sorgular yalnızca PRIMARY KEY kullansa bile, benzersiz olmayan dizinlerdeki anahtarların süreksizliği için hala BTREE dengeleme ve sayfa bölme işlemi gerekir. Bu tür BTREE yönetimi, gerçekleşmesini istemediğiniz aralıklı satır seviyesi kilitleri nedeniyle dikkate değer bir yavaşlama sağlayacaktır.

GÜNCELLEME 2011-06-14 22:19

Soru 1'den Sorgular

UPDATE people SET company_id = 1610, name = '<name>', password = '<hash>',
temp_password = NULL, reset_password_hash = NULL, email = '<redacted>@yahoo.com',
phone = NULL, mobile = '<phone>', iphone_device_id = 'android:<id>-<id>',
iphone_device_time = '2011-06-06 05:35:09', last_checkin = '2011-06-06 05:24:42',
location_lat = <lat>, location_long = -<lng>, gps_strength = 3296,
picture_blob_id = 1190,
authority = 1, active = 1, date_created = '2011-04-13 20:21:20',
last_login = '2011-06-06 05:35:09', panic_mode = 0,
battery_level = NULL, battery_state = NULL WHERE people_id = 3125

UPDATE people SET company_id = 1610, name = '<name>', password = '<hash>',
temp_password = NULL, reset_password_hash = NULL, email = '<redacted>@yahoo.com',
phone = NULL, mobile = '<phone>', iphone_device_id = 'android:<id>-<id>-<id>-<id>',
iphone_device_time = '2011-06-06 05:24:42', last_checkin = '2011-06-06 05:35:07',
location_lat = <lat>, location_long = -<lng>, gps_strength = 3296,
picture_blob_id = 1190,
authority = 1, active = 1, date_created = '2011-04-13 20:21:20',
last_login = '2011-06-06 05:35:09', panic_mode = 0,
battery_level = NULL, battery_state = NULL WHERE people_id = 3125

Olaylardaki sırayı hayal edin

  1. PRIMARY KEY ile satırı bulun
  2. Satırı ve kümelenmiş dizini kilitleme
  3. Güncellenen tüm sütunlar için MVCC Verileri oluşturma
  4. Dört sütun dizine eklenir (e-posta, şirket_kimliği, iphone_device_id, picture_blob_id)
  5. Her dizin BTREE yönetimi gerektirir
  6. Aynı işlem alanı içinde, 1-5 arasındaki adımlar aynı satırda tekrarlanmaya çalışılıyor, aynı sütunlar güncelleniyor (her iki sorguda da aynı e-posta, her iki sorguda da şirket_id, her iki sorguda da resim_blob_id, iphone_device_id farklı)

Soru 2'den Sorgular

UPDATE people SET iphone_device_id=NULL
WHERE iphone_device_id='iphone:<device_id_blah>' AND people_id<>666;

UPDATE people SET company_id = 444, name = 'Dad', password = '<pass>',
temp_password = NULL, reset_password_hash = NULL, email = '<redacted>@gmail.com',
phone = NULL, mobile = NULL, iphone_device_id = 'iphone:<device_id_blah>',
iphone_device_time = '2011-06-06 19:12:29', last_checkin = '2011-06-07 02:49:47',
location_lat = <lat>, location_long = <lng>, gps_strength = 66,
picture_blob_id = 1661,
authority = 1, active = 1, date_created = '2011-03-20 19:18:34',
last_login = '2011-06-07 11:15:01', panic_mode = 0, battery_level = 0.55,
battery_state = 'unplugged' WHERE people_id = 666;

Bu iki sorgu daha da kafa karıştırıcıdır çünkü ilk sorgu people_id 666 hariç her şeyi güncelliyor. Yüzlerce satır sadece ilk sorgu ile acıyla kilitleniyor. İkinci sorgu, 5 olay dizisini çalıştıran people_id 666'yı güncelliyor. İlk sorgu, people_id 666 hariç her satırda aynı 5 olay dizisini çalıştırıyor, ancak iphone_device_id dizini iki farklı sorgu içeren interecept bir kursta. Birinin BTREE sayfalarında ilk gelen ilk hizmet esasına göre kilitlenmesi gerekir.

Aynı BTREE sayfalarını bir endekste kilitlemek için bir çarpışma rotasında bu iki çift sorgu karşısında InnoDB veya herhangi bir ACID uyumlu RDBMS için bağırsak anahtarlama deneyimi olabilir. Bu nedenle, sorguların AUTOCOMMIT = 1 ile çalıştığını garanti etmedikçe veya kirli okumalara izin vererek (bu gibi çarpışmalar OKUMA TAAHHÜT ve OKUYUN OKULMADI bir kabus olmasına rağmen), bir dizin yavaşlaması bu sorgu çiftlerinin kaderidir.

GÜNCELLEME 2011-06-15 10:29

@RedBlueThing: 2. sorudaki sorgularda, ilk sorgu bir aralık sorgusudur, bu nedenle çok sayıda satır kilidi elde edilir. Ayrıca her iki sorgu da aynı alan kimliğini kilitlemeye çalışıyor dikkat edin 0 sayfa no 4611 n bit 152 PRIMARY KEY, diğer bir deyişle kümelenmiş dizin kilitli.

Uygulamanızın en azından beklediğiniz etkinlik serisine göre çalıştığından emin olmak için deneyebileceğiniz iki farklı seçenek vardır:

Seçenek 1) Bu tabloyu MyISAM'e dönüştürün (en azından bir geliştirme sunucusunda). Her UPDATE, INSERT ve DELETE, ilk gelen ilk hizmet esasına göre tam bir masa kilidi uygular.

Seçenek 2) SERİLEŞTİRİLEBİLİR izolasyon seviyesini kullanmayı deneyin . Bu, PAYLAŞILAN modda istenen tüm satırları kilitler.

Beklediğiniz olayların sırası, bu iki alternatif seçeneği kullanarak kesilir veya başarılı olur. Bu seçeneklerin her ikisi de başarısız olursa, uygulamanızı gözden geçirmeniz ve sorgularınızın yürütme sırasına öncelik vermeniz gerekir. Bu önceliği belirledikten sonra, bu seçenekleri geri alabilirsiniz (Seçenek 1 için InnoDB'ye geri dönün, Seçenek 2 için varsayılan yalıtım seviyesine geri dönün [SERIALIZABLE'ı kullanmayı durdurun]).


@RolandoMySQLDBA Sorumuzu istediğiniz ayrıntılarla güncelledim.
RedBlueThing

@RolandoMySQLDBA Buna bir kez daha baktığınız için teşekkürler. Merak ettim, soru 2 için yorumlar, ilk sorgu neden yüzlerce satırı kilitlesin? Yalnızca cihaz kimliğiyle eşleşen 666 olmayan satırı kilitlemekle kalmaz mı? (yani tek bir satır)
RedBlueThing

@RolandoMySQLDBA Soru 1'deki önerinize dayanarak otomatik taahhüt ayarımızı kontrol ettik ve açık olduğunu onayladık.
RedBlueThing

@RolandoMySQLDBA İlk sorudaki sorgularda (satırdaki tüm alanları güncellemenin dışında) belirli bir sorun var mı? Sorgu için 13 saniyelik yürütme süresini açıklayan bir şey mi? Dört sütunu dizine eklemenin önerebileceğiniz bir şey olmadığı hissine kapılıyorum, ancak bu gerçekten böyle düşük performansla sonuçlanır mı?
RedBlueThing

@RolandoMySQLDBA +1 ve tüm önerileriniz için teşekkürler. Sorunu çözmek için izolasyon seviyesini değiştirmedik. Bunun yerine, soru 2 için kısmi alan güncellemeleri yaptık ve güncelleme yolundaki bir sorguyu optimize ettik. İşte bu kadar! daha fazla kilitlenme yok. :)
RedBlueThing

3

DEĞİŞKENLERİ GÖSTER '% innodb' GİBİ; - Özellikle, veriler ve dizinler arabellek havuzunun boyutuna henüz ulaşmadıysa, diske öncekinden çok daha sert vuruyor olabilirsiniz. G / Ç büyük performans katilidir.

Alanlarınızın çoğu gerektiğinden iki kat büyüktür. BÜYÜK (8 bayt) çoğu kimlik için çok fazladır. 5000 satır için yalnızca SMALLINT UNSIGNED gerekir (65K sınırı, yalnızca 2 bayt). Veya bir güvenlik payı için MEDIUMINT kullanın.

DOUBLE size 8 bayt maliyetle 16 önemli basamak verir. Battery_level 2'den fazla önemli basamak içeriyor mu? FLOAT 4 bayt alır.

Demek istediğim, "daha küçük -> daha önbelleğe alınabilir -> daha hızlı".

Lütfen bize yavaş sorguları gösterin; aniden yavaşlayanlar. Sadece onlarsız tahmin yapabiliriz. Slowlog'u açın ve long_query_time = 1; bunlar en yavaş sorguları bulmanıza yardımcı olacaktır.

"Bileşik" indekslerin faydasını anlıyor musunuz?

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.