SQLite හි තත්පරයට INSERT කාර්ය සාධනය වැඩි දියුණු කරන්න


2982

SQLite ප්‍රශස්ත කිරීම උපක්‍රමශීලී ය. සී යෙදුමක තොග ඇතුළත් කිරීමේ ක්‍රියාකාරිත්වය තත්පරයට ඇතුළු කිරීම් 85 සිට තත්පරයට ඇතුළු කිරීම් 96,000 දක්වා වෙනස් විය හැකිය!

පසුබිම: අපි ඩෙස්ක්ටොප් යෙදුමක කොටසක් ලෙස SQLite භාවිතා කරමු. යෙදුම ආරම්භ කරන විට වැඩිදුර සැකසීම සඳහා එක්ස්එම්එල් ලිපිගොනු තුළ ගබඩා කර ඇති වින්‍යාස දත්ත විශාල ප්‍රමාණයක් අප සතුව ඇත. SQLite මෙම තත්වයට වඩාත් සුදුසු වන්නේ එය වේගවත් බැවින් එයට විශේෂිත වින්‍යාසයක් අවශ්‍ය නොවන අතර දත්ත සමුදාය තැටියේ තනි ගොනුවක් ලෙස ගබඩා කර ඇති බැවිනි.

තාර්කිකත්වය: මුලදී මා දුටු රංගනය ගැන මම කලකිරුණා. දත්ත සමුදාය වින්‍යාසගත කර ඇති ආකාරය සහ ඔබ ඒපීඅයි භාවිතා කරන්නේ කෙසේද යන්න මත පදනම්ව SQLite හි ක්‍රියාකාරීත්වය සැලකිය යුතු ලෙස වෙනස් විය හැකිය (තොග ඇතුළත් කිරීම් සහ තේරීම් සඳහා). සියලු විකල්ප සහ ශිල්පීය ක්‍රම මොනවාදැයි සොයා ගැනීම සුළුපටු කාරණයක් නොවූ අතර, එම විමර්ශනවලම කරදර අනෙක් අය බේරා ගැනීම සඳහා ස්ටැක් පිටාර ගැලීමේ පා readers කයන් සමඟ ප්‍රති results ල බෙදා ගැනීම සඳහා මෙම ප්‍රජා විකී ප්‍රවේශය නිර්මාණය කිරීම විචක්ෂණශීලී යැයි මම සිතුවෙමි.

අත්හදා බැලීම: සාමාන්‍ය අර්ථයෙන් කාර්ය සාධන ඉඟි ගැන කතා කරනවා වෙනුවට (එනම් "ගනුදෙනුවක් භාවිතා කරන්න!" ), මම සිතුවේ සී කේතයක් ලිවීම සහ විවිධ විකල්පවල බලපෑම මැනීම . අපි සරල දත්ත කිහිපයක් සමඟ ආරම්භ කරන්නෙමු:

  • ටොරොන්ටෝ නගරය සඳහා සම්පූර්ණ සංක්‍රාන්ති කාලසටහනේ 28 MB TAB වෙන් කරන ලද පෙළ ගොනුවක් (දළ වශයෙන් වාර්තා 865,000)
  • මගේ පරීක්ෂණ යන්ත්‍රය වින්ඩෝස් එක්ස්පී ධාවනය වන 3.60 GHz P4 ය.
  • කේතය විෂුවල් සී ++ 2005 සමඟ "මුදා හැරීම" ලෙස "පූර්ණ ප්‍රශස්තිකරණය" (/ ඔක්ස්) සහ ෆේවර් වේගවත් කේතය (/ ඔට්) සමඟ සම්පාදනය කර ඇත.
  • මගේ පරීක්ෂණ යෙදුමට කෙලින්ම සම්පාදනය කරන ලද SQLite "Amalgamation" මම භාවිතා කරමි. මා සතුව ඇති SQLite අනුවාදය ටිකක් පැරණි (3.6.7), නමුත් මෙම ප්‍රති results ල නවතම නිකුතුව සමඟ සැසඳිය හැකි යැයි මම සැක කරමි (කරුණාකර ඔබ වෙනත් ආකාරයකින් සිතන්නේ නම් අදහස් දක්වන්න).

