乐观锁与悲观锁从原理到实战:并发控制、版本号、死锁避坑指南
作为开发人员我几乎每天都在跟并发打交道。而聊到并发控制乐观锁和悲观锁这对概念怎么都绕不过去。网上讲原理的文章很多但大多停留在“乐观锁用版本号悲观锁用select for update”这种口号层面真到了线上环境锁等待超时、死锁、ABA问题、库存扣成负数这些坑往往才是让人头疼的地方。这篇文章我就结合自己实际踩过的坑把这两种锁从原理到实战彻底讲透。我不打算给你堆砌教科书式的定义而是从一个个真实场景出发看看什么时候该用悲观锁什么时候该用乐观锁以及用了之后会付出什么代价、会踩什么雷。这篇文章适合正在做业务开发的Java/Python工程师也适合刚接触分布式系统、想搞懂并发控制底层逻辑的同学。不管你是准备面试还是正在调线上代码相信都能从中找到直接能用的东西。1. 先从一次库存超卖事故说起1.1 当时线上发生了什么有次我做一个秒杀类活动成交峰值大概每秒三千笔订单。当时数据库是MySQL 5.7InnoDB引擎。由于业务比较简单我最初写扣减库存的逻辑是// 伪代码示意 int stock selectStock(goodsId); if (stock 0) { int newStock stock - 1; updateStock(goodsId, newStock); // 生成订单 }这段代码在并发量低的时候一点问题都没有。但活动一上线事故就来了后台统计发现实际成交订单数比初始库存多出了几百单。原因很好理解两个请求同时查到库存为1都判断大于0都执行扣减最后库存变成了-1但订单却都生成了。这就是典型的并发读写冲突。当时我的第一反应是加一把Java层的synchronized锁但马上发现不行服务是集群部署的每台机器各自的synchronized锁不住其他机器上的请求。于是就开始研究数据库层面的锁方案也就是本文要聊的乐观锁和悲观锁。1.2 并发冲突的本质要真正理解这两种锁得先搞清楚冲突的本质。数据库并发操作主要有三类问题脏读一个事务读到了另一个事务未提交的数据。比如事务A把库存改成0但还没提交事务B读到的库存就是0然后A回滚了B基于错误数据继续操作。不可重复读同一事务中两次读取同一数据结果不一致。因为期间有其他事务修改了数据并提交。幻读同一事务中同一查询条件两次执行结果集数量不一样。因为有其他事务插入了新记录。乐观锁和悲观锁本质上都是为了避免“更新丢失”这个问题也就是两个人同时改同一个数据结果其中一个修改被覆盖掉。但两者解决问题的思路截然相反悲观锁默认别人一定会来抢所以先锁死乐观锁默认别人大概率不来抢只在最后写的时候再确认有没有被改过。2. 悲观锁先锁死再操作2.1 悲观锁核心思想与原理悲观锁的英文叫Pessimistic Lock它的核心预设是只要我修改数据就极有可能有其他事务来竞争所以我要在修改之前先把这条数据锁住让其他人只能等我干完。这种锁实现依赖数据库本身的锁机制。在MySQL InnoDB中最常用的就是-- 在事务中查询时直接加排他锁X锁 SELECT * FROM goods WHERE id 1001 FOR UPDATE;这条语句执行后事务T1就对goods表中id1001的这条记录加上了排他锁。其他事务想再对这个记录执行SELECT ... FOR UPDATE、UPDATE、DELETE都会被阻塞直到T1提交或回滚。需要注意普通SELECT语句默认不加任何锁走的是MVCC快照读。所以SELECT * FROM goods WHERE id 1001这种是读不到锁的只有显式加了FOR UPDATE或其他锁子句才会真正触发行锁机制。2.2 行锁、表锁与间隙锁很多人以为悲观锁就是简单的行锁其实这里面的细节挺多行锁InnoDB默认加锁单位。它锁的是索引记录而不是整行数据。如果查询条件走了主键索引或唯一索引InnoDB能精确锁住对应记录。表锁如果查询条件没有索引InnoDB会做全表扫描这种情况下行锁会升级为表锁整张表都被锁住并发能力断崖式下降。间隙锁在REPEATABLE READ隔离级别下InnoDB为了避免幻读还会给索引记录之间的区间加锁。比如查询WHERE stock 10可能锁住多个区间导致其他事务连插入都插不进去。这里有三个非常容易被忽略的实操要点第一个要点不加索引的FOR UPDATE代价是毁灭性的。我见过一个团队给库存表加条件时用了普通字段但没建索引结果秒杀开始后整个表的更新操作全被堵死数据库连接瞬间被打满。第二个要点悲观锁必须在事务中才有效。如果事务没有包裹住查询和更新执行完SELECT ... FOR UPDATE后连接被释放、事务自动提交了锁也就没了后面的操作等于裸奔。第三个要点锁的粒度要尽量精确。能用主键锁就绝不锁整表能用单条记录锁就绝不锁区间。2.3 悲观锁加锁实战含SQL与事务配置我现在整理一套比较规整的悲观锁扣库存写法你可以直接参考-- 1. 开启事务 START TRANSACTION; -- 2. 对目标商品行加排他锁 SELECT id, stock, version FROM goods WHERE id 1001 FOR UPDATE; -- 3. 在应用层判断库存是否充足读到的是锁定的最新值 -- 如果stock 1则ROLLBACK并返回失败 -- 4. 执行更新 UPDATE goods SET stock stock - 1 WHERE id 1001; -- 5. 插入订单信息等其他操作 INSERT INTO orders (goods_id, user_id, ...) VALUES (1001, 888, ...); -- 6. 提交 COMMIT;在代码层面如果用的是Spring框架事务注解要注意Transactional(rollbackFor Exception.class) public boolean createOrder(Long goodsId, Long userId) { // 强制走主库执行查询并加排他锁 Goods goods goodsMapper.selectByIdForUpdate(goodsId); if (goods.getStock() 1) { throw new BusinessException(库存不足); } goodsMapper.decreaseStock(goodsId); orderMapper.insert(...); return true; }这里有个隐藏很深的坑Transactional默认传播行为是REQUIRED如果方法被同类内部调用事务注解可能失效。比如在一个类中createOrder调用同类的另一个方法那个方法的事务是不生效的。解决办法是使用代理对象调用或把事务方法拆到单独的Bean中。另外事务的隔离级别也对锁行为有影响。在READ COMMITTED下FOR UPDATE还是会对记录加锁在REPEATABLE READ下还会额外涉及间隙锁。默认情况下MySQL InnoDB是REPEATABLE READ这一点在大批量范围更新时尤其要小心。2.4 悲观锁的代价与适用场景悲观锁的优势在于强一致性写操作被串行化永远不会发生更新丢失。它非常适合以下场景金融转账、余额扣减等资金类操作库存类高竞争热点数据的扣减对数据一致性要求極高宁愿牺牲吞吐量的业务但它也有非常明显的短板性能开销大获取锁、等待锁、释放锁都需要时间持续占用数据库连接容易拖垮数据库。死锁风险多个事务以不同顺序加锁时容易产生死锁需要数据库死锁检测机制介入。扩展性差分布式场景下数据库行锁无法跨库跨服务生效需要额外引入分布式锁。我现在的选型经验是对于同一个数据库实例、单条热点记录、并发量适中的业务悲观锁完全够用而且实现简单、不容易出错。但如果你预期单条记录有极高并发比如爆款商品秒杀或者服务已经做了水平拆分那悲观锁就不太合适了这时候要考虑乐观锁甚至引入额外的分布式锁组件。3. 乐观锁最后时刻核验版本3.1 乐观锁核心思想与原理乐观锁的英文叫Optimistic Lock它的核心预设完全相反默认冲突很少发生所以我不加锁而是等到要提交数据的那一刻检查一下这段时间里有没有其他人动过这条数据。如果没人动过我就提交如果被人动过我就放弃重来。最常见的实现方式有两种版本号机制表中加一个version字段每次更新时version version 1。更新前先查出当前version更新时在WHERE条件中带上这个version如果影响行数为0说明版本已被别人修改本次更新失败。时间戳机制用updated_at时间戳代替版本号原理相同但存在时间精度问题不建议在高并发场景使用。版本号方案核心SQL如下-- 查询阶段 SELECT id, stock, version FROM goods WHERE id 1001; -- 更新阶段关键在WHERE条件带version UPDATE goods SET stock stock - 1, version version 1 WHERE id 1001 AND version 5;如果执行后返回的影响行数为1说明更新成功如果影响行数为0说明在这期间有别人改过这条记录version已经不是5了需要重试或返回失败。3.2 为什么乐观锁能控制并发而不阻塞乐观锁没有使用数据库的锁机制所以不会阻塞其他事务。它的并发控制完全依赖UPDATE语句的原子性和影响行数。关键在于UPDATE ... WHERE version 当前查询到的版本这个操作在数据库层面是原子性的。多个事务同时执行这条UPDATE时InnoDB会通过内部的行锁机制先将记录锁住再执行更新此时只有一个事务能匹配到version 5并成功更新其他事务的WHERE条件不再满足影响行数为0。也就是说乐观锁实际上是把冲突检测推迟到了写入阶段。它的好处是读操作完全不加锁吞吐量高没有死锁问题坏处是一旦冲突率高大量请求会更新失败用户体验变差需要应用层配合重试机制。3.3 乐观锁扣库存代码示例含重试逻辑用MySQL乐观锁扣库存我在项目中一般这样写-- 第一遍查询 SELECT stock, version FROM goods WHERE id #{goodsId}; -- 业务校验stock 0 -- 扣减 UPDATE goods SET stock stock - 1, version version 1 WHERE id #{goodsId} AND version #{oldVersion};Java服务层代码带重试public boolean createOrderWithOptimisticLock(Long goodsId, Long userId) { int retryCount 0; int maxRetry 3; while (retryCount maxRetry) { // 1. 查询当前版本 Goods goods goodsMapper.selectById(goodsId); // 2. 业务校验库存 if (goods.getStock() 1) { return false; } // 3. 尝试乐观锁更新 int rows goodsMapper.decreaseStockByVersion(goodsId, goods.getStock() - 1, goods.getVersion()); if (rows 0) { // 更新成功继续创建订单 orderMapper.insert(...); return true; } // 4. 更新失败说明冲突了休眠一小段时间后重试 retryCount; Thread.sleep(20); // 20ms退避 } throw new BusinessException(系统繁忙请稍后重试); }对应Mapper XMLupdate iddecreaseStockByVersion UPDATE goods SET stock #{newStock}, version version 1 WHERE id #{goodsId} AND version #{version} /update有几个点值得注意重试次数不要设太高否则会放大无效请求最好配合快速失败机制把失败结果直接返回给用户由用户决定要不要重新发起。我一般设3次以内超过就返回“人多拥挤稍后再试”。Thread.sleep(20)不适合所有场景也可以改成小范围的随机退避避免所有失败请求同时重试导致又一轮集中冲突。3.4 乐观锁的隐藏炸弹ABA问题乐观锁有个非常经典的陷阱叫ABA问题。如果我用版本号机制基本能避免它。因为版本号每次都在递增A改完变成version2B改完变成version3后来的人拿version2去更新已经匹配不上了。但如果你用的是时间戳或容易回绕的数值字段做乐观锁就可能出现ABA。比如线程1读到数据Xversion1线程2把X改成Yversion2线程2又把Y改成Xversion3假设版本是时间戳可能又变回原来的值或者直接拿记录内容做CAS线程1再去更新发现数据内容没变就以为没人动过于是提交对于纯时间戳如果时间精度不够高比如秒级时间戳同一秒内的修改可能无法被检测到。所以我的建议是如果业务对一致性极度敏感直接使用int自增版本号字段version不要用时间戳。不过在纯Java内存领域比如AtomicStampedReferenceABA问题确实需要额外处理但在数据库乐观锁场景下版本号递增方式天然规避了ABA问题。3.5 乐观锁的适用场景与限制乐观锁适合以下场景读多写少数据竞争不激烈绝大多数时候写入都能成功日志记录、点赞计数、非核心统计字段更新跨服务调用无法依赖数据库行锁的分布式场景通过携带版本号做条件更新限制也比较明显冲突率高时性能急剧下降大量请求做无用更新浪费数据库IO还会让用户感知到失败要求操作具备幂等性重试机制必须在业务层面保证不会产生重复数据不适用于长事务如果一个事务持有数据很久期间别人改了数据最后提交大概率失败用户体验极差4. 两种锁的正面对比与选型决策4.1 核心维度对比表维度悲观锁乐观锁核心思路先加锁后操作先操作提交时校验常见实现SELECT ... FOR UPDATEversion字段 条件更新并发能力低写操作串行化高读写基本不阻塞死锁风险有无适用场景写冲突严重的强一致场景读多写少、冲突率低的场景对数据库依赖依赖数据库锁机制仅依赖UPDATE原子性跨服务能力弱数据库行锁只在本实例内强可以跨服务传递版本号实现复杂度简单直接需要处理重试与失败逻辑4.2 我做选型的决策思路这么多年的经验告诉我选型不是看哪个技术好而是看业务的核心矛盾在哪。选悲观锁的典型判断条件写操作占总操作比例高比如库存扣减、余额变动业务要求强一致一旦更新失败不能通过重试掩盖单条记录是热点但并发量可控比如QPS几百到几千数据库扛得住团队技术水平一般追求简单可维护选乐观锁的典型判断条件业务以读为主写操作很少发生冲突热点数据并发量极高数据库行锁会成为瓶颈已经做了服务化拆分无法依赖单库行锁业务能接受一定比例的请求失败并加入重试机制说实话二选一的做法本身就有局限。我在真实的库存扣减项目中最常用的是“分段锁乐观锁”的组合方案把库存拆成多个桶每个桶用一个独立的库存记录用乐观锁去扣减这样既保证了最终一致性又把单点锁竞争打散。后续如果并发再上量再引入分布式锁组件做补充。4.3 混合使用事务内先乐观后悲观还有一种实践思路值得分享先用乐观锁“探路”冲突了再降级到悲观锁。为什么这么做因为乐观锁虽然并发高但在高冲突下会导致大量重试。悲观锁虽然稳定但长期持锁会拖垮并发。折中方案是正常情况下走乐观锁用版本号条件更新如果一次更新失败同一个请求立即改用悲观锁方式SELECT ... FOR UPDATE重试悲观锁重试期间其他请求被阻塞住反而能降低后续冲突这个方案适合那种“平时读多写少但偶尔会有热点突发”的业务。不过实现复杂度比单独选一种要高需要仔细设计事务边界。我一般只在核心交易链路上才这么用普通业务不值得。5. 实战经验从案例分析到疑难排查5.1 案例一秒杀超卖问题最终解决实录回到开头那个秒杀超卖事故。我最终采用的是乐观锁版本号方案但针对秒杀场景做了几个关键优化第一把库存字段拆成多个行记录。比如原本库存100拆成10个桶每桶10个库存用一个bucket_id标识。请求进来时先根据用户ID或随机数路由到某个桶只对这个桶执行乐观锁扣减。这样单行的竞争压力直接降到原来的十分之一。第二用UPDATE的原子自减代替“先查后改”。UPDATE goods SET stock stock - 1, version version 1 WHERE id #{bucketId} AND stock 0;注意这里WHERE里带了stock 0条件。这不依赖版本号就能利用UPDATE的原子性直接避免超卖。之所以这样做是因为在MySQL中UPDATE语句执行时会对匹配的行加锁同一时刻只有一个事务能对这个桶执行UPDATE后续事务进入时会等待但等待结束后它会重新读当前stock值并判断WHERE条件如果stock已经是0影响行数就是0。这种写法和纯版本号方案稍有不同它把“校验库存是否大于0”下沉到了SQL条件里从根上杜绝了超卖。第三配合热点缓存。秒杀开始前把库存预热到Redis中做流量控制真正落库的扣减请求其实是经过筛选的少量请求。这样既减轻了数据库压力也降低了乐观锁的冲突概率。最终效果从最开始超卖几百单到上线后连续多场活动零超卖数据库行锁等待时长下降了90%以上。5.2 案例二一个死锁排查的全过程有一段时间我们的订单系统频繁报错“Deadlock found when trying to get lock; try restarting transaction”。拿到的死锁日志显示涉及两张表订单表和库存表。排查后发现原因代码中两个事务加锁的顺序不一致。事务A先锁订单表再锁库存表事务B先锁库存表再锁订单表。当A持有订单表锁后想拿库存锁B持有库存锁后想拿订单锁时就形成了互相等待。解法有两个方向。第一个是规范加锁顺序所有事务都按照“先订单后库存”或“先库存后订单”的统一顺序来操作。第二个是在UPDATE语句中尽量缩小锁定行范围避免大范围间隙锁。最终我选择给所有涉及库存、订单的SQL都加上统一排序规范并且对查询条件涉及的字段全部加了索引死锁问题从此基本消失。从这个案例中要记住一条铁律批量操作多表时所有事务对多张表的访问顺序必须一致这是避免死锁最有效的手段。5.3 常见问题与排查技巧速查表问题现象可能原因排查方式与解决方案悲观锁不生效数据还是超卖事务未开启或提交过早检查是否加了Transactional注解是否在同一个类内部调用导致事务失效查看SQL日志确认FOR UPDATE是否执行大量连接堆积数据库CPU飙升无索引导致行锁升级为表锁用EXPLAIN查看执行计划确认WHERE条件是否走索引给查询字段建索引乐观锁重试后仍失败冲突率过高业务热点集中缩减重试时间间隔或改成分段库存/Async任务削峰评估是否切换悲观锁或引入分布式锁更新影响行数为0版本号不匹配或并发已修改在失败日志中打印旧版本号、当前版本号、竞争次数统计冲突率死锁日志频繁多个事务加锁顺序不一致用SHOW ENGINE INNODB STATUS查看最近死锁日志统一加锁顺序缩小事务范围乐观锁发生时区/时间戳问题使用时间戳字段做版本控制换成自增int版本号或在写入时间上统一使用数据库时间5.4 隔离性与索引对锁行为的影响这里必须再展开说一下数据库隔离级别对锁的范围有直接影响。在MySQL默认的REPEATABLE READ隔离级别下InnoDB的FOR UPDATE锁定的不止是查到的行还可能包括区间间隙锁。如果你对某个范围的查询加悲观锁这个范围内的间隙也会被锁其他事务在这个间隙插入数据会被阻塞。这种情况在报表统计、批量更新操作中尤其常见。如果你不要求可重复读可以降级为READ COMMITTED此时间隙锁会消失只锁实际命中的行。但要注意降级隔离级别会引入不可重复读的问题需要业务层面接受。另外一个必须牢记的规则锁是基于索引的如果查询没有走索引InnoDB会回溯到主键聚簇索引并且由于全表扫描可能锁住全表所有记录。因此我每次给业务表建表时都会强制要求所有WHERE条件字段必须有合适的索引这不仅是查询性能问题更是锁粒度的生死线。5.5 悲观锁的加锁策略与索引选择技巧针对悲观锁分享一下我总结的加锁策略挑选方法单行主键查询直接用主键idInnoDB只锁定这一条记录其他记录完全不受影响。非主键唯一索引查询用唯一索引字段InnoDB会先锁唯一索引记录再锁对应的主键记录。虽然锁了两条索引记录但范围很小依然优选。范围查询如果是WHERE status pending这类范围条件即使有索引也可能锁住多个区间。此时除非业务必须我通常建议先查出候选主键再对主键精准加锁。无索引查询这是最差的情况会锁全表。我一般用监控SQL定期扫描业务表找出所有查询条件未走索引的慢SQL重点优化。在实际代码里我对FOR UPDATE的规范是SELECT * FROM goods WHERE id 1001 AND status 1 FOR UPDATE;尽量把查询范围收敛到最精确的单行并且带上业务状态过滤条件减少无效锁。5.6 关于重试、幂等与最终一致性的思考乐观锁天然带来一个副作用更新失败后需要重试。但重试的前提是操作具有幂等性。比如扣减库存如果重试三次不能扣三次。这个问题的解法是引入业务的唯一流水号在创建订单时先检查流水号是否已经消费过。我通常会在业务表加一个request_id或order_no唯一索引每次请求携带业务唯一标识。扣减库存前先插入一条操作流水重复请求会因唯一索引冲突而失败从而避免重试导致的数据重复。对于非核心数据比如点赞数、浏览量这种允许轻微不准确的字段我反而觉得没必要每次都用乐观锁较真。直接用UPDATE SET count count 1 WHERE id ?这种原子自增就可以了数据库自己就处理了并发不需要版本号。只有当“统计值必须精确”时才需要上乐观锁。6. 从数据库锁到分布式锁的演进6.1 为什么单库锁在分布式环境下不够用如果你的服务只部署一个实例、只连一个数据库那悲观锁和乐观锁完全够用。但一旦服务做集群化部署情况就变了多实例共享同一个数据库行锁是数据库层面的多个应用实例依然能通过FOR UPDATE协调悲观锁还可以用多实例连接不同数据库分库分表行锁只能管住本库的记录跨库资源无法用数据库锁协调读写分离架构写库可能有多套主从同步存在延迟锁的可见性会受影响到了这一步就需要引入分布式锁。常见实现有基于Redis的分布式锁和基于数据库的唯一约束/分布式事务协调器。但要注意分布式锁和乐观锁/悲观锁并不是一回事它们处于不同的维度。分布式锁关注的是跨进程互斥乐观锁/悲观锁关注的是数据版本控制与冲突避免。在实际项目中两者经常叠加使用。6.2 分布式场景下的乐观锁实践即使在分库分表场景下乐观锁依然具有极强的适应性。因为乐观锁不依赖数据库锁它只需要在UPDATE语句中带上版本号条件。只要数据和版本号在同一个库它就能生效。我维护的某个订单中心就是按照user_id维度进行分库的。每个分库内的订单表都有乐观锁版本字段。应用层传入的请求携带用户ID和期望版本号分库路由后执行条件更新效果非常稳定。这种情况下悲观锁反而用不了因为单条用户数据可能分散在不同库无法用跨库行锁协调。所以我的判断是一旦技术架构走向分布式乐观锁的优先级天然高于悲观锁因为它从设计上就不依赖“锁”这种强耦合机制。6.3 分布式锁补充什么时候必须加悲观锁在单库场景下解决了写冲突乐观锁通过版本号解决了跨库更新问题。但有些业务既不适合乐观锁冲突率高又无法用悲观锁跨库这时候就需要真正的分布式锁了。比如一个定时任务多台机器同时触发要保证只有一台机器执行。这个场景和数据库行无关就是纯粹的互斥需求用Redis分布式锁最合适。再比如下单时需要同时扣减多个服务的库存无法在一次数据库事务内完成。这时候用分布式事务或分布式锁来协调多个服务保证业务的整体互斥性比单纯依赖乐观锁更可靠。但引入分布式锁也意味着新的问题锁超时时间设置、锁误删除、看门狗续期、集群故障时的锁失效等等。这些内容展开又是另外一篇长文了。我只提示一点能不用分布式锁就不用能用数据库约束解决就不要引入分布式锁组件这是控制复杂度的关键。7. 写在最后的几点深度体会刚开始接触并发控制时我也以为乐观锁和悲观锁就是两个对立的技术方案后来在实践中才慢慢明白它们本质上是两种不同的世界观一个假设世界冲突频繁为了安全宁可牺牲效率一个假设世界和谐只在关键时刻核对一下底线。但真实业务从来不按教科书走我见过库存用乐观锁、余额用悲观锁、统计字段用原子自增的混合架构也见过把版本号机制玩出花来实现跨服务幂等校验的团队。这里有几个我踩过很多次坑之后总结出的心得写出来供你参考。第一任何锁方案都改变不了业务链路的长短。锁只是解决并发写的问题但一个事务从开始到提交中间夹了多少次远程调用、多少次外部IO才是影响整体体验的真正瓶颈。我看到太多人拼命优化锁策略却忽略了整个事务链路里最耗时的环节。第二要给锁加上观测。线上环境里你需要知道每个加锁SQL的平均耗时、锁等待时长、死锁发生次数、乐观锁冲突重试率。这些指标直接决定了你对锁方案的判断是否正确。我建议在核心路径上埋点输出关键的锁指标数据而不是等到出事故了再去翻日志。第三没有银弹更没有万能的锁。每引入一种锁机制都会带来新的复杂度与新的风险。悲观锁简单直接但拖垮并发乐观锁高并发但冲突时用户体验差分布式锁能跨服务但引入新的不稳定因素。我现在的习惯是每设计一个方案先问自己三个问题——冲突频繁吗一致性要求多高服务是否已经拆分答案组合起来锁方案自然就清晰了。做并发控制这行掌握原理只是起点真正值钱的是在无数次线上故障、压测报告和深夜排查中累积起来的判断力。希望这篇文章能帮你少走一些我走过的弯路让你在选择乐观锁还是悲观锁时心里更有底气。

