MySQL tek tablosunda 10 milyon + satırı mümkün olduğunca hızlı nasıl güncelleyebilirim?


32

MySQL 5.6'yı, çoğu tablo için InnoDB depolama motoruyla kullanma. InnoDB arabellek havuzu boyutu 15 GB'dir ve Innodb DB + dizinleri yaklaşık 10 GB'dir. Sunucu 32GB RAM'e sahip ve Cent OS 7 x64 ile çalışıyor.

10 milyondan fazla kayıt içeren büyük bir masam var.

Her 24 saatte bir uzak sunucudan güncelleştirilmiş bir döküm dosyası alıyorum. Dosya csv formatındadır. Bu format üzerinde kontrol sahibi değilim. Dosya ~ 750 MB'dir. Bir MyISAM tablo satır satır satır veri eklemek çalıştı ve 35 dakika sürdü.

Dosyadan 10-12 satır dışında yalnızca 3 değer alıp veritabanında güncellemem gerekiyor.

Böyle bir şeyi başarmanın en iyi yolu nedir?

Bunu günlük yapmam gerekiyor.

Şu anda Akış şöyle:

  1. mysqli_begin_transaction
  2. Dökümü dosyasını satır satır oku
  3. Her kaydı Satır Satır güncelleyin.
  4. mysqli_commit

Yukarıdaki işlemlerin tamamlanması yaklaşık 30-40 dakika sürer ve bunu yaparken bana devam eden başka güncellemeler de var.

Kilitleme bekleme zaman aşımı aşıldı; işlemi yeniden başlatmayı deneyin

Güncelleme 1

veri kullanarak yeni tabloda yükleme LOAD DATA LOCAL INFILE. MyISAM'da InnoDB'de 38.93 sec7 dakika 5.21 saniye sürdü. Sonra yaptım:

UPDATE table1 t1, table2 t2
SET 
t1.field1 = t2.field1,
t1.field2 = t2.field2,
t1.field3 = t2.field3
WHERE t1.field10 = t2.field10

Query OK, 434914 rows affected (22 hours 14 min 47.55 sec)

Güncelleme 2

join sorgusu ile aynı güncelleme

UPDATE table1 a JOIN table2 b 
ON a.field1 = b.field1 
SET 
a.field2 = b.field2,
a.field3 = b.field3,
a.field4 = b.field4

(14 hours 56 min 46.85 sec)

Yorumlardaki sorulardan açıklamalar:

  • Tablodaki satırların yaklaşık% 6'sı dosya tarafından güncellenecektir, ancak bazen% 25 kadar olabilir.
  • Güncellenen alanlarda indeksler var. Masada 12 dizin bulunur ve 8 dizin güncelleme alanlarını içerir.
  • O değil gerekli bir işlemde güncelleştirmeyi yapmak. Zaman alabilir ancak 24 saatten fazla olamaz. Tüm masayı kilitlemeden 1 saat içinde halletmeyi düşünüyorum, daha sonra bu tabloya bağlı olan sfenks indeksini güncellemem gerekiyor. Veritabanının diğer görevler için uygun olduğu sürece adımların daha uzun sürmesi önemli değildir.
  • Ön işleme adımında csv biçimini değiştirebilirim. Önemli olan tek şey hızlı güncelleme ve kilitlemesiz.
  • Tablo 2, MyISAM'dir. Yükleme verilerini kullanarak csv dosyasından yeni oluşturulan tablodur. MYI dosya boyutu 452 MB'dır. Tablo 2, field1 sütununda dizine eklenmiştir.
  • MyISAM tablosunun MYD'si 663 MB'dir.

Güncelleme 3:

Her iki tablo hakkında daha fazla ayrıntı.

