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 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435
|
<!DOCTYPE html>
<html><head>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<link href="sqlite.css" rel="stylesheet">
<title>Why SQLite Uses Bytecode</title>
<!-- path= -->
</head>
<body>
<div class=nosearch>
<a href="index.html">
<img class="logo" src="images/sqlite370_banner.gif" alt="SQLite" border="0">
</a>
<div><!-- IE hack to prevent disappearing logo --></div>
<div class="tagline desktoponly">
Small. Fast. Reliable.<br>Choose any three.
</div>
<div class="menu mainmenu">
<ul>
<li><a href="index.html">Home</a>
<li class='mobileonly'><a href="javascript:void(0)" onclick='toggle_div("submenu")'>Menu</a>
<li class='wideonly'><a href='about.html'>About</a>
<li class='desktoponly'><a href="docs.html">Documentation</a>
<li class='desktoponly'><a href="download.html">Download</a>
<li class='wideonly'><a href='copyright.html'>License</a>
<li class='desktoponly'><a href="support.html">Support</a>
<li class='desktoponly'><a href="prosupport.html">Purchase</a>
<li class='search' id='search_menubutton'>
<a href="javascript:void(0)" onclick='toggle_search()'>Search</a>
</ul>
</div>
<div class="menu submenu" id="submenu">
<ul>
<li><a href='about.html'>About</a>
<li><a href='docs.html'>Documentation</a>
<li><a href='download.html'>Download</a>
<li><a href='support.html'>Support</a>
<li><a href='prosupport.html'>Purchase</a>
</ul>
</div>
<div class="searchmenu" id="searchmenu">
<form method="GET" action="search">
<select name="s" id="searchtype">
<option value="d">Search Documentation</option>
<option value="c">Search Changelog</option>
</select>
<input type="text" name="q" id="searchbox" value="">
<input type="submit" value="Go">
</form>
</div>
</div>
<script>
function toggle_div(nm) {
var w = document.getElementById(nm);
if( w.style.display=="block" ){
w.style.display = "none";
}else{
w.style.display = "block";
}
}
function toggle_search() {
var w = document.getElementById("searchmenu");
if( w.style.display=="block" ){
w.style.display = "none";
} else {
w.style.display = "block";
setTimeout(function(){
document.getElementById("searchbox").focus()
}, 30);
}
}
function div_off(nm){document.getElementById(nm).style.display="none";}
window.onbeforeunload = function(e){div_off("submenu");}
/* Disable the Search feature if we are not operating from CGI, since */
/* Search is accomplished using CGI and will not work without it. */
if( !location.origin || !location.origin.match || !location.origin.match(/http/) ){
document.getElementById("search_menubutton").style.display = "none";
}
/* Used by the Hide/Show button beside syntax diagrams, to toggle the */
function hideorshow(btn,obj){
var x = document.getElementById(obj);
var b = document.getElementById(btn);
if( x.style.display!='none' ){
x.style.display = 'none';
b.innerHTML='show';
}else{
x.style.display = '';
b.innerHTML='hide';
}
return false;
}
var antiRobot = 0;
function antiRobotGo(){
if( antiRobot!=3 ) return;
antiRobot = 7;
var j = document.getElementById("mtimelink");
if(j && j.hasAttribute("data-href")) j.href=j.getAttribute("data-href");
}
function antiRobotDefense(){
document.body.onmousedown=function(){
antiRobot |= 2;
antiRobotGo();
document.body.onmousedown=null;
}
document.body.onmousemove=function(){
antiRobot |= 2;
antiRobotGo();
document.body.onmousemove=null;
}
setTimeout(function(){
antiRobot |= 1;
antiRobotGo();
}, 100)
antiRobotGo();
}
antiRobotDefense();
</script>
<div class=fancy>
<div class=nosearch>
<div class="fancy_title">
Why SQLite Uses Bytecode
</div>
<div class="fancy_toc">
<a onclick="toggle_toc()">
<span class="fancy_toc_mark" id="toc_mk">►</span>
Table Of Contents
</a>
<div id="toc_sub"><div class="fancy-toc1"><a href="#introduction">1. Introduction</a></div>
<div class="fancy-toc2"><a href="#how_to_provide_feedback">1.1. How To Provide Feedback</a></div>
<div class="fancy-toc2"><a href="#definition_of_bytecode_">1.2. Definition Of "Bytecode"</a></div>
<div class="fancy-toc2"><a href="#definition_of_abstract_syntax_tree_or_ast_">1.3. Definition Of "Abstract Syntax Tree" or "AST"</a></div>
<div class="fancy-toc2"><a href="#dataflow_programming">1.4. Dataflow Programming</a></div>
<div class="fancy-toc1"><a href="#advantages_to_compiling_into_bytecode">2. Advantages To Compiling Into Bytecode</a></div>
<div class="fancy-toc2"><a href="#bytecode_is_easier_to_understand">2.1. Bytecode Is Easier To Understand</a></div>
<div class="fancy-toc2"><a href="#bytecode_is_easier_to_debug">2.2. Bytecode Is Easier To Debug</a></div>
<div class="fancy-toc2"><a href="#bytecode_can_be_run_incrementally">2.3. Bytecode Can Be Run Incrementally</a></div>
<div class="fancy-toc2"><a href="#bytecode_is_smaller">2.4. Bytecode Is Smaller</a></div>
<div class="fancy-toc2"><a href="#bytecode_is_faster">2.5. Bytecode Is Faster</a></div>
<div class="fancy-toc1"><a href="#advantages_of_compiling_into_a_tree_of_objects">3. Advantages Of Compiling Into A Tree Of Objects</a></div>
<div class="fancy-toc2"><a href="#query_planning_decisions_can_be_deferred_until_runtime">3.1. Query Planning Decisions Can Be Deferred Until Runtime</a></div>
<div class="fancy-toc2"><a href="#dataflow_programs_are_easy_to_parallelize">3.2. Dataflow Programs Are Easy To Parallelize</a></div>
</div>
</div>
<script>
function toggle_toc(){
var sub = document.getElementById("toc_sub")
var mk = document.getElementById("toc_mk")
if( sub.style.display!="block" ){
sub.style.display = "block";
mk.innerHTML = "▼";
} else {
sub.style.display = "none";
mk.innerHTML = "►";
}
}
</script>
</div>
<h1 id="introduction"><span>1. </span>Introduction</h1>
<p>Every SQL database engine works in roughly the same way: It first
translates the input SQL text into a "prepared statement". Then it "executes"
the prepared statement to generate a result.
</p><p>A prepared statement is an object that represents the steps needed
to accomplish the input SQL. Or, to think of it in another way,
the prepared statement is the SQL statement translated into a form that is
more easily understood by the computer.
</p><p>In SQLite, a prepared statement is an instance of the
<a href="c3ref/stmt.html">sqlite3_stmt object</a>. In other systems, the prepared
statement is usually an internal data structure that is not directly visible to
the application programmer. Developers of other SQL database engines
do not necessarily call these objects "prepared statements".
But such objects exists, whatever they might be called.
This paper will use the term "prepared statement".
</p><p>There are countless ways of implementing a prepared statement. This
paper will look at the two most common methods:
</p><ol>
<li><p>
<b>Bytecode</b> → The input SQL is translated into a virtual machine language
that is then run by a virtual machine interpreter. This is the technique
used by SQLite.
</p></li><li><p>
<b>Tree-Of-Objects</b> → The input SQL is translated in a tree of objects
that represent the processing to be done. The SQL is executed by walking this
tree. This is the technique used by MySQL and PostgreSQL.
</p></li></ol>
<p>
There are advantages and disadvantages to each of these representations of
a prepared statement. The purpose of this paper is to articulate some of those
advantages and disadvantages.
</p><h2 id="how_to_provide_feedback"><span>1.1. </span>How To Provide Feedback</h2>
<p>
This document is written from the perspective of the original author of SQLite.
If you disagree with any of the opinions offered in this document, you are
welcomed to offer corrections and/or contrary views on the
<a href="https://sqlite.org/forum">SQLite Forum</a>. Or you can email the author
directly.
</p><h2 id="definition_of_bytecode_"><span>1.2. </span>Definition Of "Bytecode"</h2>
<p>The <a href="opcode.html">bytecode generated by SQLite</a> might be a little different from what
many readers think of as bytecode. The bytecode used (for example) by the
<a href="https://en.wikipedia.org/wiki/Java_virtual_machine">Java virtual machine</a> or
by <a href="https://en.wikipedia.org/wiki/WebAssembly">WebAssembly</a> consists almost
entirely of low-level operations, similar to what physical CPUs implement:
basic math operators, comparisons, conditional jumps, and
instructions to move content between different memory locations. SQLite bytecode
has these kinds of low-level instructions, too. But SQLite bytecode also contains
some high-level operations that are specific to the needs of a database engine.
Here are just a few examples:
</p><p>
</p><ul>
<li><p><b>OP_Column</b> → Extract the value from the N-th column of the
database row that a particular cursor is currently pointing at.
</p></li><li><p><b>OP_CreateBtree</b> → Allocate space for a new B-Tree in the
database file.
</p></li><li><p><b>OP_ParseSchema</b> → Reread and reparse all or part of the
<a href="schematab.html">sqlite_schema table</a> and refresh internal symbol tables accordingly.
</p></li><li><p><b>OP_SeekGE</b> → Move a cursor on a particular B-Tree to the first
entry that is greater than or equal to a given key.
</p></li><li><p><b>OP_Next</b> → Advance a cursor on a particular B-Tree to the next
entry in the B-Tree and jump, or fall through if there are no more entries in that
B-Tree.
</p></li></ul>
<p>In other words, the "bytecode" used by SQLite is not so much a set of CPU
instructions as it is a list of database primitives that are to be run
in a particular order.
</p><h2 id="definition_of_abstract_syntax_tree_or_ast_"><span>1.3. </span>Definition Of "Abstract Syntax Tree" or "AST"</h2>
<p>
An "<a href="https://en.wikipedia.org/wiki/Abstract_syntax_tree">Abstract Syntax Tree</a>" or AST
is a data structure that describes a program or statement in some kind of formal
language. In our context, the formal language is SQL. An AST is typically
implemented as a tree of objects where each object represents one small part of
the overall SQL statement. ASTs emerge naturally from parsers for formal languages.
The usual technique is to use an
<a href="https://en.wikipedia.org/wiki/LALR_parser">LALR(1) parser</a>.
With such a parser, each terminal
symbol holds metadata that will become a leaf of the AST, and each non-terminal
symbol holds metadata that will become a sub-branch of the overall AST.
As rules of the grammar are "reduced" by the parser, new nodes of the AST are
allocated and connected to subnodes.
After the parse completes, the start-symbol of the grammar is left holding the
root of the AST.
</p><p>An AST is a tree of objects. But an AST is not a suitable form for
a prepared statement. After being generated, an AST first needs to be
transformed in various ways before it can executed. Symbols need to be
resolved. Semantic rules need to be checked. Optimizations need to be
applied that transform input SQL statement into different forms that
execute more quickly. Finally, the AST needs to be translated into an
alternative representation that is more amenable to execution.
</p><p>Some people refer to the tree of objects that is used as the
executable form for MySQL and PostgreSQL as an AST. This is probably a
misuse of the term "AST", because by the time the tree of objects is
ready to be executed, it has been changed so much that it has little
resemblance to the original SQL text. The confusion arises in part because
both the final prepared statement object and the original AST are both
trees of objects. The usual technique is for the original AST that comes
directly out of the parser to be transformed little by little, in multiple
passes, until at the end it is fully converted into an tree of objects
that is no longer strictly an AST but that can be evaluated to
generate a result. There is not necessarily a clear point during this
process when the tree-of-objects ceases to be an AST and becomes a
prepared statement instead. And because there is no clear boundary between an
AST and a prepared statement, people often refer to a prepared statement
that is represented as a tree of objects as an "AST", even though that
description is not precise.
</p><h2 id="dataflow_programming"><span>1.4. </span>Dataflow Programming</h2>
<p><a href="https://en.wikipedia.org/wiki/Dataflow_programming">Dataflow programming</a>
is a style of programming in which individual nodes specialize in doing
one small part of the overall computation. Each node receives inputs from
other nodes and sends its output to other nodes. Thus the nodes form a
directed graph that carry inputs into outputs.
</p><p>
A "dataflow program" is perhaps a better description than "AST" for
the tree of objects that an SQL database engine uses as a prepared statement.
</p><h1 id="advantages_to_compiling_into_bytecode"><span>2. </span>Advantages To Compiling Into Bytecode</h1>
<p>SQLite compiles to bytecode, and the SQLite developers are very happy
with this approach. Here is why:
</p><h2 id="bytecode_is_easier_to_understand"><span>2.1. </span>Bytecode Is Easier To Understand</h2>
<p>A flat list of opcodes can be easily printed to see exactly
how an SQL statement is being implemented. This is what happens in SQLite
when you preface an SQL statement with the "EXPLAIN" keyword: Instead of
actually running the SQL, the result is a listing of the bytecode
that would have been used to implement that SQL.
</p><p>Bytecode lends itself to this because a bytecode program is easily
represented as a table. In SQLite bytecode, each instruction
has one opcode and five operands. Thus a prepared statement can be
rendered as if it were a query against a six-column table.
</p><p>A tree-of-objects representation is more difficult to publish in
a human-readable form. The objects that comprise the tree tend to
all be very different, and thus it is tricky to come up with a
consistent and simple table representation with which to display
the objects. Any such table representation that you do come up
with would almost certainly have more than six columns, probably many more.
The problem of rendering a tree-of-objects as a table is sufficiently
difficult that nobody does it, as far as I know. Hence, no
tree-of-objects database engine provides the level
of detail in their "EXPLAIN" output that SQLite provides.
</p><h2 id="bytecode_is_easier_to_debug"><span>2.2. </span>Bytecode Is Easier To Debug</h2>
<p>Bytecode provides a clear separation between front-end parsing and
analysis and back-end evaluation of an SQL statement. When problems arise
(incorrect answers and/or poor performance), the developers can examine
the bytecode to quickly determine if the source of the trouble is either the
front-end analysis or the back-end data storage section of the product.
</p><p>In debugging builds of SQLite, the <a href="pragma.html#pragma_vdbe_trace">PRAGMA vdbe_trace=ON;</a> command will
cause a trace of the bytecode execution to appear on the console.
</p><h2 id="bytecode_can_be_run_incrementally"><span>2.3. </span>Bytecode Can Be Run Incrementally</h2>
<p>
SQL statements written in bytecode can be evaluated incrementally.
For example, a statement can be run until it generates just its first
row of output. The statement then pauses until it is stepped again.
It is not necessary to run the statement to completion before examining
the first row of output.
</p><p>
This is more difficult to achieve in a tree-of-objects design. When
the prepared statement is a tree-of-objects, execution is normally
accomplished by walking the tree. To pause the statement in the middle
of a computation means unwinding the stack back up to the caller, all
the while saving enough state to resume evaluation where it last left
off. This is not impossible to do, but is sufficiently difficult that
I have never seen it actually done.
</p><p>
Most SQL database engines do not really need to do incremental
execution of prepared statements because most SQL database engines
are client/server. In client/server engines, a single SQL statement is sent
to the server, then the complete reply comes back over the wire
all at once. Thus each statement runs to completion in a single go.
But SQLite is not client/server. SQLite is a library
that runs in the same address space and using the same stack as the
application. Being able to easily and reliably perform incremental
execution of an SQL statement is important to SQLite.
</p><h2 id="bytecode_is_smaller"><span>2.4. </span>Bytecode Is Smaller</h2>
<p>
The bytecode generated by SQLite is usually smaller than the corresponding
AST coming out of the parser. During initial processing of SQL text
(during the call to <a href="c3ref/prepare.html">sqlite3_prepare()</a> and similar) both the AST and the
bytecode exist in memory at the same time, so more memory is used then.
But that is a transient state. The AST
is quickly discarded and its memory recycled, even before the call to
<a href="c3ref/prepare.html">sqlite3_prepare()</a> returns, so the resulting <a href="c3ref/stmt.html">prepared statement</a> ends
up consuming less memory in its bytecode representation than it did as an AST.
This is important because calls to <a href="c3ref/prepare.html">sqlite3_prepare()</a> are transient, but
prepared statements are often cached for possible reuse and persist in memory
for a long time.
</p><h2 id="bytecode_is_faster"><span>2.5. </span>Bytecode Is Faster</h2>
<p>
I <i>believe</i> that a bytecode representation of a prepared statement
runs faster, because fewer decisions need to be made for each step of
the computation. Emphasis on "believe" in the previous sentence
→ it is difficult to verify
this claim experimentally since nobody has ever put in the multiple years
of effort necessary to generate equivalent bytecode and tree-of-object
representations of a prepared statement to see which one actually runs faster.
We do know that <a href="fasterthanfs.html">SQLite is very fast</a>, but we
do not have good side-by-side comparisons with other SQL databases since
the other databases spend a lot of time doing client/server message processing,
and it is difficult to untangle the message round-trip overhead from the
actual processing time.
</p><h1 id="advantages_of_compiling_into_a_tree_of_objects"><span>3. </span>Advantages Of Compiling Into A Tree Of Objects</h1>
<p>The SQLite developers think that the bytecode approach is
best, at least for the use cases the SQLite tries to fill, but the
tree-of-objects approach to processing SQL does have some advantages over
bytecode. There are always tradeoffs.
</p><h2 id="query_planning_decisions_can_be_deferred_until_runtime"><span>3.1. </span>Query Planning Decisions Can Be Deferred Until Runtime</h2>
<p>
When a prepared statement is bytecode, once the bytecode has been generated,
the algorithm is fixed and cannot be subsequently changed without completely
rewriting the bytecode.
This is not the case with a tree-of-objects prepared
statement. A tree-of-objects is easier to modify on-the-fly. The query
plan is mutable and can be tweaked as it is running, based on the progress
of the query. Thus a query can be dynamically self-tuning.
</p><h2 id="dataflow_programs_are_easy_to_parallelize"><span>3.2. </span>Dataflow Programs Are Easy To Parallelize</h2>
<p>In a dataflow program, each processing node can be assigned to a
different thread. There needs to be some kind of threadsafe queuing
mechanism for transferring intermediate results from one node to the
next. But no synchronization primitives are typically needed within
each node of the program. Node schedule is trivial: A node becomes
eligible to run when it has data available and there is space in its
output queue.
</p><p>This is an important consideration for database engines that are
designed to run large analytic queries
(<a href="https://en.wikipedia.org/wiki/Online_analytical_processing">OLAP</a>)
on large multi-core servers.
The primary focus of SQLite is transaction processing
(<a href="https://en.wikipedia.org/wiki/Online_transaction_processing">OLTP</a>)
on the internet-of-things, so there is less need to
represent prepared statements as dataflow programs in SQLite.
</p><p align="center"><small><i>This page last modified on <a href="https://sqlite.org/docsrc/honeypot" id="mtimelink" data-href="https://sqlite.org/docsrc/finfo/pages/whybytecode.in?m=d62430c8d6">2024-05-09 17:38:03</a> UTC </small></i></p>
|