අපි කේත කිහිපයක් ලියමු!

කේතය: සරල සී වැඩසටහනක් මඟින් පෙළ ගොනුව රේඛීයව කියවන අතර, එම නූල අගයන් වලට බෙදී දත්ත 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);

මෙය මන්දගාමී වනු ඇත, මන්ද සෑම ඇතුල් කිරීමක් සඳහාම SQL VDBE කේතයට සම්පාදනය වන අතර සෑම ඇතුළු කිරීමක්ම තමන්ගේම ගනුදෙනුවකදී සිදුවනු ඇත. කෙතරම් මන්දගාමීද?

තත්පර 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 ප්‍රකාශය නැවත සකස් කිරීම අප එකම SQL නැවත නැවත භාවිතා කරන්නේ නම් තේරුමක් නැත. sqlite3_prepare_v2අපගේ SQL ප්‍රකාශය එක් වරක් සම්පාදනය කර අපගේ පරාමිතීන් එම ප්‍රකාශයට බැඳ තබමු 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 රැසක්.

ප්‍රග්මා සමමුහුර්ත = අක්‍රියයි

පෙරනිමියෙන්, OSite මට්ටමේ ලිවීමේ විධානයක් නිකුත් කිරීමෙන් පසු 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);

තත්පර 12.41 කින් වාර්තා 864913 ක් ආනයනය කර ඇත

වැඩිදියුණු කිරීම් දැන් කුඩා වන නමුත් අපි තත්පරයට ඇතුළු කිරීම් 69,600 ක් දක්වා ඉහළ නංවන්නෙමු.

PRAGMA magazine_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 synchronous = OFF සහ PRAGMA magazine_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);

තත්පර 12.00 කින් වාර්තා 864913 ක් ආනයනය කර ඇත

නියමයි! අපට තත්පරයට ඇතුළු කිරීම් 72,000 ක් කළ හැකිය.

මතක මතක දත්ත ගබඩාවක් භාවිතා කිරීම

කික් සඳහා පමණක්, අපි පෙර පැවති සියලු ප්‍රශස්තිකරණයන් මත ගොඩනගා දත්ත සමුදා ගොනු නාමය නැවත අර්ථ දක්වමු, එවිට අපි සම්පූර්ණයෙන්ම RAM හි වැඩ කරමු:

#define DATABASE ":memory:"

තත්පර 10.94 කින් වාර්තා 864913 ක් ආනයනය කර ඇත

අපගේ දත්ත සමුදාය RAM තුළ ගබඩා කිරීම සුපිරි ප්‍රායෝගික නොවේ, නමුත් අපට තත්පරයට ඇතුළු කිරීම් 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 ක් වැනි නරකම අවස්ථාවක සිට තත්පරයට ඇතුළු කිරීම් 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 විමසුම් සඳහා සමාන දත්ත සම්පාදනය කරමි.


8
හොඳ කරුණක්! අපගේ නඩුවේදී අපි එක්ස්එම්එල් සහ සීඑස්වී පෙළ ලිපිගොනු වලින් කියවන ලද යතුරු / වටිනාකම් මිලියන 1.5 ක් පමණ 200k වාර්තා සමඟ ගනුදෙනු කරන්නෙමු. SO වැනි වෙබ් අඩවි ධාවනය කරන දත්ත සමුදායන් හා සසඳන විට කුඩා වේ - නමුත් SQLite ක්‍රියාකාරීත්වය සුසර කිරීම වැදගත් වේ.
මයික් විලෙක්ස්

51
"එක්ස්එම්එල් ලිපිගොනු තුළ ගබඩා කර ඇති වින්‍යාස දත්ත විශාල ප්‍රමාණයක් අප සතුව ඇති අතර ඒවා යෙදුම ආරම්භ කරන විට වැඩිදුර සැකසීම සඳහා SQLite දත්ත ගබඩාවකට පටවනු ලැබේ." එක්ස්එම්එල් හි ගබඩා කර ආරම්භක වේලාවේදී සියල්ල පටවනවා වෙනුවට ඔබ සෑම දෙයක්ම පළමු ස්ථානයේ තබා නොගන්නේ ඇයි?
CAFxX