相关新闻

UML活动图:系统行为建模与业务流程协同的工程实践

UML活动图:系统行为建模与业务流程协同的工程实践

1. 什么是UML活动图:一张能“动起来”的业务流程说明书你有没有遇到过这样的场景:团队在开发一个订单退款功能,后端说逻辑是“先校验状态→再冻结余额→最后发通知”,前端却按“先发通知→再查余额→最后更新状态”来对接&#xf…

2026/10/14 19:25:42 阅读更多 →
同一个点位,把客单价从6元提到9元,我只改了三个地方~YH

同一个点位,把客单价从6元提到9元,我只改了三个地方~YH

2026年Q1,一个写字楼点位的客单价一直停留在6元左右。用户主要买矿泉水、可乐等低价饮料。调整了三个地方后,客单价提升到9元。一、调整一:商品组合调整前:货道以低价饮料为主(矿泉水、可乐),价…

2026/10/14 19:24:41 阅读更多 →
垃圾处理器壳体易裂?矫平机3招解决

垃圾处理器壳体易裂?矫平机3招解决

垃圾处理器(厨余垃圾处理器、商用垃圾处理器、餐厨垃圾处理器、湿垃圾处理器)是城市生活垃圾源头减量的核心装备,年市场规模超过200亿元。其钣金部件(壳体、研磨腔、底座、电机壳体、法兰、连接板)的矫平精度直接影响研…