CREATE TABLE `content` (
  `hash` char(40) CHARACTER SET ascii NOT NULL DEFAULT '',
  `title` varchar(255) COLLATE utf8_unicode_ci NOT NULL DEFAULT '',
  `og_name` varchar(255) COLLATE utf8_unicode_ci NOT NULL DEFAULT '',
  `keywords` varchar(255) COLLATE utf8_unicode_ci NOT NULL DEFAULT '',
  `files_count` smallint(5) unsigned NOT NULL DEFAULT '0',
  `more_files` smallint(5) unsigned NOT NULL DEFAULT '0',
  `files` varchar(255) COLLATE utf8_unicode_ci NOT NULL DEFAULT '0',
  `category` smallint(3) unsigned NOT NULL DEFAULT '600',
  `size` bigint(19) unsigned NOT NULL DEFAULT '0',
  `downloaders` int(11) NOT NULL DEFAULT '0',
  `completed` int(11) NOT NULL DEFAULT '0',
  `uploaders` int(11) NOT NULL DEFAULT '0',
  `creation_date` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
  `upload_date` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
  `last_updated` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
  `vote_up` int(11) unsigned NOT NULL DEFAULT '0',
  `vote_down` int(11) unsigned NOT NULL DEFAULT '0',
  `comments_count` int(11) NOT NULL DEFAULT '0',
  `imdb` int(8) unsigned NOT NULL DEFAULT '0',
  `video_sample` tinyint(1) NOT NULL DEFAULT '0',
  `video_quality` tinyint(2) NOT NULL DEFAULT '0',
  `audio_lang` varchar(127) CHARACTER SET ascii NOT NULL DEFAULT '',
  `subtitle_lang` varchar(127) CHARACTER SET ascii NOT NULL DEFAULT '',
  `verified` tinyint(1) unsigned NOT NULL DEFAULT '0',
  `uploader` int(11) unsigned NOT NULL DEFAULT '0',
  `anonymous` tinyint(1) NOT NULL DEFAULT '0',
  `enabled` tinyint(1) unsigned NOT NULL DEFAULT '0',
  `tfile_size` int(11) unsigned NOT NULL DEFAULT '0',
  `scrape_source` tinyint(1) unsigned NOT NULL DEFAULT '0',
  `record_num` int(11) unsigned NOT NULL AUTO_INCREMENT,
  PRIMARY KEY (`record_num`),
  UNIQUE KEY `hash` (`hash`),
  KEY `uploaders` (`uploaders`),
  KEY `tfile_size` (`tfile_size`),
  KEY `enabled_category_upload_date_verified_` (`enabled`,`category`,`upload_date`,`verified`),
  KEY `enabled_upload_date_verified_` (`enabled`,`upload_date`,`verified`),
  KEY `enabled_category_verified_` (`enabled`,`category`,`verified`),
  KEY `enabled_verified_` (`enabled`,`verified`),
  KEY `enabled_uploader_` (`enabled`,`uploader`),
  KEY `anonymous_uploader_` (`anonymous`,`uploader`),
  KEY `enabled_uploaders_upload_date_` (`enabled`,`uploaders`,`upload_date`),
  KEY `enabled_verified_category` (`enabled`,`verified`,`category`),
  KEY `verified_enabled_category` (`verified`,`enabled`,`category`)
) ENGINE=InnoDB AUTO_INCREMENT=7551163 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci ROW_FORMAT=FIXED