14
ඔබ ඇමතීමට උත්සාහ කර sqlite3_clear_bindings(stmt);තිබේද? ප්‍රමාණවත් විය යුතු සෑම අවස්ථාවකම ඔබ බන්ධන සකසා ඇත: පළමු වරට sqlite3_step () ඇමතීමට පෙර හෝ sqlite3_reset () පසු වහාම, පරාමිතීන්ට අගයන් ඇමිණීම සඳහා යෙදුමට sqlite3_bind () අතුරුමුහුණතක් කැඳවිය හැකිය. Sqlite3_bind () සඳහා වන සෑම ඇමතුමක්ම එකම පරාමිතියක පෙර බන්ධන අභිබවා යයි (බලන්න: sqlite.org/cintro.html ). එම ශ්‍රිතය සඳහා ලේඛනයේ කිසිවක් නැත, ඔබ එය ඇමතිය යුතු යැයි කියනු ලැබේ.
ahcox

21
ඔබ නැවත නැවත මිනුම් කළාද? දේශීය දර්ශක 7 ක් මග හැරීම සඳහා 4s "ජයග්‍රහණය" අමුතු දෙයක් වන අතර එය ව්‍යාකූල ප්‍රශස්තකරණයක් යැයි උපකල්පනය කරයි.
පීටර්චෙන්

5
feof()ඔබගේ ආදාන ලූපය අවසන් කිරීම පාලනය කිරීමට භාවිතා නොකරන්න . ලබා දුන් ප්‍රති result ලය භාවිතා කරන්න fgets(). stackoverflow.com/a/15485689/827263
කීත් තොම්සන්

Answers:


786

ඉඟි කිහිපයක්:

  1. ගනුදෙනුවකට ඇතුළත් කිරීම් / යාවත්කාලීන කිරීම් දමන්න.
  2. SQLite හි පැරණි අනුවාද සඳහා - අඩු ව්‍යාකූල ජර්නල් මාදිලියක් සලකා බලන්න ( pragma journal_mode). ඇත NORMAL, පසුව ඇති OFFඔබ, OS කැඩුණා නම් හැකි දූෂිත වීම දත්ත සමුදාය ගැන ඕනෑවට කනස්සල්ලට නෑ නම් සැලකිය යුතු ඇතුලත් කරන්න වේගය වැඩි විය හැකි අතර,. ඔබගේ යෙදුම බිඳ වැටුනහොත් දත්ත හොඳින් විය යුතුය. නව අනුවාද වල, OFF/MEMORYයෙදුම් මට්ටමේ බිඳ වැටීම් සඳහා සැකසුම් ආරක්ෂිත නොවන බව සලකන්න .
  3. පිටු ප්‍රමාණයන් සමඟ සෙල්ලම් කිරීමද වෙනසක් ඇති කරයි ( PRAGMA page_size). විශාල පිටු ප්‍රමාණයක් තිබීම මඟින් විශාල පිටු මතකයේ රඳවා ඇති බැවින් කියවීම් සහ ලිවීම් ටිකක් වේගවත් කළ හැකිය. ඔබේ දත්ත සමුදාය සඳහා වැඩි මතකයක් භාවිතා කරන බව සලකන්න.
  4. ඔබට දර්ශක තිබේ නම්, CREATE INDEXඔබගේ සියලු ඇතුළත් කිරීම් සිදු කිරීමෙන් පසුව ඇමතීම සලකා බලන්න . මෙය දර්ශකය නිර්මාණය කර ඔබේ ඇතුළත් කිරීම් සිදු කිරීමට වඩා සැලකිය යුතු වේගයකින් සිදු වේ.
  5. ඔබට SQLite වෙත සමගාමී ප්‍රවේශයක් ඇත්නම් ඔබ ප්‍රවේශම් විය යුතුය, මන්ද ලිවීම් සිදු වූ විට මුළු දත්ත සමුදායම අගුළු දමා ඇති අතර, බහු පා readers කයන්ට හැකි වුවද, ලිවීම් අගුළු දමනු ඇත. නවතම SQLite අනුවාද වල WAL එකතු කිරීමත් සමඟ මෙය තරමක් වැඩිදියුණු කර ඇත.
  6. ඉඩ ඉතිරි කිරීමෙන් ප්‍රයෝජන ගන්න ... කුඩා දත්ත සමුදායන් වේගයෙන් ගමන් කරයි. උදාහරණයක් ලෙස, ඔබට යතුරු අගය යුගල තිබේ INTEGER PRIMARY KEYනම්, හැකි නම් යතුර සෑදීමට උත්සාහ කරන්න , එමඟින් වගුවේ ඇති අද්විතීය පේළි අංක තීරුව ප්‍රතිස්ථාපනය වේ.
  7. ඔබ බහු නූල් භාවිතා කරන්නේ නම්, ඔබට හවුල් පිටු හැඹිලිය භාවිතා කිරීමට උත්සාහ කළ හැකිය , එමඟින් පටවන ලද පිටු නූල් අතර බෙදා ගැනීමට ඉඩ සලසයි, එමඟින් මිල අධික I / O ඇමතුම් වළක්වා ගත හැකිය.
  8. භාවිතා නොකරන්න !feof(file)!

