Soru SQLite INSERT başına saniye performansını artırın?


SQLite'yi optimize etmek zor. Bir C uygulamasının bulk-insert performansı saniyede 85 insertden saniyede 96.000 inserte kadar değişebilir!

Arka fon: Bir masaüstü uygulamasının parçası olarak SQLite kullanıyoruz. Uygulama başlatıldığında daha fazla işlem için bir SQLite veritabanına ayrıştırılan ve yüklenen XML dosyalarında saklanan büyük miktarda yapılandırma verisi var. SQLite bu durum için idealdir çünkü hızlıdır, özel bir yapılandırma gerektirmez ve veritabanı tek bir dosya olarak diskte saklanır.

Gerekçe:  Başlangıçta gördüğüm performanstan hayal kırıklığına uğradım. SQLite performansının, veritabanının nasıl yapılandırıldığına ve API'yi nasıl kullandığınıza bağlı olarak önemli ölçüde (toplu ekler ve seçimler için) farklılık gösterebileceği ortaya çıkıyor. Tüm seçeneklerin ve tekniklerin ne olduğunu anlamanın önemsiz bir meselesi olmadığından, aynı soruşturmanın sorunlarından başkalarını kurtarmak için sonuçların Stack Overflow okuyucularıyla paylaşılması için bu topluluk wiki girişini oluşturmanın temkinli olduğunu düşündüm.

Deney: Genel anlamda performans ipuçlarından bahsetmek yerine (ör. "Bir işlem kullanın!"), Bazı C kodlarını yazmanın en iyi yol olduğunu düşündüm ve aslında ölçmek çeşitli seçeneklerin etkisi. Bazı basit verilerle başlayacağız:

  • 28 MB TAB ayrılmış metin dosyası (yaklaşık 865.000 kayıt) Toronto şehri için tam transit programı
  • Test makinem Windows XP çalıştıran bir 3.60 GHz P4.
  • Kod ile derlendi Visual C ++ 2005 "Tam Optimizasyon" (/ Ox) ve Favor Fast Code (/ Ot) ile "Release" olarak.
  • Doğrudan test uygulamamda derlenmiş olan SQLite "Amalgamation" ı kullanıyorum. Sahip olduğum SQLite sürümü biraz daha eski (3.6.7), ancak bu sonuçların en son sürümle karşılaştırılabileceğinden şüpheliyim (eğer başka bir düşünceniz varsa lütfen yorum bırakın).

Biraz kod yazalım!

Kod: Metin dosyasını satır-satır okuyan basit bir C programı, dizeyi değerlere böler ve sonra verileri bir SQLite veritabanına ekler. Kodun bu "temel" sürümünde, veritabanı oluşturulur, ancak aslında veri eklemeyiz:

/*************************************************************
    Baseline code to experiment with SQLite performance.

    Input data is a 28 MB TAB-delimited text file of the
    complete Toronto Transit System schedule/route info
    from http://www.toronto.ca/open/datasets/ttc-routes/

**************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <string.h>
#include "sqlite3.h"

#define INPUTDATA "C:\\TTC_schedule_scheduleitem_10-27-2009.txt"
#define DATABASE "c:\\TTC_schedule_scheduleitem_10-27-2009.sqlite"
#define TABLE "CREATE TABLE IF NOT EXISTS TTC (id INTEGER PRIMARY KEY, Route_ID TEXT, Branch_Code TEXT, Version INTEGER, Stop INTEGER, Vehicle_Index INTEGER, Day Integer, Time TEXT)"
#define BUFFER_SIZE 256

int main(int argc, char **argv) {

    sqlite3 * db;
    sqlite3_stmt * stmt;
    char * sErrMsg = 0;
    char * tail = 0;
    int nRetCode;
    int n = 0;

    clock_t cStartClock;

    FILE * pFile;
    char sInputBuf [BUFFER_SIZE] = "\0";

    char * sRT = 0;  /* Route */
    char * sBR = 0;  /* Branch */
    char * sVR = 0;  /* Version */
    char * sST = 0;  /* Stop Number */
    char * sVI = 0;  /* Vehicle */
    char * sDT = 0;  /* Date */
    char * sTM = 0;  /* Time */

    char sSQL [BUFFER_SIZE] = "\0";

    /*********************************************/
    /* Open the Database and create the Schema */
    sqlite3_open(DATABASE, &db);
    sqlite3_exec(db, TABLE, NULL, NULL, &sErrMsg);

    /*********************************************/
    /* Open input file and import into Database*/
    cStartClock = clock();

    pFile = fopen (INPUTDATA,"r");
    while (!feof(pFile)) {

        fgets (sInputBuf, BUFFER_SIZE, pFile);

        sRT = strtok (sInputBuf, "\t");     /* Get Route */
        sBR = strtok (NULL, "\t");            /* Get Branch */
        sVR = strtok (NULL, "\t");            /* Get Version */
        sST = strtok (NULL, "\t");            /* Get Stop Number */
        sVI = strtok (NULL, "\t");            /* Get Vehicle */
        sDT = strtok (NULL, "\t");            /* Get Date */
        sTM = strtok (NULL, "\t");            /* Get Time */

        /* ACTUAL INSERT WILL GO HERE */

        n++;
    }
    fclose (pFile);

    printf("Imported %d records in %4.2f seconds\n", n, (clock() - cStartClock) / (double)CLOCKS_PER_SEC);

    sqlite3_close(db);
    return 0;
}

Kontrol"

Kodun olduğu gibi çalıştırılması aslında herhangi bir veritabanı işlemi gerçekleştirmez, ancak ham C dosyası I / O ve dizi işleme işlemlerinin ne kadar hızlı olduğu konusunda bize bir fikir verecektir.

0.94'te 864913 adet kayıt   saniye

Harika! Herhangi bir ekleme yapmamamız koşuluyla, saniyede 920.000 ekleme yapabiliriz :-)


"En Kötü Durum Senaryosu"

SQL dizesini, dosyadan okunan değerleri kullanarak oluşturacağız ve SQL işlemini sqlite3_exec kullanarak çağırıyoruz:

sprintf(sSQL, "INSERT INTO TTC VALUES (NULL, '%s', '%s', '%s', '%s', '%s', '%s', '%s')", sRT, sBR, sVR, sST, sVI, sDT, sTM);
sqlite3_exec(db, sSQL, NULL, NULL, &sErrMsg);

Bu yavaş olacaktır çünkü SQL her insert için VDBE koduna derlenecek ve her ekleme kendi işleminde gerçekleşecektir. Ne kadar yavaş?

9933.61'de 864913 adet kayıt içe aktarıldı   saniye

Olmadı! 2 saat 45 dakika! Sadece Saniyede 85 ekleme.

Bir İşlemi Kullanma

Varsayılan olarak, SQLite her INSERT / UPDATE deyimini benzersiz bir işlem içinde değerlendirecektir. Çok sayıda ek parça varsa, işleminizi bir işlemde sarmanız önerilir:

sqlite3_exec(db, "BEGIN TRANSACTION", NULL, NULL, &sErrMsg);

pFile = fopen (INPUTDATA,"r");
while (!feof(pFile)) {

    ...

}
fclose (pFile);

sqlite3_exec(db, "END TRANSACTION", NULL, NULL, &sErrMsg);

38.03'te 864913 adet kayıt içe aktarıldı   saniye

Bu daha iyi. Eklerimizin tümünü tek bir işlemde birleştirmek, performansımızı Saniyede 23.000 ekler.

Hazırlanan Bir Deyimi Kullanma

Bir işlemin kullanılması büyük bir iyileştirme oldu, ancak SQL'in her eki için SQL ifadesini yeniden derlediğimizde, aynı SQL over-and-over'ı kullanırsak bir anlam ifade etmiyor. Kullanalım sqlite3_prepare_v2 SQL deyimimizi bir kez derlemek ve parametrelerimizi kullanarak bu ifadeye bağlamak sqlite3_bind_text:

/* Open input file and import into the database */
cStartClock = clock();

