2013-04-24 2 views
1

В SQLite, если я подготовлю оператор SELECT и начинаю переходить через него, то до достижения последней строки результатов я выполняю другой оператор, который влияет на оператор SELECT, который я перешагиваю, каков ожидаемый результат?Что такое определенное поведение SQLite при перемежении операторов, которые влияют друг на друга?

Я не могу найти что-либо в документации SQLite о том, что должно произойти, но это кажется очень распространенным случаем при программировании в многопоточной среде.

Ниже приведен файл C++, который можно скомпилировать и запустить в Windows, чтобы продемонстрировать ситуацию.

#include "stdafx.h" 
#include "sqlite3.h" 
#include <Windows.h> 
#include <iostream> 
#include <Knownfolders.h> 
#include <Shlobj.h> 
#include <wchar.h> 
#include <comdef.h> 

using namespace std; 

int exec_sql(sqlite3 *db, const char* sql) 
{ 
    char *errmsg; 
    int result = sqlite3_exec(db, sql, NULL, NULL, &errmsg); 
    if (result != SQLITE_OK) { 
     cout << errmsg << endl; 
     return -1; 
    } 

    return 0; 
} 

int _tmain(int argc, _TCHAR* argv[]) 
{ 
    cout << "Running jsqltst with SQLite version: "; 
    cout << sqlite3_libversion(); 
    cout << endl; 

    PWSTR userhome; 

    if (!SUCCEEDED(SHGetKnownFolderPath(FOLDERID_Profile, NULL, NULL, &userhome))) { 
     cout << "Failed getting user home dir\n"; 
     return -1; 
    } 

    wcout << "User home: " << userhome << endl; 

    wchar_t *ws1 = userhome, *ws2 = L"\\test.sqlite"; 
    wstring dbpath_str(ws1); 
    dbpath_str += wstring(ws2); 
    _bstr_t dbpath(dbpath_str.c_str()); 

    cout << "DB path: " << dbpath << endl; 

    sqlite3 *db; 

    int result = sqlite3_open_v2(dbpath, &db, SQLITE_OPEN_READWRITE|SQLITE_OPEN_CREATE, NULL); 
    if (result != SQLITE_OK) { 
     cout << sqlite3_errmsg(db) << endl; 
     return -1; 
    } 

    const char * create_stmt = "CREATE TABLE IF NOT EXISTS atable (id INTEGER PRIMARY KEY, name TEXT, number INTEGER);"; 
    if (exec_sql(db, create_stmt) != 0) { 
     return -1; 
    } 

    const char * delete_stmt = "DELETE FROM atable;"; 
    if (exec_sql(db, delete_stmt) != 0) { 
     return -1; 
    } 

    const char * insert_stmt = "INSERT INTO atable (name,number) VALUES ('Beta',77),('Alpha',99);"; 
    if (exec_sql(db, insert_stmt) != 0) { 
     return -1; 
    } 

    sqlite3_stmt* select_ss; 
    const char * select_stmt = "SELECT * FROM atable;"; 
    result = sqlite3_prepare_v2(db, select_stmt, -1, &select_ss, NULL); 
    if (result != SQLITE_OK) { 
     cout << sqlite3_errmsg(db) << endl; 
     return -1; 
    } 

    int i = 0; 
    boolean gotrow; 
    do { 
     result = sqlite3_step(select_ss); 
     gotrow = result == SQLITE_ROW; 
     if (gotrow) { 
      i++; 
      cout << "I got a row!" << endl; 

      if (i == 1) { 
       if (exec_sql(db, insert_stmt) != 0) { 
        return -1; 
       } 
      } 
     } 
    } while (gotrow); 

    cout << "Last result: " << result << ", errstr: " << sqlite3_errstr(result) << endl; 

    result = sqlite3_finalize(select_ss); 
    if (result != SQLITE_OK) { 
     cout << sqlite3_errmsg(db) << endl; 
     return -1; 
    } 

    return 0; 
} 
+0

Возможные ответы: http://www.sqlite.org/transactional.html http://www.sqlite.org/atomiccommit.html http://www.sqlite.org/faq.html#q6 – Patashu

+0

Спасибо за ссылки. Я уже смотрел на обоих. Первый просто объясняет, как SQLite гарантирует, что БД никогда не повреждается и не обрабатывает ошибки при совершении изменений, а второй просто говорит: «yup это потокобезопасно», но не объясняет ожидаемое поведение. – satur9nine

+0

Эта страница кажется немного более полезной, и она содержит несколько интересных моментов в конце о временных таблицах, мне нужно будет узнать больше об этих проблемах и посмотреть, обеспечивают ли они решение этой проблемы: http: //www.sqlite. org/cvstrac/wiki? p = MultiThreading – satur9nine

ответ

3

Поведение SQLite для параллельных операторов в одной транзакции не документировано и не определено.

Как вы видели, вновь вставленные записи можно увидеть, когда курсор SELECT еще не достиг этой части таблицы. Однако, если SQLite необходимо создать временную таблицу результатов для сортировки или группировки, последующие изменения в таблице не будут отображаться в этом результате. Если у вас есть временная таблица или нет, это может зависеть от решений, сделанных оптимизатором запросов, поэтому это часто не предсказуемо.

Если несколько потоков обращаются к одному и тому же соединению, SQLite блокирует БД вокруг каждого вызова sqlite3_step. Это предотвратит повреждение данных, но у вас все еще будет проблема, заключающаяся в том, что автоматическая транзакция завершается, когда заканчивается их последний активный оператор, и что явная транзакция завершится с ошибкой COMMIT, если есть другой активный оператор.

Многопоточные программы лучше использовать (по крайней мере) одно соединение на поток.

+0

Хороший ответ, было бы неплохо, если бы авторы подтвердили это в документации где-нибудь. Было бы еще лучше, если бы была функция, указывающая на то, что результаты конкретной транзакции не будут затронуты другими параллельными транзакциями. – satur9nine

+0

Результаты конкретной транзакции * всегда * не затрагиваются другими параллельными транзакциями. Ваша проблема заключается в параллельных операциях в транзакции * same *. Чтобы результаты SELECT не пострадали от них, SQLite потребовалось бы создать временную копию данных, даже если это не требуется, или изменить формат файла для хранения как старых, так и новых версий одних и тех же данных. Ни один из них не вписывается в дизайн Lite. –

+0

делает отдельные соединения на поток также решает проблему документированных потоков - перезаписывать-каждый-другой-errmsgs? Но да, если вы используете потоки, лучше использовать транзакции, изолированные от других потоков, –