@@ -735,7 +735,31 @@ public function execute_sqlite_query( string $sql, array $params = array() ): PD
735
735
*/
736
736
public function begin_transaction (): void {
737
737
if ( 0 === $ this ->transaction_level ) {
738
- $ this ->execute_sqlite_query ( 'BEGIN ' );
738
+ /*
739
+ * When we're executing a statement that will write to the database,
740
+ * we need to use "BEGIN IMMEDIATE" to open a write transaction.
741
+ *
742
+ * This is needed to avoid the "database is locked" error (SQLITE_BUSY)
743
+ * when SQLite can't upgrade a read transaction to a write transaction,
744
+ * because another connection is modifying the database.
745
+ *
746
+ * From the SQLite documentation:
747
+ *
748
+ * If a write statement occurs while a read transaction is active,
749
+ * then the read transaction is upgraded to a write transaction if
750
+ * possible. If some other database connection has already modified
751
+ * the database or is already in the process of modifying the database,
752
+ * then upgrading to a write transaction is not possible and the write
753
+ * statement will fail with SQLITE_BUSY.
754
+ *
755
+ * See:
756
+ * - https://www.sqlite.org/lang_transaction.html
757
+ * - https://www.sqlite.org/rescode.html#busy
758
+ *
759
+ * For better performance, we could also consider opening the write
760
+ * transaction later in the session - just before the first write.
761
+ */
762
+ $ this ->execute_sqlite_query ( $ this ->is_readonly ? 'BEGIN ' : 'BEGIN IMMEDIATE ' );
739
763
} else {
740
764
$ this ->execute_sqlite_query ( 'SAVEPOINT LEVEL ' . $ this ->transaction_level );
741
765
}
0 commit comments