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分为几类
算数和逻辑
比如加减乘除位运算数据移动
控制流
比如goto、gosub、return等B树相关
比如创建、销毁、清空、打开关闭B树等。其他
观察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编写的伪代码。