美文网首页
nodejs中使用mysql的注意事项和常用语句

nodejs中使用mysql的注意事项和常用语句

作者: PoWerfulHeart | 来源:发表于2021-04-19 19:25 被阅读0次

安装mysql

    npm i --save mysql

基本用法

创建管理一个连接

    var mysql = require('mysql');
    var connection = mysql.createConnection({
        host: 'aliyuncs.com',
        user: 'admin',
        password: 'pwd'
    });

    connection.connect(function(err) {
        if (err) {
            console.error('error connecting: ' + err.stack);
            return;
        }

        connection.query('SELECT * FROM ...', ..., function (error, results, fields) {
            if (error) throw error;
            
            //do something


            connection.end();
        });
    });

转义

为了避免SQL注入攻击,在SQL查询中使用任何用户提供的数据之前,始终应该对其进行转义。

手动转义

mysql提供了mysql.escape(), connection.escape() 和 pool.escape()

var userId = 'user input values';
var sql    = 'SELECT * FROM users WHERE userId = ' + connection.escape(userId);
connection.query(sql, function (error, results, fields) {
    if (error) throw error;
    //do something
});

自动转义

使用?字符作为要转义的值的占位符

connection.query('UPDATE users SET name = ?, address = ? WHERE userId = ?', [inputName, inputAddress, userId], function (error, results, fields) {
    if (error) throw error;
    //do something
});

连接和连接池

当我们一个api接口需要连接到数据库进行增删改查的时候,如果每次去创建连接然后去关闭,会大大的消耗数据库服务的性能。

连接池

连接是由池延迟创建的,基本流程是pool.getConnection() -> connection.query() -> connection.release(),有助于共享连接状态以用于后续查询。

    var mysql = require('mysql');
    var pool  = mysql.createPool({
        connectionLimit: 10,
        host: 'aliyuncs.com',
        user: 'admin',
        password: 'pwd',
        database: 'db',
        timezone: 'Asia/Shanghai'
    });

    pool.getConnection(function(err, connection) {
        if (err) throw err; 

        connection.query('SELECT * FROM ...', ..., function (error, results, fields) {
            connection.release();     //释放连接,以便其他请求使用

            if (error) throw error;

            //do something
        });
    });

连接池组

其实就是多个连接池的组合

创建一个连接池组

    var mysql = require('mysql');
    var poolCluster = mysql.createPoolCluster();

    poolCluster.add('pool1', {
        connectionLimit: 10,
        host: 'aliyuncs.com',
        user: 'admin',
        password: 'pwd',
        database: 'db',
        timezone: 'Asia/Shanghai',
        database: 'db1'
    });

    poolCluster.add('pool2', {
        connectionLimit: 10,
        host: 'aliyuncs.com',
        user: 'admin',
        password: 'pwd',
        database: 'db',
        timezone: 'Asia/Shanghai',
        database: 'db2'
    });

使用连接池

    poolCluster.getConnection('pool1', function (err, connection) {    //使用pool1池
        if (err) throw err; 

        connection.query('SELECT * FROM ...', ..., function (error, results, fields) {
            connection.release();
            if (error) throw error;

            //do something
        });
    });

特别注意:因为nodejs事件循环机制,连接池组会一直保持活跃,直到mysql服务器关闭,所以当nodejs进程退出时要手动关闭mysql链接池组

  process.on('exit', (code) => {
      poolCluster.end(function (err) {});
  });

事务组

多个连接请求“合并”成一个流程,来保证程序的正确运行

为什么需要事务组?

场景:当前有一个抽奖活动,用户点击前端抽奖按钮后调用api接口
后端逻辑:去道具表扣掉用户道具 -> 去奖品表修改奖品为已发放给用户发放奖品 -> 再中奖记录表中生成一条中奖记录

试想一下,上面的三个步骤,任何一个步骤出问题都将导致重大bug

比如:道具扣掉了,但是礼物没发放,又比如礼物发放了没有记录

如何创建事务组

    ......
    connection.beginTransaction(function(err) {
        if (err) throw err;
        connection.query('UPDATE props SET ...?', ..., function (error, results, fields) {
            if (error) {
                return connection.rollback(function() {
                    throw error;
                });
            }


            connection.query('UPDATE SET ...', ..., function (error, results, fields) {
                if (error) {
                    return connection.rollback(function() {
                        throw error;
                    });
                }

                connection.query('INSERT INTO record ...', ..., function (error, results, fields) {
                    if (error) {
                        return connection.rollback(function() {
                            throw error;
                        });
                    }
                    connection.commit(function(err) {
                        if (err) {
                            return connection.rollback(function() {
                                throw err;
                            });
                        }
                        
                        //do something
                    });
                });
            });
        });
    });

以上代码,任何一个连接操作出现问题,执行回滚,整个流程都不会执行,只有当三个连接操作都成功,执行提交,改动才生效。

beginTransaction(), commit() 和 rollback() 分别对应了 START TRANSACTION, COMMIT, 和 ROLLBACK

事务的隔离级别

业务当中的事务并发是一个不可控点,这个时候会引发一系列的bug

