Паралельний доступ до SQLite


177

Чи безпечно SQLite3 обробляє одночасний доступ за допомогою декількох процесів читання / запису з однієї БД? Чи є з цього винятки з платформи?


3
Я забув згадати щедрий голос: більшість відповідей кажуть, що це нормально: "SQLite досить швидкий", "SQLite добре обробляє паралельність" тощо. прийшов би точно в той же час (теоретичний дуже рідкісний випадок). 1) Чи призведе це до помилки та перерве програму? або 2) Чи чекатиме друга операція запису, поки перша не буде закінчена? або 3) Чи буде відмінена одна з операцій запису (втрата даних!)? 4) Щось ще? Знання обмежень одночасного написання може бути корисним у багатьох ситуаціях.
Бась

7
@Basj Коротше кажучи, 2) він буде чекати і повторювати кілька разів (Налаштовується), 1) викликає помилку, SQLITE_BUSY.3) Ви можете зареєструвати зворотний виклик для обробки помилок SQLITE_BUSY.
obgnaw

Відповіді:


112

Якщо більшість цих одночасних доходів є читаннями (наприклад, SELECT), SQLite може дуже добре впоратися з ними. Але якщо ви почнете писати одночасно, суперечка щодо блокування може стати проблемою. Тоді багато чого залежатиме від того, наскільки швидко працює ваша файлова система, оскільки сам двигун SQLite надзвичайно швидкий і має багато розумних оптимізацій, щоб мінімізувати суперечки. Особливо SQLite 3.

У більшості програм для настільних ПК / ноутбуків / планшетів / телефонів SQLite досить швидкий, оскільки не вистачає одночасності. (Firefox широко використовує SQLite для закладок, історії тощо)

Для серверних додатків хтось деякий час тому сказав, що що-небудь менше 100 000 переглядів сторінок на день може ідеально оброблятися базою даних SQLite в типових сценаріях (наприклад, блоги, форуми), і я ще не бачив жодних доказів протилежного. Насправді, із сучасними дисками та процесорами, 95% веб-сайтів та веб-сервісів добре працювали б із SQLite.

Якщо ви хочете по-справжньому швидкого доступу для читання / запису, використовуйте базу даних SQLite в пам'яті . Оперативна пам’ять на кілька порядків швидша, ніж диск.


16
ОП запитує не про ефективність та швидкість, а про одночасний доступ. Веб-сервери тут ні до чого. Те саме про базу даних пам'яті.
Jarekczek

1
Ви праві в тій мірі, але ефективність / швидкість грають роль. Більш швидкий доступ означає, що час, витрачений на очікування блокування, нижчий, тим самим зменшуючи недоліки продуктивності одночасності SQLite. Зокрема, якщо у вас мало і швидко пише, БД, схоже, не матиме проблем із одночасністю використання користувача.
Кабуз

1
як би ви керували одночасним доступом до бази даних sqlite в пам'яті?
P-Gn

42

Так, SQLite добре обробляє паралельність, але це не найкраще з точки зору продуктивності. З того, що я можу сказати, винятків із цього немає. Деталі розміщені на сайті SQLite: https://www.sqlite.org/lockingv3.html

Ця заява викликає інтерес: "Модуль пейджера гарантує, що зміни відбуваються відразу, що або відбуваються всі зміни, або жодна з них не відбувається, що два або більше процесів не намагаються одночасно отримати доступ до бази даних несумісними способами".


2
Ось кілька коментарів щодо проблем на різних платформах , а саме файлових систем NFS та Windows (хоча це може стосуватися лише старих версій Windows ...)
Nate

1
Чи можна завантажити базу даних SQLite3 в оперативну пам'ять, щоб використовувати її для всіх користувачів PHP? Я здогадуюсь, що це не процедурно
Anon343224користувач

1
@foxyfennec .. вихідна точка, хоча SQLite не може бути оптимальним db для цього випадку використання. sqlite.org/inmemorydb.html
kingPuppy

37

Так. Давайте розберемося, чому

SQLite є транзакційним

Всі зміни в межах однієї транзакції в SQLite або відбуваються повністю, або взагалі не відбуваються

Така підтримка ACID, а також одночасне читання / запис забезпечується 2 - ма способами - з допомогою так званого журналирования (назвемо його « старим ») або каротаж записи вперед (дозволяє називати його « новий шлях »)

Журналістика (Старий шлях)

У цьому режимі SQLite використовує блокування DATABASE-LEVEL . Це важливий момент для розуміння.

Це означає, що коли йому потрібно щось прочитати / записати, він спочатку набуває блокування у файлі бази даних ENTIRE . Кілька читачів можуть співіснувати і паралельно щось читати

Під час написання він гарантує придбання ексклюзивного блокування, і жоден інший процес не читає / записує одночасно, а значить, записи є безпечними.

Ось чому тут вони кажуть, що SQlite здійснює серіалізаційні транзакції

Неприємності

Оскільки для цього потрібно заблокувати всю базу даних кожного разу, і всі чекають, поки процес страждання надходить одночасно, а такі паралельні записи / читання мають досить низьку продуктивність

Відкат / відключення

Перш ніж написати щось у файл бази даних, SQLite спочатку збереже фрагмент, який потрібно змінити у тимчасовий файл. Якщо щось трапиться в середині запису у файл бази даних, він підніме цей тимчасовий файл і відновить зміни з нього

Запис вперед або WAL (новий спосіб)

У цьому випадку всі записи додаються до тимчасового файлу ( журнал попереднього запису ), і цей файл періодично об'єднується з вихідною базою даних. Коли SQLite щось шукає, спершу слід перевірити цей тимчасовий файл, а якщо нічого не знайдено, перейти до основного файлу бази даних.

Як результат, читачі не конкурують із письменниками, а продуктивність значно краща порівняно зі Старим Шляхом.

Коваджі

SQlite сильно залежить від основної функції блокування файлової системи, тому її слід використовувати з обережністю, детальніше тут

Ви також можете зіткнутися з помилкою заблокованої бази даних , особливо в режимі проїзду, тому ваша програма повинна бути розроблена з урахуванням цієї помилки


34

Здається, ніхто не згадував режим WAL (Write Ahead Log). Переконайтесь, що транзакції належним чином організовані та з увімкненим режимом WAL, немає необхідності тримати базу даних заблокованою, коли люди читають речі під час оновлення.

Єдине питання полягає в тому, що в якийсь момент WAL необхідно повторно включити в основну базу даних, і це робиться, коли завершується останнє підключення до бази даних. На дуже зайнятому сайті вам може знадобитися кілька секунд, щоб усі з'єднання були близькими, але 100 Кт звернень на день не повинні бути проблемою.


Цікаво, але працює лише на одній машині, а не на сценаріях, де доступ до бази даних є у всій мережі.
Бобік

Варто зазначити, що час очікування письменника на очікування - 5 секунд, і після цього database is lockedпомилка буде підвищена письменником
mirhossein

16

У 2019 році є два нові параметри запису, які ще не випущені, але доступні в окремих галузях.

"PRAGMA journal_mode = wal2"

Перевага цього режиму журналу перед звичайним режимом «wal» полягає в тому, що автори можуть продовжувати писати в один Wal-файл, а інший перевіряється.

ПОЧАТИ СУЧАСНИЙ - посилання на детальний док

Розширення BEGIN CONCURRENT дозволяє багатьом авторам одночасно обробляти транзакції запису, якщо база даних перебуває в режимі "wal" або "wal2", хоча система все ще послідовно виконує команди COMMIT.

Коли транзакція запису відкривається за допомогою "BEGIN CONCURRENT", фактично блокування бази даних відкладається, поки не буде виконано COMMIT. Це означає, що будь-яка кількість транзакцій, розпочатих з BEGIN CONCURRENT, може відбуватися одночасно. Система використовує оптимістичне блокування рівня сторінки, щоб запобігти здійсненню конфліктних одночасних транзакцій.

Разом вони присутні в початковому-одночасно-wal2 або в кожному окремому власному відділенні .


1
Чи є у нас ідея, коли ці функції пробиться у версію випуску? Вони справді могли б мені стати в нагоді.
Пітер Мур

2
Не маю уявлення. Можна було легко побудувати з гілок. Для .NET у мене є бібліотека з низькорівневим інтерфейсом & WAL2 + почати паралельно + FTS5: github.com/Spreads/Spreads.SQLite
VB

О, звичайно дякую. Мені більше цікаво стабільності. SQLite досить високий рівень, коли справа доходить до їх випусків, але я не знаю, наскільки ризиковано було б використовувати галузь у виробничому коді.
Пітер Мур

2
Дивіться цю тему github.com/Expensify/Bedrock/isissue/65 та Bedrock загалом. Вони використовують його у виробництві і підштовхують цей begin concurrentматеріал.
VB

13

SQLite має блокування читачів-авторів на рівні бази даних. Кілька з'єднань (можливо, належать різним процесам) можуть одночасно зчитувати дані з однієї бази даних, але лише один може записувати в базу даних.

SQLite підтримує необмежену кількість одночасних читачів, але це дозволить лише одному автору в будь-який момент. Для багатьох ситуацій це не проблема. Черга письменника вгору. Кожна програма працює, що її база даних працює швидко і продовжується, і жодне блокування не триває більше кількох десятків мілісекунд. Але є деякі програми, які потребують більшої сумісності, і ці програми, можливо, повинні шукати інше рішення. - Відповідне використання для SQLite @ SQLite.org

Блокування читачів-записувачів дозволяє незалежну обробку транзакцій, і вона реалізується за допомогою ексклюзивних та спільних блокувань на рівні бази даних.

Перед тим, як з'єднання виконує операцію запису в базі даних, слід отримати ексклюзивний замок. Після отримання ексклюзивного блокування операції зчитування та запису з інших з'єднань блокуються до повторного звільнення блокування.

Деталі про реалізацію для випадку одночасного запису

SQLite має таблицю блокування, яка допомагає блокувати базу даних якомога пізніше під час операції запису, щоб забезпечити максимальну одночасність.

