SQLite এর INSERT-per-second পারফরম্যান্স উন্নত করবেন?




performance optimization (6)

বাল্ক ঢালাই উপর

এই পোস্ট দ্বারা এবং স্ট্যাক ওভারফ্লো প্রশ্ন দ্বারা অনুপ্রাণিত যা আমাকে এখানে নিয়ে এসেছে - একটি SQLite ডাটাবেসের মধ্যে একাধিক সারি সন্নিবেশ করা কি সম্ভব? - আমি আমার প্রথম Git রিপোজিটরি পোস্ট করেছি:

https://github.com/rdpoor/CreateOrUpdate

কোন বাল্কটি MySQL , SQLite বা PostgreSQL ডেটাবেসে ActiveRecords এর একটি অ্যারে লোড করে। এটি বিদ্যমান রেকর্ডগুলি উপেক্ষা করতে, তাদের উপরে লেখার বা ত্রুটি বাড়াতে একটি বিকল্প অন্তর্ভুক্ত করে। আমার মৌলিক benchmarks ক্রমিক সংখ্যা তুলনায় একটি 10x গতি উন্নতি প্রদর্শন - YMMV।

আমি উত্পাদন কোডে এটি ব্যবহার করছি যেখানে আমি প্রায়শই বড় ডেটাসেট আমদানি করতে চাই, এবং আমি এটির সাথে বেশ খুশি।

SQLite অপ্টিমাইজিং চতুর। একটি সি অ্যাপ্লিকেশন এর বাল্ক-সন্নিবেশ কর্মক্ষমতা প্রতি সেকেন্ডে 85 টি সন্নিবেশ থেকে প্রতি সেকেন্ডে 96,000 এরও বেশি সন্নিবেশ হতে পারে!

পটভূমি: আমরা একটি ডেস্কটপ অ্যাপ্লিকেশন অংশ হিসাবে SQLite ব্যবহার করা হয়। এক্সএমএল ফাইলগুলিতে সঞ্চিত কনফিগারেশন ডেটা প্রচুর পরিমাণে রয়েছে যা অ্যাপ্লিকেশনটি শুরু হওয়ার পরে আরও প্রক্রিয়াকরণের জন্য বিশ্লেষণ এবং SQLite ডেটাবেসে লোড করা হয়। SQLite এই পরিস্থিতির জন্য আদর্শ কারণ এটি দ্রুত, এটি কোন বিশেষ কনফিগারেশন প্রয়োজন, এবং একটি ফাইল হিসাবে ডিস্কে ডেটাবেস সংরক্ষণ করা হয়।

সংশোধন: প্রাথমিকভাবে আমি যে পারফরম্যান্সটি দেখছিলাম তাতে হতাশ হয়েছিলাম। ডাটাবেসটি কীভাবে কনফিগার করা হয় এবং কিভাবে আপনি API ব্যবহার করছেন তার উপর নির্ভর করে SQLite এর কার্যকারিতা উল্লেখযোগ্যভাবে (বাল্ক-সন্নিবেশ এবং নির্বাচন উভয় ক্ষেত্রে) পরিবর্তিত হতে পারে। সমস্ত বিকল্প এবং কৌশলগুলি কী ছিল তা নির্ধারণ করার জন্য এটি একটি তুচ্ছ ব্যাপার ছিল না, তাই আমি এই সম্প্রদায়ের উইকি এন্ট্রিটি তৈরি করতে বিচক্ষণ ছিলাম যে স্ট্যাক ওভারফ্লো পাঠকদের সাথে ফলাফলগুলি ভাগ করে নেওয়ার জন্য অন্যদের একই তদন্তের সমস্যাগুলি সংরক্ষণ করা হবে।

