区别

我对性能的要求,还远没有到如此计较的地步,让我从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什么问题,烦请可以留言备注一下,一起交流下,等我下次遇到了也好有个心理准备。。。