sprintf(sSQL, "INSERT INTO TTC VALUES (NULL, @RT, @BR, @VR, @ST, @VI, @DT, @TM)");
sqlite3_prepare_v2(db,  sSQL, BUFFER_SIZE, &stmt, &tail);

sqlite3_exec(db, "BEGIN TRANSACTION", NULL, NULL, &sErrMsg);

pFile = fopen (INPUTDATA,"r");
while (!feof(pFile)) {

    fgets (sInputBuf, BUFFER_SIZE, pFile);

    sRT = strtok (sInputBuf, "\t");   /* Get Route */
    sBR = strtok (NULL, "\t");        /* Get Branch */
    sVR = strtok (NULL, "\t");        /* Get Version */
    sST = strtok (NULL, "\t");        /* Get Stop Number */
    sVI = strtok (NULL, "\t");        /* Get Vehicle */
    sDT = strtok (NULL, "\t");        /* Get Date */
    sTM = strtok (NULL, "\t");        /* Get Time */

    sqlite3_bind_text(stmt, 1, sRT, -1, SQLITE_TRANSIENT);
    sqlite3_bind_text(stmt, 2, sBR, -1, SQLITE_TRANSIENT);
    sqlite3_bind_text(stmt, 3, sVR, -1, SQLITE_TRANSIENT);
    sqlite3_bind_text(stmt, 4, sST, -1, SQLITE_TRANSIENT);
    sqlite3_bind_text(stmt, 5, sVI, -1, SQLITE_TRANSIENT);
    sqlite3_bind_text(stmt, 6, sDT, -1, SQLITE_TRANSIENT);
    sqlite3_bind_text(stmt, 7, sTM, -1, SQLITE_TRANSIENT);

    sqlite3_step(stmt);

    sqlite3_clear_bindings(stmt);
    sqlite3_reset(stmt);

    n++;
}
fclose (pFile);

sqlite3_exec(db, "END TRANSACTION", NULL, NULL, &sErrMsg);

printf("Imported %d records in %4.2f seconds\n", n, (clock() - cStartClock) / (double)CLOCKS_PER_SEC);

sqlite3_finalize(stmt);
sqlite3_close(db);

return 0;

16.27’de 864913 adet kayıt   saniye

Güzel! Biraz daha fazla kod var. sqlite3_clear_bindings ve sqlite3_reset), ancak performansımızı iki katına çıkardık. Saniyede 53.000 ekler.

PRAGMA senkron = KAPALI

Varsayılan olarak, bir OS düzeyinde yazma komutu verildikten sonra SQLite duraklar. Bu, verilerin diske yazıldığını garanti eder. Ayarlayarak synchronous = OFFSQLite'ye yazı yazmak için verileri işletim sistemine aktarmayı ve sonra devam etmeyi öğretiyoruz. Veri tabanı tabana yazılmadan önce bilgisayar bir katastrofik kilitlenme (veya elektrik kesintisi) geçirirse veritabanı dosyasının bozulma ihtimali vardır:

/* Open the database and create the schema */
sqlite3_open(DATABASE, &db);
sqlite3_exec(db, TABLE, NULL, NULL, &sErrMsg);
sqlite3_exec(db, "PRAGMA synchronous = OFF", NULL, NULL, &sErrMsg);

12.41'de 864913 adet kayıt içe aktarıldı   saniye

Geliştirmeler artık daha küçük, ancak biz Saniyede 69.600 ekler.

PRAGMA journal_mode = BELLEK

Geri alma günlüğünü değerlendirerek bellekte depolamayı düşünün. PRAGMA journal_mode = MEMORY. İşleminiz daha hızlı olacaktır, ancak bir işlem sırasında güç kaybederseniz veya program çökerse, veritabanı kısmen tamamlanmış bir işlemle bozuk bir durumda bırakılabilir:

/* Open the database and create the schema */
sqlite3_open(DATABASE, &db);
sqlite3_exec(db, TABLE, NULL, NULL, &sErrMsg);
sqlite3_exec(db, "PRAGMA journal_mode = MEMORY", NULL, NULL, &sErrMsg);

