LINUX.ORG.RU

запись в sqlite базу из многопоточного приложения

 ,


1

0

есть многопоточное приложение. оно пишет в sqlite базу. Здесь нет ничего сложного, так как sqlite поддерживает multiple connections. Но есть одно но. часто после вставки нужно взять last inserted id.

подскажите алгоритм, наиболее подходящий для данной задачи. пока что склоняюсь к этому

если это имеет значение - использую ruby

★★★★★

Последнее исправление: ZuBB (всего исправлений: 2)

пока что склоняюсь к этому

Как оно поможет взять last inserted id?

Безопаснее всего выделить один поток под базу и отправлять ему задачи, когда нужно что-то узнать после вставки, — блокирующие.

note173 ★★★★★
()
Ответ на: комментарий от JFreeM

всегда было так, что после сохранения в базу, требуется заполнить поле ключа в entity.

+1 К примеру, на ЛОР-е когда пишешь коммент, тебя сразу редиректит на страницу к твоему комменту. ( linux.org.ru/forum/category/666#message_id_22 ).

Я так понимаю, что без last_id это было бы невозможно сделать.

anonymous
()

вроде как last inserted id хранится только в sqlite3*, так что вариантов по сути всего два:

а) лочить всю БД с помощью мутекса - sqlite3_db_mutex б) вынести работу с БД в один поток

wota ★★
()

а он разве не в пределах конекшна?

redixin ★★★★
()
Ответ на: комментарий от JFreeM

а чего вдоль то?

А того. Что этоксотыль

всегда было так, что после сохранения в базу, требуется заполнить поле ключа в entity.

автоинкремент всегда был потенциальной проблемой. И решали её всегда одинаково - сиквенсом.

no-dashi ★★★★★
()
Ответ на: комментарий от no-dashi

автоинкремент всегда был потенциальной проблемой. И решали её всегда одинаково - сиквенсом.

где хоть слово было про автоинкремент? сиквенс - это правильно, я согласен.

JFreeM ★★★☆
()

В треде замечены тяжелые вещества. Какие, блжад, мьютексы и локи??? Вы упоролись что ли?

import sqlite3

from threading import Thread
from itertools import groupby

create_stm = """CREATE TABLE IF NOT EXISTS boo (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    title varchar(50)
)"""

def get_conn(path):
    conn = sqlite3.Connection(path, timeout=60)
    return conn

def create_db(conn):
    conn.execute(create_stm)
    conn.commit()

def writer(path, data):
    conn = get_conn(path)
    cursor = conn.cursor()
    for _ in xrange(1000):
        cursor.execute('INSERT INTO boo(title) VALUES (?)', ('boo',))
        conn.commit()
        data.append(cursor.lastrowid)


db_name = '/tmp/boo.db'
create_db(get_conn(db_name))
data = []
threads = []

for _ in xrange(10):
    t = Thread(target=writer, args=(db_name, data))
    t.start()
    threads.append(t)

for t in threads:
    t.join()

for k, v in groupby(sorted(data)):
    assert len(list(v)) == 1, k
baverman ★★★
()

там гдето была функция с waitом

visual ★★★
()
Ответ на: комментарий от note173

Как оно поможет взять last inserted id?

думал извлекать last inserted id в том же блоке что и инсерт. тоесть всегда (при инсерте). чем этот подход плох (кроме небольшого оверхеда)?

когда нужно что-то узнать после вставки, — блокирующие.

чтото голова уже не варит. разьясните поподробнее

ZuBB ★★★★★
() автор топика
Ответ на: комментарий от ZuBB

Ссылка в начале ведет на пул потоков (workers), не очень представляю, как это может помочь в синхронизации доступа. Или может, синхронизация вообще не нужна?

note173 ★★★★★
()
Ответ на: комментарий от baverman

В треде замечены тяжелые вещества.

добро пожаловать из уютного мира питона в темный мир С и библиотек на нем

import sqlite3
...

еще раз убеждаюсь, что питон - ужасный ЯП, мало того, что там GIL, так еще:

а) твой пример не работает на python3 (не проблема была исправить, но...)
б) ты его сам запускал? там сплошные исключения: «OperationalError: database is locked», да-да - «Какие, блжад, мьютексы и локи???», именно так

wota ★★
()

Тут и думать-то нечего.

http://www.sqlite.org/c3ref/last_insert_rowid.html

This routine returns the rowid of the most recent successful INSERT into the database from the database connection in the first argument.

Использование тредпула ортогонально получению идентификатора.

dmitry_vk ★★★
()
Ответ на: комментарий от baverman