මම ද සමාන ප්රශ්න ඇසූ තියෙනවා මෙතන හා මෙතන .



4
ටික කලක් ගතවී ඇත, මගේ යෝජනා WAL හඳුන්වා දීමට පෙර පැරණි අනුවාද සඳහා අදාළ විය. 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විමසුම සිදු කරන තුරු ඔබ එය ලබා දුන් මතක ලිපිනය වලංගු වන බව එයට කියයි (මෙම ලූපයේ සෑම විටම එසේ වේ). මෙමඟින් ඔබට එක් ලූපයකට වෙන් කිරීම, පිටපත් කිරීම සහ ඉවත් කිරීම වැනි මෙහෙයුම් කිහිපයක් ඉතිරි වේ. විශාල දියුණුවක් විය හැකිය.


110

වළකින්න sqlite3_clear_bindings(stmt).

පරීක්ෂණයේ කේතය ප්‍රමාණවත් විය යුතු සෑම අවස්ථාවකම බන්ධන සකසයි.

මෙම සී API නිරයක් වූ SQLite ලේඛන සිට පවසයි:

පළමු වරට sqlite3_step () ඇමතීමට පෙර හෝ sqlite3_reset () පසු වහාම , පරාමිතීන්ට අගයන් ඇමිණීම සඳහා යෙදුමට sqlite3_bind () අතුරුමුහුණත් කැඳවිය හැකිය . Sqlite3_bind () සඳහා වන සෑම ඇමතුමක්ම එකම පරාමිතිය මත පෙර බන්ධන අභිබවා යයි

sqlite3_clear_bindingsහුදෙක් බන්ධන සැකසීමට අමතරව ඔබ එය ඇමතිය යුතු යැයි පැවසීමට ලේඛනයේ කිසිවක් නොමැත .

වැඩි විස්තර: වළකින්න_ක්ලයිට් 3_ ක්ලෙයාර්_බයින්ඩින්ස් ()


5
පුදුම සහගත ලෙස: "බොහෝ දෙනාගේ ප්‍රතිභාවට පටහැනිව, sqlite3_reset () සූදානම් කළ ප්‍රකාශයක් මත බන්ධන නැවත සකසන්නේ නැත. සියලුම ධාරක පරාමිතීන් NULL වෙත නැවත සැකසීමට මෙම පුරුද්ද භාවිතා කරන්න." - sqlite.org/c3ref/clear_bindings.html
ෆ්‍රැන්සිස් ස්ට්‍රැකියා

63

තොග ඇතුළත් කිරීම් මත

මෙම පෝස්ට් එකෙන් සහ මා මෙහි ගෙන ගිය ස්ටැක් පිටාර ගැලීමේ ප්‍රශ්නයෙන් දේවානුභාවයෙන් - SQLite දත්ත ගබඩාවක වරකට පේළි කිහිපයක් ඇතුළත් කළ හැකිද? - මම මගේ පළමු Git ගබඩාව පළ කළෙමි :

https://github.com/rdpoor/CreateOrUpdate

