Many hyperlinks are disabled.
Use anonymous login
to enable hyperlinks.
Overview
Comment: | Initial work on documentation describing the optimizer. (CVS 2645) |
---|---|
Downloads: | Tarball | ZIP archive |
Timelines: | family | ancestors | descendants | both | trunk |
Files: | files | file ages | folders |
SHA1: |
5cebd7ba3ccbdd0f4c8fe77091992f52 |
User & Date: | drh 2005-08-30 22:44:06.000 |
Context
2005-08-30
| ||
22:44 | Adjust the makefile for new optimizer documentation. (CVS 2646) (check-in: 14668c85b8 user: drh tags: trunk) | |
22:44 | Initial work on documentation describing the optimizer. (CVS 2645) (check-in: 5cebd7ba3c user: drh tags: trunk) | |
20:12 | The CSV output mode does not sign-extend bytes where the high-order bit is set. Ticket #1397. (CVS 2644) (check-in: 528df777e5 user: drh tags: trunk) | |
Changes
Added www/direct1b.gif.
cannot compute difference between binary files
Added www/fullscanb.gif.
cannot compute difference between binary files
Added www/index-ex1-x-b.gif.
cannot compute difference between binary files
Added www/indirect1b1.gif.
cannot compute difference between binary files
Added www/optimizer.tcl.
> > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 | # # Run this TCL script to generate HTML for the goals.html file. # set rcsid {$Id: optimizer.tcl,v 1.1 2005/08/30 22:44:06 drh Exp $} source common.tcl header {The SQLite Query Optimizer} proc CODE {text} { puts "<blockquote><pre>" puts $text puts "</pre></blockquote>" } proc IMAGE {name {caption {}}} { puts "<center><img src=\"$name\">" if {$caption!=""} { puts "<br>$caption" } puts "</center>" } proc PARAGRAPH {text} { puts "<p>$text</p>\n" } proc HEADING {level name} { puts "<h$level>$name</h$level>" } HEADING 1 {The SQLite Query Optimizer} PARAGRAPH { This article describes how the SQLite query optimizer works. This is not something you have to know in order to use SQLite - many programmers use SQLite successfully without the slightest hint of what goes on in the inside. But a basic understanding of what SQLite is doing behind the scenes will help you to write more efficient SQL. And the knowledge gained by studying the SQLite query optimizer has broad application since most other relational database engines operate similarly. A solid understanding of how the query optimizer works is also required before making meaningful changes or additions to the SQLite, so this article should be read closely by anyone aspiring to hack the source code. } HEADING 2 Background PARAGRAPH { It is important to understand that SQL is a programming language. SQL is a perculiar programming language in that it describes <u>what</u> the programmer wants to compute not <u>how</u> to compute it as most other programming languages do. But perculiar or not, SQL is still just a programming language. } PARAGRAPH { It is very helpful to think of each SQL statement as a separate program. An important job of the SQL database engine is to translate each SQL statement from its descriptive form that specifies what the information is desired (the <u>what</u>) into a procedural form that specifies how to go about acquiring the desired information (the <u>how</u>). The task of translating the <u>what</u> into a <u>how</u> is assigned to the query optimizer. } PARAGRAPH { The beauty of SQL comes from the fact that the optimizer frees the programmer from having to worry over the details of <u>how</u>. The programmer only has to specify the <u>what</u> and then leave the optimizer to deal with all of the minutae of implementing the <u>how</u>. Thus the programmer is able to think and work at a much higher level and leave the optimizer to stress over the low-level work. } HEADING 2 {Database Layout} PARAGRAPH { An SQLite database consists of one or more "b-trees". Each b-tree contains zero or more "rows". A single row contains a "key" and some "data". In general, both the key and the data are arbitrary binary data of any length. The keys must all be unique within a single b-tree. Rows are stored in order of increasing key values - each b-tree has a comparision functions for keys that determines this order. } PARAGRAPH { In SQLite, each SQL table is stored as a b-tree where the key is a 64-bit integer and the data is the content of the table row. The 64-bit integer key is the ROWID. And, of course, if the table has an INTEGER PRIMARY KEY, then that integer is just an alias for the ROWID. } PARAGRAPH { Consider the following block of SQL code: } CODE { CREATE TABLE ex1( id INTEGER PRIMARY KEY, x VARCHAR(30), y INTEGER ); INSERT INTO ex1 VALUES(NULL,'abc',12345); INSERT INTO ex1 VALUES(NULL,456,'def'); INSERT INTO ex1 VALUES(100,'hello','world'); INSERT INTO ex1 VALUES(-5,'abc','xyz'); INSERT INTO ex1 VALUES(54321,NULL,987); } PARAGRAPH { This code generates a new b-tree (named "ex1") containing 5 rows. This table can be visualized as follows: } IMAGE table-ex1b2.gif PARAGRAPH { Note that the key for each row if the b-tree is the INTEGER PRIMARY KEY for that row. (Remember that the INTEGER PRIMARY KEY is just an alias for the ROWID.) The other fields of the table form the data for each entry in the b-tree. Note also that the b-tree entries are in ROWID order which is different from the order that they were originally inserted. } PARAGRAPH { Now consider the following SQL query: } CODE { SELECT y FROM ex1 WHERE x=456; } PARAGRAPH { When the SQLite parser and query optimizer are handed this query, they have to translate it into a procedure that will find the desired result. In this case, they do what is call a "full table scan". They start at the beginning of the b-tree that contains the table and visit each row. Within each row, the value of the "x" column is tested and when it is found to match 456, the value of the "y" column is output. We can represent this procedure graphically as follows: } IMAGE fullscanb.gif PARAGRAPH { A full table scan is the access method of last resort. It will always work. But if the table contains millions of rows and you are only looking a single one, it might take a very long time to find the particular row you are interested in. In particular, the time needed to access a single row of the table is proportional to the total number of rows in the table. So a big part of the job of the optimizer is to try to find ways to satisfy the query without doing a full table scan. } PARAGRAPH { The usual way to avoid doing a full table scan is use a binary search to find the particular row or rows of interest in the table. Consider the next query which searches on rowid instead of x: } CODE { SELECT y FROM ex1 WHERE rowid=2; } PARAGRAPH { In the previous query, we could not use a binary search for x because the values of x were not ordered. But the rowid values are ordered. So instead of having to visit every row of the b-tree looking for one that has a rowid value of 2, we can do a binary search for that particular row and output its corresponding y value. We show this graphically as follows: } IMAGE direct1b.gif PARAGRAPH { When doing a binary search, we only have to look at a number of rows with is proportional to the logorithm of the number of entries in the table. For a table with just 5 entires as in the example above, the difference between a full table scan and a binary search is negligible. In fact, the full table scan might be faster. But in a database that has 5 million rows, a binary search will be able to find the desired row in only about 23 tries, whereas the full table scan will need to look at all 5 million rows. So the binary search is about 200,000 times faster in that case. } PARAGRAPH { A 200,000-fold speed improvement is huge. So we always want to do a binary search rather than a full table scan when we can. } PARAGRAPH { The problem with a binary search is that the it only works if the fields you are search for are in sorted order. So we can do a binary search when looking up the rowid because the rows of the table are sorted by rowid. But we cannot use a binary search when looking up x because the values in the x column are in no particular order. } PARAGRAPH { The way to work around this problem and to permit binary searching on fields like x is to provide an index. An index is another b-tree. But in the index b-tree the key is not the rowid but rather the field or fields being indexed followed by the rowid. The data in an index b-tree is empty - it is not needed or used. The following diagram shows an index on the x field of our example table: } IMAGE index-ex1-x-b.gif PARAGRAPH { An important point to note in the index are that they keys of the b-tree are in sorted order. (Recall that NULL values in SQLite sort first, followed by numeric values in numerical order, then strings, and finally BLOBs.) This is the property that will allow use to do a binary search for the field x. The rowid is also included in every key for two reasons. First, by including the rowid we guarantee that every key will be unique. And second, the rowid will be used to look up the actual table entry after doing the binary search. Finally, note that the data portion of the index b-tree serves no purpose and is thus kept empty to save space in the disk file. } PARAGRAPH { Remember what the original query example looked like: } CODE { SELECT y FROM ex1 WHERE x=456; } PARAGRAPH { The first time this query was encountered we had to do a full table scan. But now that we have an index on x, we can do a binary search on that index for the entry where x==456. Then from that entry we can find the rowid value and use the rowid to look up the corresponding entry in the original table. From the entry in the original table, we can find the value y and return it as our result. The following diagram shows this process graphically: } IMAGE indirect1b1.gif PARAGRAPH { With the index, we are able to look up an entry based on the value of x after visiting only a logorithmic number of b-tree entries. Unlike the case where we were searching using rowid, we have to do two binary searches for each output row. But for a 5-million row table, that is still only 46 searches instead of 5 million for a 100,000-fold speedup. } HEADING 3 {Parsing The WHERE Clause} # parsing the where clause # rowid lookup # index lookup # index lookup without the table # how an index is chosen # joins # join reordering # order by using an index # group by using an index # OR -> IN optimization # Bitmap indices # LIKE and GLOB optimization # subquery flattening # MIN and MAX optimizations |
Added www/table-ex1b2.gif.
cannot compute difference between binary files