cursor = conn.cursor()

почему оно гуглится только для питона? даже на сайте sqlite я ничего не нашел

ZuBB ★★★★★
() автор топика
Ответ на: комментарий от wota

добро пожаловать из уютного мира питона в темный мир С и библиотек на нем

http://www.sqlite.org/c3ref/last_insert_rowid.html

Тоже самое.

a) python3 не нужен

б) УМВРЧЯДНТ?

мало того, что там GIL

Сдается мне, ты не знаешь матчасти.

baverman ★★★
()
Ответ на: комментарий от baverman

Тоже самое.

не тоже самое, sqlite3_last_insert_rowid нельзя использовать из нескольких потоков вот так просто

Сдается мне, ты не знаешь матчасти.

может быть, насколько я знаю - есть реализации свободные от GIL, но это немногое меняет

wota ★★
()
Ответ на: комментарий от wota

sqlite3_last_insert_rowid нельзя использовать из нескольких потоков вот так просто

Если не заметил, то в примере у каждого потока свой коннекшен. Учи матчасть, что еще могу сказать.

но это немногое меняет

ИЧСХ, в подавляющем числе случаев GIL мешает кому угодно, только не питонистам.

baverman ★★★
()
Ответ на: комментарий от wota

sqlite3_last_insert_rowid нельзя использовать из нескольких потоков вот так просто

Один sqlite3* вообще нельзя использовать из нескольких нитей.

dmitry_vk ★★★
()
Ответ на: комментарий от baverman

Если не заметил, то в примере у каждого потока свой коннекшен.

если ты еще не заметил - твой пример нерабочий, а знаешь почему? я в тебя верю - ты догадаешься

ИЧСХ, в подавляющем числе случаев GIL мешает кому угодно, только не питонистам.

за сколько у тебя этот пример выполняется? ;) у меня он выполняется оооочень долго, а пример с потоками на С выполнится практически мгновенно

wota ★★
()
Ответ на: комментарий от dmitry_vk

Один sqlite3* вообще нельзя использовать из нескольких нитей.

так было до версии 3.3.1

wota ★★
()
Ответ на: комментарий от ZuBB

Тогда это превращается в обработку очереди в выделенном потоке, которую я и предлагал.

note173 ★★★★★
()
Ответ на: комментарий от wota

если ты еще не заметил - твой пример нерабочий

Гм, было бы очень интересно снять шляпу, но навряд ли перед тобой.

у меня он выполняется оооочень долго

Уверяю, в этом не GIL виноват, хе-хе.

baverman ★★★
()
Ответ на: комментарий от wota

за сколько у тебя этот пример выполняется?

4с на атоме.

baverman ★★★
()
Ответ на: комментарий от baverman

Гм, было бы очень интересно снять шляпу, но навряд ли перед тобой.

не стоит, конечно

Уверяю, в этом не GIL виноват, хе-хе.

да - в этом виноват лично ты, написавший нерабочий и оооочень медленный код

wota ★★
()
Ответ на: комментарий от wota

написавший нерабочий и оооочень медленный код

Походу, не работает он только у тебя, бедная жертва мейнтейнеров. У меня он прекрасно себя чувствует и под вторым и под третьим питоном.

Кстати, давай свой код, чо.

baverman ★★★
()
Ответ на: комментарий от baverman

кхм, у меня тоже твой код тоже «нерабочий и оооочень медленный»

валится с ошибками, как указали выше

OperationalError: database is locked

python 2.7.2 в генту

ZuBB ★★★★★
() автор топика
Ответ на: комментарий от wota

мало того, что там GIL

Хватит уже чушь пороть. Всегда удивляли люди, которые наслушаются умных словечек, и козыряют ими, направо и налево.

SAA ★★★
()
Ответ на: комментарий от ZuBB

Что я могу сказать? Видимо, так собран sqlite или питонячье расширение. Переделал с мультипроцессингом — все равно работает.

baverman ★★★
()
Ответ на: комментарий от SAA

Хватит уже чушь пороть

вы о чем?

Всегда удивляли люди, которые наслушаются умных словечек, и козыряют ими, направо и налево.

совсем не из таких, но разве GIL не ограничивает производительность? я готов слушать, и не против, если мне что-то объяснят

wota ★★
()
Ответ на: комментарий от wota

вы о чем?

Извините, я немного резко высказался.

совсем не из таких, но разве GIL не ограничивает производительность? я готов слушать, и не против, если мне что-то объяснят

Не накладывает он столько ограничений, сколько о нем говорят. Примеров масса.

SAA ★★★
()
Ответ на: комментарий от SAA

