Earlier quoted context omitted.
> A lot of modern DB engines like Clickhouse tend to just work around this problem by being so fast at full table scans that they don’t even need any sophisticated indexing set up at all. There's only so much you can do with this approach due how to the algorithmic complexity scales as more joins are added. At some points you'll need some additional data structures to speed things up, though they not be indexes in na…
Clickhouse isn’t fast at table scans, it’s just columnar. Indexes are basically a maintained transform from row storage to column storage; columnar databases are essentially already “indexed” by their nature (and they auto-apply some additional indexes on top, like zone maps). It’s only fast for table-scans in the sense that you probably aren’t doing a select * from table, so it’s only iterating over a few columns of…
Subtleties of SQLite Indexes
21–30 of 63 posts
Re: Subtleties of SQLite Indexes
#22I use the mental model of nested maps for "column order matters". For example, an index "published, low_quality_probability, lang" is just a Map >> in my mental model. These maps are ordered by the order the index possesses. That explains why column order matters and why one cannot skip columns and why it stops at range queries. Just imagine getting a final rowId from these nested maps and you'll see why the index wo…
Because they’re tuple keys in a b-tree. That also explains how ranges work efficiently.
Re: Subtleties of SQLite Indexes
#23Sure, if you are a database expert, it might be disappointing but I enjoyed reading it.
Re: Subtleties of SQLite Indexes
#24Re: Subtleties of SQLite Indexes
#25The article is a developers journey into indexes and not a bad journey or travelogue imho. Sure, if you are a database expert, it might be disappointing but I enjoyed reading it.
Re: Subtleties of SQLite Indexes
#26Not a great article; I clicked expecting something super technical about SQLite internals and found a mix of rdbms basics and some misconceptions. The limitations in the blog post aren't really specific to SQLite (for the most part), they're just how indexes (indices) and database engines work across the board. And some of the things phrased as "SQLite [can't] do this" is stuff that wouldn't make sense to do in the f…
The fact that it stops at the first range isn't intuitive to me at all, and I've been using sqlite for 20 years now. Given a covering index on (thing_date, thing_color) I would think a scan would not be needed for the query: select thing_date, thing_color where thing_date date and thing_color = 'blue' I also can't think of a reason this is the case given the underlying data structures.
Re: Subtleties of SQLite Indexes
#27Re: Subtleties of SQLite Indexes
#28Earlier quoted context omitted.
The fact that it stops at the first range isn't intuitive to me at all, and I've been using sqlite for 20 years now. Given a covering index on (thing_date, thing_color) I would think a scan would not be needed for the query: select thing_date, thing_color where thing_date date and thing_color = 'blue' I also can't think of a reason this is the case given the underlying data structures.
It's a flattened tree, and you've used the index to reach a point where you have multiple child nodes that meet the precondition thing_date I asked an llm to give me an ascii representation so it'll be easier to see what I mean; consider the case where you want thing_date Root (internal) +---------------------+ | keys: 14 17 | +----+--------+--------+ | | | v v v +----------------+ +----------------+ +---------------…
[
{"date": 10, "color": "red", "id": 101},
{"date": 11, "color": "blue", "id": 111},
{"date": 12, "color": "green", "id": 121},
{"date": 13, "color": "red", "id": 131},
{"date": 14, "color": "blue", "id": 141},
{"date": 14, "color": "red", "id": 142},
{"date": 15, "color": "yellow", "id": 151},
{"date": 16, "color": "blue" , "id": 161},
{"date": 17, "color": "green", "id": 171},
{"date": 18, "color": "blue", "id": 181},
{"date": 19, "color": "red", "id": 191},
{"date": 20, "color": "green", "id": 201},
]
And here's an example of how we'd represent an "index" as a set of nested maps, where that index is (date, color): {10: {'red': [101]},
11: {'blue': [111]},
12: {'green': [121]},
13: {'red': [131]},
14: {'blue': [141],
'red': [142]},
15: {'yellow':[151]},
16: {'blue': [161]},
17: {'green': [171]},
18: {'blue': [181]},
19: {'red': [191]},
20: {'green': [201]}}
Notice that since this index is built from nested maps, if we want to use this index to find things, we HAVE to first do it by checking the keys in the "outermost" map, the 'date' column. It's a compound index but its order matters. This 'order matters' property is true in our map-based index and it's also true in our SQLite based index. It's also true that because this 'date color isn't super helpful for the query 'WHERE color = blue AND date would be good for is a query like 'WHERE color = red AND date = 14'. That would not require any scans; if we were writing application code such a query with this index would be like calling `index[14][red]` which as we all know is super fast.A better index for this, which is a different index, comes from swapping the order of the columns when forming the index. Instead of (date, color), this better index would be (color, date). That index looks like this:
{'blue': {11: [111],
14: [141],
16: [161],
18: [181]},
'green': {12: [121],
17: [171],
20: [201]},
'red': {10: [101],
13: [131],
14: [142],
19: [191]},
'yellow': {15: [151]}}
Now with this new index to fulfill the exact same query of 'WHERE color = blue AND date Anyway, here's a gist of some Python if you want to play with this concept: https://gist.github.com/lelandbatey/d09557fed38c48a797bf1b15...Re: Subtleties of SQLite Indexes
#29The main takeaway from this for me is that SQLite’s query planner seems to be pretty limited. It’s reliant on stuff like the order in which WHERE conditions are specified, isn’t able to use multiple indexes in queries in many cases, bails out to scans when a variety of different operations show up in queries, etc. It might be the case that SQLite has a simpler or less sophisticated query planner than other databases…
Just to clarify one thing: the order of WHERE conditions in a query does not matter. The order of columns in an index does.
I just ran this test locally with a table I created that has 50 million rows:
``` » time sqlite3 test.db "select count() from test WHERE a != 'a' AND a != 'b' AND a != 'c' AND a != 'd' AND b != 'c' AND d != 'd' AND e != 'f' AND f = 'g'" sqlite3 test.db 5.50s user 0.72s system 99% cpu 6.225 total » time sqlite3 test.db "select count() from test WHERE f = 'g' AND a != 'a' AND a != 'b' AND a != 'c' AND a != 'd' AND b != 'c' AND d != 'd' AND e != 'f'" sqlite3 test.db 1.51s user 0.72s system 99% cpu 2.231 total ```
The only difference is swapping the `f = 'g'` condition from last to first. That condition never matches in this query, so it's able to fail fast and skip all of the work of checking the other conditions.
Re: Subtleties of SQLite Indexes
#30The main takeaway from this for me is that SQLite’s query planner seems to be pretty limited. It’s reliant on stuff like the order in which WHERE conditions are specified, isn’t able to use multiple indexes in queries in many cases, bails out to scans when a variety of different operations show up in queries, etc. It might be the case that SQLite has a simpler or less sophisticated query planner than other databases…
> The main takeaway from this for me is that SQLite’s query planner seems to be pretty limited. This doesn't appear to be true at all. The order of WHERE conditions does not matter; the order of columns in an index does. Everything you're describing is pretty much just how indexes fundamentally work in all databases. Which is why you're saying it hasn't been "solved" by anyone. Indexes aren't magic -- if you understa…
The order of conditions in a WHERE definitely does matter, especially in cases where the conditions are on non-indexed columns or there are CPU-intensive search operations like regex, string ops, etc.
I just ran this test locally with a table I created that has 50 million rows:
``` » time sqlite3 test.db "select count() from test WHERE a != 'a' AND a != 'b' AND a != 'c' AND a != 'd' AND b != 'c' AND d != 'd' AND e != 'f' AND f = 'g'" sqlite3 test.db 5.50s user 0.72s system 99% cpu 6.225 total » time sqlite3 test.db "select count() from test WHERE f = 'g' AND a != 'a' AND a != 'b' AND a != 'c' AND a != 'd' AND b != 'c' AND d != 'd' AND e != 'f'" sqlite3 test.db 1.51s user 0.72s system 99% cpu 2.231 total ```
The only difference is swapping the `f = 'g'` condition from last to first. That condition never matches in this query, so it's able to fail fast and skip all of the work of checking the other conditions.