Початковий стан НЕ ЗАБЕЗПЕЧЕНО, і в цьому стані з'єднання ще не отримало доступу до бази даних. Коли до бази даних підключено процес і навіть розпочато транзакцію з BEGIN, з'єднання все ще знаходиться в режимі НЕ ЗАБЕЗПЕЧЕНО.

Після НЕЗАЄМОГО стану наступним станом є стан СЕКРАНІ. Для того, щоб мати можливість читати (не записувати) дані з бази даних, з'єднання спочатку повинно перейти в стан SHARED, отримавши БІЛЬШЕ Блокування. Кілька з’єднань можуть одночасно отримувати та підтримувати блоки SHARED, тому багато з’єднань можуть читати дані з однієї бази даних одночасно. Але поки навіть лише один блок SHARED залишається невипущеним, жодне з'єднання не може успішно завершити запис у базу даних.

Якщо з'єднання хоче записати в базу даних, воно спочатку має заблокувати заблокований.

Одночасно може бути активним лише один заблокований замок, хоча кілька блоків SHARED можуть співіснувати з одним блоком RESERVED. RESERVED відрізняється від PENDING тим, що нові блоки SHARED можна придбати, поки є заблокований замок. - Блокування файлів та сумісність у версії 3 SQLite @ SQLite.org

Після того, як з'єднання отримає RESERVED-замок, воно може почати обробку операцій з модифікації бази даних, хоча ці зміни можна проводити лише в буфері, а не насправді записати на диск. Зміни, внесені до вмісту зчитування, зберігаються в буфері пам'яті. Коли з'єднання хоче надіслати модифікацію (або транзакцію), необхідно оновити замок RESERVED до ЕКСКЛЮЗИВНОГО блокування. Щоб дістати замок, спершу потрібно підняти замок до ВІДКЛЮЧЕНОГО замка.

Блокування PENDING означає, що процес, що тримає замок, хоче якомога швидше записати в базу даних і просто чекає, коли всі поточні блоки SHARED будуть очищені, щоб він міг отримати ЕКСКЛЮЗИВНИЙ блокування. Ніякі нові блоки SHARED проти бази даних заборонені, якщо блокування PENDING активне, хоча існуючі блоки SHARED дозволено продовжувати.

ЕКСКЛЮЗИВНИЙ замок потрібен для запису у файл бази даних. У файлі дозволено лише одне ЕКСКЛЮЗИВНЕ блокування, і жоден інший замок будь-якого типу не може співіснувати із ЕКСКЛЮЗИВНИМ замком. Для того, щоб збільшити одночасність, SQLite працює над тим, щоб мінімізувати кількість часу, коли зберігаються ЕКСКЛЮЗИВНІ блокування. - Блокування файлів та сумісність у версії 3 SQLite @ SQLite.org

Отже, ви можете сказати, що SQLite безпечно обробляє одночасний доступ за допомогою декількох процесів, що записуються в один і той же БД, просто тому, що він не підтримує його! Ви отримаєте SQLITE_BUSYабо SQLITE_LOCKEDдля другого автора, коли він потрапить на обмеження повторного спроби.


Дякую. Приклад коду з двома авторами був би надзвичайно чудовим, щоб зрозуміти, як це працює.
Баш

1
@ Коротко кажучи, sqlite має файл читання блокування читання-запису у файлі бази даних. Це те саме, що одночасно записувати файл. І з WAL все ще не можна одночасно писати, але WAL може прискорити запис, а читання і запис може бути одночасно.
obgnaw

2
Чи можете ви використовувати Чергу та мати декілька потоків, що подають Чергу та лише один потік, що записує до БД, використовуючи заяви SQL у черзі. Що - щось на зразок цього
Gabriel

7

Цей потік старий, але я думаю, що було б добре поділитися результатами моїх тестів, зроблених на sqlite: я запустив 2 екземпляри програми python (різні процеси, та сама програма), виконуючи операції SELECT та UPDATE sql команди в рамках транзакції з ЕКСКЛЮЗИВНІм блокуванням та таймаутом, встановленим на 10 секунд, щоб отримати замок, і результат був розчарувальним. Кожен екземпляр робив у кроці 10000 кроків:

  • підключіться до db з ексклюзивним замком
  • виберіть один рядок, щоб прочитати лічильник
  • оновіть рядок новим значенням, рівним лічильнику, збільшеному на 1
  • тісний зв’язок з db

Навіть якщо sqlite отримав ексклюзивний замок на транзакцію, загальна кількість дійсно виконаних циклів не дорівнювала 20 000, але менше (загальна кількість ітерацій за один лічильник, що рахується для обох процесів). Програма Python майже не кинула жодного винятку (лише один раз під час вибору за 20 виконання). Ревізія sqlite в момент тестування становила 3,66, а python v3.3 CentOS 6.5. На мою думку, краще знайти більш надійний продукт для подібної роботи або обмежитися записом на sqlite до єдиного унікального процесу / потоку.


5
Схоже, вам потрібно сказати кілька магічних слів, щоб отримати замок у python, про що йшлося тут: stackoverflow.com/a/12848059/1048959 Це незважаючи на те, що документація python sqlite спонукає вас вважати, що with conцього достатньо.
Dan Stahlke
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.