එය තොග වශයෙන් ActiveRecords සමූහයක් MySQL , SQLite හෝ PostgreSQL දත්ත සමුදායන් වෙත පටවනු ලැබේ . පවත්නා වාර්තා නොසලකා හැරීම, ඒවා නැවත ලිවීම හෝ දෝෂයක් මතු කිරීම සඳහා විකල්පයක් එයට ඇතුළත් වේ. මගේ මුලික මිණුම් සලකුණු මඟින් අනුක්‍රමික ලිවීම් හා සසඳන විට 10x වේගයේ වර්ධනයක් පෙන්නුම් කරයි - YMMV.

මම නිතරම විශාල දත්ත කට්ටල ආනයනය කිරීමට අවශ්‍ය නිෂ්පාදන කේතයේ එය භාවිතා කරමි, ඒ ගැන මම සතුටු වෙමි.


4
Ess ජෙස්: ඔබ සබැඳිය අනුගමනය කරන්නේ නම්, ඔහු අදහස් කළේ කණ්ඩායම් ඇතුළත් කිරීමේ වාක්‍ය ඛණ්ඩයයි.
ඇලික්ස් ඇක්සෙල්

48

ඔබේ INSERT / UPDATE ප්‍රකාශයන් කපා හැරිය හැකි නම් තොග ආනයනය වඩාත් හොඳින් ක්‍රියාත්මක වන බව පෙනේ . 10,000 ක් හෝ ඊට වැඩි වටිනාකමක් පේළි කිහිපයක් පමණක් ඇති මේසයක් මත මට හොඳින් වැඩ කර ඇත, YMMV ...


22
ඔබට x = 10,000 සුසර කිරීමට අවශ්‍ය නිසා x = හැඹිලිය [= cache_size * page_size] / ඔබේ ඇතුළු කිරීමේ සාමාන්‍ය ප්‍රමාණය.
ඇලික්ස් ඇක්සෙල්

43

ඔබ කියවීම ගැන පමණක් සැලකිලිමත් වන්නේ නම්, තරමක් වේගවත් (නමුත් පරණ දත්ත කියවිය හැක) අනුවාදය යනු බහු නූල් වලින් බහු සම්බන්ධතාවයන්ගෙන් කියවීමයි (එක් නූල් එකකට සම්බන්ධතාවය).

පළමුව වගුවේ අයිතම සොයා ගන්න:

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) db සඳහා මෙය 50-75% වේගවත් කිරීමක් (වින්ඩෝස් 7 හි 3.8.0.2 64-බිට්) විය. අපගේ වගු දැඩි ලෙස සාමාන්‍යකරණය වී නොමැත (තීරු 1000-1500, දළ වශයෙන් පේළි 100,000 හෝ ඊට වැඩි).

බොහෝ හෝ ඉතා කුඩා කෙඳි එය නොකරනු ඇත, ඔබ මිණුම් සලකුණු කර ඔබම පැතිකඩ කළ යුතුය.

අප වෙනුවෙන්ද, SHAREDCACHE කාර්ය සාධනය මන්දගාමී විය, එබැවින් මම PRIVATECACHE අතින් තැබුවෙමි (එයට හේතුව එය ගෝලීයව අප වෙනුවෙන් සක්‍රීය කර තිබීමයි)


29

මම හැඹිලි_ ප්‍රමාණය ඉහළ අගයක් දක්වා ඉහළ නංවන තෙක් ගනුදෙනු වලින් කිසිදු වාසියක් මට නොලැබේ PRAGMA cache_size=10000;


ධනාත්මක අගයක් භාවිතා කිරීම පිටු ගණන හැඹිලිය සඳහා cache_sizeසකසයි , මුළු RAM ප්‍රමාණය නොවේ. පෙරනිමි පිටු ප්‍රමාණය 4kB සමඟ, මෙම සැකසුම විවෘත ගොනුවකට දත්ත 40MB දක්වා රඳවා තබා ගනී (හෝ හවුල් හැඹිලියක් සමඟ ක්‍රියාත්මක වන්නේ නම් ).
ග්‍රූ

21

මෙම නිබන්ධනය කියවීමෙන් පසු, මම එය මගේ වැඩසටහනට ක්‍රියාත්මක කිරීමට උත්සාහ කළෙමි.

