SQLite源码分析4 bytecode引擎


介绍

https://sqlite.org/opcode.html

每条sql语句都会转换成一个虚拟机可执行的“程序”,包含一系列opcode,可以看作一门内部语言。 历史原因,bytecode引擎也会被称为Virtual DataBase Engine,即vdbe。 虚拟机、bytecode引擎、vdbe都是指一个东西。 相关代码都在带vdbe字样名字的文件。


指令格式

bytecode程序包含一条或多条指令。一个指令包含一个opcode和5个操作符P1 P2 P3 P4 P5。 P1 P2 P3是32位有符号整数,一般指寄存器。 对于操作btree的cursor来说,P1是cursor号。 对于jump指令,P2是跳转目标。 P4可能是32位有符号整数、64位有符号整数、64位浮点数、字符串、Blob、字符串比较函数指针、sql函数指针或其他值。 P5是16位无符号整数,用来存一些标志位。可能对opcode有一些影响。 有的opcode用五个参数,有的用一两个、有的一个都不用。

opcode分为几类

  1. 算数和逻辑 比如加减乘除位运算

  2. 数据移动

  3. 控制流 比如goto、gosub、return等

  4. B树相关 比如创建、销毁、清空、打开关闭B树等。

  5. 其他


观察opcode

每条sql语句都会转换成一个虚拟机可执行的“小程序”。 在sqlite命令行里可以在sql语句前加explain来观察这条语句转换后的代码。

例如

    sqlite> EXPLAIN select * from tbl1 where two < 15;
    addr  opcode         p1    p2    p3    p4             p5  comment      
    ----  -------------  ----  ----  ----  -------------  --  -------------
    0     Init           0     10    0                    00  Start at 10  
    1     OpenRead       0     2     0     2              00  root=2 iDb=0; tbl1
    2     Rewind         0     9     0                    00               
    3       Column         0     1     1                    00  r[1]=tbl1.two
    4       Ge             2     8     1     (BINARY)       54  if r[1]>=r[2] goto 8
    5       Column         0     0     3                    00  r[3]=tbl1.one
    6       Column         0     1     4                    00  r[4]=tbl1.two
    7       ResultRow      3     2     0                    00  output=r[3..4]
    8     Next           0     3     0                    01               
    9     Halt           0     0     0                    00               
    10    Transaction    0     0     1     0               01  usesStmtJournal=0
    11    Integer        15    2     0                    00  r[2]=15      
    12    Goto           0     1     0                    00  

每个opcode都会有一个数值,代码里对应的是OP_OpenRead、OP_Rewind这些名字。 但是找不到定义,因为定义是编译时生成的(见tool/mkopcodeh.tcl)。 这个脚本遍历整个vdbe.c,把opcode一个个抠出来做成了宏定义。 对于有些找不到的定义,可以在整合的源代码里找(https://sqlite.org/download.html的amalgamation版本)。 所有代码整合成了几个文件,所有定义都能找到。


按定义过一遍例子的opcode。

正常是顺序执行,除非碰到跳转命令。

Init是第一个命令,会跳转到P2=10,也就是Transaction。

Transaction的P1是数据库编号,0号是主数据库文件,1用于临时表。

往下执行Integer。把P1的15写到索引为P2的寄存器,即r[2]=15。

Goto跳转的P2的值,即指令1的OpenRead。

OpenRead。打开一个只读的cursor,root为P2的2。 P3为数据库文件,这里是0主文件。

Rewind。下一个对于数据库P1的Column命令将会作用在数据库或索引的第一条数据。 如果数据库或索引是空的,跳转到P2即Halt。

Column。取出cursor P1指向的数据的第P2列。 把这个数据存在P3寄存器即r[1]里。

Ge。如果P3寄存器的值>=P1寄存器的值,跳转到P2。后面的注释很清楚。 比如如果当前cursor取的值是20,那么20>15跳转到Next。

Next。cursor P1前进一格,即读表里的下一个数据。如果没数据了,往下执行(Halt),否则跳转到P2。

假设还有数据,又跳转到Column,这时读出10。

10<15不满足Ge,往下走。

读出第0列存到r[3]。

读出第1列存到r[4]。

因为表定义一共就两列,所以读出两列的值。

ResultRow。当前,从P1寄存器开始的P2个寄存器存了一条结果数据。可以把他们拿出来存好。

继续走Next查看下一条数据,直到cursor没有数据。

Halt。立即退出。关闭所有cursor等。

这样看来parser实际是把sql转成了用opcode编写的伪代码。