c - تحسين أداء INSERT في الثانية من SQLite؟




performance optimization (6)

على إدراجات بالجملة

مستوحاة من هذا المنشور وبواسطة سؤال Stack Overflow الذي أدى بي إلى هنا - هل من الممكن إدراج صفوف متعددة في وقت واحد في قاعدة بيانات SQLite؟ - لقد قمت بنشر أول مستودع لـ Git :

https://github.com/rdpoor/CreateOrUpdate

الذي يحمّل مجموعة كبيرة من ActiveRecords إلى MySQL أو SQLite أو PostgreSQL . ويتضمن خيارًا لتجاهل السجلات الموجودة أو استبدالها أو رفع خطأ. تظهر مقاييس بدائية بلدي تحسين سرعة 10x مقارنة مع يكتب متسلسل - YMMV.

أستخدمه في رمز الإنتاج حيث أحتاج كثيرًا إلى استيراد مجموعات كبيرة من البيانات ، وأنا سعيد جدًا بها.

تحسين SQLite أمر صعب. يمكن أن يختلف أداء الإدخال الجماعي لتطبيق C من 85 إدخالاً في الثانية إلى أكثر من 96،000 إدخال في الثانية!

الخلفية: نحن نستخدم SQLite كجزء من تطبيق سطح المكتب. لدينا كميات كبيرة من بيانات التكوين المخزنة في ملفات XML التي يتم تحليلها وتحميلها في قاعدة بيانات SQLite لمزيد من المعالجة عند تهيئة التطبيق. يعتبر SQLite مثاليًا لهذا الموقف نظرًا لأنه سريع ، ولا يتطلب تكوينًا مخصصًا ، ويتم تخزين قاعدة البيانات على القرص كملف واحد.

الأساس المنطقي: في البداية شعرت بخيبة الأمل من الأداء الذي كنت أراه. وتبين أن أداء SQLite يمكن أن يختلف اختلافاً كبيراً (بالنسبة للإدخالات والتحديدات المجمعة) بناءً على كيفية تكوين قاعدة البيانات وكيفية استخدامك لواجهة برمجة التطبيقات (API). لم يكن الأمر مسألة تافهة لمعرفة ما هي جميع الخيارات والأساليب ، لذلك كنت أعتقد أنه من الحكمة إنشاء هذا الويكي الجماعي للمشاركة في النتائج مع قراء Stack Overflow من أجل إنقاذ الآخرين عناء نفس التحقيقات.

التجربة: بدلاً من مجرد التحدث عن نصائح الأداء بالمعنى العام (أي "استخدم معاملة!" ) ، فكرت في كتابة بعض رموز لغة C وقياس تأثير الخيارات المختلفة. سنبدأ ببعض البيانات البسيطة:

  • ملف نصي يبلغ حجمه 28 ميغابايت من TAB (حوالي 865000 سجل) من جدول النقل العام الكامل لمدينة تورنتو
  • جهاز الاختبار الخاص بي هو P4 3.60 جيجا هرتز يعمل بنظام التشغيل Windows XP.
  • يتم تصنيف التعليمات البرمجية مع Visual C ++ 2005 كـ "الإصدار" مع "أمثلية كاملة" (/ Ox) و "سريع رمز الإحسان" (/ Ot).
  • أنا باستخدام "ملغم" سكليتي ، تجميعها مباشرة في تطبيق الاختبار الخاص بي. إصدار SQLite الذي أواجهه هو أقدم قليلاً (3.6.7) ، ولكني أظن أن هذه النتائج ستكون قابلة للمقارنة مع الإصدار الأخير (يرجى ترك تعليق إذا كنت تعتقد خلاف ذلك).

دعونا نكتب بعض التعليمات البرمجية!

الرمز: برنامج C بسيط يقوم بقراءة الملف النصي سطرًا تلو الآخر ، يقسم السلسلة إلى قيم ثم يقوم بإدراج البيانات في قاعدة بيانات 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;
}

السيطرة"

إن تشغيل الكود كما هو في الواقع لا يؤدي أي عمليات قاعدة بيانات ، ولكنه سيعطينا فكرة عن مدى سرعة عمليات الإدخال / الإخراج لملف C الخام وعمليات معالجة السلسلة.

