Discord统计机器人MySQL更新异常:始终插入新行而非更新
我来帮你排查这个问题!你遇到的核心问题是机器人查询不到应该更新的行,所以每次都走INSERT逻辑,而手动查询能正常找到目标行,这大概率是时区不匹配或者SQL逻辑里的小疏漏导致的,我们一步步来解决:
1. 最可能的元凶:数据库与机器人的时区不一致
当你的机器人用NOW()插入数据时,这个时间是机器人运行环境的系统时区;而数据库执行DATE_SUB(NOW(), INTERVAL 5 MINUTE)时,用的是数据库自身的时区。如果两者时区不同(比如机器人在UTC+8,数据库在UTC),就会导致插入的date不在查询的5分钟时间窗口内,自然查不到已有行,只能反复插入新行。
验证方法:
- 在机器人代码里加一行打印当前时间:
console.log(new Date().toString()) - 在phpMyAdmin里执行
SELECT NOW(); - 对比两个时间是否一致,如果差了几个小时,就说明时区不匹配。
解决方法:
- 统一两者的时区,比如把数据库时区设置为和机器人一致:
在MySQL执行:SET GLOBAL time_zone = '+8:00';(根据你的实际时区调整),或者在连接数据库时指定时区(比如用mysql2库的话,连接选项里加timezone: 'Z'或对应时区) - 或者在代码里统一使用UTC时间,避免时区差异:
// INSERT时用UTC_TIMESTAMP() sql = `INSERT INTO channel_stats (channel_id, date, channel_name, channel_message_count) VALUES ('${message.channel.id}', UTC_TIMESTAMP(), '${message.channel.name}', 1)`; // 查询时也用UTC_TIMESTAMP() connection.query(`SELECT * FROM channel_stats WHERE channel_id = '${message.channel.id}' AND date BETWEEN DATE_SUB(UTC_TIMESTAMP(), INTERVAL 5 MINUTE) AND UTC_TIMESTAMP()`, ...)
2. 修复UPDATE语句的逻辑漏洞
即使解决了时区问题,你当前的UPDATE语句也有隐患:它只根据channel_id更新,会把该频道所有历史行的channel_message_count都加1,而不是只更新近5分钟的那一行。你需要在UPDATE的WHERE条件里加上时间范围:
sql = `UPDATE channel_stats SET channel_message_count = ${channel_message_count + 1}, channel_name = '${message.channel.name}' WHERE channel_id = '${message.channel.id}' AND date BETWEEN DATE_SUB(NOW(), INTERVAL 5 MINUTE) AND NOW()`;
3. 必须修复的SQL注入风险
你当前的代码直接把用户可控内容(比如message.channel.name、message.channel.id)拼接到SQL语句里,存在严重的SQL注入风险,可能导致数据库被攻击。建议改用参数化查询,以mysql库为例:
// 查询时用参数化 connection.query( 'SELECT * FROM channel_stats WHERE channel_id = ? AND date BETWEEN DATE_SUB(NOW(), INTERVAL 5 MINUTE) AND NOW()', [message.channel.id], (err, rows) => { if(err) throw err; let sql; if(rows.length < 1) { // INSERT参数化 sql = 'INSERT INTO channel_stats (channel_id, date, channel_name, channel_message_count) VALUES (?, NOW(), ?, 1)'; connection.query(sql, [message.channel.id, message.channel.name]); } else { // UPDATE参数化 let newCount = rows[0].channel_message_count + 1; sql = 'UPDATE channel_stats SET channel_message_count = ?, channel_name = ? WHERE channel_id = ? AND date BETWEEN DATE_SUB(NOW(), INTERVAL 5 MINUTE) AND NOW()'; connection.query(sql, [newCount, message.channel.name, message.channel.id]); } } );
这样既安全,又能避免字符串拼接带来的语法错误(比如频道名里有单引号的情况)。
额外优化:固定5分钟时间窗口
当前的“近5分钟”窗口是随时间移动的,如果你想统计固定的5分钟块(比如0-5分、5-10分这类整点窗口),可以用时间戳生成固定窗口的起始时间,逻辑更规范:
// 查询固定5分钟窗口的行 connection.query( 'SELECT * FROM channel_stats WHERE channel_id = ? AND date = FROM_UNIXTIME(FLOOR(UNIX_TIMESTAMP(NOW())/300)*300)', [message.channel.id], (err, rows) => { // INSERT时也用FROM_UNIXTIME(FLOOR(UNIX_TIMESTAMP(NOW())/300)*300)作为date值 } );
内容的提问来源于stack exchange,提问作者Red Spider
相关产品推荐
相关产品推荐