13.50 adet içinde 864913 adet kayıt   saniye

Önceki optimizasyondan biraz daha yavaş Saniyede 64.000 ekleme.

PRAGMA senkron = KAPALI ve PRAGMA journal_mode = BELLEK

Önceki iki optimizasyonu birleştirelim. Biraz daha riskli (bir çarpışma durumunda), ama biz sadece veri alıyoruz (bir banka çalıştırmıyoruz):

/* Open the database and create the schema */
sqlite3_open(DATABASE, &db);
sqlite3_exec(db, TABLE, NULL, NULL, &sErrMsg);
sqlite3_exec(db, "PRAGMA synchronous = OFF", NULL, NULL, &sErrMsg);
sqlite3_exec(db, "PRAGMA journal_mode = MEMORY", NULL, NULL, &sErrMsg);

12.00'de 864913 kayıt alındı   saniye

Fantastik! Yapabiliyoruz Saniyede 72.000 insert.

Bellek içi veritabanı kullanma

Sadece tekmeler için, önceki tüm optimizasyonların üzerine inşa edelim ve veritabanı dosya ismini yeniden tanımlayalım, böylece tamamen RAM'de çalışıyoruz:

#define DATABASE ":memory:"

10.94'te 864913 adet kayıt   saniye

Veritabanımızı RAM'de saklamak çok pratik değil, ancak gerçekleştirebileceğimiz etkileyici. Saniyede 79.000 ekleme.

Refactoring C Kodu

Özellikle bir SQLite geliştirme olmasa da, ekstradan hoşlanmıyorum char*atama işlemleri while döngü. Bu kodun çıkışını geçmek için bu kodu hızla yeniden düzenleyelim strtok() doğrudan içine sqlite3_bind_text()ve derleyicinin işleri hızlandırmaya çalışmasına izin verin:

pFile = fopen (INPUTDATA,"r");
while (!feof(pFile)) {

    fgets (sInputBuf, BUFFER_SIZE, pFile);

    sqlite3_bind_text(stmt, 1, strtok (sInputBuf, "\t"), -1, SQLITE_TRANSIENT); /* Get Route */
    sqlite3_bind_text(stmt, 2, strtok (NULL, "\t"), -1, SQLITE_TRANSIENT);    /* Get Branch */
    sqlite3_bind_text(stmt, 3, strtok (NULL, "\t"), -1, SQLITE_TRANSIENT);    /* Get Version */
    sqlite3_bind_text(stmt, 4, strtok (NULL, "\t"), -1, SQLITE_TRANSIENT);    /* Get Stop Number */
    sqlite3_bind_text(stmt, 5, strtok (NULL, "\t"), -1, SQLITE_TRANSIENT);    /* Get Vehicle */
    sqlite3_bind_text(stmt, 6, strtok (NULL, "\t"), -1, SQLITE_TRANSIENT);    /* Get Date */
    sqlite3_bind_text(stmt, 7, strtok (NULL, "\t"), -1, SQLITE_TRANSIENT);    /* Get Time */

    sqlite3_step(stmt);        /* Execute the SQL Statement */
    sqlite3_clear_bindings(stmt);    /* Clear bindings */
    sqlite3_reset(stmt);        /* Reset VDBE */

    n++;
}
fclose (pFile);

Not: Gerçek bir veritabanı dosyası kullanmaya geri döndük. Bellek içi veritabanları hızlıdır, ancak pratik değil

8.94'te 864913 kayıt   saniye

Parametre bağlayıcımızda kullanılan dizi işlem koduna küçük bir refactoring işlemi yapmamıza izin verdi. Saniyede 96.700 insert. Bunun olduğunu söylemenin güvenli olduğunu düşünüyorum. bol hızlı. Diğer değişkenleri (yani sayfa boyutu, dizin oluşturma, vb.) Düzeltmeye başladığımız zaman, bu bizim referansımız olacaktır.


Özet (şimdiye kadar)