CREATE TABLE `content_csv_dump_temp` (
  `hash` char(40) CHARACTER SET ascii NOT NULL DEFAULT '',
  `title` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
  `category_id` int(11) unsigned NOT NULL DEFAULT '0',
  `uploaders` int(11) unsigned NOT NULL DEFAULT '0',
  `downloaders` int(11) unsigned NOT NULL DEFAULT '0',
  `verified` tinyint(1) unsigned NOT NULL DEFAULT '0',
  PRIMARY KEY (`hash`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci

ve burada contentverileri kullanarak tabloyu güncelleyen güncelleme sorgusucontent_csv_dump_temp

UPDATE content a JOIN content_csv_dump_temp b 
ON a.hash = b.hash 
SET 
a.uploaders = b.uploaders,
a.downloaders = b.downloaders,
a.verified = b.verified

güncelleme 4:

Yukarıdaki testlerin tümü test makinesinde yapıldı. Ancak şimdi üretim makinesinde de aynı testleri yaptım ve sorgular çok hızlı.

mysql> UPDATE content_test a JOIN content_csv_dump_temp b
    -> ON a.hash = b.hash
    -> SET
    -> a.uploaders = b.uploaders,
    -> a.downloaders = b.downloaders,
    -> a.verified = b.verified;
Query OK, 2673528 rows affected (7 min 50.42 sec)
Rows matched: 7044818  Changed: 2673528  Warnings: 0

hatam için özür dilerim. Her kayıt güncellemesi yerine birleştirmek daha iyidir. şimdi rick_james tarafından önerilen dizini kullanarak mpre'yi geliştirmeye çalışıyorum, bench-marking yapıldıktan sonra güncellenecek.


Herhangi bir sıra ile bir kompozit var INDEX(field2, field3, field4)mı? Lütfen bize göster SHOW CREATE TABLE.
Rick James,

1
12 ve 8 endeksleri probleminizin ciddi bir parçası. MyISAM başka bir ciddi kısımdır. InnoDB veya TokuDB, çoklu indekslerle çok daha iyi performans gösteriyor.
Rick James,

İki tane farklı var UPDATEs . Lütfen bize tam olarak basit deyimi görünüyor csv veri tablosunu güncellemek için ne gibi. O zaman gereksinimlerinizi karşılayan bir teknik tasarlamanıza yardımcı olabiliriz.
Rick James,

@RickJames orada sadece bir tanesidir updateve güncellenmiş soruyu kontrol ediniz, teşekkürler.
AMB

Yanıtlar:


17

Tecrübelerime dayanarak, CSV Dosyanızı içe aktarmak için LOAD DATA INFILE kullanırdım.

LOAD DATA INFILE deyimi, bir metin dosyasından satırları çok yüksek bir hızla bir tabloya okur.

İnternette Bulunan Örnek Veri Yükle örneği . Bu örneği kutumda test ettim ve iyi çalıştım

Örnek Tablo

CREATE TABLE example (
  `Id` int(11) NOT NULL AUTO_INCREMENT,
  `Column2` varchar(14) NOT NULL,
  `Column3` varchar(14) NOT NULL,
  `Column4` varchar(14) NOT NULL,
  `Column5` DATE NOT NULL,
  PRIMARY KEY (`Id`)
) ENGINE=InnoDB

Örnek CSV Dosyası

# more /tmp/example.csv
Column1,Column2,Column3,Column4,Column5
1,A,Foo,sdsdsd,4/13/2013
2,B,Bar,sdsa,4/12/2013
3,C,Foo,wewqe,3/12/2013
4,D,Bar,asdsad,2/1/2013
5,E,FOObar,wewqe,5/1/2013

MySQL konsolundan çalıştırılacak İfadeyi İçe Aktar

LOAD DATA LOCAL INFILE '/tmp/example.csv'
    -> INTO TABLE example
    -> FIELDS TERMINATED BY ','
    -> LINES TERMINATED BY '\n'
    -> IGNORE 1 LINES
    -> (id, Column3,Column4, @Column5)
    -> set
    -> Column5 = str_to_date(@Column5, '%m/%d/%Y');

Sonuç

MySQL [testcsv]> select * from example;
+----+---------+---------+---------+------------+
| Id | Column2 | Column3 | Column4 | Column5    |
+----+---------+---------+---------+------------+
|  1 |         | Column2 | Column3 | 0000-00-00 |
|  2 |         | B       | Bar     | 0000-00-00 |
|  3 |         | C       | Foo     | 0000-00-00 |
|  4 |         | D       | Bar     | 0000-00-00 |
|  5 |         | E       | FOObar  | 0000-00-00 |
+----+---------+---------+---------+------------+

IGNORE, basitçe sütun başlıkları olan ilk satırı görmezden gelir.

IGNORE'dan sonra, sorunuzun ölçütlerinden biriyle eşleşen içe aktarılacak sütunları (sütun 2'yi atlama) belirliyoruz.

İşte doğrudan Oracle'dan başka bir örnek: LOAD DATA INFILE örneği

Başlamanız için bu yeterli olmalı.


i geçici tablosundaki veri yükleme için yük verileri kullanmak ve sonra ana tabloda güncellemek için diğer sorguları kullanabilirsiniz sayesinde.
AMB

14

Bahsedilen her şey ışığında, darboğazın birleşmenin kendisi olduğu anlaşılıyor.

ASPECT # 1: Arabellek Boyutuna Katıl

Her ihtimalde , join_buffer_size'niz muhtemelen çok düşüktür.

MySQL Belgelerine Göre MySQL'in Join Tampon Önbelleğini Nasıl Kullandığı Konusunda

Kullanılmış sütunları tüm satırlara değil yalnızca birleştirme arabelleğine depolarız.

Bu durumda, birleştirme arabelleğinin tuşlarının RAM'de kalmasını sağlayın.

Her anahtar için 10 milyon satır 4 bayt var. Bu yaklaşık 40 milyon.

Seansta 42M'ye (40M'den biraz daha büyük) çarpmayı deneyin

SET join_buffer_size = 1024 * 1024 * 42;
UPDATE table1 a JOIN table2 b 
ON a.field1 = b.field1 
SET 
a.field2 = b.field2,
a.field3 = b.field3,
a.field4 = b.field4;

Bu hile yaparsa, bunu eklemek için devam my.cnf

[mysqld]
join_buffer_size = 42M

Yeni bağlantılar için MySQL'i yeniden başlatmak gerekli değildir. Sadece koş

mysql> SET GLOBAL join_buffer_size = 1024 * 1024 * 42;

