-
-
Notifications
You must be signed in to change notification settings - Fork 1.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Database is locked -- Why do we inform people to enabled shared cache mode? #632
Comments
Also of note is the fact that if you only have a single connection to a given database, using a shared cache is pointless and just adds unnecessary overhead. No doubt adding to the confusion is the fact that when getting the error message, the SQLite library itself turns As for why shared cache is recommended, one of the earliest bug reports about "database is locked" I can find is #39. The reason turning on the shared cache fixes the problem is:
Taking these points all together, it seems like people were trying to do something that, while allowed by SQLite, is disallowed by the standard library. The standard library then opens a second connection behind the scenes, and the user gets a mysterious The driver does seem to support the unlock notify API now, provided you build with the sqlite_unlock_notify tag. It seems to me that there a few ways to use this library correctly:
tx, err := db.Begin()
if err != nil {
return err
}
defer tx.Rollback()
rows, err := tx.Query(...)
if err != nil {
return err
}
defer rows.Close()
for rows.Next() {
if err := rows.Scan(...); err != nil {
return err
}
if err := tx.Exec(...); err != nil {
return err
}
}
if err := rows.Err(); err != nil {
return err
}
return tx.Commit()
c, err := db.Conn(ctx)
if err != nil {
return err
}
defer c.Close()
rows, err := c.QueryContext(ctx, ...)
if err != nil {
return err
}
defer rows.Close()
for rows.Next() {
if err := rows.Scan(...); err != nil {
return err
}
if err := c.ExecContext(ctx, ...); err != nil {
return err
}
}
if err := rows.Err(); err != nil {
return err
}
return c.Close() |
No activity / response; closed. |
Turns out shared cache mode limits db connections, which ultimate triggers a lot of the locking issues: mattn/go-sqlite3#632 (comment) Stop using shared cache mode, in favour of a busy timeout. This lets us remove the mutexes, and appears to solve the issue.
I am using transactions to provide a consistent view and atomicity to a RPC handler. Each handler creates a transaction, which typically live for some milliseconds, and uses it to read and write several tables. This needs to happen in parallel, as they cannot block other clients in the meantime.
But it seems it's not working for me. This is the connection string: Is there any solution available that allows several write transactions to coexist simultaneously? Or did I not implement correctly @rittneje 's solution? |
@juagargi You need to use Also, FYI you are missing the "file:" prefix on your call to db, err := sql.Open("sqlite3", "file:database_file.sqlite?cache=private&mode=rwc&_busy_timeout=10000&_journal_mode=WAL") |
Many thanks @rittneje ! Wouldn't For now I have "overriden" the write functions of my DB handled by a Tx object, to check if they return a |
Fundamentally, SQLite itself does not really support two concurrent write transactions. You can kind of do it with shared cache mode, but there are some caveats, and the SQLite maintainers discourage its use anyway. If all your transactions are going to read and write, then yes, in essence |
Thank you again for all the insight, I appreciate it. |
* Upgrade to 1.18, use newer docker build stuff The new stuff automatics tags and things like that * Disable spatialite, WAL by default spatialite has proven to be a continual pain in the ass for something we don't actually use in the app, so drop it for now. Can continue to use it in the CLI for ad-hoc if we want. Enable WAL mode by default. This ruins the 'one file' thing, but also is what should be used in a serving environment * Use os/gh actions lint, normal sqlite build * Drop cache mode, like we did in #30 mattn/go-sqlite3#632 (comment) * Set a busy timeout * Remove DB write mutex we shouldn't actually need that, esp. with wal mode + busy timeout etc. * Upgrade go version / sqlite driver again * Tests use real DB on disk too rather than potentially masking over db config issues by using in-mem * Drop transaction around venue sync we ran that in one big tx, which took a couple seconds and locked the DB. I can't think of any real reason why it can't be an incremental update, the writes are all upserts. So drop the TX * upgrade actions to 1.19 too * Fix lint errors
I think that this project is suffering from a misunderstanding of
SQLITE_LOCK
vsSQLITE_BUSY
. I'd like to start a discussion about this, and why anyone has ever encouraged to enabled shared cache mode to solve aSQLITE_LOCK
error.The
SQLITE_BUSY
result code indicates that the database file could not be written (or in some cases read) because of concurrent activity by some other database connection, usually a database connection in a separate process.The
SQLITE_LOCKED
result code indicates that a write operation could not continue because of a conflict within the same database connection or a conflict with a different database connection that uses a shared cache.https://www.sqlite.org/cvstrac/wiki?p=DatabaseIsLocked
Without shared cache mode:
SQLITE_BUSY
is expected to happen with any multithreaded application. This just means that two connections are conflicting with each other. You need to set up a busy timeout so that SQLite can automatically retry in this case. Problem solved.SQLITE_LOCKED
only happens if a single SQLite connection is doing something that doesn't make sense, such as dropping a table while still being in the middle of a SELECT statement. If someone is getting this error, either they're doing something crazy, or a connection was returned to the pool without being ready to be returned to the pool, because statements are still executing without being finalized.Without shared cache mode, these are the only problems that you should expect.
But we've encouraged everyone to enable shared cache mode, so now the problem has changed. In shared cache mode, there is really only ever a single connection to the database at a time, and all "connections" are just sharing the same connection.
With shared cache mode:
SQLITE_BUSY
pretty much doesn't happen. You can't conflict with other connections if there is only a single connection.SQLITE_LOCKED
will happen much more often. Now, normal, seemingly separate connections will getSQLITE_LOCKED
if they happen to run conflicting statements at the same time. Worse, you can't set a busy timeout to fix it. If a lock error happens, it's failed. The only way around this now is to support the unlock_notify API, which this driver currently does not support. So we dig our heels in even further and limit the connection pool to only a single connection.So basically, it seems incorrect to me that you should ever suggest people turn on shared cache mode to fix
SQLITE_LOCKED
errors. It should only increase the frequency ofSQLITE_LOCKED
. Is there something that I'm misunderstanding? Is there a reason I'm not understanding whySQLITE_LOCKED
errors are unavoidable without shared cache mode?The text was updated successfully, but these errors were encountered: