News for hackers. Stuff that matters.
Re:Many small queries are efficient in SQLite by danielbarla on Tuesday February 16, 2021 @02:29AM (#26151721)
Out of curiosity, just how many rows were you trying to insert, for this to be a problem? My memory is a bit fuzzy, but on SQLite even standard INSERT statements can scale to hundreds of thousands per second, if you do them in one transaction. Just curious about the scenario here.
Re:Many small queries are efficient in SQLite by prox on Tuesday February 16, 2021 @03:38AM (#26152074)
A need little trick is to explicitly state “begin transaction “ and “end transaction” in my app. Not sure how general use this is.
Re:Many small queries are efficient in SQLite by hnlmorg on Tuesday February 16, 2021 @04:50AM (#26152472)
This is exactly how I accomplished high performance in sqlite too.

I'm surprised more people don't use transactions in sqlite given transactions are a staple of using any enterprise RDBMS.

Re:Many small queries are efficient in SQLite by AtlasBarfed on Tuesday February 16, 2021 @10:41AM (#26154898)
Once you travel code boundaries (classes, functions, whatever) transaction management gets a bit hairy.

The question "prove this program reliably closes the transaction I started" starts to become equivalent to "prove this program halts"

Obviously they are useful tools and heavily used, but it's not like they are a zero-overhead feature.

Re:Many small queries are efficient in SQLite by hnlmorg on Tuesday February 16, 2021 @12:31PM (#26156382)
This is quite a generalised point so difficult to really respond to but some frameworks do provide a mechanism to check if a transaction has been closed. The stateless nature of web development does also make working with transactions easier as you have a clear entry and exit point of each request. However I appreciate not all uses of RDBMSs are stateless, let alone web applications. But a similar approach can sometimes be applied with static stateful applications where you can calve up the transactional operations into logical code paths that are easier to mentally visualise. As always though, it really depends on how your code is structured and the specific problems you're trying to solve.

Where you know code boundaries are an issue I've found functional designs tend to work a little better than OOP with regards to managing transactions but a lot of that could just be down to how my brain is wired (while I'm not a FP evangelist I do tend to favour breaking code down to stateless functions rather than stateful classes).

It's fair to say spaghetti code will be a problem on most reasonably mature code bases but there are approaches and frameworks that help somewhat with managing transactions across code boundaries -- just as there are tools that make working with transactions harder. But in my experience there are much harder problems to solve than working with transactions.

> but it's not like they are a zero-overhead feature.

Is there such thing as a zero overhead feature? (I say this semi-flippantly).

Re:Many small queries are efficient in SQLite by Spivak on Tuesday February 16, 2021 @11:48AM (#26155850)
I mean I have to deal with this crap at $dayjob but I genuinely can't believe of the terrible code I see that borrows a resource (connection, transaction, file handle) and then only the happy path gives it back. I desperately wish that languages would make this a compile error if all code paths don't lead to the resource being freed.

The only thing that should ever stop you from returning a resource is a malicious scheduler.

Re:Many small queries are efficient in SQLite by lixtra on Tuesday February 16, 2021 @01:50PM (#26157323)
> I desperately wish that languages would make this a compile error if all code paths don't lead to the resource being freed.

You might want to take a look at rust.

Re:Many small queries are efficient in SQLite by AtlasBarfed on Thursday February 18, 2021 @01:20PM (#26183451)
I can see the obvious parallels between memory management lifetimes and transaction management, does rust have explicit features for extending lifetimes to resources besides memory?
Re:Many small queries are efficient in SQLite by lixtra on Friday February 19, 2021 @01:44AM (#26190412)
Yes, it works for files, network connections and database transactions[1].

To span a transaction over two functions, it has to be assigned to some variable and the lifetime of this variable is tracked by the compiler.

[1] https://docs.rs/sqlx/0.3.0/sqlx/struct.Transaction.html

Re:Many small queries are efficient in SQLite by smallnamespace on Tuesday February 16, 2021 @11:36AM (#26155660)
You don’t need to solve the halting program, you just need a way to construct programs that halt (or close the connection), which is way easier.

Many languages have some sort of `finally` or `with` construct tailored for this use case.

Remember, we’re code writers, not arbitrary discriminators.

Re:Many small queries are efficient in SQLite by starik36 on Tuesday February 16, 2021 @11:52AM (#26155908)
I actually avoided transactions in MSSQL when I could because it escalates locks real quick. Which is a death knell for a busy system.
Re:Many small queries are efficient in SQLite by Aeolun on Tuesday February 16, 2021 @05:51AM (#26152774)
Eh? They exist, that doesn’t mean anyone actually uses them.
Re:Many small queries are efficient in SQLite by hnlmorg on Tuesday February 16, 2021 @06:21AM (#26152909)
My point is that using transactions should be drilled into people who work on databases and where consistency is a requirement because transactions turn multiple complex SQL requests into one atomic operation:

http://db4beginners.com/blog/relationaldb-transaction/

Re:Many small queries are efficient in SQLite by Aeolun on Thursday February 18, 2021 @07:45AM (#26179165)
Oh, I agree. It’s just that my recent experiences have included mostly people foreign to both transactions and foreign keys.
Re:Many small queries are efficient in SQLite by mikeyjk on Tuesday February 16, 2021 @07:35AM (#26153294)
I've never worked at a place that doesn't use them. What if the 3rd insert in a series fails and consequently writes the wrong thing down on the 4th with an update?

That's just my experience so I guess it may be meaningless but I'm surprised to hear it may not be often used.

Re:Many small queries are efficient in SQLite by konha on Tuesday February 16, 2021 @08:19AM (#26153555)
> I've never worked at a place that doesn't use them.

Consider yourself lucky then. I know of a place that doesn’t use transactions in a homegrown ERP solution, of all things.

Re:Many small queries are efficient in SQLite by FpUser on Tuesday February 16, 2021 @09:56AM (#26154386)
I've seen all kinds of wonders during my life so this one is no surprise. If however someone is doing stupid things it is their problem. They're free to complain to themselves.
Re:Many small queries are efficient in SQLite by mumblemumble on Tuesday February 16, 2021 @10:35AM (#26154819)
Tangentially - it may be their problem, but I hesitate to say it's their fault. I'm continually dismayed at how spotty and superficial education about how to use an RDBMS can be. Even in formal education on the subject.
Re:Many small queries are efficient in SQLite by FpUser on Wednesday February 17, 2021 @04:56AM (#26164674)
It does not take PhD and rocket science for one to figure that sometimes operations must be bunched and executed with success/failure as a single unit. That would come as a business requirements. For curious person it would not take much to do some search on a subject and discover and read about transactions.
Re:Many small queries are efficient in SQLite by slaymaker1907 on Tuesday February 16, 2021 @04:22PM (#26158957)
You hardly ever need transactions if you track validity explicitly in your schema.

Suppose T1 has a 1-many relationship with T2. Declare in your assumptions that any rows in T2 with no corresponding valid row in T1 are not valid.

Additionally, have an is_valid field on T1 so selecting all valid data from T2 is done with "select * from T2 inner join T1 on T2.t1id = T1.id where T1.is_valid".

To insert data, insert a row into T1 first but initially have is_valid be false. Then insert all necessary data into T2. Finally, do an update and change the original row in T1 to have is_valid be true.

For deletions to T1, just do an update and set is_valid to false. Thanks to the validity logic, this has the effect of also invalidating all T2 rows.

Updates are trickier, but you can allow them to work without transactions by having two ids for for every table. The first id is the one we worked with before which is used for joins. The second id is used by applications to look for explicit records. Therefore, just never do any updates aside from the one setting is_valid to true (which is really storage logic and not application logic). Instead, just insert a new row into T1 whenever you want to update something in T1. The final update now just needs to flip the is_valid bit for the old row and the new row and will also need to verify that the old row is valid as well as any other rows the current update relies on (basically need to turn it into complex CAS).

All of this is pretty messy, but it does let you have CRUD without any transaction support from your DB. Also, even if your DB has transactions, this scheme has the advantage of being lock-free so your application cannot deadlock.

If you have many updates/deletes, you can do garbage collection either by allowing the GC to use a transaction or by changing adding in a check for insertions to T1 that verify the number of associated rows in T2 before setting is_valid.

Unfortunately, while updates and inserts with GC can still be lock-free, they are not wait-free since an insert or update can fail. If you never do updates or GC though, this is actually wait-free and guarantees that every create, read, and delete operation will succeed in the absence of hardware/network failures.

Still, this overhead probably isn't worth it unless you already need to track the history explicitly for auditing or something. At the company where we used this, we didn't have an is_valid row, we had valid_from and valid_to which were timestamps.

Re:Many small queries are efficient in SQLite by rc48 on Thursday February 18, 2021 @03:57AM (#26177541)
Same as @mikeyjk! Can you point toward more information on this topic? The applications I'm dealing with in my day job are constantly plagued by deadlocks in the database surrounding bulk transactions. I've theorized that a "T1 1-many T2" refactoring of the tables - like what you've described - might be a solution , but haven't settled on how to implement. Would absolutely love to learn more on this esoteric, hard-to-google topic.
Re:Many small queries are efficient in SQLite by mikeyjk on Sunday February 21, 2021 @10:04PM (#26220372)
I guess I'm not sure how you can get away without needing transactionality for these extra columns / is_valid columns
Re:Many small queries are efficient in SQLite by hnlmorg on Wednesday February 17, 2021 @06:46AM (#26165353)
All of this sounds like a lot of additional work just to provide some of the functionality transactions provide out of the box. And I say "some" because you've not addressed rollbacks.

That said, it's still an interesting thought experiment so glad you shared.

Re:Many small queries are efficient in SQLite by mikeyjk on Tuesday February 16, 2021 @11:18PM (#26162638)
Wow, this has broken my brain but has really piqued my interest - thanks. Do you have any reading on this topic / strategy to help a mouth breather like me out?
Re:Many small queries are efficient in SQLite by rc48 on Thursday February 18, 2021 @03:59AM (#26177550)
My sentiments exactly!
Re:Many small queries are efficient in SQLite by FpUser on Tuesday February 16, 2021 @09:54AM (#26154356)
Not using transaction is just very bad practice. If people are using wrong approach to solve the task they should not complain about results.
Re:Many small queries are efficient in SQLite by derekp7 on Tuesday February 16, 2021 @07:01AM (#26153121)
Another trick if you have multiple process (users) accessing the DB and you don't want to lock the DB for a long time, is to insert into a temp table (possibly with a transaction, although the time savings is not as dramatic with temp tables). Then copy the temp table to the main one (insert into ... from ...). The advantage is lets say you are reading in a bunch of items from something else, that will take a chunk of time more than just the DB time. So by going to a temp table you aren't locking the target table for anyone else while gathering the data. Then combine this with flushing the temp table every X rows or X seconds, and you have a number of efficient updates to the table without long lock times.

Also have WAL mode on to get multi-user access going.

Re:Many small queries are efficient in SQLite by uh_uh on Tuesday February 16, 2021 @09:53AM (#26154349)
This surprises me as I thought SQLite locks are db-level, not table-level. Is this not the case?
Re:Many small queries are efficient in SQLite by derekp7 on Tuesday February 16, 2021 @11:06AM (#26155180)
If you have wal-mode enabled then the automatic locks are table level. So one process can be updating one table and another one can work on a different one. Also, you only need to enable wal mode on the DB once (pragma journal_mode=wal), it "sticks" for each connection.

In my application that uses SQLite (Snebu backup), as data comes in (as a TAR format stream) I have one process extracting the data and metadata, then serializing the metadata to another process that owns the DB connection. This process dumps the metadata to a temp table, then every 10 seconds "flushes" the metadata to the various tables that it needs to go to. This way I can easily have multiple backups going simultaneously, as each process spends a small amount of time (relatively) flushing the data to the permanent tables, and a greater part of the time compressing and writing backup data to the disk vault directory.

I've been working with this for the past 8 years or so, and have picked up a few tricks on keeping as much as possible batched up in transactions, but also keeping the transaction times short relative to other operations. So far seems to work out fairly well.

Note, that in addition to journal_mode=wal, you need to have a busy handler defined that infinitely retries transactions with a 250 ms delay between each retry.

Edit: On further review of the docs, I'm not sure if wal mode enables table-level locking, it may be that when writing to a temp table, that temp tables are part of a separate schema (or are otherwise separate from the main DB) -- which makes sense, as temp tables are only visible to the process that owns them. So a temp table can be locked in a transaction, while the rest of the DB is writable.

Re:Many small queries are efficient in SQLite by uh_uh on Thursday February 18, 2021 @04:39PM (#26186019)
It seems like the locking is page-level in "wal" and "wal2" modes:

> Usually, SQLite allows at most one writer to proceed concurrently. The BEGIN CONCURRENT enhancement allows multiple writers to process write transactions simultanously if the database is in "wal" or "wal2" mode, although the system still serializes COMMIT commands.

> When a write-transaction is opened with "BEGIN CONCURRENT", actually locking the database is deferred until a COMMIT is executed. This means that any number of transactions started with BEGIN CONCURRENT may proceed concurrently. The system uses optimistic page-level-locking to prevent conflicting concurrent transactions from being committed.

> When a BEGIN CONCURRENT transaction is committed, the system checks whether or not any of the database pages that the transaction has read have been modified since the BEGIN CONCURRENT was opened. In other words - it asks if the transaction being committed operates on a different set of data than all other concurrently executing transactions. If the answer is "yes, this transaction did not read or modify any data modified by any concurrent transaction", then the transaction is committed as normal. Otherwise, if the transaction does conflict, it cannot be committed and an SQLITE_BUSY_SNAPSHOT error is returned. At this point, all the client can do is ROLLBACK the transaction.

The page also mentions:

> The key to maximizing concurrency using BEGIN CONCURRENT is to ensure that there are a large number of non-conflicting transactions. In SQLite, each table and each index is stored as a separate b-tree, each of which is distributed over a discrete set of database pages. This means that:

> Two transactions that write to different sets of tables never conflict

Source: https://www.sqlite.org/cgi/src/doc/begin-concurrent/doc/begi...

Re:Many small queries are efficient in SQLite by rc48 on Thursday February 18, 2021 @04:15AM (#26177670)
+1. I was not aware of the 'stickiness' of the wal pragma. Thank you for the tip. I will be defaulting to this going forward. For a quick confirmation, I used:

  $ f=$(mktemp)
  $ sqlite3 $f "pragma journal_mode"
  delete
  $ sqlite3 $f "pragma journal_mode=wal
  wal
  $ sqlite3 $f "pragma journal_mode"
  wal
Edit: struggling to format code markup correctly on my mobile.
Re:Many small queries are efficient in SQLite by chrisweekly on Tuesday February 16, 2021 @01:23PM (#26157021)
+1, thanks for this and your previous comment. Have you written up your approach in any more detail? I'd love to reference it.
Re:Many small queries are efficient in SQLite by derekp7 on Tuesday February 16, 2021 @02:03PM (#26157468)
The code is on github.com/derekp7/snebu if you want to take a look. The next task on my list is to write up developer / contributor documentation on how the whole thing is put together along with various tricks (and simplified demonstration code).

But specifically, look in "snebu-main.c" that is where the opendb function is (so you can see the pragma statements), and there is a busy_retry function that gets referenced (all it does is sleep for .1 seconds). I believe that you don't need the busy-retry function, if you use the built-in busy handler, but I'm not really sure and don't want to take a chance and break working code.

For the temp tables, look in snebu-submitfiles.c -- the function at the top handles the DB operations, one towards the bottom handles the non-DB tar file consumption operations, and there is a circular queue in the middle to handle buffering so the data ingestion can keep going while the data is getting flushed (these three run as separate processes). I should learn threads, as there may be more flexibility in that, but not comfortable enough with thread programming yet.

Re:Many small queries are efficient in SQLite by chrisweekly on Wednesday February 17, 2021 @10:23PM (#26175216)
Thanks so much! The phrase "unreasonably helpful" comes to mind. Also, kudos for the extraordinarily high-quality documentation in snebu!
Re:Many small queries are efficient in SQLite by quietbritishjim on Tuesday February 16, 2021 @07:42AM (#26153333)
> A need little trick is to explicitly state “begin transaction “ and “end transaction” in my app.

That is exactly what the parent comment already said:

> if you do them in one transaction

The commands you stated are exactly how to do (multiple) things in a transaction.

Re:Many small queries are efficient in SQLite by danielbarla on Tuesday February 16, 2021 @03:55AM (#26152165)
Indeed, without this, performance would seem quite lacklustre. I believe it's very common in the SQLite community.
Re:Many small queries are efficient in SQLite by fctorial on Tuesday February 16, 2021 @04:50AM (#26152471)
Why would I do a hundred thousand insertions in a single transaction in a crud app?
Re:Many small queries are efficient in SQLite by dwohnitmok on Tuesday February 16, 2021 @05:32AM (#26152686)
I think parent means that for some careful selection of N (where N > 1) insertions per bulk transaction you can scale up to hundreds of thousands of insertions per second, rather than putting hundreds of thousands of insertions in a single transaction.
Re:Many small queries are efficient in SQLite by Tuna-Fish on Tuesday February 16, 2021 @05:45AM (#26152736)
No, the opposite. In SQLite, starting and ending transactions that write things to the db is a relatively expensive operation, and running queries outside transaction is effectively the same as running each of them in an independent transaction.

If you need to do a lot of inserts (or updates, etc), the slowest possible way to do them is to do them outside of a transaction. The fastest way to do them is to wrap them all into a single transaction.

Re:Many small queries are efficient in SQLite by quietbritishjim on Tuesday February 16, 2021 @08:09AM (#26153490)
> If you need to do a lot of inserts (or updates, etc), the slowest possible way to do them is to do them outside of a transaction. The fastest way to do them is to wrap them all into a single transaction.

This doesn't seem to contradict the comment you're replying to. They're suggesting wrapping operations into transactions in batches e.g. (just making some numbers up) if you have 100,000 inserts maybe you'd do 100 transactions of 1000 inserts each. I wouldn't call that "the opposite" of your one mega-transaction suggestion. In fact I'd expect it to still have most or all of the speed benefit of using one single transaction, or potentially even be slightly faster.

Re:Many small queries are efficient in SQLite by dwohnitmok on Tuesday February 16, 2021 @06:37AM (#26153000)
Oh fascinating, you actually put hundreds of thousands of statements in a single SQLite transaction in an online CRUD app (as opposed to offline processing)? I've never done more than a couple hundred and even then usually they're "logically batched," both because I'm worried about forcing unnecessary read to write transaction promotions for concurrent reads and thereby increasing busy errors, but also because that affects durability to have a transaction open that long (it's not great to let your HTTP response hang for a second before responding as you keep your transaction open).

For serialized writers in any system I'm sure keeping a transaction open as long as possible is the ideal case for throughput, but there's other problems with that in a CRUD app no?

Re:Many small queries are efficient in SQLite by danielbarla on Wednesday February 17, 2021 @01:45AM (#26163453)
Well, that was my original question to the the parent poster, too. You can achieve 100k+ /sec, but they went looking for better performance, apparently.

But to answer your question at face value, one common use for SQLite is as a file format for complex desktop apps, or perhaps a game save format for certain types of games (mostly the non-realtime types). One great advantage to this is that if you use a DB migration library, you ensure backwards compatibility with previous versions of your saved files. However, it's easy to imagine a fair bit of data getting inserted into such a new file each time it is created. It might only be 5000 rows, but I'd prefer to not have to wait 5 seconds just to persist it, if a single change can make it 0.05 seconds.

Re:Many small queries are efficient in SQLite by kenniskrag on Tuesday February 16, 2021 @06:05AM (#26152835)
db seeding with dummy data, migration, analytics db sync etc.
Re:Many small queries are efficient in SQLite by fctorial on Tuesday February 16, 2021 @06:42AM (#26153020)
These are all one time/one a year operations.
Re:Many small queries are efficient in SQLite by imtringued on Tuesday February 16, 2021 @09:38AM (#26154199)
You still don't want to wait 5 hours to let your inefficient query complete.
Re:Many small queries are efficient in SQLite by cztomsik on Tuesday February 16, 2021 @05:02AM (#26152540)
because bulk kinda implies that you want all-or-nothing :)