你学废了吗:对比下node的mysql和mysql2

上次写了一篇node使用mysql的入门:juejin.cn/post/730224...,结果写完才发现mysql模块有一个新的版本:mysql2。那就来看看mysql2模块对比mysql模块有什么区别。

mysql模块的问题

nodejs的mysql模块虽然功能强大,但是由于发布的时间比较久,后续的版本有些地方跟不上大前端技术的迭代脚步(个人觉得是项目过于庞大,支持新的语法需要做大量的重构),还是存在一些缺点:

  1. 回调问题,mysql的操作都是通过回调函数来执行,这就回到了我们熟悉的"回调地狱",过多的回调函数嵌套,会大大的破坏代码的可读性和可维护性。
  2. Promise和async/await,跟早起的node其他模块一样,mysql模块并不支持Promise和async/await语法,要使用这些语法,需要自形封装mysql的操作。
  3. 缺少高级功能支持,例如:语句预处理、流式查询等,对新版本的mysql支持不足(mysql8的密码加密格式问题等)
  4. 性能问题,在大数据量或者高并发的情况下,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和事件循环,这对其性能影响比较大。

其次,在协议解析器上做了更多的改进:

  1. 更高效的数据解析方式:采用更高效的数据解析方式,能更快地解析MySQL协议中的数据,如使用Buffer对象来存储数据,使用ES6的解构赋值来解析数据等,使得数据解析更加快速和高效。
  2. 更高效的数据序列化方式:采用了更高效的数据序列化方式,能更快地将数据序列化为MySQL协议中的格式。如使用ES6的模板字符串来生成SQL语句,使用Buffer对象来存储数据等。
  3. 增加流式解析的支持:数据可以在解析的同时被处理,不需要等待整个数据包解析完成。

其他的还有像连接池之类的,减少创建链接的额外开销等方式来减少性能消耗。

做个小实验

接下来我们来做个对比实验,我们在同一个数据库创建两个一样的表,分别用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的性能。

相关推荐
Estar.Lee4 小时前
查手机号归属地免费API接口教程
android·网络·后端·网络协议·tcp/ip·oneapi
y先森4 小时前
CSS3中的伸缩盒模型(弹性盒子、弹性布局)之伸缩容器、伸缩项目、主轴方向、主轴换行方式、复合属性flex-flow
前端·css·css3
前端Hardy4 小时前
纯HTML&CSS实现3D旋转地球
前端·javascript·css·3d·html
susu10830189114 小时前
vue3中父div设置display flex,2个子div重叠
前端·javascript·vue.js
IT女孩儿5 小时前
CSS查缺补漏(补充上一条)
前端·css
2401_857610035 小时前
SpringBoot社团管理:安全与维护
spring boot·后端·安全
凌冰_6 小时前
IDEA2023 SpringBoot整合MyBatis(三)
spring boot·后端·mybatis
码农飞飞6 小时前
深入理解Rust的模式匹配
开发语言·后端·rust·模式匹配·解构·结构体和枚举
一个小坑货6 小时前
Rust 的简介
开发语言·后端·rust
吃杠碰小鸡6 小时前
commitlint校验git提交信息
前端