استيراد 864913 سجلات في 0.94 ثانية

عظيم! يمكننا أن نفعل 920،000 إدراج في الثانية ، شريطة ألا نقوم في الواقع بأي إدراج :-)

"أسوأ سيناريو القضية"

سنقوم بإنشاء سلسلة SQL باستخدام القيم التي تتم قراءتها من الملف واستدعاء عملية 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);

سيكون هذا بطيئاً لأن SQL سيتم تجميعها في رمز VDBE لكل إدراج وكل عملية إدراج ستحدث في معاملتها الخاصة. كيف بطيء؟

استيراد 864913 سجلات في 9933.61 ثانية

ييكيس! ساعتان و 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);

استيراد 864913 سجلات في 38.03 ثانية

هذا أفضل. ببساطة كل من إدراج إدراج لدينا في صفقة واحدة حسّنت أدائنا إلى 23،000 إدراج في الثانية.

باستخدام بيان جاهز

استخدام المعاملة كان تحسنا هائلا ، ولكن إعادة ترجمة جملة SQL لكل إدراج لا معنى لها إذا استخدمنا نفس SQL over-over-over. دعونا نستخدم sqlite3_prepare_v2 لتجميع عبارة SQL الخاصة بنا مرة واحدة ثم ربط 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;

استيراد 864913 سجلات في 16.27 ثانية

لطيف! هناك رمز أكثر قليلاً (لا تنس أن تتصل بـ sqlite3_clear_bindings و sqlite3_reset ) ، ولكننا ضاعفنا أداءنا إلى 53.000 إدخال في الثانية.

متزامن PRAGMA = OFF

بشكل افتراضي ، سيتم إيقاف SQLite بعد إصدار أمر كتابة على مستوى نظام التشغيل. هذا يضمن أن البيانات مكتوبة على القرص. من خلال ضبط synchronous = OFF ، نطلب من SQLite ببساطة تسليم البيانات إلى نظام التشغيل للكتابة ثم المتابعة. هناك احتمال أن يصبح ملف قاعدة البيانات تالفًا إذا كان الكمبيوتر يعاني من تعطل كارثي (أو انقطاع في الطاقة) قبل كتابة البيانات إلى الطبق:

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

استيراد 864913 سجلات في 12.41 ثانية

أصبحت التحسينات أصغر الآن ، ولكننا نصل إلى 69،600 إدخال في الثانية.

PRAGMA journal_mode = MEMORY

خذ بعين الاعتبار تخزين دفتر الاستعادة في الذاكرة عن طريق تقييم 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);

تم استيراد 864913 سجل في 13.50 ثانية

أبطأ قليلاً من التحسين السابق في 64،000 إدراج في الثانية.

PRAGMA متزامن = OFF و 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 synchronous = OFF", NULL, NULL, &sErrMsg);
sqlite3_exec(db, "PRAGMA journal_mode = MEMORY", NULL, NULL, &sErrMsg);

تم استيراد 864913 سجل في 12.00 ثانية

رائع! يمكننا إجراء 72000 إدخال في الثانية.

استخدام قاعدة بيانات في الذاكرة

فقط للركلات ، دعنا نبني على كل التحسينات السابقة ونعيد تحديد اسم ملف قاعدة البيانات حتى نعمل بشكل كامل في ذاكرة الوصول العشوائي:

#define DATABASE ":memory:"

استيراد 864913 سجلات في 10.94 ثانية

ليس من العملي جدًا تخزين قاعدة البيانات الخاصة بنا في ذاكرة الوصول العشوائي ، ولكن من المثير للإعجاب أن نتمكن من إجراء 79،000 إدخال في الثانية.

إعادة بناء كود C

على الرغم من أنه ليس بالتحديد تحسين SQLite ، لا أحب عمليات التعيين char* الإضافية في حلقة while . دعونا بسرعة refactor هذا الرمز لتمرير إخراج 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);

ملاحظة: لقد عدنا إلى استخدام ملف قاعدة بيانات حقيقي. قواعد البيانات في الذاكرة سريعة ، ولكنها ليست عملية بالضرورة

استيراد 864913 سجلات في 8.94 ثانية

