nodejs中使用mysql的注意事項和常用語句

安裝mysql

    npm i --save mysql

基本用法

創(chuàng)建管理一個連接

    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查詢中使用任何用戶提供的數(shù)據(jù)之前,始終應該對其進行轉義属划。

手動轉義

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接口需要連接到數(shù)據(jù)庫進行增刪改查的時候恬叹,如果每次去創(chuàng)建連接然后去關閉,會大大的消耗數(shù)據(jù)庫服務的性能榴嗅。

連接池

連接是由池延遲創(chuàng)建的妄呕,基本流程是pool.getConnection() -> connection.query() -> connection.release(),有助于共享連接狀態(tài)以用于后續(xù)查詢嗽测。

    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
        });
    });

連接池組

其實就是多個連接池的組合

創(chuàng)建一個連接池組

    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事件循環(huán)機制肿孵,連接池組會一直保持活躍,直到mysql服務器關閉疏魏,所以當nodejs進程退出時要手動關閉mysql鏈接池組

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

事務組

多個連接請求“合并”成一個流程停做,來保證程序的正確運行

為什么需要事務組?

場景:當前有一個抽獎活動大莫,用戶點擊前端抽獎按鈕后調用api接口
后端邏輯:去道具表扣掉用戶道具 -> 去獎品表修改獎品為已發(fā)放給用戶發(fā)放獎品 -> 再中獎記錄表中生成一條中獎記錄

試想一下蛉腌,上面的三個步驟,任何一個步驟出問題都將導致重大bug

比如:道具扣掉了只厘,但是禮物沒發(fā)放烙丛,又比如禮物發(fā)放了沒有記錄

如何創(chuàng)建事務組

    ......
    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
                    });
                });
            });
        });
    });

以上代碼,任何一個連接操作出現(xiàn)問題羔味,執(zhí)行回滾河咽,整個流程都不會執(zhí)行,只有當三個連接操作都成功赋元,執(zhí)行提交忘蟹,改動才生效。

beginTransaction(), commit() 和 rollback() 分別對應了 START TRANSACTION, COMMIT, 和 ROLLBACK

事務的隔離級別

業(yè)務當中的事務并發(fā)是一個不可控點搁凸,這個時候會引發(fā)一系列的bug

事務并發(fā)帶來的問題

  • 臟讀:事務1讀取了事務2中未提交的數(shù)據(jù)
  • 虛讀(不可重復讀):事務1多次讀取一條數(shù)據(jù)得到了不同的結果(事務2也修改了當前數(shù)據(jù))
  • 幻讀:事務1讀取到了事務2插入或者刪除的數(shù)據(jù)

MySql中的事務的隔離級別

隔離級別 描述 臟讀 不可重復讀 幻讀
讀未提交(RU) 允許讀取尚未提交的數(shù)據(jù)變更
讀已提交(RC) 允許讀取并發(fā)事務已經(jīng)提交的數(shù)據(jù)
可重復讀(RR) 對同一字段的多次讀取結果都是一致的媚值,除非數(shù)據(jù)是被本身事務自己所修改
串行化 最高的隔離級別,完全服從ACID的隔離級別

事務的隔離級別和并發(fā)度成反比的

需要注意的是與其他數(shù)據(jù)庫不同的是护糖,InnoDB 存儲引擎在 可重復讀(RR)事務隔離級別下使用的是Next-Key Lock 鎖算法褥芒,實現(xiàn)了串行化隔離級別的功能,同時沒有嚴重影響并發(fā)嫡良。

常見的防并發(fā)語句

防重復插入

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: "系統(tǒng)內部錯誤喂很,請稍后再試"
                    });
                    return;
                }

                connection.beginTransaction(function (err) {
                    if (err) {
                        reject({
                            msg: "系統(tǒng)內部錯誤,請稍后再試"
                        });
                        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('系統(tǒng)內部錯誤:10001');
                    return;
                }

                connection.query('UPDATE lucky SET ...', ..., function (error, results, fields) {
                    if (error) {
                        rollback('系統(tǒng)內部錯誤:10002');
                        return;
                    }

                    connection.query('INSERT INTO record ...', ..., function (error, results, fields) {
                        if (error) {
                            rollback('系統(tǒng)內部錯誤:10003');
                            return;
                        }

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

    console.log(res);

UNION 與 UNION ALL

UNION會去重皆刺,UNION ALL 則不會,搭配GROUP BY 和 HAVING 可以執(zhí)行很多復雜的查詢

UNION要求執(zhí)行的多條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
        });
    });

SELECT嵌套

很多場景我們需要在多張表中查詢數(shù)據(jù),嵌套寫法可以優(yōu)化性能

  1. SELECT查詢執(zhí)行了SELECT查詢的表(因為SELECT查詢執(zhí)行后其實也是一張表锨亏,所以可以進行二次SELECT)
SELECT table1.name, table2.total FROM (SELECT name, COUNT(DISTINCT user_id) AS total FROM temp WHERE DATE_FORMAT(create_time, '%Y-%m-%d') = '2021-08-12' GROUP BY name) AS table2, table1 WHERE table2.name = table1.name
  1. SELECT帶WHERE查詢執(zhí)行了SELECT查詢的結果集(SELECT查詢執(zhí)行后的結果集痴怨,可以進行二次SELECT)
SELECT * FROM (SELECT * FROM table2 WHERE all_number IN(SELECT number FROM table1 WHERE number = 51)) AS table3 WHERE table3.name LIKE '%亞%'

JOIN

LEFT JOIN:以左表為主,返回左表全部記錄和右表滿足ON條件的記錄器予,右表沒有的會以null填充
RIGHT JOIN:和LEFT JOIN相反
INNER JOIN:僅返回兩個表中滿足ON條件的記錄

JOIN中ON和WHERE的注意事項:
WHERE是對已經(jīng)搜索出的結果的過濾條件
ON是兩者JOIN形成新表時用到的匹配條件
INNER JOIN中WHERE在查詢語句中使用或者在ON后面使用浪藻,沒有區(qū)別

eg: 查詢訂單表A每筆訂單購買的產(chǎn)品名稱

    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
        });
    });

