使用JavaScript Fetch+MySQL后端时,如何避免SQL重复插入?
解决双击提交导致重复数据插入的方案
前端层面(即时阻止重复请求)
1. 提交时禁用按钮
点击提交后立刻禁用按钮,直到请求完成(成功/失败)再恢复,从交互层面直接阻止重复点击:
const submitBtn = document.getElementById('submit-btn'); const form = document.getElementById('data-form'); form.addEventListener('submit', async (e) => { e.preventDefault(); // 禁用按钮并修改提示文本 submitBtn.disabled = true; submitBtn.textContent = '提交中...'; const formData = new FormData(form); try { await fetch('/api/submit', { method: 'POST', body: formData }); alert('提交成功'); } catch (err) { console.error('提交失败:', err); alert('提交失败,请重试'); } finally { // 请求结束后恢复按钮状态 submitBtn.disabled = false; submitBtn.textContent = '提交'; } });
2. 使用状态锁防止重复触发
用变量标记当前是否处于提交状态,避免同一时间发起多个请求:
let isSubmitting = false; const form = document.getElementById('data-form'); form.addEventListener('submit', async (e) => { e.preventDefault(); if (isSubmitting) return; // 正在提交则直接终止 isSubmitting = true; const formData = new FormData(form); try { await fetch('/api/submit', { method: 'POST', body: formData }); alert('提交成功'); } catch (err) { console.error(err); alert('提交失败'); } finally { isSubmitting = false; } });
后端层面(兜底防重复插入)
前端限制可能被绕过(比如手动修改DOM、直接调用接口),必须在后端做兜底校验:
1. 数据库添加唯一索引
根据业务逻辑,给表添加唯一约束,比如针对用户ID+提交内容的组合创建唯一索引:
-- 示例:给表单提交表添加唯一索引,防止同一用户重复提交相同内容 ALTER TABLE form_submissions ADD UNIQUE KEY idx_user_content (user_id, MD5(content));
后端插入时捕获唯一索引冲突错误,直接返回提示:
// Express 接口示例 app.post('/api/submit', async (req, res) => { const { userId, content } = req.body; try { await db.query( 'INSERT INTO form_submissions (user_id, content) VALUES (?, ?)', [userId, content] ); res.status(200).json({ success: true, msg: '提交成功' }); } catch (err) { // 捕获MySQL唯一索引冲突错误(错误码1062) if (err.code === 'ER_DUP_ENTRY') { return res.status(400).json({ success: false, msg: '请勿重复提交' }); } res.status(500).json({ success: false, msg: '服务器错误' }); } });
2. 接口幂等性设计
前端提交时生成唯一requestId(比如UUID),后端先检查该ID是否已处理,未处理则执行插入并记录ID:
// 前端生成requestId const requestId = crypto.randomUUID(); // 浏览器支持crypto API,或用第三方库生成 // 后端接口处理 app.post('/api/submit', async (req, res) => { const { requestId, userId, content } = req.body; // 检查requestId是否已存在(可存在Redis或专门的幂等记录表) const exists = await db.query('SELECT id FROM request_records WHERE request_id = ?', [requestId]); if (exists.length > 0) { return res.status(200).json({ success: true, msg: '已提交' }); } try { // 开启事务,同时插入提交记录和幂等记录 await db.beginTransaction(); await db.query('INSERT INTO form_submissions (user_id, content) VALUES (?, ?)', [userId, content]); await db.query('INSERT INTO request_records (request_id) VALUES (?)', [requestId]); await db.commit(); res.status(200).json({ success: true, msg: '提交成功' }); } catch (err) { await db.rollback(); if (err.code === 'ER_DUP_ENTRY') { return res.status(400).json({ success: false, msg: '请勿重复提交' }); } res.status(500).json({ success: false, msg: '服务器错误' }); } });
3. 事务加排他锁
通过数据库事务加排他锁,确保同一时间只有一个请求能处理相同提交:
app.post('/api/submit', async (req, res) => { const { userId, content } = req.body; try { await db.beginTransaction(); // 查询时加排他锁(FOR UPDATE),锁定当前查询结果 const existing = await db.query( 'SELECT id FROM form_submissions WHERE user_id = ? AND content = ? FOR UPDATE', [userId, content] ); if (existing.length > 0) { await db.rollback(); return res.status(400).json({ success: false, msg: '请勿重复提交' }); } // 插入新记录 await db.query('INSERT INTO form_submissions (user_id, content) VALUES (?, ?)', [userId, content]); await db.commit(); res.status(200).json({ success: true, msg: '提交成功' }); } catch (err) { await db.rollback(); res.status(500).json({ success: false, msg: '服务器错误' }); } });
内容的提问来源于stack exchange,提问作者Adeniyi Abraham
相关产品推荐
相关产品推荐