لقد سمح لنا إعادة بناء طفيف لشفرة معالجة السلسلة المستخدمة في ربط المعلمة لدينا بإجراء 96،700 إدخالاً في الثانية. أعتقد أنه من الآمن أن نقول أن هذا سريع للغاية . بينما نبدأ في تعديل متغيرات أخرى (مثل حجم الصفحة ، إنشاء الفهرس ، إلخ) ، سيكون هذا بمثابة معيارنا.

ملخص (حتى الآن)

آمل أنك ما زلت معي! السبب في أننا بدأنا في هذا الطريق هو أن أداء الإدخال بالجملة يتغير بشدة مع SQLite ، وليس من الواضح دائمًا ما هي التغييرات التي يجب إجراؤها لتسريع عمليتنا. باستخدام نفس مترجم (وخيارات مترجم) ، نفس الإصدار من SQLite ونفس البيانات قمنا بتحسين الكود الخاص بنا واستخدامنا لـ SQLite للانتقال من سيناريو أسوأ حالة من 85 إدراج في الثانية إلى أكثر من 96،000 إدراج في الثانية الواحدة!

إنشاء CREATE 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);
...

استيراد 864913 سجلات في 18.13 ثانية

إدراج البيانات ثم إنشاء الفهرس

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

تم استيراد 864913 سجل في 13.66 ثانية

كما هو متوقع ، فإن عمليات الإدخال المجمعة تكون أبطأ إذا تمت فهرسة أحد الأعمدة ، إلا أنه يحدث فرقًا إذا تم إنشاء الفهرس بعد إدخال البيانات. لدينا خط الأساس لا يوجد مؤشر 96،000 إدراج في الثانية. إن إنشاء الفهرس أولاً ثم إدخال البيانات يعطينا 47،700 إدخالاً في الثانية ، بينما يعطينا إدخال البيانات أولاً ثم إنشاء المؤشر 63،300 إدخالاً في الثانية.

كان من دواعي سرور اتخاذ اقتراحات لسيناريوهات أخرى لمحاولة ... وسيتم تجميع بيانات مشابهة للاستعلامات SELECT قريبا.


إذا كنت تهتم فقط بالقراءة ، فإن الإصدار الأسرع إلى حد ما (ولكن قد يقرأ البيانات القديمة) هو القراءة من اتصالات متعددة من سلاسل عمليات متعددة (اتصال لكل مؤشر ترابط).

العثور أولاً على العناصر ، في الجدول:

 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

بالنسبة إلى ديسيبل الصغيرة (200mb) ديسيبل هذا جعل سرعة 50-75٪ (3.8.0.2 64 بت على Windows 7). جداولنا غير قابلة للتطبيع بشكل كبير (1000-1500 عمود ، ما يقرب من 100000 أو أكثر من الصفوف).

الكثير من المواضيع أو القليل منها لن تفعل ذلك ، فأنت بحاجة إلى وضع معايير ومعرفة شخصية.

أيضا بالنسبة لنا ، جعل SHAREDCACHE الأداء أبطأ ، لذلك أنا وضعت PRIVATECACHE يدويا (لأنها مكنت عالميا بالنسبة لنا)


بعد قراءة هذا البرنامج التعليمي ، حاولت تنفيذه إلى برنامجي.

لدي 4-5 ملفات تحتوي على عناوين. يحتوي كل ملف على 30 مليون سجل تقريبًا. أستخدم نفس التكوين الذي تقترحه ، لكن عدد INSERT في الثانية منخفض (10.000 سجل لكل ثانية).

هنا حيث فشل اقتراحك. يمكنك استخدام معاملة واحدة لجميع السجلات وإدراج واحد مع عدم وجود أخطاء / فشل. لنفترض أنك تقوم بتقسيم كل سجل إلى عدة إدراجات على جداول مختلفة. ماذا يحدث إذا تم قطع السجل؟

لا يتم تطبيق أمر CONFLICT ON ، إذا كان لديك 10 عناصر في سجل وتحتاج إلى إدراج كل عنصر في جدول مختلف ، إذا كان العنصر 5 يحصل على خطأ CONSTRAINT ، فيجب عندئذٍ أن تدخل جميع الـ4 إدراجات السابقة أيضًا.

