进行原子操作

目前数据存储服务使用 MongoDB 3.4,不具备处理事务的能力,但这不代表开发者无法进行并发操作。阅读本文,了解 MongoDB 提供的原子操作符以及使用方法。

原子插入

要保证同一条信息不可插入两次,在集合上新增一个字段并且设置 unique index,如下所示:

  1. // 创建唯一索引
  2. basement.db.collection('order').createIndex(
  3. { 'orderId': 1 },
  4. { 'unique': true }
  5. );
  6. // 插入时验证
  7. function placeOrder(items) {
  8. const orderId = uuid();
  9. basement.db.collection('order').insertOne({
  10. orderId,
  11. items,
  12. }).then(res => {
  13. console.log('order placed successful');
  14. }).catch(err => {
  15. console.error('insert failed');
  16. });
  17. }

原子更新

原子更新可以通过判断当前的最后更新时间来匹配是否可以更新。例如,在发送一个礼物时,A 和 B 同时领取,在不支持原子更新的情况下,可能导致两个用户都抢到的情况。

  1. basement.db.collection('gifts').createIndex('inventory.itemId');
  2. function giveOutGift(owner, item) {
  3. // 发礼物
  4. return basement.db.collection('inventory').updateOne({
  5. user: owner,
  6. }, {
  7. $addToSet: { // 保证唯一性,subdocument 不能建立唯一索引
  8. inventory: {
  9. item,
  10. itemId: item.id,
  11. status: 'unclaimed',
  12. },
  13. },
  14. }, { upsert: true });
  15. }
  16. function freezeGift(item) {
  17. return basement.db.collection('gifts').updateOne({
  18. inventory: {
  19. $elemMatch: { // 找到数组里对应的礼物
  20. itemId: item.id,
  21. status: 'unclaimed',
  22. },
  23. },
  24. }, {
  25. $set: {
  26. 'inventory.$.status': { status: 'transferring' }, // 设置成转移中
  27. },
  28. }).then(res => {
  29. if (res.affectedDocs !== 1) {
  30. return Promise.reject();
  31. }
  32. });
  33. }
  34. function claimGift(to, item) {
  35. return basement.db.collection('gifts').updateOne({
  36. user: to,
  37. }, {
  38. $addToSet: {
  39. inventory: {
  40. item,
  41. itemId: item.id,
  42. status: 'owned'
  43. },
  44. },
  45. }, {
  46. upsert: true,
  47. }).then(res => {
  48. if (res.affectedDocs !== 1) {
  49. return Promise.reject();
  50. }
  51. });
  52. }
  53. function claimUserGift(from, to, item) {
  54. return freezeGift().then(claimGift).then(() => basement.db.collection('gifts').updateOne({
  55. user: from,
  56. inventory: {
  57. $elemMatch: {
  58. itemId: item.id,
  59. status: 'transferring',
  60. },
  61. },
  62. }, {
  63. $set: {
  64. 'inventory.$.status': 'claimed',
  65. },
  66. });
  67. }
  68. giveOutGift('alice', {
  69. name: 'umbrella',
  70. id: '5b9642e109d54b4c12d68c7e',
  71. }).then(res => claimUserGift('alice', 'bob', {
  72. name: 'umbrella',
  73. id: '5b9642e109d54b4c12d68c7e',
  74. });

原文: https://docs.alipay.com/mini/cloud-service/shr0ud