Umarım hala yanımda olursun! Bu yolda başladığımızın nedeni, toplu yükleme performansının SQLite ile bu denli değişkenlik göstermesidir ve operasyonumuzu hızlandırmak için ne tür değişiklikler yapılması gerektiği her zaman açık değildir. Aynı derleyiciyi (ve derleyici seçenekleri) kullanarak, SQLite ve aynı veriyi kullanarak kodumuzu optimize ettik ve SQLite kullanımımızı geliştirdik Saniyede 85 ekleme / saniyenin en kötü durum senaryosundan saniyede 96.000 adetten fazla insana!


İNDEKSİ CREATE sonra INSERT ve INSERT sonra INDEX CREATE

Ölçmeye başlamadan önce SELECT performans, biz indeksler oluşturacağımızı biliyoruz. Aşağıdaki yanıtlardan bir tanesinin, yığın ekleri yapılırken, veri eklendikten sonra dizinin oluşturulmasının daha hızlı olduğu (önce dizinin oluşturulmasının ardından verileri yerleştirmenin aksine) önerilmiştir. Hadi deneyelim:

Dizin Oluşturun ve sonra Veri Ekle

sqlite3_exec(db, "CREATE  INDEX 'TTC_Stop_Index' ON 'TTC' ('Stop')", NULL, NULL, &sErrMsg);
sqlite3_exec(db, "BEGIN TRANSACTION", NULL, NULL, &sErrMsg);
...

18.13'te 864913 adet kayıt   saniye

Veri Ekle ve Dizin Oluştur

...
sqlite3_exec(db, "END TRANSACTION", NULL, NULL, &sErrMsg);
sqlite3_exec(db, "CREATE  INDEX 'TTC_Stop_Index' ON 'TTC' ('Stop')", NULL, NULL, &sErrMsg);

13.66’da 864913 kayıt   saniye

Beklendiği gibi, bir sütun dizine eklendiğinde toplu ekler daha yavaştır, ancak veri eklendikten sonra dizin oluşturulduğunda fark yaratır. Endeks olmayan başlangıç ​​rakamımız saniyede 96.000 ek. İlk önce indeks oluşturmak ve daha sonra veri eklemek saniyede 47.700 insert verirken, önce veriyi yerleştirmek ve sonra indeksi oluşturmak saniyede 63.300 insert verir.


Denemek için diğer senaryolar için önerileri almak isterdim ... Ve yakında SELECT sorgular için benzer verileri derlemek olacaktır.


2573


Menşei


İyi bir nokta! Bizim durumumuzda XML ve CSV metin dosyalarından okunan yaklaşık 1.5 milyon anahtar / değer çiftini 200 bin kayıtla ele alıyoruz. SO gibi siteleri çalıştıran veritabanlarına kıyasla küçüktür - ancak SQLite performansının ayarlanmasını önemli ölçüde büyük hale getirir. - Mike Willekes
"Uygulama başlatıldığında daha fazla işlem için ayrıştırılan ve SQLite veritabanına yüklenen XML dosyalarında depolanan büyük miktarda yapılandırma verisi var." Neden XML'de depolamak ve herşeyi başlatma zamanında yüklemek yerine her şeyi sqlite veritabanında tutmuyorsunuz? - CAFxX
Aramadım denediniz mi sqlite3_clear_bindings(stmt);? Bağlamaları her zaman yeterli olacak şekilde ayarladınız: Sqlite3_step () öğesini ilk defa veya sqlite3_reset () işlevinden hemen sonra çağırmadan önce, uygulama, parametrelere değer eklemek için sqlite3_bind () arabirimlerinden birini çağırır. Sqlite3_bind () öğesine yapılan her çağrı, aynı parametredeki önceki bağları geçersiz kılar (görmek: sqlite.org/cintro.html). İçinde hiçbir şey yok bu işlev için dokümanlar onu araman gerektiğini söylüyorsun. - ahcox
ahcox: bağlanma, değişkene değil de işaret edilen adrese bağlıdır. strtok her seferinde yeni bir işaretçi döndürür. Ya sahip olmalısın strcpy Her birinden sonra strtok veya her zaman dize boyunca okuduğu şekilde kopyalayan kendi belirteçinizi yapın. - nemetroid
Tekrarlanan ölçümleri yaptın mı? 7 yerel işaretçiden kaçınmak için 4s "win", karışık bir optimizer bile varsa, garip. - peterchen