إذن هنا يأتي دور التراجع. المشكلة الوحيدة مع العودة إلى الحالة السابقة هي أنك تفقد كل عمليات الإدراج وتبدأ من الجزء العلوي. كيف يمكنك حل هذا؟

كان الحل الخاص بي هو استخدام معاملات متعددة . أبدأ وأضع معاملة في كل 10.000 سجل (لا تسأل لماذا هذا الرقم ، كان أسرع اختبار قمت باختباره). أنا خلقت مجموعة مصفوفة 10.000 وإدراج السجلات الناجحة هناك. عند حدوث الخطأ ، أفعل التراجع ، بدء معاملة ، إدراج السجلات من صفيفي ، ارتكاب ثم تبدأ معاملة جديدة بعد السجل المكسور.

ساعدني هذا الحل في تجاوز المشاكل التي تواجهها عند التعامل مع الملفات التي تحتوي على سجلات سيئة / مكررة (كان لدي سجلات سيئة تقريبًا 4٪).

ساعدتني الخوارزمية التي أنشأتها في تقليل عملي لمدة ساعتين. عملية التحميل النهائية للملف 1 ساعة 30 متر والتي لا تزال بطيئة ولكنها لا تقارن بالساعة 4 ساعات التي استغرقتها في البداية. تمكنت من تسريع إدراج من 10.000 / ثانية إلى ~ 14.000 / ثانية

إذا كان لدى أي شخص أي أفكار أخرى حول كيفية تسريعه ، فأنا منفتح على الاقتراحات.

تحديث :

بالإضافة إلى جوابي أعلاه ، يجب أن تضع في اعتبارك أن الإدخالات في الثانية تعتمد على القرص الصلب الذي تستخدمه أيضًا. اختبرت ذلك على 3 أجهزة كمبيوتر مختلفة مع محركات الأقراص الصلبة المختلفة وحصلت على اختلافات كبيرة في بعض الأحيان. PC1 (1hr 30m) ، PC2 (6hr) PC3 (14hrs) ، لذلك بدأت أتساءل لماذا سيكون ذلك.

بعد أسبوعين من البحث والتحقق من موارد متعددة: محرك الأقراص الثابتة ، ذاكرة الوصول العشوائي ، ذاكرة التخزين المؤقت ، اكتشفت أن بعض الإعدادات على محرك الأقراص الثابتة يمكن أن تؤثر على معدل الإدخال / الإخراج. بالنقر فوق خصائص على محرك الأقراص الذي تريده ، يمكنك رؤية خيارين في علامة التبويب العامة. Opt1: ضغط محرك الأقراص هذا ، Opt2: السماح بفهرسة محتويات محرك الأقراص هذا.

عن طريق تعطيل هذين الخيارين ، تستغرق جميع أجهزة الكمبيوتر الشخصية الثلاثة الآن تقريبًا نفس الوقت للانتهاء (ساعة واحدة و 20 إلى 40 دقيقة). إذا واجهت إدخالات بطيئة ، تحقق مما إذا كان محرك الأقراص الثابت الخاص بك قد تمت تهيئته باستخدام هذه الخيارات. سيوفر لك الكثير من الوقت والصداع في محاولة للعثور على الحل


حاول استخدام SQLITE_STATIC بدلاً من SQLITE_TRANSIENT لتلك SQLITE_STATIC .

سيتسبب SQLITE_TRANSIENT SQLite لنسخ البيانات سلسلة قبل العودة.

يخبرك SQLITE_STATIC أن عنوان الذاكرة الذي قدمته سيكون صالحًا حتى يتم تنفيذ الاستعلام (والذي يكون دائمًا في هذه الحلقة). هذا سيوفر لك عدة تخصيص ونسخ وإلغاء تخصيص العمليات لكل حلقة. ربما تحسن كبير.


لم أحصل على أي ربح من المعاملات حتى رفعت cache_size إلى قيمة أعلى مثل PRAGMA cache_size=10000;


يبدو أن عمليات الاستيراد السائبة تؤدي بشكل أفضل إذا كان بإمكانك تجميع عبارات INSERT / UPDATE . عملت قيمة 10000 أو نحو ذلك جيد بالنسبة لي على طاولة مع عدد قليل من الصفوف ، YMMV ...





optimization