将 MySQL While 循环从 PHP 转换为 NodeJS?

Posted

技术标签:

【中文标题】将 MySQL While 循环从 PHP 转换为 NodeJS?【英文标题】:Convert MySQL While Loop from PHP to NodeJS? 【发布时间】:2021-08-29 14:53:56 【问题描述】:

我正在将我的 php/mysql 代码转换为 NodeJS。但是我被困在 While 循环中,不知道在哪里返回数据。这是代码;

var mysql = require("mysql");
var express = require("express");
var jwt = require("jsonwebtoken");
var config = require("../config");
var connection = require("../database");

var getFCM = function (req, res) 
    const bodyuserid = req.body.id;

    var query = "SELECT ?? FROM ?? WHERE userid = ? ";
    var table = ["audioid", "subscribe", bodyuserid];
    query = mysql.format(query, table);

    connection.query(query, function (err, rows) 
        if (err) 
            res.json( Error: true, Message: "Error executing MySQL query" );
         else 
            var n = 0;
            var thislength = rows.length;
            var newaudioid = rows[n].audioid;

            var query = `SELECT ap.audioid, ap.title, us.name FROM audioposts ap INNER JOIN audioposts a2 ON a2.audioid = ap.opid INNER JOIN accounts us ON us.id = a2.userid WHERE ap.opid = ? AND ap.opid <> 0`;
            var table = [newaudioid];
            query = mysql.format(query, table);

            while (n < thislength) 
                connection.query(query, function (err, data) 
                    if (err) 
                        res.json( Error: true, Message: "Error executing MySQL query" );
                     else 
                        console.log(data);
                        res.json(data);
                    
                );
                n++;
            
        
    );
;
module.exports = getFCM;

所以第一个查询从某个useridsubscribe表中选择所有audioids。

下一个查询从第一个查询中获取结果 audioid,并从 audiopost 表中获取所有帖子,其中每个结果 audioid 都是原始帖子 ID (opid)。

代码几乎可以运行,但没有为第二个查询的每个结果返回所需的标题、名称和音频。相反,它只返回一个结果。即;

[
  
    "audioid": 147,
    "title": "Goat Heaven",
    "name": "Josie Jones"
  
]

如何让循环返回正确的 JSON 数据?

这是 NodeJS 控制台中的错误消息;

[
  RowDataPacket 
    audioid: 147,
    title: 'Goat Heaven',
    name: 'Josie Jones'
  
]
/opt/bitnami/apache/htdocs/Node-MySQL-JWT-Signup-Login-API/node_modules/mysql/li                                             b/protocol/Parser.js:437
      throw err; // Rethrow non-MySQL errors
      ^

Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the cli                                             ent
    at ServerResponse.setHeader (_http_outgoing.js:561:11)
    at ServerResponse.header (/opt/bitnami/apache/htdocs/Node-MySQL-JWT-Signup-L                                             ogin-API/node_modules/express/lib/response.js:771:10)
    at ServerResponse.send (/opt/bitnami/apache/htdocs/Node-MySQL-JWT-Signup-Log                                             in-API/node_modules/express/lib/response.js:170:12)
    at ServerResponse.json (/opt/bitnami/apache/htdocs/Node-MySQL-JWT-Signup-Log                                             in-API/node_modules/express/lib/response.js:267:15)
    at Query.<anonymous> (/opt/bitnami/apache/htdocs/Node-MySQL-JWT-Signup-Login                                             -API/middleware/getFCM.js:34:10)
    at Query.<anonymous> (/opt/bitnami/apache/htdocs/Node-MySQL-JWT-Signup-Login                                             -API/node_modules/mysql/lib/Connection.js:526:10)
    at Query._callback (/opt/bitnami/apache/htdocs/Node-MySQL-JWT-Signup-Login-A                                             PI/node_modules/mysql/lib/Connection.js:488:16)
    at Query.Sequence.end (/opt/bitnami/apache/htdocs/Node-MySQL-JWT-Signup-Logi                                             n-API/node_modules/mysql/lib/protocol/sequences/Sequence.js:83:24)
    at Query._handleFinalResultPacket (/opt/bitnami/apache/htdocs/Node-MySQL-JWT                                             -Signup-Login-API/node_modules/mysql/lib/protocol/sequences/Query.js:149:8)
    at Query.EofPacket (/opt/bitnami/apache/htdocs/Node-MySQL-JWT-Signup-Login-A                                             PI/node_modules/mysql/lib/protocol/sequences/Query.js:133:8) 
  code: 'ERR_HTTP_HEADERS_SENT'