2026/10/14 19:24:41 阅读更多 →

最新新闻

yolov8-face 核心算子全解析:NMS、边界框格式转换与掩码处理的源码级指南

yolov8-face 核心算子全解析:NMS、边界框格式转换与掩码处理的源码级指南

人工智能计算机视觉深度学习 【免费下载链接】yolov8-face yolov8 face detection with landmark 项目地址: https://gitcode.com/gh_mirrors/yo/yolov8-face 点击查看 免费下载 本文是 ops.md 参考文档的深度展开,以 ultralytics/yolo/utils/ops.py 为…

2026/10/14 20:49:14 阅读更多 →
自学嵌入式第二天(C语言篇——数据类型)

自学嵌入式第二天(C语言篇——数据类型)

琐碎的敲代码内功。(以为很简单 ,结果全是坑)1.C语言提供的数据类型2.整数类型常量:在程序运行过程中,其值不能被改变的量变量:在程序运行过程中,其值能被改变的量(1)整型…

2026/10/14 20:49:14 阅读更多 →
openJiuwen 记忆引擎基础模型:MemInfo 单条记忆信息结构全解

openJiuwen 记忆引擎基础模型:MemInfo 单条记忆信息结构全解

人工智能AI AgentAgent 框架工具调用Agent 工作流RAGMCP Clients强化学习 【免费下载链接】agent-core-java openJiuwen agent core Java是 openJiuwen Core的 Java版本,提供AI Agent开发、运行、调优与演进相关的全套SDK能力。 项目地址: https://gitco…