事务并发带来的问题

  • 脏读:事务1读取了事务2中未提交的数据
  • 虚读(不可重复读):事务1多次读取一条数据得到了不同的结果(事务2也修改了当前数据)
  • 幻读:事务1读取到了事务2插入或者删除的数据

MySql中的事务的隔离级别

隔离级别 描述 脏读 不可重复读 幻读
读未提交(RU) 允许读取尚未提交的数据变更
读已提交(RC) 允许读取并发事务已经提交的数据
可重复读(RR) 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改
串行化 最高的隔离级别,完全服从ACID的隔离级别

事务的隔离级别和并发度成反比的

需要注意的是与其他数据库不同的是,InnoDB 存储引擎在 可重复读(RR)事务隔离级别下使用的是Next-Key Lock 锁算法,实现了串行化隔离级别的功能,同时没有严重影响并发。

常见的防并发语句

防重复插入

INSERT INTO SELECT

INSERT INTO A(name1, name2, status) SELECT 1, 2, 0 WHERE NOT EXISTS (SELECT * FROM A WHERE create_time > '2020-01-01 00:00:00')

防重复修改

UPDATE WHERE带上初始条件

UPDATE A SET status = 1 WHERE name1 = 1 AND status = 0

利用Promise封装一个事务组方法

    ......
    var transactionQuery = (database, callback) => {
        return new Promise((resolve, reject) => {
            poolCluster.getConnection(database, function (err, connection) {
                if (err) {
                    reject({
                        msg: "系统内部错误,请稍后再试"
                    });
                    return;
                }

                connection.beginTransaction(function (err) {
                    if (err) {
                        reject({
                            msg: "系统内部错误,请稍后再试"
                        });
                        return;
                    }

                    callback.apply(null, [connection, resolve, reject]);
                });
            });
        });
    }

    //调用

    const res = await common.transactionQuery('pool1', (connection, resolve, reject) => {
            const rollback = (msg) => {
                connection.rollback(() => {
                    if (msg) {
                        reject({
                            msg: msg
                        });
                    }
                    connection.release();
                });
            }

            connection.query('UPDATE props SET ...?', ..., function (error, results, fields) {
                if (error) {
                    rollback('系统内部错误:10001');
                    return;
                }

                connection.query('UPDATE lucky SET ...', ..., function (error, results, fields) {
                    if (error) {
                        rollback('系统内部错误:10002');
                        return;
                    }

                    connection.query('INSERT INTO record ...', ..., function (error, results, fields) {
                        if (error) {
                            rollback('系统内部错误:10003');
                            return;
                        }

                        connection.commit(() => {
                            resolve();
                            connection.release();
                        });
                    });
                });
        });
    });

    console.log(res);

UNION 与 UNION ALL

UNION会去重,UNION ALL 则不会,搭配GROUP BY 和 HAVING 可以执行很多复杂的查询

UNION要求执行的多条SELECT查询字段必须相同,当某张表不存在某个字段时可以使用AS去指定

eg: 查询订单表A和订单表B每个用户的支付总金额

    poolCluster.getConnection('pool1', function (err, connection) {    //使用pool1池
        if (err) throw err; 

        connection.query('SELECT user_id, SUM(amount) AS total FROM A GROUP BY user_id UNION ALL SELECT uid AS user_id, SUM(amount) AS total FROM B GROUP BY user_id', [], function (error, results, fields) {
            connection.release();
            if (error) throw error;

            //do something
        });
    });

eg: 查询订单表A和订单表B每个用户的支付总金额,并支付金额大于100的用户

    poolCluster.getConnection('pool1', function (err, connection) {    //使用pool1池
        if (err) throw err; 

        connection.query('SELECT user_id, SUM(amount) AS total FROM A GROUP BY user_id UNION ALL SELECT uid AS user_id, SUM(amount) AS total FROM B GROUP BY user_id HAVING total > 100', [], function (error, results, fields) {
            connection.release();
            if (error) throw error;

            //do something
        });
    });

JOIN

LEFT JOIN:以左表为主,返回左表全部记录和右表满足ON条件的记录,右表没有的会以null填充
RIGHT JOIN:和LEFT JOIN相反
INNER JOIN:仅返回两个表中满足ON条件的记录

JOIN中ON和WHERE的注意事项:
WHERE是对已经搜索出的结果的过滤条件
ON是两者JOIN形成新表时用到的匹配条件
INNER JOIN中WHERE在查询语句中使用或者在ON后面使用,没有区别

eg: 查询订单表A每笔订单购买的产品名称

    poolCluster.getConnection('pool1', function (err, connection) {    //使用pool1池
        if (err) throw err; 

        connection.query('SELECT A.order_num, B.product_name FROM A LEFT JOIN B ON A.product_id = B.product_id', [], function (error, results, fields) {
            connection.release();
            if (error) throw error;

            //do something
        });
    });

相关文章

网友评论

      本文标题:nodejs中使用mysql的注意事项和常用语句

      本文链接:https://www.haomeiwen.com/subject/cxlwlltx.html