区别
- 在性能上,官网文档中显示,
better-sqlite3
的select
和insert
都更快一些。 - 在使用上,
better-sqlite3
是同步接口。node-sqlite3
是callback(err,data)
形式的异步接口, node-sqlite3
中each
逻辑无法等待异步操作。better-sqlite3
可以。
我对性能的要求,还远没有到如此计较的地步,让我从node-sqlite3
切到better-sqlite3
的主要原因在于第三点。假设在使用node-sqlite3
时,有如下逻辑:
const {Database} = require('sqlite3');
const initDb = new Promise((resolve, reject) => {
const db = new Database('test.db', (err) => {
if (err) reject(err)
else resolve(db);
})
})
initDb.then((db) => {
db.each('select * from comment', async (err, row) => {
await get('/getSomeResource?id=' + row.id);
})
})
由于each()
不等待promise
完成,所以这里会立即发出大量/getSomeResource
请求。
如果换成better-sqlite3
,可以写成以下形式:
const Database = require('better-sqlite3');
const db = new Database('test.db');
const stmt = db.prepare('select * from comment');
(async () => {
for (const row of stmt.iterate()) {
await get('/getSomeResource?id=' + row.id);
}
})()
使用iterate()
,会等待前一个/getSomeResource
请求完成后,才发出新的请求。
我实际也并没有在each()
上遇到太大的问题,但就是心里觉得十分不爽,然后换成了better-sqlite3
。换完就后悔了,因为better-sqlite3
有更多潜在的坑,而这种底层依赖的改动,又造成项目有许多未知的风险。
遇到的问题
number到text的转换
TL;DR
在better-sqlite3
中,先把number
转化为real
,然后再存到SQLite
。
详细介绍
由于SQLite
是动态类型,在插入时,会自动做类型转换。假设存在一个表create table comment(id text)
,然后执行如下代码:
// node-sqlite3:
db.run('insert into comment values($id)', {$id: 666});
此时,实际保存到数据库的值为字符串"666"
,所以我在使用node-sqlite3
时,并没过多在意数据类型是否匹配的问题。
但是,切换到better-sqlite3
后,如下:
// better-sqlite3
db.prepare('insert into comment values($id)').run({id: 666})
此时保存的值变成了字符串"666.0"
,这是因为在better-sqlite3
中,会把JavaScript中的number
作为real
处理,对应SQL
可以理解为如下形式:
insert into comment values( cast(666 as real) );
保存boolean数据
TL;DR
在better-sqlite3
中,参数只允许下列类型:numbers
, strings
, bigints
, buffers
, null
。
如果参数是boolean
类型,在better-sqlite3
中,会报错。而在node-sqlite3
中,会提交给SQLite
做默认的转换,true
变成1
,false
变成0
。
详细介绍
如下代码:
// node-sqlite3:
db.run('insert into comment(id) values($id)', {$id: true});
如果数据库中id
字段为int
,则实际保存的值为1
,如果id
字段为text
,则保存的值为字符串"1"
。
而如果使用better-sqlite3
传入boolean
字段,则会报错:
// better-sqlite3
db.prepare('insert into comment values($id)').run({id: true});
// TypeError: SQLite3 can only bind numbers, strings, bigints, buffers, and null
严格的参数限制
TL;DR
当sql中没有参数的时候,对应执行sql的方法也不能传参数,否则会报错:RangeError: Too many parameter values were provided
。
详细介绍
例如下列代码会报错。
db.prepare('select * from topic').iterate(undefined);
该iterate
方法,要么不传任何参数,要么传一个空对象,如下:
const stmt = db.prepare('select * from topic');
stmt.iterate();
stmt.iterate({});
而在使用node-sqlite3
的时候,可以传入undefined
而不报错。
依赖包安装
在阿里云服务器的Alibaba Cloud Linux
系统中,执行npm install better-sqlite3
,提示缺少依赖:g++ not found
。
g++ not found
可以通过 yum install gcc
解决
gc++版本过低
郁闷(yum
)包管理的特点就是,安装完之后贼郁闷,安装的都是特么一个世纪前的版本。执行了yum install gcc
之后,再次执行npm install better-sqlite3
,发现有如下报错:
unrecognized command line option ‘-std=c 14’
这是因为gcc
的版本过低了。可以通过手动编译最新的gcc
解决,命令如下。
该命令参考链接: gcc: error: unrecognized command line option ‘-std=c++14’ 问题解决
其中 make && make install
的时间相当长,先设置好ssh
的心跳,防止连接断开。
cd /usr/local/src
# 下载gcc5.2.0源码
wget http://ftp.gnu.org/gnu/gcc/gcc-5.2.0/gcc-5.2.0.tar.bz2
tar -jxvf gcc-5.2.0.tar.bz2
# 进入gcc目录安装
cd gcc-5.2.0
# 下载某些依赖包
./contrib/download_prerequisites
# 创建bulid文件夹
mkdir build
cd build
../configure --prefix=/usr/local/gcc --enable-languages=c,c++ --disable-multilib
# 编译安装,此过程耗时较长
make && make install
# 修改软连接
mv /usr/bin/gcc /usr/bin/gcc_bak
ln -s /usr/local/gcc/bin/gcc /usr/bin/gcc
mv /usr/bin/g++ /usr/bin/g++_bak
ln -s /usr/local/gcc/bin/g++ /usr/bin/g++
# 查看升级后版本
gcc --verson
g++ --version
GLIBCXX_3.4.20 not found
走到这一步,better-sqlite3
的安装总算顺利通过了,但是执行的时候,报如下错误:
Error: /lib64/libstdc++.so.6: version `GLIBCXX_3.4.20' not found (required by /root/gitee/sqlite3/node_modules/better-sqlite3/build/Release/better_sqlite3.node)
先执行下列命令:
yum install libstdc++
执行完之后尝试执行代码,大概率还是报同样的错。同样是因为安装的版本过低的问题,通过如下步骤解决:
该解决方式参考链接: Check /lib64/libstdc++.so.6: version GLIBCXX_3.4.20', GLIBCXX_3.4.21' by default
# 检查/lib64/libstdc++.so.6中的可用版本
strings /usr/lib64/libstdc++.so.6 | grep GLIBCXX
# 输出以下内容,可见的确缺少了GLIBCXX_3.4.20
#GLIBCXX_3.4
#GLIBCXX_3.4.1
#GLIBCXX_3.4.2
#GLIBCXX_3.4.3
#GLIBCXX_3.4.4
#GLIBCXX_3.4.5
#GLIBCXX_3.4.6
#GLIBCXX_3.4.7
#GLIBCXX_3.4.8
#GLIBCXX_3.4.9
#GLIBCXX_3.4.10
#GLIBCXX_3.4.11
#GLIBCXX_3.4.12
#GLIBCXX_3.4.13
#GLIBCXX_3.4.14
#GLIBCXX_3.4.15
#GLIBCXX_3.4.16
#GLIBCXX_3.4.17
#GLIBCXX_3.4.18
#GLIBCXX_3.4.19
#GLIBCXX_DEBUG_MESSAGE_LENGTH
# 在服务器上搜索安装的libstdc++包
find / -name "libstdc++.so.6*"
# 在我的服务器上,输出以下内容
#/usr/lib64/libstdc++.so.6
#/usr/lib64/libstdc++.so.6.0.19
#/usr/local/src/gcc-5.2.0/build/prev-x86_64-unknown-linux-gnu/libstdc++-v3/src/.libs/libstdc++.so.6
#/usr/local/src/gcc-5.2.0/build/prev-x86_64-unknown-linux-gnu/libstdc++-v3/src/.libs/libstdc++.so.6.0.21
#/usr/local/src/gcc-5.2.0/build/x86_64-unknown-linux-gnu/libstdc++-v3/src/.libs/libstdc++.so.6
#/usr/local/src/gcc-5.2.0/build/x86_64-unknown-linux-gnu/libstdc++-v3/src/.libs/libstdc++.so.6.0.21
#/usr/local/src/gcc-5.2.0/build/stage1-x86_64-unknown-linux-gnu/libstdc++-v3/src/.libs/libstdc++.so.6
#/usr/local/src/gcc-5.2.0/build/stage1-x86_64-unknown-linux-gnu/libstdc++-v3/src/.libs/libstdc++.so.6.0.21
#/usr/local/gcc/lib64/libstdc++.so.6.0.21-gdb.py
#/usr/local/gcc/lib64/libstdc++.so.6
#/usr/local/gcc/lib64/libstdc++.so.6.0.21
#/usr/share/gdb/auto-load/usr/lib64/libstdc++.so.6.0.19-gdb.pyo
#/usr/share/gdb/auto-load/usr/lib64/libstdc++.so.6.0.19-gdb.pyc
#/usr/share/gdb/auto-load/usr/lib64/libstdc++.so.6.0.19-gdb.py
# 挑选上面最高的一个版本,即:/usr/local/gcc/lib64/libstdc++.so.6.0.21
cp /usr/local/gcc/lib64/libstdc++.so.6.0.21 /usr/lib64/
mv /usr/lib64/libstdc++.so.6 /usr/lib64/libstdc++.so.6.bkp
ln -s /usr/lib64/libstdc++.so.6.0.21 /usr/lib64/libstdc++.so.6
此时再执行代码,终于都看似正常了,但是,不知道后面还会遇到什么坑,心好累。
总结
没事不要乱优化,心好累。
如果你也遇到了better-sqlite3
什么问题,烦请可以留言备注一下,一起交流下,等我下次遇到了也好有个心理准备。。。