Cevaplar:


Birkaç ipucu:

  1. Bir işleme ekler / güncellemeler koyun.
  2. SQLite'nin eski sürümleri için - Daha az paranoyak günlük modunu düşünün (pragma journal_mode). Var NORMALve sonra orada OFFEğer işletim sistemi çökerse muhtemelen veritabanı hakkında çok endişeli değilseniz, ekleme hızı önemli ölçüde artırabilir. Uygulamanız çökerse veriler iyi olmalıdır. Daha yeni sürümlerde, OFF/MEMORY Uygulama düzeyinde çökmeler için ayarlar güvenli değildir.
  3. Sayfa boyutları ile oynamak da bir fark yaratıyor (PRAGMA page_size). Daha büyük sayfa boyutlarına sahip olmak, daha büyük sayfalar bellekte tutulduğu için okuma ve yazma işlemlerini biraz daha hızlı yapabilir. Veritabanınız için daha fazla belleğin kullanılacağını unutmayın.
  4. Endeksleriniz varsa, aramayı düşünün CREATE INDEXtüm eklerinizi yaptıktan sonra. Bu, dizini oluşturup eklemelerinizi yapmaktan önemli ölçüde daha hızlıdır.
  5. SQLite'e eşzamanlı erişiminiz varsa, tüm veritabanı kilitliyken kilitlendiğinden ve çoklu okuyucuların mümkün olmasına rağmen, yazmaların kilitleneceğinden oldukça dikkatli olmalısınız. Bu, yeni SQLite sürümlerinde WAL eklenmesiyle biraz geliştirildi.
  6. Tasarruf alanından yararlanın ... daha küçük veritabanları daha hızlı ilerler. Örneğin, anahtar değer çiftleriniz varsa, anahtarı INTEGER PRIMARY KEY Mümkünse, tabloda belirtilen benzersiz satır numarası sütununun yerini alacak.
  7. Birden fazla iş parçacığı kullanıyorsanız, kullanmayı deneyebilirsiniz. paylaşılan sayfa önbelleğiyüklenen sayfaların, pahalı I / O çağrılarını önleyebilecek şekilde iş parçacıkları arasında paylaşılmasına izin verecek.
  8. Kullanma !feof(file)!

Aynı soruları da sordum İşte ve İşte.


672



Dokümanlar bir PRAGMA journal_mode NORMAL bilmiyor sqlite.org/pragma.html#pragma_journal_mode - OneWorld
Biraz zaman geçti, önerilerim WAL'in kullanıma sunulmasından önce eski versiyonlar için başvurdu. DELETE yeni normal ayar olarak görünüyor ve şimdi KAPALI ve BELLEK ayarları da var. OFF / MEMORY, veritabanı bütünlüğü pahasına yazma performansını artıracak ve OFF, geri dönüşleri tamamen devre dışı bırakır. - Snazzer
# 7 için nasıl etkinleştirileceğine dair bir örnek var mı paylaşılan sayfa önbelleği c # system.data.sqlite sarıcı kullanarak? - Aaron Hudon
# 4, eski hatıraları geri getirdi. - Daha önce bir grubun ekledikten sonra bir dizin ekledikten sonra yeniden ekledikleri en az bir vaka vardı. Bazı sistemler için hala modern sistemlerde daha çabuk çalışabilir, ancak bu süre için tabloya tek erişiminiz olduğunu bilirsiniz. - Bill K


Kullanmayı deneyin SQLITE_STATIC yerine SQLITE_TRANSIENT bu ekler için.

SQLITE_TRANSIENT SQLite, döndürmeden önce dize verilerini kopyalamasına neden olur.

SQLITE_STATIC Verdiğiniz bellek adresinin sorgulama yapılıncaya kadar geçerli olacağını (bu döngüde her zaman durum böyle olur) söyler. Bu, döngü başına birkaç tahsis, kopyalama ve tahsis işlemlerini kurtaracak. Muhtemelen büyük bir gelişme.