මා සතුව ලිපිගොනු 4-5 ක් ඇත. සෑම ගොනුවකම දළ වශයෙන් මිලියන 30 ක වාර්තා ඇත. ඔබ යෝජනා කරන වින්‍යාසයම මම භාවිතා කරමි, නමුත් මගේ තත්පරයට INSERT ගණන අඩුය (තත්පරයට records 10.000 වාර්තා).

ඔබේ යෝජනාව අසමත් වන ස්ථානය මෙන්න. ඔබ සියලු වාර්තා සඳහා තනි ගනුදෙනුවක් භාවිතා කරන අතර කිසිදු දෝෂයක් හෝ අසමත් වීමක් නොමැති තනි ඇතුළු කිරීමක් භාවිතා කරයි. ඔබ එක් එක් වාර්තාව විවිධ වගු මතට ඇතුළු කිරීම් කිහිපයකට බෙදා ඇති බව කියමු. වාර්තාව බිඳී ගියහොත් කුමක් සිදුවේද?

ON CONFLICT විධානය අදාළ නොවේ, ඔබට වාර්තාවක මූලද්‍රව්‍ය 10 ක් තිබේ නම් සහ ඔබට එක් එක් මූලද්‍රව්‍යය වෙනත් වගුවකට ඇතුළත් කිරීමට අවශ්‍ය නම්, 5 වන මූලද්‍රව්‍යය CONSTRAINT දෝෂයක් ලැබුනේ නම්, පෙර ඇතුළත් කිරීම් 4 ටම යා යුතුය.

ඉතින් මෙන්න රෝල්බැක් එන තැන. පෙරළීමේ එකම ගැටළුව නම්, ඔබගේ සියලු ඇතුළත් කිරීම් නැති වී ඉහළ සිට ආරම්භ කිරීමයි. ඔබට මෙය විසඳිය හැක්කේ කෙසේද?

මගේ විසඳුම වූයේ බහු ගනුදෙනු භාවිතා කිරීමයි . මම සෑම 10.000 ක්ම ගනුදෙනුවක් ආරම්භ කර අවසන් කරමි (එම අංකය ඇයිදැයි විමසන්න එපා, එය මා පරීක්ෂා කළ වේගවත්ම එකයි). මම 10.000 ප්‍රමාණයේ අරාවක් නිර්මාණය කර එහි සාර්ථක වාර්තා ඇතුළත් කරමි. දෝෂය සිදු වූ විට, මම ආපසු හැරවීමක් කරමි, ගනුදෙනුවක් ආරම්භ කරමි, මගේ අරාවෙන් වාර්තා ඇතුල් කරන්න, බැඳී පසුව බිඳුණු වාර්තාවෙන් පසුව නව ගනුදෙනුවක් ආරම්භ කරන්න.

නරක / අනුපිටපත් වාර්තා අඩංගු ලිපිගොනු සමඟ කටයුතු කිරීමේදී මට ඇති ගැටළු මඟහරවා ගැනීමට මෙම විසඳුම මට උදව් විය (මට 4% කට ආසන්න නරක වාර්තා තිබුණි).

මා විසින් නිර්මාණය කරන ලද ඇල්ගොරිතම මගේ ක්‍රියාවලිය පැය 2 කින් අඩු කිරීමට උපකාරී විය. 1hr 30m ගොනුවේ අවසාන පැටවීමේ ක්‍රියාවලිය තවමත් මන්දගාමී නමුත් එය මුලින් ගත් පැය 4 ට සාපේක්ෂව නොවේ. ඇතුළත් කිරීම් 10.000 / s සිට ~ 14.000 / s දක්වා වේගවත් කිරීමට මට හැකි විය

එය වේගවත් කරන්නේ කෙසේද යන්න පිළිබඳව යමෙකුට වෙනත් අදහස් තිබේ නම්, මම යෝජනා සඳහා විවෘතව සිටිමි.

යාවත්කාලීන කිරීම :

