上次写了一篇node使用mysql的入门:juejin.cn/post/730224...,结果写完才发现mysql模块有一个新的版本:mysql2。那就来看看mysql2模块对比mysql模块有什么区别。
mysql模块的问题
nodejs的mysql模块虽然功能强大,但是由于发布的时间比较久,后续的版本有些地方跟不上大前端技术的迭代脚步(个人觉得是项目过于庞大,支持新的语法需要做大量的重构),还是存在一些缺点:
- 回调问题,mysql的操作都是通过回调函数来执行,这就回到了我们熟悉的"回调地狱",过多的回调函数嵌套,会大大的破坏代码的可读性和可维护性。
- Promise和async/await,跟早起的node其他模块一样,mysql模块并不支持Promise和async/await语法,要使用这些语法,需要自形封装mysql的操作。
- 缺少高级功能支持,例如:语句预处理、流式查询等,对新版本的mysql支持不足(mysql8的密码加密格式问题等)
- 性能问题,在大数据量或者高并发的情况下,mysql模块会出现性能受限的情况,主要原因是其无法发挥nodejs的异步特性和性能特性。
针对上面的问题,mysql2做了很多改进,mysql2基于mysql-native项目进行改进,同时兼容mysql的API。
mysql2的使用
安装
shell
npm install mysql2
建立链接
js
const mysql2 = require('mysql2');
const connection = mysql2.createConnection({
host: 'localhost',
user: 'root',
password: '123456',
database: 'demo'
});
执行sql
js
// 查询
connection.query(
'select * from demo',
function(err, results, fields) {
console.log(results);
}
);
// 插入
connection.execute(
'insert into demo set name="sk"',
function(err, results, fields) {
console.log(results);
}
)
写法看起来跟mysql也没什么不一样,这主要是为了向下兼容。但是sql的执行结果会自动转换格式,不需要再自行处理:
js
connection.query(
'select * from demo',
function(err, results, fields) {
console.log(results); // [ { id: 1, name: '0' }, { id: 2, name: '1' } ]
}
);
如果需要使用mysql2的新特性,需要引入对应的模块。
使用Promise和async/await
要使用promise的特性,需要引入promise的模块
js
const mysql2 = require('mysql2/promise'); // 替换掉原来的require('mysql2')
引入promise模块后就可以使用promise的写法:
js
// promise
mysql2.createConnection({
host: 'localhost',
user: 'root',
password: '123456',
database: 'demo'
}).then(res => {
connection = res;
});
// async/await
const connection = await mysql2.createConnection({
host: 'localhost',
user: 'root',
password: '123456',
database: 'demo'
});
// 查询
connection.query('select * from demo2').then(([results, fields]) => {
console.log(results);
});
let [results, fields] = await connection.query('select * from demo2 where id > 3');
注意:这里返回的结果是一个数组,包括results和fields,results为执行的结果,fields为与结果相关的字段的元数据。
使用TypeScript
mysql2内置TS的支持。
js
import mysql2, { RowDataPacket } from 'mysql2';
const connection = mysql2.createConnection({
host: 'localhost',
user: 'root',
password: '123456',
database: 'demo'
});
connection.query<RowDataPacket[]>('select * from demo2', (err, rows) => {
console.log(rows);
});
具体内容可以参考官方文档:github.com/sidorares/n...
其他
其他新特性包括连接池、流式查询等,这些涉及到其他的mysql知识点,后面有机会再聊,使用的方法可以参考官方文档:github.com/sidorares/n...
mysql2与mysql的性能对比
mysql2官方说明第一个点就是它比mysql模块具备更好的性能。那么mysql2是怎么做到的?
首先,最大的点应该是使用了libmysqlclient,libmysqlclient是使用C语言实现的库,mysql2是基于该库开发,使得其可以跟mysql数据库进行低级别的通讯,从而提高性能和效率。使用编译后的库启动速度也更高。与mysql模块对比,mysql模块使用的是JavaScript实现的,没有依赖其他库,只能使用node底层的异步I/O和事件循环,这对其性能影响比较大。
其次,在协议解析器上做了更多的改进:
- 更高效的数据解析方式:采用更高效的数据解析方式,能更快地解析MySQL协议中的数据,如使用Buffer对象来存储数据,使用ES6的解构赋值来解析数据等,使得数据解析更加快速和高效。
- 更高效的数据序列化方式:采用了更高效的数据序列化方式,能更快地将数据序列化为MySQL协议中的格式。如使用ES6的模板字符串来生成SQL语句,使用Buffer对象来存储数据等。
- 增加流式解析的支持:数据可以在解析的同时被处理,不需要等待整个数据包解析完成。
其他的还有像连接池之类的,减少创建链接的额外开销等方式来减少性能消耗。
做个小实验
接下来我们来做个对比实验,我们在同一个数据库创建两个一样的表,分别用mysql和mysql2模块来进行插入和查询,对比下时间的消耗。
表结构如下,只有一个id和name,id自增。mysql使用demo1表,mysql2使用demo2表。
sql
CREATE TABLE `demo1` (
`id` int NOT NULL AUTO_INCREMENT,
`name` varchar(60) DEFAULT NULL COMMENT 'name',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=10001 DEFAULT CHARSET=utf8mb3 ROW_FORMAT=DYNAMIC COMMENT='demo表1';
插入数据的代码:
js
function insertRow(i) {
return new Promise((resolve, reject) => {
connection.query(
`insert into demo1 set name='${i}'`,
(error, results, fields) => {
if (error) {
reject();
throw error;
}
resolve();
}
);
});
}
async function run(times) {
let i = 0;
console.time();
for(i; i < times; i++) {
await insertRow(i);
}
console.timeEnd();
}
为了尽量减少代码写法不一致带来的误差,mysql2使用向下兼容的代码,保证执行的代码一致。同时在相同配置的容器中执行代码,减少系统层面的误差。
我们执行代码,分别插入1000、4000、7000、10000条数据,每种次数执行5次,不会清表,看看两边的耗时对比:
次数 | 1000 | 4000 | 7000 | 10000 | |
---|---|---|---|---|---|
mysql | 1 | 3.065s | 13.784s | 20.749s | 27.704s |
2 | 2.923s | 11.573s | 20.362s | 27.652s | |
3 | 2.941s | 11.941s | 20.254s | 31.822s | |
4 | 2.917s | 11.767s | 24.963s | 31.362s | |
5 | 2.839s | 11.457s | 27.387s | 31.450s | |
平均值 | 2.937s | 12.104s | 22.743s | 29.997s | |
mysql2 | 1 | 3.085s | 11.776s | 19.406s | 28.061s |
2 | 2.830s | 11.380s | 19.229s | 27.601s | |
3 | 2.850s | 11.424s | 19.157s | 27.292s | |
4 | 2.874s | 11.620s | 19.070s | 27.234s | |
5 | 2.790s | 11.387s | 23.615s | 27.947s | |
平均值 | 2.885s | 11.517s | 20.095s | 27.627s |
可能在高频插入的情况下,mysql2的性能会更优一些:
接下来我们看看数据查询的情况:
查询的代码:
js
function select(rows) {
console.time();
connection.query(
`select name from demo1 limit ${rows}`,
(error, results, fields) => {
if (error) {
throw error;
}
console.timeEnd();
}
);
}
我们分别查询:1000、5000、10000、20000、25000条数据,每次查询重复一次,看看性能的对比:
次数 | 1000 | 5000 | 10000 | 20000 | 25000 | |
---|---|---|---|---|---|---|
mysql | 1 | 10.622ms | 17.358ms | 19.193ms | 26.517ms | 27.842ms |
2 | 2.64ms | 6.366ms | 8.962ms | 11.735ms | 15.87ms | |
3 | 2.733ms | 4.452ms | 5.599ms | 9.328ms | 11.247ms | |
4 | 3.45ms | 3.357ms | 5.844ms | 7.765ms | 18.947ms | |
5 | 1.873ms | 3.072ms | 4.635ms | 7.773ms | 9.666ms | |
平均值 | 4.264ms | 6.921ms | 8.847ms | 12.623ms | 16.714ms | |
去除第一次平均值 | 2.674ms | 4.312ms | 6.260ms | 9.1502ms | 13.933ms | |
mysql2 | 1 | 35.555ms | 40.431ms | 43.244ms | 48.772ms | 48.815ms |
2 | 1.636ms | 4.386ms | 6.949ms | 12.606ms | 13.701ms | |
3 | 1.736ms | 3.822ms | 6.46ms | 7.888ms | 15.633ms | |
4 | 1.556ms | 3.351ms | 3.906ms | 12.852ms | 10.309ms | |
5 | 1.577ms | 2.727ms | 3.317ms | 5.998ms | 12.669ms | |
平均值 | 8.412ms | 10.943ms | 12.775ms | 17.623ms | 20.225ms | |
去除第一次平均值 | 1.626ms | 3.573ms | 5.158ms | 9.836ms | 13.078ms |
可以看到,mysql2在第一次查询的时候明显慢一些,应该是与底层建立链接的时间消耗比较大,之后的消耗的时间明显小于mysql。
但是这个性能还是不够强,使用mysql2的连接池和流式查询,再看看整体的性能:
js
const mysql = require('mysql2');
// 创建链接池
const pool = mysql.createPool({
host: 'local-mysql',
port: 3306,
user: 'root',
password: '123456',
database: 'demo',
connectionLimit: 20 // 最多20个链接
});
// 执行查询
pool.getConnection(
(err, connection) => {
if (err) {
throw err;
}
const query = connection.query('select name from demo1 limit 10000');
console.time();
query
.stream()
.on('data', (row) => { })
.on('error', (err) => {})
.on('end', () => {
console.timeEnd();
});
}
);
执行结果如下:
次数 | 1000 | 5000 | 10000 | 20000 | 25000 |
---|---|---|---|---|---|
1 | 4.855ms | 11.471ms | 15.148ms | 18.993ms | 20.689ms |
2 | 2.421ms | 7.133ms | 8.781ms | 13.117ms | 14.738ms |
3 | 2.64ms | 3.72ms | 4.863ms | 7.158ms | 7.498ms |
4 | 1.522ms | 3.18ms | 5.483ms | 6.909ms | 7.451ms |
5 | 1.241ms | 2.417ms | 4.493ms | 5.801ms | 8.017ms |
性能明显提升了很多。
总结
本文对node的mysql和mysql2模块做了简单的对比,我们做了个小实验对比了两者在插入和查询的性能对比,当然这只是一个小实验,场景简单,样本也比较少,但是还是能看出mysql2的性能更优。从整个对比来看,mysql2在新技术特性适配和性能上明显优于mysql模块。如果是新项目或者项目比较好更新,建议使用mysql2,如果使用第三方封装的mysql库,可以看下是基于哪个mysq模块,如果没有依赖亦可以对比下其与mysql2的性能。