পরীক্ষা: সাধারন অর্থে কর্মক্ষমতা টিপসের কথা বলার পরিবর্তে (অর্থাত্ "একটি লেনদেন ব্যবহার করুন!" ), আমি মনে করি এটি কিছু সি কোড লিখতে ভাল এবং আসলে বিভিন্ন বিকল্পের প্রভাব পরিমাপ করে। আমরা কিছু সহজ তথ্য দিয়ে শুরু করতে যাচ্ছি:

  • টরন্টো শহরের জন্য সম্পূর্ণ ট্রানজিট সময়সূচীর একটি 28 মেগাবাইট ট্যাব-সীমাবদ্ধ পাঠ্য ফাইল (প্রায় 865,000 রেকর্ড)
  • আমার টেস্ট মেশিন একটি 3.60 গিগাহার্জ পি 4 উইন্ডোজ এক্সপি চলমান।
  • কোডটি ভিজ্যুয়াল সি ++ 2005 এর সাথে "সম্পূর্ণ অপটিমাইজেশন" (/ অক্স) এবং অনুভুতির দ্রুত কোড (/ Ot) সহ "রিলিজ" হিসাবে সংকলিত হয়।
  • আমি SQLite "Amalgamation" ব্যবহার করছি, সরাসরি আমার পরীক্ষার অ্যাপ্লিকেশনে সংকলিত। এসকিউএলাইট সংস্করণটি আমার আগের চেয়ে কিছুটা পুরোনো (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 ব্যবহার করে এসকিউএল ক্রিয়াকলাপকে আহ্বান জানাচ্ছি:

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 সন্নিবেশ আমাদের কর্মক্ষমতা উন্নত

একটি প্রস্তুত বিবৃতি ব্যবহার করে

একটি লেনদেন ব্যবহার করে একটি বিশাল উন্নতি ছিল, কিন্তু আমরা যদি একই এসকিউএল ওভার-ও-ওভার ব্যবহার করে থাকি তবে প্রতিটি সন্নিবেশের জন্য SQL বিবৃতিটি পুনরায় কম্পাইল করা হয় না। sqlite3_prepare_v2 ব্যবহার করে এসকিউএল স্টেটমেন্ট কম্পাইল করার জন্য একবার এবং তারপর সেই বিবৃতিতে sqlite3_bind_text ব্যবহার করে আমাদের পরামিতিগুলি 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 টি সন্নিবেশ আমরা আমাদের কর্মক্ষমতা দ্বিগুণ করেছি

PRMA সিঙ্ক্রোনাস = বন্ধ

ডিফল্টরূপে, SQLite একটি OS- লেভেল লেখার কমান্ড প্রদান করার পরে বিরতি দেবে। এই গ্যারান্টি তথ্য ডিস্ক লেখা হয়। synchronous = OFF , আমরা SQL এর তথ্যটিকে কেবল লেখার জন্য ওএস-এ তথ্যটি হস্তান্তর করার নির্দেশ দিয়েছি এবং তারপরে অবিরত। প্ল্যাটফর্মের ডেটা লিখিত হওয়ার আগে কম্পিউটারটি একটি বিপর্যয়কর ক্র্যাশ (বা পাওয়ার ব্যর্থতা) ভুগলে ডেটাবেস ফাইলটি দূষিত হতে পারে এমন একটি সুযোগ রয়েছে:

/* 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 টি সন্নিবেশ পূর্ববর্তী অপ্টিমাইজেশান চেয়ে সামান্য ধীর

PRMA সিঙ্ক্রোনাস = বন্ধ এবং 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 টি সন্নিবেশ করতে সক্ষম

একটি মেমরি ডাটাবেস ব্যবহার করে

শুধু কিক্সের জন্য, আসুন আমরা আগের সমস্ত অপটিমাইজেশন এবং ডাটাবেস ফাইল নামটি পুনরায় সংজ্ঞায়িত করব তাই আমরা সম্পূর্ণরূপে RAM তে কাজ করছি:

#define DATABASE ":memory:"

10.94 সেকেন্ডে 864913 রেকর্ড আমদানি করা হয়েছে

RAM তে আমাদের ডাটাবেস সংরক্ষণ করার জন্য এটি অতি ব্যবহারিক নয়, তবে এটি প্রতিফলিত যে আমরা প্রতি সেকেন্ডে 79,000 টি সন্নিবেশ সম্পাদন করতে পারি

Refactoring সি কোড

যদিও বিশেষভাবে একটি SQLite উন্নতি না, আমি অতিরিক্ত লুপ char* while লুপ মধ্যে অ্যাসাইনমেন্ট অপারেশন পছন্দ করি না। আসুন দ্রুত কোডটি strtok() সরাসরি strtok() পাস করতে এবং কম্পাইলারটি আমাদের জন্য জিনিসগুলিকে গতিতে দেওয়ার চেষ্টা করি:

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.9 4 সেকেন্ডে 864913 রেকর্ড আমদানি করা হয়েছে

আমাদের পরামিতি বাঁধাই ব্যবহৃত স্ট্রিং প্রক্রিয়াকরণ কোড একটি সামান্য refactoring আমাদের প্রতি সেকেন্ডে 96,700 সন্নিবেশ সঞ্চালন করার অনুমতি দেওয়া হয়েছে আমি এটা নিরাপদ বলে মনে হয় নিরাপদ মনে হয়। আমরা অন্যান্য ভেরিয়েবলগুলি (যেমন পৃষ্ঠা আকার, সূচী তৈরি ইত্যাদি) টিম শুরু করতে শুরু করব এটি আমাদের বেঞ্চমার্ক হবে।

সারসংক্ষেপ (এতদূর)

আশা করি তুমি এখনও আমার সাথে আছো! আমরা এই রাস্তাটি শুরু করার কারণটি হল যে বাল্ক-সন্নিবেশ কর্মক্ষমতা SQLite এর সাথে খুব বন্যভাবে পরিবর্তিত হয় এবং এটি আমাদের অপারেশনটি দ্রুততর করার জন্য কোন পরিবর্তনগুলি করা দরকার তা সর্বদা স্পষ্ট নয়। একই কম্পাইলার (এবং কম্পাইলার অপশন) ব্যবহার করে, একই রকম সংস্করণ এবং এসকিউএলাইটের একই সংস্করণটি আমরা আমাদের কোডটি এবং SQLite এর আমাদের ব্যবহারকে সর্বোত্তম -কেস দৃশ্যকল্প থেকে প্রতি সেকেন্ডে 85 টি সন্নিবেশ থেকে প্রতি সেকেন্ডে 96,000 এরও বেশি সন্নিবেশ থেকে সরিয়ে নিয়েছি!

INDEX তৈরি করুন তারপর INSERT বনাম INSERT তারপর 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 টি ইনসার্ট দেয়।

আমি খুশিভাবে অন্যান্য পরিস্থিতিতে চেষ্টা করার জন্য পরামর্শ চাই ... এবং শীঘ্রই SELECT প্রশ্নের জন্য অনুরূপ তথ্য কম্পাইল করা হবে।


আপনার প্রশ্নের উত্তর হল নতুন sqlite3 কর্মক্ষমতা উন্নত হয়েছে, যে ব্যবহার করুন।

এই উত্তরটি sqlite3 কেন sqlite দিয়ে সরাসরি স্ল্লাইট 3 ব্যবহার করে 25 বার ধীর করে ঢোকানো হয়? এসএলএলএলকিমি অর্ম এর লেখক 0.5 সেকেন্ডে 100 কে ঢোকান এবং আমি পাইথন-সাক্লাইট এবং স্ক্লআলাইমি এর অনুরূপ ফলাফল দেখেছি। যা আমাকে বিশ্বাস করে যে কর্মক্ষমতা sqlite3 সঙ্গে উন্নত হয়েছে


আমি লেনদেন থেকে কোনও লাভ পাইনি যতক্ষণ না আমি cache_size কে উচ্চতর মান হিসাবে উত্থাপিত করি অর্থাৎ PRAGMA cache_size=10000;


এই টিউটোরিয়ালটি পড়ার পর, আমি আমার প্রোগ্রামে এটি প্রয়োগ করার চেষ্টা করেছি।

আমি ঠিকানা ধারণকারী 4-5 ফাইল আছে। প্রতিটি ফাইল প্রায় 30 মিলিয়ন রেকর্ড আছে। আমি প্রস্তাব করছি যে একই কনফিগারেশন ব্যবহার করছি, তবে প্রতি সেকেন্ডে INSERTs আমার নম্বরটি কম (~ সেকেন্ড প্রতি সেকেন্ডে 10.000 রেকর্ড)।

আপনার পরামর্শ ব্যর্থ হয় যেখানে এখানে। আপনি সমস্ত রেকর্ডের জন্য একক লেনদেন এবং কোনও ত্রুটি / ব্যর্থতার সাথে একক সন্নিবেশ ব্যবহার করেন। চলুন আপনি বিভিন্ন টেবিলে একাধিক সন্নিবেশ প্রতিটি রেকর্ড splitting হয় বলে। রেকর্ড ভাঙলে কি হবে?

ON CONFLICT কমান্ডটি প্রযোজ্য নয়, যদি আপনার রেকর্ডে 10 টি উপাদান থাকে এবং আপনাকে একটি পৃথক টেবিলের প্রতিটি উপাদান যোগ করার প্রয়োজন হয়, যদি উপাদান 5 একটি কনস্ট্রাস্টেন্ট ত্রুটি পায় তবে সমস্ত পূর্ববর্তী 4 টি সন্নিবেশও যেতে হবে।

তাই এখানে রোলব্যাক আসে যেখানে। রোলব্যাকের সাথে একমাত্র সমস্যা হল যে আপনি আপনার সমস্ত সন্নিবেশ হারান এবং শীর্ষ থেকে শুরু করুন। আপনি কিভাবে এই সমাধান করতে পারেন?

আমার সমাধান একাধিক লেনদেন ব্যবহার ছিল। আমি প্রত্যেক 10.000 রেকর্ডের একটি লেনদেন শুরু করি এবং শেষ করি (যে নম্বরটি জিজ্ঞাসা করবেন না কেন, আমি এটি পরীক্ষা করে দ্রুততম ছিলাম)। আমি একটি অ্যারের আকার 10.000 তৈরি এবং সেখানে সফল রেকর্ড সন্নিবেশ। যখন ত্রুটি ঘটে, আমি একটি রোলব্যাক করি, একটি লেনদেন শুরু করি, আমার অ্যারে থেকে রেকর্ডগুলি সন্নিবেশ করান, প্রতিশ্রুতিবদ্ধ করুন এবং ভাঙা রেকর্ডের পরে একটি নতুন লেনদেন শুরু করুন।

এই সমাধানটি আমাকে খারাপ / সদৃশ রেকর্ড ধারণকারী ফাইলগুলিতে (যখন আমার প্রায় 4% খারাপ রেকর্ড ছিল) ডিল করার সময় সমস্যাগুলিকে উপেক্ষা করে।

আমি তৈরি অ্যালগরিদম আমাকে আমার প্রক্রিয়া হ্রাস করতে সাহায্য করে 2 ঘন্টা। ফাইলটির 1hr 30m এর চূড়ান্ত লোডিং প্রক্রিয়া যা এখনও ধীরে ধীরে কিন্তু 4hrs এর সাথে তুলনা করা হয়নি যা এটি প্রাথমিকভাবে নেওয়া হয়েছিল। আমি 10.000 / সেকেন্ড থেকে ~ 14.000 / গুলি থেকে সন্নিবেশ গতি পরিচালিত

যে কেউ এটি গতিতে কিভাবে অন্য কোন ধারনা আছে, আমি পরামর্শ খোলা।

আপডেট

উপরের আমার উত্তরের সংযোজনে, আপনার মনে রাখা উচিত যে আপনি যে হার্ড ড্রাইভটি ব্যবহার করছেন তার উপর নির্ভর করে প্রতি সেকেন্ডে সন্নিবেশ করা উচিত। আমি বিভিন্ন হার্ড ড্রাইভ সহ 3 টি ভিন্ন পিসিতে পরীক্ষা করেছি এবং মাঝে মাঝে বিশাল পার্থক্য পেয়েছি। পিসি 1 (1 এইচআর 30 এম), পিসি 2 (6 ঘন্টা) পিসি 3 (14 ঘন্টা), তাই আমি ভাবতে শুরু করলাম যে কেন হবে।

দুই সপ্তাহ গবেষণা এবং একাধিক সংস্থার চেক করার পরে: হার্ড ড্রাইভ, রাম, ক্যাশে, আমি খুঁজে পেয়েছি যে আপনার হার্ড ড্রাইভে কিছু সেটিংস I / O হারকে প্রভাবিত করতে পারে। আপনার পছন্দসই আউটপুট ড্রাইভে বৈশিষ্ট্যগুলি ক্লিক করে আপনি সাধারণ ট্যাবে দুটি বিকল্প দেখতে পারেন। অপট 1: এই ড্রাইভটি সংকুচিত করুন, Opt2: এই ড্রাইভের ফাইল সূচীভুক্ত করার অনুমতি দিন।

এই দুটি বিকল্পগুলি অক্ষম করে সমস্ত 3 টি পিসি এখন শেষ করতে প্রায় একই সময় নেয় (1hr এবং 20 থেকে 40min)। আপনার হার্ড ড্রাইভ এই বিকল্পগুলির সাথে কনফিগার করা হয়েছে কিনা তা ধীর ইনসটের সম্মুখীন হয়েছেন। এটি সমাধান খুঁজে বের করার চেষ্টা করার সময় এবং মাথাব্যাথা আপনাকে অনেক সংরক্ষণ করবে


যদি আপনি শুধুমাত্র পড়ার বিষয়ে যত্নশীল হন, তবে কিছুটা দ্রুত (তবে পুরাতন ডেটা পড়তে পারে) সংস্করণটি একাধিক থ্রেড থেকে (সংযোগ প্রতি-থ্রেড) একাধিক সংযোগ থেকে পড়তে হয়।

প্রথম আইটেম, টেবিল মধ্যে খুঁজে:

 SELECT COUNT(*) FROM table

তারপর পৃষ্ঠাগুলিতে পড়া (LIMIT / OFFSET)

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

কোথায় এবং প্রতি থ্রেড গণনা করা হয়, এই মত:

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

প্রতিটি থ্রেড জন্য:

int offset = thread_index * limit

আমাদের ছোট (200 এমবি) ডিবি জন্য এটি 50-75% স্পিড-আপ (উইন্ডোজ 7 এ 3.8.0.2 64-বিট) তৈরি করেছে। আমাদের টেবিলগুলি ব্যাপকভাবে অ-স্বাভাবিক (1000-1500 কলাম, প্রায় 100,000 বা তার বেশি সারি)।

অনেকগুলি বা খুব কম থ্রেড এটি করবে না, আপনাকে বেঞ্চমার্ক এবং নিজেকে প্রোফাইল করতে হবে।

এছাড়াও আমাদের জন্য, SHAREDCACHE কর্মক্ষমতাটি ধীর করে তুলল, তাই আমি ব্যক্তিগতভাবে PRIVATECACHE রাখি (এটি আমাদের জন্য বিশ্বব্যাপী সক্ষম হয়েছিল)


সেই সন্নিবেশের জন্য SQLITE_STATIC পরিবর্তে SQLITE_TRANSIENT ব্যবহার করে দেখুন।

SQLITE_TRANSIENT ফিরে যাওয়ার আগে স্ট্রিং ডেটা কপি করতে SQLite SQLITE_TRANSIENT করবে।

SQLITE_STATIC এটি বলে যে আপনি যে মেমরি ঠিকানাটি দিয়েছেন তা ক্যোয়ারীটি সম্পন্ন না হওয়া পর্যন্ত বৈধ হবে (যা এই লুপটি সর্বদা ক্ষেত্রে হয়)। এটি আপনাকে প্রতি কয়েকটি বরাদ্দ, অনুলিপি এবং প্রতি লুপ অপারেশন মোছা হবে। সম্ভবত একটি বড় উন্নতি।







optimization