2026/10/14 20:49:14 阅读更多 →
模拟退火求解二维装箱问题:原理、实现与工程调优

模拟退火求解二维装箱问题:原理、实现与工程调优

模拟退火算法求解装箱问题,我第一次真正上手,是在一个托盘排样项目里。需求方只说了一句话:把尺寸不一的纸箱尽量多地放进托盘,允许90度旋转,不许压坏。我当时觉得,这就是矩形排样,先求个面积下…

2026/10/14 20:49:14 阅读更多 →
51单片机DHT11温湿度采集实战:从仿真到实物的完整拆解

51单片机DHT11温湿度采集实战:从仿真到实物的完整拆解

简介:一套以51单片机为核心的温湿度采集监控完整工程,面向电子爱好者、嵌入式初学者及课程设计学生。资料基于Keil平台,由C语言编写,实现温湿度采集(SHT10传感器)、日历时钟(DS1302)…

2026/10/14 20:49:14 阅读更多 →
Towards Automated Regulatory Compliance Verification in Financial Auditing with Large Language Mo...

Towards Automated Regulatory Compliance Verification in Financial Auditing with Large Language Mo...

文章主要内容总结 本文聚焦于利用大型语言模型(LLMs)实现财务审计中监管合规性的自动验证,旨在解决传统审计中人工验证合规性的低效问题。研究对比了开源LLMs(如Llama-2系列)与专有LLMs(如OpenAI的GPT系列)在合规性检测中的性能,使用了普华永道(PwC)提供的两个自定义…

