状态标记(业务锁)实现方案
<h1 id="单据数据污染解决方案-状态标记业务锁">单据数据污染解决方案-<strong>状态标记(业务锁)</strong></h1><p>单据数据污染解决方案-<strong>状态标记(业务锁)</strong></p>
<p>感觉本篇对你有帮助可以关注一下我的<mark>微信公众号(深入浅出谈java)</mark>,会不定期更新知识和面试资料、技巧!!!</p>
<p><img src="https://img2024.cnblogs.com/blog/2719585/202504/2719585-20250424145859162-898697358.png" alt="" loading="lazy"></p>
<p>如何防止单据数据被多人操作,造成数据污染?</p>
<p>确保同一时间只有一个用户可以编辑单据,或者至少能检测到并发修改,避免数据冲突。比如当用户尝试编辑一个被锁定的单据时,提示“当前单据正被其他用户编辑”。同时,在服务端处理并发请求时,确保更新状态的原子性,避免竞态条件。</p>
<p>这时候,我应该考虑常见的并发控制方法。比如乐观锁和悲观锁,这两种方法在开发中经常用到。</p>
<p>本文主要针对的是 <strong>状态标记(业务锁)</strong></p>
<h2 id="解决方案">解决方案</h2>
<p>常见的解决方案包括:</p>
<ol>
<li>乐观锁:通过版本号或时间戳检测冲突。</li>
<li>悲观锁:在事务中使用行级锁。</li>
<li>状态标记:结合超时机制防止死锁。</li>
<li>前端提示和实时检测。</li>
<li>操作日志和版本比对。</li>
<li>分布式锁应对多实例环境。</li>
<li>权限控制减少冲突可能性。</li>
</ol>
<p>需要根据具体场景选择合适的方案,或者组合使用多种方法。例如,使用乐观锁处理大部分情况,结合前端提示和状态标记来提升用户体验。或者在高并发场景下使用悲观锁,同时设置合理的超时时间。</p>
<p>其他解决办法可阅读:单据污染解决方案 - 古渡蓝按 - 博客园</p>
<h2 id="流程图">流程图</h2>
<p><img src="https://img2024.cnblogs.com/blog/2719585/202504/2719585-20250428101722026-2115739352.png" alt="" loading="lazy"></p>
<h2 id="核心逻辑">核心逻辑</h2>
<p>状态标记。在单据表中增加一个状态字段,比如“编辑中”,当用户开始编辑时,将状态设为不可编辑,保存后再恢复。这种方法需要前端和后端配合,比如用户点击编辑时,后端检查状态是否为可编辑,如果是,就设为编辑中,防止其他人编辑。但可能存在用户忘记保存或者异常退出的情况,导致状态一直处于编辑中,所以可能需要一个超时机制,自动释放状态。</p>
<h3 id="状态标记业务锁核心逻辑"><strong>状态标记(业务锁)核心逻辑</strong></h3>
<p>通过业务字段标记单据的编辑状态,确保同一时间只有一个用户能操作单据。<br>
<strong>关键点</strong>:</p>
<ol>
<li>使用数据库字段(如 <code>status</code>)标记单据是否被锁定。</li>
<li>用户尝试编辑时,检查并抢占锁;编辑完成后释放锁。</li>
<li><strong>超时机制</strong>:防止用户未提交导致锁永久占用。</li>
</ol>
<h3 id="具体实现步骤"><strong>具体实现步骤</strong></h3>
<h4 id="1-数据库设计"><strong>1. 数据库设计</strong></h4>
<p>例如:在单据表中添加字段</p>
<table>
<thead>
<tr>
<th style="text-align: left">字段名</th>
<th style="text-align: left">类型</th>
<th style="text-align: left">说明</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: left"><code>status</code></td>
<td style="text-align: left">int</td>
<td style="text-align: left">0-可编辑,1-编辑中</td>
</tr>
<tr>
<td style="text-align: left"><code>lock_by</code></td>
<td style="text-align: left">varchar</td>
<td style="text-align: left">锁定者(用户ID)</td>
</tr>
<tr>
<td style="text-align: left"><code>lock_time</code></td>
<td style="text-align: left">datetime</td>
<td style="text-align: left">锁定时间(用于超时释放)</td>
</tr>
</tbody>
</table>
<hr>
<h4 id="2-用户操作流程"><strong>2. 用户操作流程</strong></h4>
<table>
<thead>
<tr>
<th style="text-align: left">步骤</th>
<th style="text-align: left">用户行为</th>
<th style="text-align: left">前端</th>
<th style="text-align: left">服务端逻辑</th>
<th style="text-align: left">数据库操作</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: left">1</td>
<td style="text-align: left">用户A点击“编辑”按钮</td>
<td style="text-align: left">发送<code>/lock</code>请求,携带单据ID</td>
<td style="text-align: left">检查当前单据状态: 若<code>status=0</code>,更新为<code>status=1</code>,记录<code>lock_by</code>和<code>lock_time</code></td>
<td style="text-align: left"><code>UPDATE table SET status=1, lock_by='A', lock_time=NOW() WHERE id=123 AND status=0</code></td>
</tr>
<tr>
<td style="text-align: left">2</td>
<td style="text-align: left">用户A编辑表单</td>
<td style="text-align: left">表单展示,禁用其他用户操作入口</td>
<td style="text-align: left">保持锁定状态</td>
<td style="text-align: left">无</td>
</tr>
<tr>
<td style="text-align: left">3</td>
<td style="text-align: left">用户A提交保存</td>
<td style="text-align: left">发送保存请求,携带修改数据</td>
<td style="text-align: left">更新单据数据,并释放锁(<code>status=0</code>)</td>
<td style="text-align: left"><code>UPDATE table SET ..., status=0 WHERE id=123 AND lock_by='A'</code></td>
</tr>
<tr>
<td style="text-align: left">4</td>
<td style="text-align: left">用户B尝试编辑同一单据</td>
<td style="text-align: left">发送<code>/lock</code>请求</td>
<td style="text-align: left">检查状态:<code>status=1</code>,返回“单据已</td>
<td style="text-align: left"></td>
</tr>
</tbody>
</table>
<hr>
<h4 id="超时释放锁"><strong>超时释放锁</strong></h4>
<ul>
<li>
<p><strong>后台定时任务</strong>:每隔N分钟扫描<code>lock_time</code>超时的单据,自动释放锁:</p>
<pre><code class="language-sql">UPDATE table SET status=0
WHERE status=1 AND lock_time < NOW() - INTERVAL '5 MINUTE';
</code></pre>
</li>
</ul>
<h2 id="伪代码">伪代码</h2>
<h4 id="1-服务端锁定逻辑伪代码"><strong>1. 服务端锁定逻辑(伪代码)</strong></h4>
<pre><code class="language-python">def lock_order(order_id, user_id):
# 原子操作:尝试锁定
updated = execute_sql(
"UPDATE orders SET status=1, lock_by=%s, lock_time=NOW() "
"WHERE id=%s AND status=0",
)
if updated == 0:
# 获取当前锁定者信息
locked_by = execute_sql(
"SELECT lock_by FROM orders WHERE id=%s",
)
return {"success": False, "message": f"单据已被{locked_by}占用"}
return {"success": True}
</code></pre>
<h4 id="2-前端提示逻辑伪代码"><strong>2. 前端提示逻辑(伪代码)</strong></h4>
<pre><code class="language-javascript">// 用户点击编辑按钮
async function handleEdit(orderId) {
const res = await fetch('/lock', { method: 'POST', body: { orderId } });
if (!res.success) {
alert(res.message); // 如“单据已被张三锁定”
} else {
// 进入编辑模式
}
}
</code></pre>
<h2 id="注意事项"><strong>注意事项</strong></h2>
<ol>
<li><strong>原子性</strong>:锁定操作需通过单条SQL保证原子性(避免竞态条件)。</li>
<li><strong>锁释放</strong>:
<ul>
<li>用户提交后必须释放锁。</li>
<li>异常场景(如用户关闭页面)依赖后台超时释放。</li>
</ul>
</li>
<li><strong>用户体验</strong>:
<ul>
<li>前端可轮询检查锁状态(如每30秒查询一次)。</li>
<li>提示具体锁定者(如“单据正被张三编辑”)。</li>
</ul>
</li>
</ol>
<p>通过状态标记机制,可低成本实现多人操作冲突控制,适合中小型系统或业务流程明确的场景。</p>
<p>最后文章有啥不对,欢迎大佬在评论区指点!!!<br>
如果感觉对你有帮助就点赞推荐或者关注一下吧!!!<br>
<img src="https://img2024.cnblogs.com/blog/2719585/202409/2719585-20240927091023464-1188976011.gif" alt="img" loading="lazy"></p><br><br>
来源:https://www.cnblogs.com/blbl-blog/p/18851084
頁:
[1]