ඉහත මගේ පිළිතුරට අමතරව, ඔබ ද භාවිතා කරන දෘ drive තැටිය මත පදනම්ව තත්පරයට ඇතුළු කරන බව මතක තබා ගත යුතුය. මම විවිධ දෘ hard තැටි සහිත විවිධ පළාත් සභා 3 ක එය පරීක්ෂා කළ අතර කාලයාගේ ඇවෑමෙන් විශාල වෙනස්කම් ඇති විය. PC1 (1hr 30m), PC2 (පැය 6) PC3 (පැය 14), ඒ නිසා මම කල්පනා කරන්න පටන් ගත්තා ඇයි එහෙම වෙන්නේ කියලා.

සති දෙකක පර්යේෂණ සහ බහු සම්පත් පරික්ෂා කිරීමෙන් පසු: දෘ Hard තැටිය, රාම්, හැඹිලිය, ඔබේ දෘ hard තැටියේ සමහර සැකසුම් I / O අනුපාතයට බලපාන බව මම සොයා ගතිමි. ඔබ අපේක්ෂිත ප්‍රතිදාන ධාවකයේ ඇති ගුණාංග ක්ලික් කිරීමෙන් ඔබට සාමාන්‍ය පටිත්තෙහි විකල්ප දෙකක් දැකිය හැකිය. Opt1: මෙම ධාවකය සම්පීඩනය කරන්න, Opt2: මෙම ධාවකයේ ලිපිගොනු අන්තර්ගතය සුචිගත කිරීමට ඉඩ දෙන්න.

මෙම විකල්ප දෙක අක්‍රීය කිරීමෙන් පළාත් සභා 3 ම දැන් නිම කිරීමට ආසන්න වශයෙන් එකම කාලයක් ගතවේ (පැය 1 යි පැය 20 සිට 40 දක්වා). ඔබට මන්දගාමී ඇතුළත් කිරීම් හමු වුවහොත් ඔබේ දෘ hard තැටිය මෙම විකල්ප සමඟ වින්‍යාස කර තිබේදැයි පරීක්ෂා කරන්න. විසඳුම සෙවීමට උත්සාහ කිරීමෙන් ඔබට බොහෝ කාලයක් හා හිසරදයක් ඉතිරි වේ


මම පහත කරුණු යෝජනා කරමි. * පිටපතක් වළක්වා ගැනීම සඳහා SQLITE_STATIC එදිරිව SQLITE_TRANSIENT භාවිතා කරන්න. ගනුදෙනුව ක්‍රියාත්මක කිරීමට පෙර නූල වෙනස් නොවන බවට ඔබ සහතික විය යුතුය. ,?), (NULL,?,?,?,?,?,?,?,?), (NULL,?,?,?,?,?,?,? ,?,?,?,?,?,?,?,?), (NULL,?,?,?,?,?,?,?,?,? syscalls.
rouzier

තත්පර 11.51 කින් වාර්තා 5,582,642 ක් ආනයනය කිරීමට මට හැකි වීම
rouzier

12

ඔබගේ ප්‍රශ්නයට පිළිතුර නම් නවතම SQLite 3 කාර්ය සාධනය වැඩි දියුණු කර ඇති බවයි.

මෙම පිළිතුර sqlal3 කෙලින්ම sqlite3 භාවිතා කිරීමට වඩා 25 ගුණයකින් මන්දගාමී වන්නේ ඇයි? SqlAlchemy Orm කතෘට තත්පර 0.5 කින් 100k ඇතුළු කිරීම් ඇති අතර, පයිතන්-ස්ක්ලයිට් සහ SqlAlchemy සමඟ සමාන ප්‍රති results ල මා දැක ඇත. SQLite 3 සමඟ කාර්ය සාධනය වැඩි දියුණු වී ඇති බව මට විශ්වාස කිරීමට එය හේතු වේ.


-1

තොග දත්ත db තුළට ඇතුළත් කිරීම සඳහා ContentProvider භාවිතා කරන්න. තොග දත්ත සමුදායට ඇතුළත් කිරීම සඳහා පහත ක්‍රමය භාවිතා කරයි. මෙය SQLite හි තත්පරයට INSERT කාර්ය සාධනය වැඩි දියුණු කළ යුතුය.

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 කොටස භාවිතා කිරීම පරීක්ෂා කරන්න

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.