ASPECT # 2: Birleştirme İşlemi

Birleştirici işleminin stilini, optimize ediciyi arayarak değiştirebilirsiniz.

MySQL Belgelerine Göre Blok İç İçe Döngü ve Toplu Anahtar Erişim Birleşmeleri

BKA kullanıldığında, join_buffer_size değeri, anahtar grubunun her istekte depolama motoruna ne kadar büyük olduğunu tanımlar. Tampon ne kadar büyükse, birleştirme işleminin sağ tarafındaki tabloya o kadar fazla erişim sağlanacaktır, bu da performansı önemli ölçüde artırabilir.

BKA'nın kullanılması için, optimizer_switch sistem değişkeninin batched_key_access bayrağı açık olarak ayarlanmalıdır. BKA MRR kullanıyor, bu yüzden mrr bayrağı da açık olmalı. Şu anda, MRR için maliyet tahmini çok karamsar. Bu nedenle, BKA'nın kullanılması için mrr_cost_based'in kapalı olması da gereklidir.

Bu aynı sayfa, bunu yapmanızı önerir:

mysql> SET optimizer_switch='mrr=on,mrr_cost_based=off,batched_key_access=on';

ASPECT # 3: Diske Güncellemeler Yazma (İSTEĞE BAĞLI)

Kirli sayfaları arabellek havuzundan daha hızlı yazmak için innodb_write_io_threads değerini arttırmayı unutmayın .

[mysqld]
innodb_write_io_threads = 16

Bu değişiklik için MySQL'i yeniden başlatmanız gerekecek

BİR ŞANS VER !!!


Güzel! Ayarlanabilir birleştirilebilir arabellek ucu için +1. Katılmak zorundaysan, hafızana katıl. İyi bahşiş!
Peter Dixon-Moses

3
  1. CREATE TABLE CSV ile eşleşen
  2. LOAD DATA bu masanın içine
  3. UPDATE real_table JOIN csv_table ON ... SET ..., ..., ...;
  4. DROP TABLE csv_table;

Adım 3, satır satır satırlardan çok daha hızlı olacaktır, ancak önemsiz bir süre boyunca tablodaki tüm satırları hala kilitleyecektir. Bu kilitleme süresi, tüm işlemin ne kadar sürdüğünden daha önemliyse, ...

Eğer masaya başka bir şey yazmıyorsa, o zaman ...

  1. CREATE TABLECSV ile eşleşen; Hiçbir endeksleri ihtiyaç duyulan şeylerden hariç JOINiçinde UPDATE. Eğer benzersizse, yap PRIMARY KEY.
  2. LOAD DATA bu masanın içine
  3. real_tableiçin new_table( CREATE ... SELECT) kopyala
  4. UPDATE new_table JOIN csv_table ON ... SET ..., ..., ...;
  5. RENAME TABLE real_table TO old, new_table TO real_table;
  6. DROP TABLE csv_table, old;

3. adım, özellikle gereksiz dizinler kapalıysa, güncellemeden daha hızlıdır.
Adım 5 "anlık" dır.


Diyelim ki saniye cinsinden örnek, 3. adımdan sonra 4. adımı yapıyoruz, daha sonra yeni veriler real_table'a ekleniyor, böylece bu verileri new_table'da kaçıracağız? bunun için geçici çözüm nedir? teşekkürler
AMB

Bak ne oldu pt-online-schema-digest; Bu tür sorunları a TRIGGER.
Rick James,

Muhtemelen do not masaya üzerinde herhangi dizinleri gerek LOAD DATA. Gereksiz dizinler eklemek masraflıdır (zamanla).
Rick James,

En son bilgilere dayanarak, bir MyISAM tablosuna yüklenen CSV dosyasına yalnızca bir tane ile yaslanıp AUTO_INCREMENT, ardından PK temelli bir seferde 1K satırları tıklatıyorum. Ancak ayrıntıları açıklamaya çalışmadan önce tüm gereklilikleri ve masa şemasını görmem gerekiyor .
Rick James,

hash olarak ayarlamıştım PRIMARY index, ancak 50k'de chunking komutunu kullanarak sipariş sorgusu daha fazla zaman alıyor., otomatik artış yaratırsam daha iyi olur mu? ve olarak ayarlayın PRIMARY index?
AMB

3