[nodemon] app crashed - waiting for file changes before starting...

【问题讨论】:

陷入这样的循环,从客户端代码对数据库进行大量单独调用,保证会降低性能。更糟糕的是,这是一个网络应用程序,它显然是。 你会建议什么? 好吧,如果可能的话,我建议同时运行多个查询。 MySQL 的语法使得插入等操作非常容易。对于选择,您只需用分号分隔语句,您将收到多个结果集。当然,如果您只是将选择标准替换为 SQL 字符串,则存在 SQL 注入攻击的风险。有一些方法可以使用准备好的语句来做到这一点,但我实际上并没有从 node.js 做到这一点,所以我不能 100% 确定实用性。一般原则是每次拨打电话都会有很大的开销...... ...到数据库。在许多情况下,调用的开销可能大于执行查询本身的开销。因此,如果您陷入循环并一遍又一遍地执行此操作,那么您将增加大量的性能抢夺开销。最好在客户端聚合查询并将它们作为一批发送。然而;我也很实用。如果这是几个查询,而不是数千个,我不会太担心。这种循环并不少见,因为这是最容易做的事情。您在这里遇到的基本问题是每个 HTTP 请求只能调用 res.json() 一次。 【参考方案1】:

这里似乎while 循环尝试发送响应两次,这是不可能的,要么您需要将查询数据存储到另一个变量,然后在所有处理完成后发送res.json();

下面是简单的 sn-p 抛出异常的地方。

app.post('/api', async (req, res) => 
    let n=0
    while(n<2)
        res.json(msg:"Recevied")
        n++
    

);

可以用下面的sn-p修复:

app.post('/api', async (req, res) => 
    let n = 0
    while (n < 2) 
        n++
    
    res.json( msg: "Recevied" + n )

);

【讨论】:

【参考方案2】:

您应该将 MySQL 的结果存储到数组中,并在循环后返回所有数据:

var getFCM = function (req, res) 
    const bodyuserid = req.body.id;

    var query = "SELECT ?? FROM ?? WHERE userid = ? ";
    var table = ["audioid", "subscribe", bodyuserid];
    query = mysql.format(query, table);

    connection.query(query, function (err, rows) 
        if (err) 
            res.json( Error: true, Message: "Error executing MySQL query" );
         else 
            var n = 0;
            var thislength = rows.length;
            var newaudioid = rows[n].audioid;

            var query = `SELECT ap.audioid, ap.title, us.name FROM audioposts ap INNER JOIN audioposts a2 ON a2.audioid = ap.opid INNER JOIN accounts us ON us.id = a2.userid WHERE ap.opid = ? AND ap.opid <> 0`;
            var table = [newaudioid];
            query = mysql.format(query, table);
           
            var result = []; // init result array

            while (n < thislength) 
                connection.query(query, function (err, data) 
                    if (err) 
                        res.json( Error: true, Message: "Error executing MySQL query" );
                     else 
                        console.log(data);

                        result.push(data); // push data
                    
                );
                n++;
            
            res.json(result); // response fetched data
        
    );
;
module.exports = getFCM;

【讨论】:

该代码出现此错误; ReferenceError: data is not defined at Query. (/opt/bitnami/apache/htdocs/Node-MySQL-JWT-Signup-Login-API/middleware/getFCM.js:40:22) 。 40:22 是 res.json(data); 我也试过这个 - res.json(result);但是只有这个 [] 空的 Json 回来了 这几乎奏效了,但我得到一个空的 [] 作为 res.json(result); 尝试将let result = [];改为var result = []; 我在 var result = [] 时遇到了一个奇怪的错误。错误:监听 EADDRINUSE:地址已在使用 :::3000 所以我将位置移动到 var 查询和表的正上方。这停止了​​错误,但结果再次为 []【参考方案3】:

res.json() 每次请求只能调用一次,因为它一次性发送所有标头和数据,没有缓冲。再次调用它会产生异常。您在 while 循环中调用 res.json(),所以它第一次成功执行,然后第二次抛出异常。

您可以组合多个 sql 语句,并在一次调用中将它们全部发送到 MySQL。我更喜欢这种方法,因为它的性能更高。如果您不了解并且不采取措施防止它,您将创建的最严重的问题是,这可能会使您面临 SQL 注入攻击。您可以使用准备好的语句(我的 real 偏好)来做到这一点,但这需要更多的工作,而且并非所有数据库客户端在这方面都具有同等能力。

但是;因为每次通过循环循环并调用 MySQL 是很常见的——这是在这种情况下编写的最简单的代码——防止在此处引发异常的最快解决方法是附加 JSON结果到一个数组中,然后在过程结束时通过一次调用res.json(result) 序列化整个数组。

如果在代码功能正确后出现性能问题,请寻找解决方案以批量发送所有查询。

我所说的一般原则是每次对数据库进行往返调用时都会产生大量通信开销。往返通信开销可能比查询本身的执行更重要。每个数据库服务器都是如此,MySQL 也不例外。事实上,不仅对数据库的调用如此,对文件系统的调用也是如此。你有没有注意到如果你复制 1000 个小文件比复制一个大小相同或大于小文件总和的大文件需要更长的时间?所有这些损失的性能都将用于打开和关闭文件句柄以及以小块移动数据,而这些数据并未充分利用 IO 子系统。此外,不仅每次调用的开销很大,而且在循环运行的整个过程中,您都需要打开一个稀缺/昂贵的数据库连接。这可以防止任何其他 HTTP 请求能够利用该连接。循环运行的时间将比最佳状态更长,因为您正在等待数据库响应每个单独的调用。如果您要打四五个电话,那将不是什么大问题。如果你做数千个,这将成为一个非常大的问题。

【讨论】:

我考虑合并多个 mysql 语句,但我需要第一个查询的结果才能形成第二个查询。你能建议一种方法吗? 咦,记录之间是什么关系?您确定不能使用适当的连接条件通过单个 sql 语句检索它们吗?我曾经剖析过一个巨大的 PL/SQL (Oracle) 存储过程,它有两个嵌套游标在循环中遍历,根据从外部循环中的游标检索到的值在内部循环中进行单独的查找。它运行了数百万行,并且运行了至少一个半小时(我有点模糊,这是很久以前的事了)。 使用它的报表永远不能在工作时间运行。我实际上使用了三种颜色的荧光笔来标记存储过程的打印输出,该存储过程持续了大约 12 页。一旦我确定我理解了它在做什么并且我没有遗漏任何东西,我就能够用一个带有几个表连接的适度复杂的 sql 查询来替换它,调整索引,它在两秒钟内运行。 【参考方案4】:

我建议使用 sequelize(https://sequelize.org/) 或 knex(https://knexjs.org & https://vincit.github.io/objection.js/) 之类的 ORM,而不是使用会导致 SQL 注入的自定义 SQL 查询。

当然,有一点学习曲线,但这就是你成为一名优秀程序员的方式。

【讨论】:

我的理解是 sequelize 不适合复杂的查询? 如果您想在特定情况下使用它还支持原始查询 没有什么比在网站上调用 ORM 的所有开销来降低性能更好的了。 ;-)

以上是关于将 MySQL While 循环从 PHP 转换为 NodeJS?的主要内容,如果未能解决你的问题,请参考以下文章

PHP / MySQL:如何使用WHILE循环插入数据库[重复]

PHP / MYSQL:如何在while循环中循环来自查询的匹配结果

从 PHP 将 textarea 元素插入 MySQL

在 PHP While 循环中使用 MySQL JOIN 而不是查询

在Java 8中将while循环转换为流

从 php While 循环中调用的 jQuery 脚本无法正常工作