Sails.js model 使用 Update 时竞态条件

前言

一直使用 mysql 做 Apple APNs 队列, 之前使用拖一批出来, 全部处理好取下一批. 中间碰到超时问题, 导致一批很难处理完. 有些 worker 在等超时结束, 导致整个过程 block 住, 即使有些 worker 是空闲的. 即是单入口, 多 worker 的形式. 现在改成多入口, 多 worker 的形式. 每个 worker 先去 DB update一下, 然后去处理它领取的. 即 update-then-select 模式.

  • 可以使用 uuid, 标识, 然后 select. 缺点是额外增加了字段
  • 然后发现 sails update 有返回结果
  • 查到可以使用变量获取被 update 的 id, http://stackoverflow.com/a/1751282/115478
  • 使用 sails 发现存在并发问题, 多个 worker 更新的是同一个

Issue

https://github.com/balderdashy/sails-mysql/issues/253

sails-mysql update 实现

  1. Query with the given criteria to get a list of ids
  2. Update with the given criteria
  3. Query with the list of ids from step 1 to get the updated objects

先根据 update where 查一遍, 再更新, 再用操作1的 ids 查询一遍

于是有了问题

  1. 没有使用事务, 再并发使用 Model.update 情况下, 假设每次只想 LIMIT 1, 根据操作1, 被 update 的就是同一条了
  2. sails 为了使 update 操作有结果, 在 update 完使用操作1 得到的 primary key 查询一次

使用事务能解决么?

https://github.com/balderdashy/sails-mysql/issues/253#issuecomment-175243009

  • MySql 默认事务隔离级别是 REPEATEDABLE READ, 会使用 非锁定一致性读, 然而即使是这样, 本事务的 update/insert/delete 语句依然可以影响其他事务提交的行
  • 对操作1加上 FOR UPDATE 之后, 对记录加上了 X锁, 另一个事务的 select 即需要等待锁释放

使用事务 + SELECT X锁可以解决

mysql udpated id

还是这种比较好

1
2
3
4
5
6
SET @uids := null;
UPDATE footable
SET foo = 'bar'
WHERE fooid > 5
AND ( SELECT @uids := CONCAT_WS(',', fooid, @uids) );
SELECT @uids;