2026/10/14 20:48:13 阅读更多 →

日新闻

AnyPS5通用适配层设计:兼容性与性能的工程取舍

AnyPS5通用适配层设计:兼容性与性能的工程取舍

1. 从"AnyPS5"这个名字说起:它到底想解决什么问题第一次看到"AnyPS5"这个标题,我脑子里冒出来的第一个念头是:这大概率是一个围绕"跨平台运行"或者"通用适配"做文章的项目。名字里的"Any"…

2026/10/14 0:00:41 阅读更多 →
Flutter跨平台开发实战:二手交易应用与HarmonyOS适配

Flutter跨平台开发实战:二手交易应用与HarmonyOS适配

1. 项目概述:Flutter跨平台二手交易应用开发作为一名长期从事移动应用开发的工程师,我最近完成了一个基于Flutter框架的二手物品交易平台项目。这个项目最吸引我的地方在于它完美结合了Flutter的跨平台优势与HarmonyOS的生态特性,实现了"…

2026/10/14 0:00:41 阅读更多 →
DFS算法解决飞机降落调度问题实战

DFS算法解决飞机降落调度问题实战

1. 项目背景与核心思路飞机降落问题是一个经典的调度优化场景,也是算法竞赛和面试中的高频题目。这个题目要求我们合理安排多架飞机的降落顺序和时间,确保所有飞机都能安全着陆而不发生冲突。在实际航空管制中,这类问题直接关系到飞行安全和机…

2026/10/14 0:00:41 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/14 3:30:28 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/14 16:30:29 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/14 3:29:58 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/13 22:01:27 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/13 14:57:39 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/14 7:53:34 阅读更多 →