Не накладывает он столько ограничений, сколько о нем говорят. Примеров масса.

не буду спорить, действительно слышал о них только в теории

wota ★★
()
Ответ на: комментарий от baverman

Походу, не работает он только у тебя, бедная жертва мейнтейнеров

okay

Кстати, давай свой код, чо.

#include <csignal>
#include <set>
#include <sqlite3.h>
#include <thread>
using namespace std;

int main() 
{
    sqlite3* db;
    sqlite3_open_v2( ":memory:", &db, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE, 0 );
    sqlite3_exec( db, "CREATE TABLE boo( id INT PRIMARY KEY AUTOINCREMENT, title TEXT)", 0, 0, 0 );

    set<int> ids;
    for( size_t i = 0 ; i < 10 ; ++i )
        thread( [db,&ids] {
            for( size_t j = 0 ; j < 1000 ; ++j )
            {
                sqlite3_mutex_enter( sqlite3_db_mutex(db) );
                sqlite3_exec( db, "INSERT INTO boo(title) VALUES ('boo')", 0, 0, 0 );
                if( !ids.insert( sqlite3_last_insert_rowid(db) ).second ) raise( SIGTRAP );
                sqlite3_mutex_leave( sqlite3_db_mutex(db) );
            }
        } ).join();
}
wota ★★
()
Ответ на: комментарий от baverman

Я знал, я знал.
Где теперь твоя скорость?

давай поочереди предъявлять требования - напиши вариант с работой в памяти, это будет правильный бенчмарк кода примера, а не скорости записи на диск, сразу после этого я переделаю на диск, ок?

wota ★★
()
Ответ на: комментарий от baverman

ну и для такого случая давай по 1млн итераций в потоке, т.к. на тысяче мерять нечего

wota ★★
()
Ответ на: комментарий от ZuBB

OperationalError: database is locked

Всё, вспомнил где косяк. Надо соединения заранее открывать.

import sqlite3

from threading import Thread
from itertools import groupby
from Queue import Queue

create_stm = """CREATE TABLE IF NOT EXISTS boo (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    title varchar(50)
)"""

def get_conn(path):
    conn = sqlite3.Connection(path)
    return conn

def create_db(conn):
    conn.execute(create_stm)
    conn.commit()

def writer(path, data, q):
    conn = get_conn(path)
    cursor = conn.cursor()
    count = 0
    while q.get(True):
        count += 1
        cursor.execute('INSERT INTO boo(title) VALUES (?)', ('boo',))
        conn.commit()
        data.append(cursor.lastrowid)

    print count

db_name = '/tmp/boo.db'
create_db(get_conn(db_name))
data = []
threads = []
q = Queue(1000)

for _ in range(4):
    t = Thread(target=writer, args=(db_name, data, q))
    t.start()
    threads.append(t)

for _ in range(10000):
    q.put(True)

for _ in threads:
    q.put(False)

for t in threads:
    t.join()

for k, v in groupby(sorted(data)):
    assert len(list(v)) == 1, k
baverman ★★★
()
Ответ на: комментарий от wota

напиши вариант с работой в памяти

У каждого соединения своя :memory:. Поэтому невозможно.

А без коммита у меня тоже мгновенно отрабатывает. sync всегда был очень дорогой операцией.

baverman ★★★
()
Ответ на: комментарий от baverman

Поэтому невозможно.

я правильно понял? в питоне _невозможно_ из двух потоков писать в один кеш, например? не верю, ты просто не подумал хорошо

А без коммита у меня тоже мгновенно отрабатывает

а у меня нет, хотя я же забыл - я же «бедная жертва мейнтейнеров», все верно, так и должно быть

wota ★★
()
Ответ на: комментарий от baverman

Всё, вспомнил где косяк. Надо соединения заранее открывать.

кстати косяк конечно не в этом, этот вариант все так же сыплет: OperationalError: database is locked

wota ★★
()
Ответ на: комментарий от wota

я правильно понял?

Нет. Тебе ручник мешает. Расшифровываю — мой вариант (без локов), используя несколько соединений на :memory: невозможен.

baverman ★★★
()
Ответ на: комментарий от baverman

Нет. Тебе ручник мешает

а я уже надейлся на культурный разговор

Расшифровываю — мой вариант (без локов), используя несколько соединений на :memory: невозможен.

ес-но, я это изначально знал ;)

wota ★★
()
Ответ на: комментарий от baverman

ну и попробуй, попробуй уже догадаться в чем причина ошибки «OperationalError: database is locked», это не сложно

wota ★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.