Sen söyledin:

  • Güncellemeler tablonuzun% 6-25'ini etkiler
  • Bunu olabildiğince hızlı yapmak istiyorsunuz (<1saat)
  • kilitlemeden
  • tek bir işlemde olmak zorunda değil
  • Henüz (Rick James'in cevabı üzerine yapılan yorumda), yarış koşullarıyla ilgili endişelerinizi dile getiriyorsunuz

Bu ifadelerin çoğu çelişkili olabilir. Örneğin, büyük güncellemeler masayı kilitlemeden büyük güncellemeler. Veya dev bir işlem kullanarak yarış koşullarından kaçınmak.

Ayrıca, tablonuz yoğun bir şekilde dizine alındığından, ekler ve güncellemeler yavaş olabilir.


Yarış koşullarından kaçınmak

Güncellenen ekleyebilirsenizTablonuza zaman damgası , yarış koşullarını çözebilir ve aynı zamanda tek bir işlemde yarım milyon güncellemeyi kaydetmekten kaçınabilirsiniz.

Bu işlem, sizi şu anki satır güncellemelerini (şu anda yaptığınız gibi) gerçekleştirmenize, ancak otomatik işlem veya daha makul işlem gruplarına bırakmanıza olanak tanır.

Daha sonraki bir güncellemenin gerçekleşmemiş olup olmadığını kontrol ederek yarış koşullarından (satır satır güncellenirken) kaçınırsınız (UPDATE ... WHERE pk = [pk] AND updated < [batchfile date] )

Ve, en önemlisi, bu paralel güncellemeleri çalıştırmanızı sağlar .


Mümkün olduğu kadar çabuk koşma - Paralelleştirme

Bu zaman damgası ile şimdi yerinde kontrol edin:

  1. Toplu iş dosyanızı, makul boyutta bazı parçalara bölün (50.000 satır / dosya)
  2. Paralel olarak, her dosyada bir komut dosyası okuyun ve 50.000 UPDATE ifadesi içeren bir dosya çıktı alın.
  3. Paralel olarak, (2) bittiğinde, mysqlher sql dosyasını çalıştırın.

(örneğin yılında bashbakmak splitve xargs -Pkolayca yollarını bir komut birçok yolu paralel. paralellik derecesi bağlıdır kaç ipler sen adamaya istekli güncelleme )


"Satır satır" en az 100'lük gruplar halinde işlem yapmaktan 10 kat daha yavaş olabileceğini unutmayın.
Rick James

Emin olmak için bu durumda kıyaslamalısın. Bir tablonun% 6-25'ini güncelleyerek (güncellenen sütunlarla ilgili 8 dizinle), dizin bakımının tıkanıklık haline gelme ihtimalini eğlendiririm.
Peter Dixon-Moses,

Yani, bazı durumlarda endeksleri düşürmek, toplu güncelleme yapmak ve sonra yeniden oluşturmak daha hızlı olabilir ... ancak OP aksama süresi istemiyordur.
Peter Dixon-Moses,

1

Büyük güncellemeler G / Ç bağlıdır. Öneririm:

  1. Sık güncellenen 3 alanınızı depolayacak ayrı bir tablo oluşturun. Let çağrısı bir tablo assets_static iyi, tutmak, statik veri ve diğer assets_dynamic yükleyicilerden, indiriciler depolamak ve doğrulanmış olacaktır.
  2. Mümkünse MEMORY motorunu, varlıklar_dinamik tablosu için kullanın . (her güncellemeden sonra diske yedekleme).
  3. Senin hafif ve çevik güncelleyin assets_dynamic Güncellemenize 4 (yani YÜK INFILE ... INTO geçici gereğince; GÜNCELLEME assets_dynamic bir [güncellenmesi ne] a.id = b.id SET üzerinde geçici b JOIN Bu daha kısa bir sürer. dakika. (Sistemimizde, active_dynamic 95M satırlara sahiptir ve 40'lardan biraz fazla bir sürede ~ 6M satırları etkiler.
  4. Sphinx'in indeksleyicisini çalıştırdığınızda, JOIN asset_static ve asset_dynamic (bu alanlardan birini nitelik olarak kullanmak istediğinizi varsayalım ).

0

İçin UPDATEhızlı koşmak için, sen gerekmez

INDEX(uploaders, downloaders, verified)

Her iki masada da olabilir. Üç alan herhangi bir sırada olabilir.

Bu UPDATE, iki tablo arasındaki satırları hızlı bir şekilde eşleştirmeyi kolaylaştıracaktır .

Ve veri tiplerini iki tabloda da aynı yapın (her ikisi INT SIGNEDveya her ikisi de INT UNSIGNED).


bu aslında güncellemeyi yavaşlattı.
AMB

Hmmm ... Lütfen sağlayın EXPLAIN UPDATE ...;.
Rick James,
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.