SQLite के INSERT-per-second प्रदर्शन में सुधार


2975

SQLite का अनुकूलन मुश्किल है। C अनुप्रयोग का बल्क-इंसर्ट प्रदर्शन 85 आवेषण प्रति सेकंड से बढ़कर 96,000 आवेषण प्रति सेकंड तक हो सकता है!

पृष्ठभूमि: हम एक डेस्कटॉप अनुप्रयोग के हिस्से के रूप में SQLite का उपयोग कर रहे हैं। हमारे पास XML फ़ाइलों में संग्रहित बड़ी मात्रा में कॉन्फ़िगरेशन डेटा है जो अनुप्रयोग प्रारंभ होने पर आगे की प्रक्रिया के लिए SQLite डेटाबेस में पार्स और लोड किए जाते हैं। SQLite इस स्थिति के लिए आदर्श है क्योंकि यह तेज़ है, इसे किसी विशेष कॉन्फ़िगरेशन की आवश्यकता नहीं है, और डेटाबेस को डिस्क पर एक फ़ाइल के रूप में संग्रहीत किया जाता है।

Rationale: शुरू में मैं जो प्रदर्शन देख रहा था उससे निराश था। यह पता चलता है कि SQLite का प्रदर्शन काफी भिन्न हो सकता है (दोनों थोक-आवेषण और चयन के लिए) इस बात पर निर्भर करता है कि डेटाबेस कैसे कॉन्फ़िगर किया गया है और आप एपीआई का उपयोग कैसे कर रहे हैं। सभी विकल्पों और तकनीकों के बारे में जानने के लिए यह एक तुच्छ मामला नहीं था, इसलिए मैंने स्टैक ओवरफ्लो पाठकों के साथ परिणामों को साझा करने के लिए इस समुदाय विकी प्रविष्टि को बनाने के लिए विवेकपूर्ण विचार किया ताकि दूसरों को समान जांच की परेशानी से बचाया जा सके।

प्रयोग: केवल सामान्य अर्थों में प्रदर्शन युक्तियों के बारे में बात करने के बजाय (यानी "लेन-देन का उपयोग करें!" ), मैंने कुछ सी कोड लिखना और वास्तव में विभिन्न विकल्पों के प्रभाव को मापना सबसे अच्छा समझा । हम कुछ सरल डेटा के साथ शुरुआत करने जा रहे हैं:

  • टोरंटो शहर के लिए पूर्ण पारगमन अनुसूची का 28 एमबी TAB-सीमांकित पाठ फ़ाइल (लगभग 865,000 रिकॉर्ड)
  • मेरी परीक्षण मशीन 3.60 गीगाहर्ट्ज़ पी 4 है जो विंडोज़ एक्सपी चल रही है।
  • कोड को "पूर्ण अनुकूलन" (/ ऑक्स) और फेवरेट फास्ट कोड (/ ओटी) के साथ विजुअल C ++ 2005 के साथ "रिलीज़" के रूप में संकलित किया गया है ।
  • मैं अपने परीक्षण आवेदन में सीधे संकलित SQLite "समामेलन" का उपयोग कर रहा हूं। मेरे द्वारा किया जाने वाला SQLite संस्करण थोड़ा पुराना है (3.6.7), लेकिन मुझे संदेह है कि ये परिणाम नवीनतम रिलीज़ के लिए तुलनीय होंगे (यदि आप अन्यथा सोचते हैं तो एक टिप्पणी छोड़ दें)।

कुछ कोड लिखते हैं!

कोड: एक साधारण सी प्रोग्राम जो पाठ फ़ाइल लाइन-बाय-लाइन पढ़ता है, स्ट्रिंग को मूल्यों में विभाजित करता है और फिर डेटा को SQLite डेटाबेस में सम्मिलित करता है। कोड के इस "बेसलाइन" संस्करण में, डेटाबेस बनाया जाता है, लेकिन हम वास्तव में डेटा नहीं डालेंगे:

/*************************************************************
    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;
}

नियंत्रण"

कोड को चलाने के रूप में-वास्तव में किसी भी डेटाबेस संचालन नहीं करता है, लेकिन यह हमें एक विचार देगा कि कच्ची सी फ़ाइल I / O और स्ट्रिंग प्रोसेसिंग ऑपरेशन कितनी तेजी से होते हैं।

0.94 सेकंड में 864913 रिकॉर्ड आयात किया

महान! हम प्रति सेकंड 920,000 आवेषण कर सकते हैं, बशर्ते हम वास्तव में कोई आवेषण नहीं करते हैं :-)


"सबसे खराब मामला-परिदृश्य"

हम फ़ाइल से पढ़े गए मानों का उपयोग करके SQL स्ट्रिंग उत्पन्न करने जा रहे हैं और sqlite3_exec का उपयोग करते हुए उस SQL ​​कार्रवाई को लागू करते हैं:

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);

यह धीमा होने जा रहा है क्योंकि एसक्यूएल प्रत्येक डालने के लिए वीडीबीई कोड में संकलित किया जाएगा और प्रत्येक प्रविष्टि अपने लेनदेन में होगी। कितना धीमा?

9933.61 सेकंड में 864913 रिकॉर्ड आयात किया

ओह! 2 घंटे और 45 मिनट! यह केवल 85 आवेषण प्रति सेकंड है।

लेन-देन का उपयोग करना

डिफ़ॉल्ट रूप से, SQLite प्रत्येक INSERT / UPDATE स्टेटमेंट का मूल्यांकन एक अद्वितीय लेनदेन के भीतर करेगा। यदि बड़ी संख्या में आवेषण करते हैं, तो अपने ऑपरेशन को लेनदेन में लपेटना उचित है:

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 सेकंड में 864913 रिकॉर्ड आयात किया

वह बेहतर है। बस एक ही लेन-देन में हमारे सभी आवेषण को लपेटने से हमारे प्रदर्शन में 23,000 आवेषण प्रति सेकंड में सुधार हुआ

एक तैयार कथन का उपयोग करना

लेन-देन का उपयोग करना एक बहुत बड़ा सुधार था, लेकिन अगर हम एक ही एसक्यूएल का उपयोग कर रहे हैं, तो हर प्रविष्टि के लिए एसक्यूएल स्टेटमेंट को फिर से जमा करने का कोई मतलब नहीं है। आइए sqlite3_prepare_v2एक बार हमारे एसक्यूएल स्टेटमेंट को संकलित करने के लिए उपयोग करें और फिर हमारे मापदंडों को उस स्टेटमेंट में बाँधें 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 सेकंड में 864913 रिकॉर्ड आयात किया

अच्छा! थोड़ा अधिक कोड है (कॉल करना न भूलें sqlite3_clear_bindingsऔर sqlite3_reset), लेकिन हमने अपने प्रदर्शन को दोगुना कर 53,000 आवेषण प्रति सेकंड कर दिया है।

PRAGMA तुल्यकालिक = बंद

डिफ़ॉल्ट रूप से, SQLite OS-level लिखने की कमांड जारी करने के बाद विराम देगा। यह गारंटी देता है कि डेटा डिस्क पर लिखा गया है। सेटिंग करके synchronous = OFF, हम SQLite को निर्देश दे रहे हैं कि वह डेटा को OS के लिए लिखने के लिए बस हाथ से बंद कर दे और फिर जारी रखें। एक मौका है कि डेटाबेस फ़ाइल दूषित हो सकती है यदि डेटा को प्लाटर को लिखे जाने से पहले कंप्यूटर एक भयावह दुर्घटना (या बिजली की विफलता) से ग्रस्त है:

/* 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 सेकंड में 864913 रिकॉर्ड आयात किया

सुधार अब छोटे हैं, लेकिन हम प्रति सेकंड 69,600 आवेषण तक हैं।

PRAGMA journal_mode = मेमोरी

मूल्यांकन करके मेमोरी में रोलबैक जर्नल को संग्रहीत करने पर विचार करें PRAGMA journal_mode = MEMORY। आपका लेन-देन तेज़ होगा, लेकिन यदि आप किसी लेनदेन के दौरान बिजली खो देते हैं या आपका प्रोग्राम क्रैश हो जाता है, तो आप डेटाबेस को एक आंशिक रूप से पूर्ण किए गए लेनदेन के साथ भ्रष्ट स्थिति में छोड़ सकते हैं:

/* 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 सेकंड में 864913 रिकॉर्ड आयात किया गया

64,000 आवेषण प्रति सेकंड पिछले अनुकूलन की तुलना में थोड़ा धीमा

PRAGMA सिंक्रोनस = ऑफ़ और PRAGMA journal_mode = मेमोरी

चलो पिछले दो अनुकूलन को मिलाते हैं। यह थोड़ा अधिक जोखिम भरा है (दुर्घटना के मामले में), लेकिन हम सिर्फ डेटा आयात कर रहे हैं (बैंक नहीं चला रहे हैं):

/* 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 सेकंड में 864913 रिकॉर्ड आयात किया गया

बहुत खुबस! हम प्रति सेकंड 72,000 आवेषण करने में सक्षम हैं

इन-मेमोरी डेटाबेस का उपयोग करना

बस किक के लिए, चलो पिछली सभी ऑप्टिमाइज़ेशन पर निर्माण करते हैं और डेटाबेस फ़ाइलनाम को फिर से परिभाषित करते हैं ताकि हम पूरी तरह से रैम में काम कर सकें:

#define DATABASE ":memory:"

10.94 सेकंड में 864913 रिकॉर्ड आयात किया

यह हमारे डेटाबेस को रैम में संग्रहीत करने के लिए सुपर-व्यावहारिक नहीं है, लेकिन यह प्रभावशाली है कि हम प्रति सेकंड 79,000 आवेषण प्रदर्शन कर सकते हैं

री कोडिंग सी कोड

हालांकि विशेष रूप से एक SQLite सुधार नहीं है, मुझे लूप char*में अतिरिक्त असाइनमेंट संचालन पसंद नहीं है while। आइए उस कोड को तुरंत रिफलेक्टर strtok()करते हैं sqlite3_bind_text()और सीधे आउटपुट को पास करते हैं , और कंपाइलर हमारे लिए चीजों को गति देने की कोशिश करते हैं:

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);

नोट: हम एक वास्तविक डेटाबेस फ़ाइल का उपयोग करने के लिए वापस आ गए हैं। इन-मेमोरी डेटाबेस तेज़ हैं, लेकिन जरूरी नहीं कि व्यावहारिक हो

8.94 सेकंड में 864913 रिकॉर्ड आयात किया

हमारे पैरामीटर बाइंडिंग में उपयोग किए जाने वाले स्ट्रिंग प्रोसेसिंग कोड के लिए थोड़ी सी भी रिफैक्टिंग ने हमें प्रति सेकंड 96,700 आवेषण प्रदर्शन करने की अनुमति दी है मुझे लगता है कि यह कहना काफी सुरक्षित है कि यह काफी तेज है । जैसा कि हम अन्य चर (यानी पृष्ठ आकार, सूचकांक निर्माण, आदि) को ट्विस्ट करना शुरू करते हैं, यह हमारा बेंचमार्क होगा।


सारांश (अब तक)

मुझे आशा है कि आप अभी भी मेरे साथ हैं! हमने इस सड़क को शुरू करने का कारण यह बताया कि बल्क-इंसर्ट का प्रदर्शन SQLite के साथ बहुत बेतहाशा भिन्न होता है, और यह हमेशा स्पष्ट नहीं होता है कि हमारे ऑपरेशन को गति देने के लिए किन बदलावों की आवश्यकता है। एक ही संकलक (और संकलक विकल्प) का उपयोग करते हुए, SQLite का एक ही संस्करण और उसी डेटा को हमने अपने कोड और SQLite के उपयोग को 85 आवेषण प्रति सेकंड 85,000 से अधिक आवेषण के सबसे खराब स्थिति वाले परिदृश्य से जाने के लिए अनुकूलित किया है !


फिर INDEX बनाएँ, INSERT बनाम INSERT फिर CREATE INDEX

इससे पहले कि हम SELECTप्रदर्शन को मापना शुरू करें , हम जानते हैं कि हम सूचकांक बना रहे हैं। यह नीचे दिए गए उत्तरों में से एक में सुझाव दिया गया है कि जब थोक आवेषण करते हैं, तो डेटा डालने के बाद सूचकांक बनाने के लिए तेज़ होता है (जैसा कि पहले सूचकांक बनाने के लिए विरोध किया जाता है फिर डेटा सम्मिलित किया जाता है)। कोशिश करते हैं:

इंडेक्स बनाएं फिर डेटा डालें

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

18.13 सेकंड में 864913 रिकॉर्ड आयात किया

डेटा डालें फिर इंडेक्स बनाएं

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

13.66 सेकंड में 864913 रिकॉर्ड आयात किया

जैसा कि अपेक्षित है, बल्क-इंसर्ट्स अगर एक कॉलम इंडेक्स किए जाते हैं, तो धीमे होते हैं, लेकिन डेटा डालने के बाद इंडेक्स बनाया जाता है, तो इससे फर्क पड़ता है। हमारी नो-इंडेक्स बेसलाइन 96,000 इन्सर्ट प्रति सेकंड है। पहले इंडेक्स बनाना फिर डेटा डालना हमें प्रति सेकंड 47,700 इंसर्ट देता है, जबकि पहले डेटा डालना फिर इंडेक्स बनाने से हमें प्रति सेकंड 63,300 इंसर्ट मिलते हैं।


मैं ख़ुशी से अन्य परिदृश्यों के लिए सुझाव लेने की कोशिश करूँगा ... और जल्द ही चयनित प्रश्नों के लिए समान डेटा संकलित किया जाएगा।


8
अच्छी बात! हमारे मामले में हम XML और CSV पाठ फ़ाइलों से पढ़ी गई लगभग 1.5 मिलियन कुंजी / मान युग्म 200k रिकॉर्ड में दे रहे हैं। एसओ जैसी साइटों को चलाने वाले डेटाबेस की तुलना में छोटा - लेकिन इतना बड़ा कि SQLite प्रदर्शन ट्यूनिंग महत्वपूर्ण हो जाता है।
माइक विलेक्स

51
"हमारे पास XML फ़ाइलों में संग्रहित बड़ी मात्रा में कॉन्फ़िगरेशन डेटा है, जिन्हें अनुप्रयोग प्रारंभ होने पर आगे की प्रक्रिया के लिए SQLite डेटाबेस में पार्स और लोड किया जाता है।" एक्सएमएल में स्टोर करने और फिर शुरुआती समय में सब कुछ लोड करने के बजाय, आप सबकुछ पहले से ही साइक्लाइट डेटाबेस में क्यों नहीं रखते हैं?
CAFxX

14
क्या आपने फोन करने की कोशिश नहीं की sqlite3_clear_bindings(stmt);? आप हर बार बाइंडिंग सेट करते हैं जिसके माध्यम से पर्याप्त होना चाहिए: sqlite3_step () sqlite3_reset () के बाद पहली बार कॉल करने से पहले, एप्लिकेशन पैरामीटर में मान संलग्न करने के लिए sqlite3_stind () इंटरफेस में से किसी एक को आमंत्रित कर सकता है। Sqlite3_bind () पर प्रत्येक कॉल एक ही पैरामीटर पर पूर्व बाइंडिंग को ओवरराइड करता है (देखें: sqlite.org/cintro.html )। उस फ़ंक्शन के लिए डॉक्स में ऐसा कुछ भी नहीं है जो कह रहा हो कि आपको इसे कॉल करना होगा।
आहॉक्स

21
क्या आप बार-बार माप करते हैं? 7 स्थानीय संकेत से बचने के लिए 4 जी "जीत" अजीब है, यहां तक ​​कि एक भ्रमित आशावादी भी।
peterchen

5
feof()अपने इनपुट लूप की समाप्ति को नियंत्रित करने के लिए उपयोग न करें । द्वारा लौटाए गए परिणाम का उपयोग करें fgets()stackoverflow.com/a/15485689/827263
कीथ थॉम्पसन

जवाबों:


785

कई सुझाव:

  1. एक लेन-देन में आवेषण / अपडेट रखें।
  2. SQLite के पुराने संस्करणों के लिए - एक कम पागल पत्रिका मोड ( pragma journal_mode) पर विचार करें । वहाँ है NORMAL, और फिर वहाँ है OFF, जो काफी डालने की गति बढ़ा सकते हैं यदि आप डेटाबेस के बारे में बहुत चिंतित नहीं हैं तो संभवतः ओएस के दुर्घटनाग्रस्त होने पर दूषित हो सकता है। यदि आपका एप्लिकेशन क्रैश हो जाता है तो डेटा ठीक होना चाहिए। ध्यान दें कि नए संस्करणों में, OFF/MEMORYसेटिंग एप्लिकेशन स्तर क्रैश के लिए सुरक्षित नहीं हैं।
  3. पेज साइज के साथ खेलने से भी फर्क पड़ता है PRAGMA page_size। बड़े पेज साइज़ होने से रीडिंग और लिखाई थोड़ी तेज़ हो सकती है क्योंकि बड़े पेज मेमोरी में होते हैं। ध्यान दें कि आपके डेटाबेस के लिए अधिक मेमोरी का उपयोग किया जाएगा।
  4. यदि आपके पास सूचकांक हैं, तो CREATE INDEXअपने सभी आवेषण करने के बाद कॉल करने पर विचार करें। यह सूचकांक बनाने और फिर अपना आवेषण करने की तुलना में काफी तेज है।
  5. यदि आप SQLite में समवर्ती पहुँच रखते हैं, तो आपको काफी सावधान रहना होगा क्योंकि पूरा डेटाबेस लॉक हो जाता है जब लिखते हैं, और यद्यपि कई पाठक संभव हैं, लेखन बंद हो जाएगा। यह नए SQLite संस्करणों में एक वाल के अलावा के साथ कुछ हद तक सुधार हुआ है।
  6. अंतरिक्ष को बचाने का लाभ उठाएं ... छोटे डेटाबेस तेजी से चलते हैं। उदाहरण के लिए, यदि आपके पास कुंजी मूल्य जोड़े हैं, तो कुंजी को INTEGER PRIMARY KEYयदि संभव हो तो बनाने का प्रयास करें , जो तालिका में निहित अद्वितीय पंक्ति संख्या स्तंभ को बदल देगा।
  7. यदि आप कई थ्रेड्स का उपयोग कर रहे हैं, तो आप साझा किए गए पृष्ठ कैश का उपयोग करने का प्रयास कर सकते हैं , जो लोड किए गए पृष्ठों को थ्रेड्स के बीच साझा करने की अनुमति देगा, जिससे महंगी I / O कॉल से बचा जा सकता है।
  8. उपयोग न करें !feof(file)!

मैंने भी यहाँ और यहाँ ऐसे ही सवाल पूछे हैं


9
डॉक्स एक PRAGMA पता नहीं है सामान्य journal_mode sqlite.org/pragma.html#pragma_journal_mode
वनवर्ल्ड

4
कुछ समय हो गया है, मेरे सुझाव एक वाल पेश किए जाने से पहले पुराने संस्करणों के लिए लागू किए गए थे। ऐसा लगता है कि DELETE नई सामान्य सेटिंग है, और अब OFF और MEMORY सेटिंग भी है। मुझे लगता है कि OFF / MEMORY डेटाबेस अखंडता की कीमत पर लेखन प्रदर्शन में सुधार करेगा, और OFF पूरी तरह से रोलबैक को अक्षम करता है।
स्नैज़र

4
# 7 के लिए, क्या आपके पास c # system.data.sqlite रैपर का उपयोग करके साझा पेज कैश को सक्षम करने का एक उदाहरण है ?
आरोन हडोन

4
# 4 ने युगों पुरानी यादों को वापस ला दिया - पहले के समय में कम से कम एक मामला वापस आ गया था जहाँ एक इंडेक्स को ऐड्स के समूह से पहले ड्राप करना और बाद में इसे फिर से बनाना सम्मिलित करता है। मई अभी भी आधुनिक सिस्टम पर जल्दी से काम करता है कुछ के लिए कहते हैं कि आप जानते हैं कि आपके पास अवधि के लिए तालिका तक एकमात्र पहुंच है।
बिल के

# 1 के लिए अंगूठे: मैंने स्वयं लेनदेन के साथ बहुत अच्छी किस्मत पाई है।
एननो

146

उन आवेषणों के SQLITE_STATICबजाय उपयोग करने का प्रयास करें SQLITE_TRANSIENT

SQLITE_TRANSIENT SQLite लौटने से पहले स्ट्रिंग डेटा की प्रतिलिपि बनाने का कारण होगा।

SQLITE_STATICयह बताता है कि आपके द्वारा दिया गया मेमोरी पता तब तक मान्य होगा जब तक कि क्वेरी का प्रदर्शन नहीं किया जाता है (जो इस लूप में हमेशा होता है)। यह आपको प्रति लूप में कई आवंटित, कॉपी और प्रचालनों को बचाने में मदद करेगा। संभवतः एक बड़ा सुधार।


109

से बचें sqlite3_clear_bindings(stmt)

परीक्षण में कोड हर बार बाइंडिंग सेट करता है जिसके माध्यम से पर्याप्त होना चाहिए।

सी एपीआई परिचय SQLite डॉक्स से कहते हैं:

पहली बार sqlite3_step (sqlite3_reset) के बाद कॉल करने के लिए sqlite3_step () कॉल करने से पहले , एप्लिकेशन पैरामीटरों को मान संलग्न करने के लिए sqlite3_bind () इंटरफेस को आमंत्रित कर सकता है । Sqlite3_bind () के लिए प्रत्येक कॉल एक ही पैरामीटर पर पूर्व बाइंडिंग को ओवरराइड करता है

डॉक्स में sqlite3_clear_bindingsयह कहने के लिए कुछ भी नहीं है कि आपको बस बाइंडिंग सेट करने के अलावा इसे कॉल करना होगा।

अधिक विवरण: Avoid_sqlite3_clear_bindings ()


5
आश्चर्यजनक रूप से सही: "कई के अंतर्ज्ञान के विपरीत, sqlite3_reset () तैयार किए गए विवरण पर बाइंडिंग को रीसेट नहीं करता है। NULL को सभी होस्ट पैरामीटर रीसेट करने के लिए इस दिनचर्या का उपयोग करें।" - sqlite.org/c3ref/clear_bindings.html
फ्रांसिस स्ट्रैचिया

63

थोक आवेषण पर

इस पोस्ट और स्टैक ओवरफ्लो प्रश्न से प्रेरित होकर जिसने मुझे यहां तक ​​पहुंचाया - क्या SQL सर्वर डेटाबेस में एक बार में कई पंक्तियों को सम्मिलित करना संभव है? - मैंने अपना पहला Git रिपॉजिटरी पोस्ट किया है:

https://github.com/rdpoor/CreateOrUpdate

कौन सा थोक MySQL , SQLite या PostgreSQL डेटाबेस में ActiveRecords की एक सरणी लोड करता है। इसमें मौजूदा रिकॉर्ड को अनदेखा करने, उन्हें अधिलेखित करने या त्रुटि बढ़ाने का विकल्प शामिल है। मेरे अल्पविकसित मानदंड क्रमिक लेखन की तुलना में 10x गति सुधार दिखाते हैं - YMMV।

मैं इसे उत्पादन कोड में उपयोग कर रहा हूं जहां मुझे अक्सर बड़े डेटासेट आयात करने की आवश्यकता होती है, और मैं इससे बहुत खुश हूं।


4
@Jess: यदि आप लिंक का पालन करते हैं, तो आप देखेंगे कि उसका मतलब बैच इन्सर्ट सिंटैक्स है।
एलिक्स एक्सल

48

यदि आप अपने INSERT / UPDATE कथनों को रद्द कर सकते हैं तो थोक आयात सबसे अच्छा प्रदर्शन करता है । 10,000 या तो के मूल्य पर केवल कुछ पंक्तियों के साथ एक मेज पर मेरे लिए अच्छा काम किया है, YMMV ...


22
आप x = 10,000 को ट्यून करना चाहेंगे ताकि x = cache [= cache_size * page_size] / आपके इंसर्ट का औसत आकार हो।
एलिक्स एक्सल

43

यदि आप केवल पढ़ने के बारे में परवाह करते हैं, तो कुछ तेजी से (लेकिन बासी डेटा पढ़ सकते हैं) संस्करण को कई थ्रेड्स (कनेक्शन प्रति थ्रेड) से कई कनेक्शनों से पढ़ना है।

पहले आइटम खोजें, तालिका में:

SELECT COUNT(*) FROM table

फिर पृष्ठों में पढ़ें (लिमिट / ओएफएफएसईटी):

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

प्रति-थ्रेड की गणना कहां और इस तरह की जाती है:

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

प्रत्येक धागे के लिए:

int offset = thread_index * limit

हमारे छोटे (200mb) db के लिए इसने 50-75% स्पीड-अप (विंडोज 7 पर 3.8.0.2 64-बिट) बनाया। हमारी टेबल भारी गैर-सामान्यीकृत हैं (1000-1500 कॉलम, लगभग 100,000 या अधिक पंक्तियाँ)।

बहुत अधिक या बहुत कम धागे ऐसा नहीं करेंगे, आपको खुद को बेंचमार्क और प्रोफ़ाइल करने की आवश्यकता है।

हमारे लिए भी, SHAREDCACHE ने प्रदर्शन धीमा कर दिया है, इसलिए मैंने मैन्युअल रूप से PRIVATECACHE लगा दिया (क्योंकि यह हमारे लिए विश्व स्तर पर सक्षम था)


29

जब तक मैंने cache_size को उच्च मूल्य पर नहीं उठाया, तब तक मुझे लेनदेन से कोई लाभ नहीं मिला PRAGMA cache_size=10000;


ध्यान दें कि पृष्ठोंcache_size की संख्या को कैश करने के लिए सकारात्मक मान का उपयोग करते हुए , कुल रैम आकार नहीं। 4kB के डिफ़ॉल्ट पृष्ठ आकार के साथ, यह सेटिंग 40MB डेटा प्रति खुली फ़ाइल (या प्रति प्रक्रिया, यदि साझा कैश के साथ चल रही है ) तक होगी।
ग्रू

21

इस ट्यूटोरियल को पढ़ने के बाद, मैंने इसे अपने प्रोग्राम पर लागू करने की कोशिश की।

मेरे पास 4-5 फाइलें हैं जिनमें पते हैं। प्रत्येक फ़ाइल में लगभग 30 मिलियन रिकॉर्ड हैं। मैं उसी कॉन्फ़िगरेशन का उपयोग कर रहा हूं जो आप सुझा रहे हैं, लेकिन प्रति सेकंड INSERTs की संख्या कम है (प्रति सेकंड ~ 10.000 रिकॉर्ड)।

यहाँ वह जगह है जहाँ आपका सुझाव विफल रहता है। आप सभी रिकॉर्ड के लिए एक एकल लेनदेन का उपयोग करते हैं और बिना किसी त्रुटि / असफलता के साथ एक एकल सम्मिलित करते हैं। मान लें कि आप प्रत्येक रिकॉर्ड को अलग-अलग तालिकाओं पर कई आवेषण में विभाजित कर रहे हैं। अगर रिकॉर्ड टूट गया तो क्या होगा?

ON CONFLICT कमांड लागू नहीं होता है, क्योंकि यदि आपके पास रिकॉर्ड में 10 तत्व हैं और आपको प्रत्येक तत्व को एक अलग तालिका में सम्मिलित करने की आवश्यकता है, यदि तत्व 5 को एक CONSTRAINT त्रुटि मिलती है, तो पिछले सभी 4 आवेषणों को भी जाने की आवश्यकता है।

तो यहाँ है जहाँ रोलबैक आता है। रोलबैक के साथ एकमात्र मुद्दा यह है कि आप अपने सभी आवेषण खो देते हैं और ऊपर से शुरू करते हैं। आप इसे कैसे हल कर सकते हैं?

मेरा समाधान कई लेनदेन का उपयोग करना था । मैं हर 10.000 रिकॉर्ड के लिए लेनदेन शुरू करता हूं और समाप्त करता हूं (यह न पूछें कि वह संख्या क्यों है, यह सबसे तेज एक मैंने परीक्षण किया था)। मैंने एक सरणी आकार बनाया 10.000 और वहां सफल रिकॉर्ड डालें। जब त्रुटि होती है, तो मैं एक रोलबैक करता हूं, एक लेनदेन शुरू करता हूं, मेरे सरणी से रिकॉर्ड सम्मिलित करता हूं, प्रतिबद्ध करता हूं और फिर टूटे हुए रिकॉर्ड के बाद एक नया लेनदेन शुरू करता हूं।

इस समाधान से मुझे उन मुद्दों को दरकिनार करने में मदद मिली जो मेरे पास खराब / डुप्लिकेट रिकॉर्ड्स वाली फाइलों के साथ काम करते हैं (मेरे पास लगभग 4% खराब रिकॉर्ड थे)।

मैंने जो एल्गोरिथ्म बनाया, उसने मुझे अपनी प्रक्रिया को 2 घंटे कम करने में मदद की। फ़ाइल 1hr 30 मीटर की अंतिम लोडिंग प्रक्रिया जो अभी भी धीमी है लेकिन 4hrs की तुलना में नहीं है जो इसे शुरू में लगी थी। मैं 10.000 / s से ~ 14.000 / s तक आवेषण को गति देने में कामयाब रहा

अगर किसी के पास इस बारे में कोई अन्य विचार है कि इसे कैसे तेज किया जाए, तो मैं सुझावों के लिए तैयार हूं।

अद्यतन :

ऊपर दिए गए मेरे उत्तर के अतिरिक्त, आपको यह ध्यान रखना चाहिए कि आपके द्वारा उपयोग किए जा रहे हार्ड ड्राइव के आधार पर प्रति सेकंड आवेषण। मैंने इसे 3 अलग-अलग पीसी पर अलग-अलग हार्ड ड्राइव के साथ परीक्षण किया और कई बार बड़े पैमाने पर अंतर मिला। PC1 (1hr 30m), PC2 (6hrs) PC3 (14hrs), इसलिए मैं सोचने लगा कि ऐसा क्यों होगा।

दो हफ्तों के अनुसंधान और कई संसाधनों की जांच करने के बाद: हार्ड ड्राइव, राम, कैश, मुझे पता चला कि आपकी हार्ड ड्राइव की कुछ सेटिंग्स I / O दर को प्रभावित कर सकती हैं। अपने वांछित आउटपुट ड्राइव पर गुणों पर क्लिक करके आप सामान्य टैब में दो विकल्प देख सकते हैं। Opt1: इस ड्राइव को संपीड़ित करें, Opt2: इस ड्राइव की फ़ाइलों को अनुक्रमित सामग्री की अनुमति दें।

इन दो विकल्पों को अक्षम करने से सभी 3 पीसी अब समाप्त होने में लगभग एक ही समय लेते हैं (1hr और 20 से 40min)। यदि आप धीमे आवेषणों की जांच करते हैं कि क्या आपकी हार्ड ड्राइव इन विकल्पों के साथ कॉन्फ़िगर की गई है। यह आपको बहुत समय और सिर दर्द से बचाने के लिए समाधान खोजने की कोशिश कर रहा होगा


मैं निम्नलिखित सुझाव दूंगा। * एक स्ट्रिंग प्रतिलिपि से बचने के लिए SQLITE_STATIC बनाम SQLITE_TRANSIENT का उपयोग करें। सुनिश्चित करें कि लेन-देन निष्पादित होने से पहले स्ट्रिंग को परिवर्तित नहीं किया जाएगा। ,?), (NULL,?,?,?,?,?,?,?,?,?,?), (NULL,?,?,?,?,?,?,?,?), (NULL) ,?,?,?,?,?,?,?,?, (?,?), (? NULL; -?,?, -, और, -,?,?,?) * * Mmap फ़ाइल संख्या कम करने के लिए syscalls।
रौज़ियर

यह करते हुए कि मैं 11.51 सेकंड में 5,582,642 रिकॉर्ड आयात करने में सक्षम हूं
rouzier

11

आपके प्रश्न का उत्तर यह है कि नए SQLite 3 ने प्रदर्शन में सुधार किया है, इसका उपयोग करें।

यह उत्तर क्यों SQLAlchemy को sqlite3 के साथ सीधे sqlite3 का उपयोग करने की तुलना में धीमी गति से सम्मिलित करता है? SqlAlchemy द्वारा ऑरम लेखक में 0.5 सेकंड में 100k आवेषण हैं, और मैंने अजगर-साइक्लाइट और SqlAlchemy के समान परिणाम देखे हैं। जो मुझे विश्वास दिलाता है कि SQLite 3 के साथ प्रदर्शन में सुधार हुआ है।


-1

Db में बल्क डेटा सम्मिलित करने के लिए ContentProvider का उपयोग करें। डेटाबेस में बल्क डेटा सम्मिलित करने के लिए नीचे दी गई विधि। इससे SQLite के INSERT-per-second प्रदर्शन में सुधार होना चाहिए।

private SQLiteDatabase database;
database = dbHelper.getWritableDatabase();

public int bulkInsert(@NonNull Uri uri, @NonNull ContentValues[] values) {

database.beginTransaction();

for (ContentValues value : values)
 db.insert("TABLE_NAME", null, value);

database.setTransactionSuccessful();
database.endTransaction();

}

कॉल करें

App.getAppContext().getContentResolver().bulkInsert(contentUriTable,
            contentValuesArray);

लिंक: https://www.vogella.com/tutorials/AndroidSQLite/article.html अधिक जानकारी के लिए ContentProvider अनुभाग का उपयोग करके जांच करें

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.