Является ли SQLite потокобезопасным?

Ответ

SQLite является потокобезопасным, но с критически важными условиями.

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

Основное правило: *Одно соединение с базой (`sqlite3`) должно использоваться только в одном потоке за раз.** Нельзя передавать соединение между потоками.

Правильный паттерн использования:

  1. Каждый поток, которому нужен доступ к базе, должен открывать свое собственное соединение.
  2. Каждое соединение работает независимо.

Пример (с использованием библиотеки GRDB.swift):

// Создаем очередь для базы данных, которая управляет соединением.
let dbQueue = try DatabaseQueue(path: "/path/to/database.sqlite")

// Запись из одного потока
DispatchQueue.global().async {
    try! dbQueue.write { db in
        try db.execute(sql: "INSERT INTO users (name) VALUES (?)", arguments: ["Alice"])
    }
}

// Чтение из другого потока
DispatchQueue.global().async {
    try! dbQueue.read { db in
        let names = try String.fetchAll(db, sql: "SELECT name FROM users")
        print(names)
    }
}

Что может пойти не так?

  • Разделение одного соединения между потоками: Приведет к неопределенному поведению и крашам.
  • Блокировки: Длинные транзакции в одном соединении могут блокировать запись для других соединений.

Вывод: SQLite предоставляет механизмы для потокобезопасности, но ответственность за их корректное использование лежит на разработчике.

Ответ 18+ 🔞

А, слушай, смотри, про SQLite и потоки — тут такая, блядь, история, как с одним унитазом на всю общагу. Вроде можно, но если все начнут одновременно срать — будет пиздец.

По дефолту он, сука, собран в каком-то там режиме SERIALIZED. Это типа он сам за собой прибирает, чтобы потоки друг другу в кашу не насрали. Но, внимание, ёпта, это не волшебная таблетка! Это как если дать каждому по своей совковой лопате — работать можно, но если ты начнёшь одной лопатой махать вчетвером, вы друг другу ебальники переломаете.

Главное правило, которое надо выжечь на жопе: *Одно соединение с базой (`sqlite3`) — это как зубная щётка. Только для одного потока.** Нельзя передавать его, как горячую картошку, друг другу. Это не совместное пользование, это путь в ад с крашами и невнятными ошибками.

Как делать правильно, чтобы не было мучительно больно:

  1. Каждый поток, которому вдруг приспичило к базе, открывает своё личное, блядь, соединение. Свою дверь в храм данных.
  2. И пусть себе там ковыряется. Каждое соединение живёт своей жизнью.

Вот, смотри, как умные люди делают (на примере GRDB.swift):

// Создаём очередь для базы — это такой управляющий, который за соединениями следит.
let dbQueue = try DatabaseQueue(path: "/path/to/database.sqlite")

// Один поток пишет
DispatchQueue.global().async {
    try! dbQueue.write { db in
        try db.execute(sql: "INSERT INTO users (name) VALUES (?)", arguments: ["Alice"])
    }
}

// Другой, блядь, в это же время читает
DispatchQueue.global().async {
    try! dbQueue.read { db in
        let names = try String.fetchAll(db, sql: "SELECT name FROM users")
        print(names)
    }
}

А где можно обосраться?

  • Поделить одно соединение на всех: Это как начать драку в телефонной будке. Результат — неопределённое поведение и краш в лучших традициях.
  • Долгие транзакции: Если один поток взял и заперся в транзакции на полчаса, остальные будут ждать, как лохи, и тихо ненавидеть тебя. Терпения, блядь, ноль.

Короче, вывод, ёпта: SQLite даёт тебе инструменты, чтобы не устроить потоп из говна. Но если ты распиздяй и будешь тыкать палкой в механизмы — получишь по ебалу. Всё в твоих руках, чувак.