GROUP BY

用于對SELECT查詢后的數(shù)據(jù)進行分組,可以理解為先執(zhí)行SELECT再進行GROUP BY

例子乾翔,當有如下用戶消費金額表table

id date name cost
1 2019-08-10 12:00:00 小明 5
2 2019-08-11 04:00:00 小明 200
3 2019-08-11 05:00:00 小紅 100
4 2019-08-11 06:00:00 小李 90

現(xiàn)需要查詢所有用戶的總消費額
執(zhí)行sql:

SELECT name, SUM(cost) AS total FROM `table` GROUP BY name

分析

SELECT省略

  1. GROUP BY name
    查詢后的表結構為:
name cost
小明 5, 200
小紅 100
小李 90
  1. SUM(cost) AS total
    SUM在GROUP BY中是對分組后的每一組cost做了一個和的處理

  2. 最終結果

name total
小明 205
小紅 100
小李 90

這里還可以使用HAVING對聚合結果進行篩選

執(zhí)行sql:

SELECT name, SUM(cost) AS total FROM `table` GROUP BY name HAVING total > 200

最終結果就會變成:

name total
小明 205

HAVING和WHERE的區(qū)別主要在于:
WHERE用于對數(shù)據(jù)表中字段的過濾爱葵,不可以用字段別名(可以用表別名)施戴,后面不能跟聚合函數(shù)
HAVING用于對分組過濾,可以用字段別名萌丈,后面可以跟聚合函數(shù)

使用別名

有些時候數(shù)據(jù)表里面的數(shù)據(jù)不符合我們的分組規(guī)則赞哗,這個時候就可以使用別名進行分組,因為是先SELECT再GROUP BY

現(xiàn)需要查詢當日消費的用戶總數(shù):
執(zhí)行sql:

SELECT DATE_FORMAT(cur_time, '%Y年%m月%d日') AS time, COUNT(DISTINCT name) AS total FROM `table` GROUP BY time

DISTINCT

DISTINCT進行去重的主要原理是通過先對要進行去重的數(shù)據(jù)進行分組操作辆雾,然后從分組后的每組數(shù)據(jù)中取一條返回

分析

  1. SELECT DATE_FORMAT(cur_time, '%Y年%m月%d日') AS time
    查詢后的time字段:
time
2019年08月10日
2019年08月11日
2019年08月11日
2019年08月11日
  1. GROUP BY time
    查詢后的表結構為:
time name
2019年08月10日 小明,小明
2019年08月11日 小紅,小李
  1. COUNT(DISTINCT name) AS total
    COUNT在GROUP BY中是對分組后的每一組去重和統(tǒng)計的處理

  2. 最終結果

time total
2019年08月10日 1
2019年08月11日 2
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末肪笋,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子度迂,更是在濱河造成了極大的恐慌藤乙,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件惭墓,死亡現(xiàn)場離奇詭異坛梁,居然都是意外死亡,警方通過查閱死者的電腦和手機诅妹,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進店門罚勾,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人吭狡,你說我怎么就攤上這事尖殃。” “怎么了划煮?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵送丰,是天一觀的道長。 經(jīng)常有香客問我弛秋,道長器躏,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任蟹略,我火速辦了婚禮登失,結果婚禮上,老公的妹妹穿的比我還像新娘挖炬。我一直安慰自己揽浙,他們只是感情好,可當我...
    茶點故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布意敛。 她就那樣靜靜地躺著馅巷,像睡著了一般。 火紅的嫁衣襯著肌膚如雪草姻。 梳的紋絲不亂的頭發(fā)上钓猬,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天,我揣著相機與錄音撩独,去河邊找鬼敞曹。 笑死账月,一個胖子當著我的面吹牛,可吹牛的內容都是我干的异雁。 我是一名探鬼主播捶障,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼纲刀!你這毒婦竟也來了项炼?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤示绊,失蹤者是張志新(化名)和其女友劉穎锭部,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體面褐,經(jīng)...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡拌禾,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了展哭。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片湃窍。...
    茶點故事閱讀 40,040評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖匪傍,靈堂內的尸體忽然破棺而出您市,到底是詐尸還是另有隱情,我是刑警寧澤役衡,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布茵休,位于F島的核電站,受9級特大地震影響手蝎,放射性物質發(fā)生泄漏榕莺。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一棵介、第九天 我趴在偏房一處隱蔽的房頂上張望钉鸯。 院中可真熱鬧,春花似錦邮辽、人聲如沸亏拉。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至莽使,卻和暖如春锐极,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背芳肌。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工灵再, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留肋层,地道東北人。 一個月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓翎迁,卻偏偏與公主長得像栋猖,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子汪榔,可洞房花燭夜當晚...
    茶點故事閱讀 44,979評論 2 355

推薦閱讀更多精彩內容