简介:本资源为 sqlite3 易语言支持库与模块的 esqlite3 V1.1 版本,面向使用易语言进行数据库开发的中高级开发者,重点解决多线程环境下 SQLite 操作的稳定性与功能扩展问题。相比 1.0,本次更新新增了 S3 互斥体进入/退出、S3 聚合上下文、S3 取数据库自上下文等全局命令,并为 zySqlite 数据库补充了繁忙超时、繁忙处理、取文件名、是否只读、取互斥体、是否自动提交、进度处理、取下一记录集、取总影响行等命令,记录集也新增是否繁忙、是否只读、取数据库句柄、取行数等接口。数据库开始事务增加事务锁状态参数,对多线程场景尤为关键;记录集改为必须手动关闭,附加数据库支持密码参数,取记录集多个可解析分号分割的 SQL 并返回全部结果集。压缩包共 413 个文件,约 17.39MB,以 vcxproj、h、c、cpp、sln 等工程源码与构建文件为主,另含 dll、lib、sql、e 等模块与示例,便于直接集成与二次开发。已有 342 人学习下载。
1. 从一份 esqlite3 V1.1 源码包说起:它到底补上了哪些坑
如果你在易语言里做过稍微复杂一点的本地数据存储,大概率绕不开 sqlite3 这个嵌入式数据库。它单文件、零配置、跨平台,天生适合桌面端的小型数据管理场景。但易语言本身没有原生 sqlite3 绑定,社区里长期靠第三方支持库和模块来桥接。这份 esqlite3 V1.1 就是在这个背景下更新的一版支持库加模块源码包,里面包含sqlite3.c、shell.c、rijndael.c、codec.c以及配套的configure.ac、Makefile.am和appveyor.bat等构建脚本。相比 1.0,它把多线程事务锁、记录集手动关闭、附加数据库密码、多语句记录集获取这些实际开发中反复被吐槽的点做了补齐。适合谁?适合已经用易语言写过数据库操作、被 1.0 的自动关闭记录集和事务并发问题折腾过、想升级到更可控版本的一线开发者。如果你只是偶尔读写一个配置表,1.0 够用;但只要涉及多线程写入、加密数据库、批量执行多条 SQL,这份 V1.1 的更新点就值得你认真拆一遍。
2. 拆开源码包:从 configure.ac 到 codec.c 的编译链路
2.1 源码文件构成与各自职责
拿到这个包,第一件事不是急着编译,而是先看清楚每个文件在干什么。sqlite3.c是 SQLite 的合并源文件,官方推荐的单文件分发形式,包含了数据库引擎的全部核心逻辑。shell.c是 SQLite 自带的命令行工具源码,在这个包里主要用来做功能验证和调试。rijndael.c和codec.c是加密相关的实现,rijndael 是 AES 算法的具体实现,codec 负责编解码接口的封装,两者配合实现数据库文件的透明加密。configure.ac和Makefile.am是 Autotools 构建体系的配置文件,前者定义编译时的特性检测和宏开关,后者描述目标文件的依赖关系和编译规则。appveyor.bat和appveyor-test.bat是 Windows 平台上的持续集成脚本,用来在提交代码后自动跑编译和测试。
常见做法是,如果你只需要在易语言里调用编译好的支持库,可以跳过 Autotools 那套,直接用现成的 DLL 或静态库。但如果你想自己改加密参数、裁剪功能模块、或者把 sqlite3 编译成适合自己项目的尺寸,那就得从configure.ac和Makefile.am入手。我一般会先看configure.ac里有没有开启SQLITE_ENABLE_COLUMN_METADATA、SQLITE_HAS_CODEC这类宏,因为它们直接决定编译出来的库支持哪些接口。
2.2 用 Autotools 构建支持库的完整步骤
在 Linux 或 MSYS2 环境下,构建流程是标准的 Autotools 三步走。先确保系统里装了 autoconf、automake、libtool 和 gcc。然后进入源码目录,依次执行:
# 生成 configure 脚本,检查系统环境 autoreconf -i # 配置编译选项,--enable-codec 开启加密支持 ./configure --enable-codec --disable-shared --enable-static # 编译并安装到指定前缀 make -j4 make installautoreconf -i会根据configure.ac和Makefile.am生成configure脚本和Makefile.in模板。--enable-codec是打开加密功能的关键开关,不加这个参数,rijndael.c和codec.c不会被编进去。--disable-shared和--enable-static表示只生成静态库,方便后续链接到易语言的 DLL 工程里。make -j4用四个并行任务加速编译,make install把生成的库文件和头文件放到/usr/local下。
提示:如果你在 Windows 上直接用 MSVC 编译,Autotools 这套用不了,得手动把
sqlite3.c、rijndael.c、codec.c加进工程,并在预处理器里定义SQLITE_HAS_CODEC和SQLITE_ENABLE_COLUMN_METADATA。
2.3 编译参数对易语言调用的影响
编译出来的库最终要被易语言通过 DLL 命令调用,所以导出函数的名称和调用约定必须匹配。configure.ac里如果有AC_LIBTOOL_WIN32_DLL之类的宏,会影响导出符号的修饰方式。我一般会在编译完成后用dumpbin /exports或nm -D检查一下导出的函数名,确认sqlite3_open、sqlite3_exec、sqlite3_key这些关键接口没有被加上额外的下划线或前缀。如果发现导出名不对,回头改Makefile.am里的libsqlite3_la_LDFLAGS,加上-Wl,--kill-at或类似的选项来修正。
另一个容易翻车的点是字符编码。易语言默认用 GBK 处理字符串,而 sqlite3 内部用 UTF-8。编译时如果没开SQLITE_ENABLE_UTF8相关的转换,传中文进去会变乱码。常见做法是在易语言侧调用sqlite3_open之前,先把 SQL 语句和参数用编码转换命令转成 UTF-8,取数据时再转回来。这个转换开销不大,但能省掉后面排查乱码的大量时间。
3. V1.1 新增命令的落地用法:互斥体、事务锁与多记录集
3.1 全局命令与数据库对象的互斥体操作
V1.1 增加了S3互斥体进入、S3互斥体退出、S3聚合上下文、S3取数据库自上下文这几个全局命令。互斥体进入和退出是给多线程环境用的,用来保护共享的数据库句柄不被同时写入。典型用法是在线程函数开头调用S3互斥体进入,在结尾调用S3互斥体退出,中间的所有数据库操作都在这把锁的保护下执行。
# 伪代码示意:易语言中互斥体保护的写入流程 S3互斥体进入 (数据库句柄) try: 数据库.开始事务 (事务锁状态) 数据库.执行SQL ("INSERT INTO logs VALUES (...)") 数据库.提交事务 () finally: S3互斥体退出 (数据库句柄)S3聚合上下文和S3取数据库自上下文是给自定义聚合函数用的。当你在易语言里注册一个自定义聚合函数时,sqlite3 会传一个上下文指针进来,这两个命令就是用来存取这个指针的。参数上,互斥体进入接收数据库句柄,返回一个状态码;聚合上下文接收上下文指针,返回一个整数标识。实际写的时候,互斥体进入和退出必须成对出现,中间如果抛异常或提前返回,锁没释放,其他线程就卡死了。血泪经验是,把互斥体退出放在finally块里,别指望每个分支都记得写。
3.2 数据库对象的繁忙超时与进度处理
zySqlite数据库新增了繁忙超时、繁忙处理、取文件名、是否只读、取互斥体、是否自动提交、进度处理、取下一记录集、取总影响行这些命令。繁忙超时对应 sqlite3 的sqlite3_busy_timeout,设置一个毫秒数,当数据库被其他连接锁住时,当前操作会等待指定时间再返回错误,而不是立刻失败。我一般设 5000 毫秒,给并发写入留出缓冲。
# 在 sqlite3 shell 里验证繁忙超时行为 sqlite3 test.db sqlite> PRAGMA busy_timeout = 5000; sqlite> BEGIN IMMEDIATE; # 另一个连接此时写入会等待 5 秒繁忙处理是设置一个回调函数,当繁忙超时触发时,不是直接返回错误,而是调用你指定的处理逻辑,比如重试、记录日志、或者切换到只读模式。进度处理对应sqlite3_progress_handler,可以设置每执行 N 条虚拟机指令就回调一次,用来做长时间查询的中断检查。取下一记录集和取总影响行是配合多语句执行用的,下面会细说。
3.3 记录集手动关闭与多语句获取的配合
V1.1 最需要适应的变化是:记录集必须手动关闭,任何内部方法都不再自动关闭。1.0 里你执行完查询,记录集对象可能被内部逻辑顺手关掉了,你再去读数据就报错。V1.1 把这个控制权交还给开发者,好处是你可以在记录集上做多次遍历、取多行数据,坏处是忘了关就会泄漏句柄,时间一长数据库连接数暴涨。
# 易语言中手动关闭记录集的正确姿势 记录集 = 数据库.取记录集 ("SELECT * FROM users") while 记录集.取下一行 (): 输出 (记录集.读字段 ("name")) 记录集.关闭 () # 必须显式调用数据库.取记录集多个()支持分号分割的 SQL 语句,并获取所有相应的记录集。比如你传"SELECT * FROM a; SELECT * FROM b",它会返回一个记录集数组,每个元素对应一条语句的结果。用的时候先调取下一记录集拿到第一个,处理完手动关闭,再拿下一个。参数上,分号分割的语句里如果有一条失败,后面的语句默认不执行,除非你在连接上开了sqlite3_exec的继续执行标志。常见做法是把多条语句包在一个事务里,要么全成功,要么全回滚。
3.4 附加数据库的密码参数与事务锁状态
数据库.附加数据库()增加了密码参数,对应ATTACH DATABASE ... KEY ...语法。当你附加一个加密的 sqlite3 文件时,必须提供正确的密钥,否则查询会返回file is encrypted or is not a database错误。参数顺序一般是:文件路径、别名、密码。密码传空字符串表示不加密。
数据库.开始事务()增加了事务锁状态参数,这个参数在多线程中非常重要。它对应BEGIN IMMEDIATE、BEGIN EXCLUSIVE或BEGIN DEFERRED。IMMEDIATE表示立刻获取写锁,其他连接不能写但可以读;EXCLUSIVE表示独占,其他连接连读都不行;DEFERRED是默认行为,等到第一次写操作才获取锁。多线程写入场景下,我一般用IMMEDIATE,避免两个线程同时从DEFERRED升级到写锁时死锁。
4. 避坑与排查:升级到 V1.1 后最容易翻车的五个点
4.1 记录集未关闭导致句柄泄漏
现象:程序运行一段时间后,数据库操作开始报database is locked或too many open files,重启后恢复正常。原因:V1.1 不再自动关闭记录集,代码里如果有分支提前返回或异常跳出,记录集.关闭()没被执行,句柄一直挂着。解决:把所有记录集操作包在try...finally里,finally块里强制关闭。另外可以用数据库.取总影响行和连接状态做监控,发现句柄数异常增长就主动排查。
4.2 多线程下事务锁状态选错导致死锁
现象:两个线程同时写同一个数据库,一个卡在开始事务,另一个也卡住,程序假死。原因:两个线程都用了默认的DEFERRED事务,各自先读后写,读锁升级写锁时互相等待。解决:写操作统一用IMMEDIATE锁状态,让第二个线程在开始事务阶段就等待,而不是在写的时候才冲突。配合繁忙超时设置 5000 毫秒,超时后返回错误让上层重试。
4.3 附加加密数据库时密码参数位置传错
现象:附加数据库后查询报file is encrypted or is not a database,但密码明明是对的。原因:附加数据库的密码参数位置和 1.0 不同,或者密码字符串被易语言的编码转换搞乱了。解决:确认参数顺序是路径、别名、密码,密码字符串在传入前不要做多余的编码转换,保持原始字节。如果还是不行,用sqlite3命令行工具手动ATTACH ... KEY ...验证密码是否正确。
4.4 多语句记录集获取时分号分割的边界问题
现象:取记录集多个返回的记录集数量不对,或者某条语句的结果为空。原因:SQL 语句里本身包含分号,比如字符串字面量里的分号被误判为语句分隔符。解决:避免在字符串字面量里直接写分号,用参数绑定代替拼接。如果必须写,用char(59)或转义处理。另外注意,分号分割的语句里如果有BEGIN或COMMIT,事务边界要自己控制,别指望内部自动处理。
4.5 编译时未开启 SQLITE_HAS_CODEC 导致加密命令无效
现象:调用附加数据库带密码参数,但数据库文件没有加密,或者加密文件打不开。原因:编译sqlite3.c时没有定义SQLITE_HAS_CODEC宏,codec.c和rijndael.c根本没被链接进去。解决:在configure.ac里确认--enable-codec生效,或者手动在编译命令里加-DSQLITE_HAS_CODEC。编译完成后用sqlite3_key函数是否导出做验证,没导出就是没编进去。
5. 进阶验证:用 shell.c 和 appveyor 脚本做回归测试
5.1 用 shell.c 验证加密与多语句行为
shell.c编译出来的sqlite3命令行工具是验证 V1.1 行为的最快方式。你可以用它测试加密数据库的打开、附加、查询,以及多语句执行的结果集数量。具体做法是:
# 编译带加密支持的 shell gcc -o sqlite3 shell.c sqlite3.c rijndael.c codec.c -DSQLITE_HAS_CODEC -lpthread -ldl # 创建加密数据库 ./sqlite3 encrypted.db sqlite> PRAGMA key = 'mysecret'; sqlite> CREATE TABLE t (id INTEGER PRIMARY KEY, val TEXT); sqlite> INSERT INTO t VALUES (1, 'hello'); sqlite> .quit # 重新打开,验证密码 ./sqlite3 encrypted.db sqlite> PRAGMA key = 'mysecret'; sqlite> SELECT * FROM t;如果密码错误,SELECT会报file is encrypted or is not a database。如果密码正确,数据正常返回。多语句测试可以用.read命令加载一个包含多条 SQL 的文件,观察输出是否按顺序打印每个语句的结果。
5.2 用 appveyor.bat 做 Windows 平台的持续集成
appveyor.bat和appveyor-test.bat是给 AppVeyor 用的 CI 脚本。appveyor.bat负责拉取代码、配置编译环境、执行编译;appveyor-test.bat负责跑测试用例。如果你在 Windows 上改了源码,想确认没有破坏现有功能,可以把这两个脚本挂到自己的 CI 流程里,或者手动在本地跑一遍。
# 本地模拟 appveyor 的编译流程 call appveyor.bat if %errorlevel% neq 0 exit /b %errorlevel% call appveyor-test.bat if %errorlevel% neq 0 exit /b %errorlevel%appveyor.bat里一般会设置MSVC的环境变量,调用cl.exe编译sqlite3.c和shell.c,生成sqlite3.dll和sqlite3.exe。appveyor-test.bat会跑一些基本的 SQL 操作,比如建表、插入、查询、删除,确认编译出来的二进制能正常工作。我一般会在改完codec.c或rijndael.c后强制跑一遍这两个脚本,避免加密逻辑改出问题自己不知道。
5.3 一个具体的验证技巧:用进度处理做超时中断
进度处理这个命令在实际项目里很有用,但容易被忽略。你可以设置一个回调,每执行 1000 条虚拟机指令就检查一次是否超时,如果超时就返回非零值中断查询。这样即使遇到一个没有索引的全表扫描,也不会把界面卡死。
# 易语言中设置进度处理回调 数据库.进度处理 (1000, &超时检查) # 超时检查函数返回 1 表示中断,返回 0 表示继续参数上,第一个参数是指令间隔数,第二个是回调函数指针。回调函数里可以用取启动时间和当前时间做差,超过阈值就返回 1。这个技巧在处理用户输入的模糊查询时特别有用,避免一个LIKE '%xxx%'把整个数据库扫一遍导致界面无响应。
从那以后我每次升级 sqlite3 支持库,都会先用shell.c编一个命令行工具,把加密、多语句、事务锁这三个场景手动跑一遍,确认行为符合预期再往项目里集成。希望帮到你。
本文还有配套的精品资源,点击获取