102





Sqlite3_clear_bindings (stmt) 'dan kaçının;

Testteki kod, her seferinde yeterli olması gereken bağlayıcıları belirler.

SQLite dokümanlarından C API girişi diyor ki

Sqlite3_step () öğesini ilk defa veya hemen aramadan önce   sqlite3_reset () 'den sonra, uygulama   sqlite3_bind () parametrelere değer eklemek için arayüzler. Her   sqlite3_bind () çağrısı, aynı parametredeki önceki bağları geçersiz kılar

(görmek: sqlite.org/cintro.html). Dokümanlar için hiçbir şey yok bu fonksiyon sadece bağlamaları ayarlamak için ek olarak çağırmanız gerektiğini söyleyerek.

Daha fazla detay: http://www.hoogli.com/blogs/micro/index.html#Avoid_sqlite3_clear_bindings ()


80



Şaşırtıcı derecede doğru: "Birçoklarının aksine, sqlite3_reset (), hazırlanmış bir ifadedeki bağları sıfırlamaz. Tüm ana bilgisayarı parametrelerini NULL olarak sıfırlamak için bu rutini kullanın." - sqlite.org/c3ref/clear_bindings.html - Francis Straccia


Toplu ekler

Bu gönderiden ve beni buraya getiren Yığın Taşması sorusundan esinlenerek - Bir SQLite veritabanında bir seferde birden fazla satır eklemek mümkün mü? - İlk mesajımı gönderdim Git depo:

https://github.com/rdpoor/CreateOrUpdate

hangi toplu ActiveRecords bir dizi içine yükler MySQL, SQLite veya PostgreSQL veritabanları. Mevcut kayıtları yok saymak, üzerine yazmak veya bir hata yapmak için bir seçenek içerir. Rasyonel kriterlerim sıralı yazımlara kıyasla 10x hız artışı gösteriyor - YMMV.

Büyük veri kümelerini almak için sık sık ihtiyaç duyduğum üretim kodunda kullanıyorum ve oldukça memnunum.


47



@Jess: Bağlantıyı takip ederseniz, toplu iş sözdizimini kastettiğini göreceksiniz. - Alix Axel


Toplu içe aktarma, en iyi performansı gösterir. INSERT / UPDATE ifadeleri. 10.000 ya da öylesine bir değer benim için sadece birkaç satır, YMMV ile bir tabloda iyi çalıştı ...


40



X = 10,000 değerini ayarlamak istersiniz, böylece x = cache [= cache_size * page_size] / ekinizin ortalama büyüklüğü. - Alix Axel


Sadece okumayı önemsiyorsanız, biraz daha hızlı (ancak eski verileri okuyabilir) sürümü, birden fazla ileti dizisinden çoklu bağlantıdan (iş parçacığı başına bağlantı) okunur.

Öncelikle tablodaki öğeleri bulun:

 SELECT COUNT(*) FROM table

sonra sayfalarda oku (LIMIT / OFFSET)

  SELECT * FROM table ORDER BY _ROWID_ LIMIT <limit> OFFSET <offset>

nerede olduğu ve nerede olduğu gibi, bu şekilde hesaplanır:

int limit = (count + n_threads - 1)/n_threads;

her iş parçacığı için:

int offset = thread_index * limit

Bizim küçük (200mb) db için bu% 50-75 hız yaptı (Windows 7 3.8.0.2 64-bit). Bizim masalarımız ağır normalize edilmemiştir (1000-1500 sütun, kabaca 100.000 veya daha fazla satır).

Çok fazla veya çok az iş parçacığı bunu yapmayacaktır, kendiniz karşılaştırmalı ve profil oluşturmalısınız.

Ayrıca bizim için SHAREDCACHE, performansı daha yavaş hale getirdi, bu yüzden PRIVATECACHE'yi manuel olarak koydum (çünkü bu bizim için küresel olarak etkinleştirildi)


32





Ben cache_size'yi daha yüksek bir değere yükseltene kadar işlemlerden kazanç elde etmem. PRAGMA cache_size=10000;


20