关于小程序中多个函数并发修改同一条数据

关于小程序中多个函数并发修改同一条数据

背景

开发小程序的时候,遇到了如下情况:多个函数可能存在并发修改同一条数据的情况。

修改操作如下

const db = cloud.database();
const _ = db.command;
db.collection('xxx')
            .doc('yyy')
            .update({
              data: {
                order: _.pull(zzz)
              }
            })
            .then(res => {
              console.log(res);
            })
            .catch(err => {
              console.log(err);
            })

在这里我们对数据库进行更新操作,使用 $pull操作符删除order字段中的某个元素(且该元素不会存在重复值,原因是order是对象数组,我们在每个对象里面存的值也不一样)

那么在这样的情况下,会存在冲突的现象吗?

结论

如果第一个操作删除了该元素之后,第二个操作在检查到初始状态时仍认为存在匹配元素,当第二个操作尝试删除时,由于实际上该元素已被删除,可能会导致错误。

因此,在同时有两个操作在同一个数组字段上使用 $pull 操作符删除同一个元素,并且该元素在数组字段上只有一个的情况下,存在潜在的冲突风险。

解决方案

为了避免这种冲突,可以考虑使用事务或锁机制来处理并发删除冲突。

首先介绍一下锁机制的做法,代码如下

// 云函数代码示例(concurrentDelete)
const cloud = require('wx-server-sdk');
cloud.init();

exports.main = async (event, context) => {
  const db = cloud.database();
  const _ = db.command;
  const collection = db.collection('myCollection');
  const docId = event.docId; // 要删除的数据的 _id

  // 尝试获取锁标记
  const lock = await collection.doc(docId).get();

  if (lock.data && lock.data.isDeleting) {
    // 存在锁标记,其他函数正在删除该数据,放弃删除操作
    return { success: false, message: '数据正在删除中,请稍后再试' };
  }

  try {
    // 添加锁标记
    await collection.doc(docId).update({
      data: { isDeleting: true }
    });

    // 执行删除操作
    const result = await collection.doc(docId).update({
                      data: {
                        order: _.pull(event.zzz)
                      }
    			});

    // 删除完成后释放锁标记
    await collection.doc(docId).update({
      data: { isDeleting: false }
    });

    return { success: true, result };
  } catch (error) {
    // 发生错误时也需要释放锁标记
    await collection.doc(docId).update({
      data: { isDeleting: false }
    });
    throw error;
  }
};

在云函数中,首先尝试获取锁标记。如果锁标记存在,表示其他函数正在删除该数据,当前函数放弃删除操作。如果锁标记不存在,则添加锁标记,并执行删除操作。删除完成后释放锁标记。当函数发生错误时也需要释放锁标记,以确保数据的一致性。

在多个函数中调用并发删除的云函数:

// 函数A调用并发删除的云函数
wx.cloud.callFunction({
  name: 'concurrentDelete',
  data: {
    docId: 'xxx' // 要删除的数据的 _id
  },
  success: (res) => {
    if (res.result.success) {
      console.log('函数A删除成功', res.result.result);
    } else {
      console.error('函数A删除失败', res.result.message);
    }
  },
  fail: (err) => {
    console.error('函数A调用失败', err);
  }
});

// 函数B调用并发删除的云函数
wx.cloud.callFunction({
  name: 'concurrentDelete',
  data: {
    docId: 'xxx' // 要删除的数据的 _id
  },
  success: (res) => {
    if (res.result.success) {
      console.log('函数B删除成功', res.result.result);
    } else {
      console.error('函数B删除失败', res.result.message);
    }
  },
  fail: (err) => {
    console.error('函数B调用失败', err);
  }
});

通过调用并发删除的云函数,确保在任意时间点只有一个函数成功删除数据,其他函数会放弃删除操作。

除了锁机制,还可以使用事务操作来确保多个删除操作的一致性。事务操作可以将多个操作作为一个原子性的单元执行,从而保证操作的顺序和结果的一致性。

下面介绍使用事务的做法,代码如下:

//runTransaction接口
const db = wx.cloud.database();
const collection = db.collection('myCollection');

db.runTransaction(async transaction => {
  const doc = await transaction.get(collection.doc('xxx'));

  // 从数组中删除元素
  const updatedArray = doc.data.arrayField.filter(element => element !== 'element');

  // 更新数组字段
  transaction.update(collection.doc('xxx'), {
    data: {
      arrayField: updatedArray
    }
  });
}).then(res => {
  console.log('事务执行成功', res);
}).catch(err => {
  console.error('事务执行失败', err);
});


在使用事务操作时,需要将多个操作封装在一个函数内,并使用 runTransaction 方法来执行事务。在事务函数中,可以获取并操作要更新的数据,并通过 transaction.update 方法更新数组字段。

你也可以通过startTransaction接口来实现

// 云函数代码示例(concurrentDelete)
const cloud = require('wx-server-sdk');
cloud.init();

exports.main = async (event, context) => {
  const db = cloud.database();
  const collection = db.collection('myCollection');
  const docId = event.docId; // 要删除的数据的 _id

  const transaction = await db.startTransaction();

  try {
    const result = await transaction.run(() => {
      return collection.doc(docId).remove();
    });

    await transaction.commit();
    return result;
  } catch (error) {
    await transaction.rollback();
    throw error;
  }
};

使用事务操作可以保证多个删除操作在同一事务中执行,从而避免并发删除冲突,并确保操作的一致性。

关于更多事务的细节,可以去官方文档查看

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
1. 选择题 1.在进程的组成部分之,进程在运行不可修改的部分就是___B__。 A.私用程序段 B.共享程序段 C.数据段 D.进程控制块 2.在消息缓冲通信,消息队列属于___A___资源。 A. 临界 B. 共享 C. 永久 D. 可剥夺 3.进程间的同步就是指进程间在逻辑上的相互___B___关系。 A. 联接 B. 制约 C. 继续 D. 调用 4.进程A与进程B通过共享缓冲区协作完成数据处理,进程A负责生产数据并放入缓冲区 ,进程B从缓冲区数据并输出。进程A与进程B之间的关系就是____B__。 A.互斥关系 B.同步关系 C.互斥与同步 D.无制约关系 5.进程从运行状态到阻塞状态可能就是由于_C____。 A.进程调度程序的调度 B.现运行进程的时间片耗尽 C.现运行进程执行了wait操作 D.现运行进程执行了signal操作 6.下面关于线程的叙述正确的就是__A____。 A.线程包含CPU现场,可以独立执行程序 B.每个线程有自己独立的地址空间 C.线程之间的通信必须使用系统调用函数 D.进程只能包含一个线程 7.并发进程之间___D___。 A.彼此无关 B.必须同步 C.必须互斥 D.可能需要同步或互斥 8.信号量S不能用于___D___操作。 A.signal B.wait C.赋初值 D.运算表达式 9.___D___就是一种只能进行wait操作与signal操作的特殊变量 A. 调度 B. 进程 C. 同步 D. 信号量 10.分配给进程占用处理机的时间到而强迫进程P让出处理器,或有更高优先级的进程 要运行,迫使正在运行的进程P让出处理器,则进程P状态变化的情况为___A___ A. 运行态->就绪态 B. 运行态->等待态 C. 就绪态->运行态 D. 等待态->就绪态 11.下面关于进程的叙述正确的就是__A____。 A.进程获得CPU运行就是通过调度得到的 B.优先级就是进程调度的重要依据,一旦确定就不能改变 C.在单CPU的系统,任何时刻都有一个进程处于运行状态 D.进程申请CPU得不到满足时,其状态变为阻塞 12.操作系统通过__B____对进程进行管理。 A. 进程 B. 进程控制块 C. 进程启动程序 D. 进程控制区 13、 若一个进程拥有100个线程,这些线程属于用户级线程,它们在系统调度执行时间上占用的 时间片个数就是__A____。 A.1 B.100 C.1/100 D.0 14、 到其她3种状态的进程状态就是__D____。 A.就绪 B.阻塞 C.完成 D.执行 15.信号量的初值为2,当前值为-3,则表示等待进程有___C___。 A.1个 B.2个 C.3个 D.5个 16.wait操作可能导致___C___。 A.进程就绪 B.进程结束 C.进程阻塞(等待) D.新进程创建 17.下列的进程状态变化不可能发生的变化就是___A___。 A.等待 运行 B.运行 等待 C.运行 就绪 D.等待 就绪 18.如果有三个进程共享同一互斥段,而且每次最多允许两个进程进入该互斥段,则信 号量的初值应设置为__C____。 A. 3 B. 1 C. 2 D. 0 19.一个进程释放一种资源将有可能导致一个或几个进程___D___。 A.由就绪变运行 B.由运行变就绪 C.由阻塞变运行 D.由阻塞变就绪 20.进程控制块PCB不包括的内容就是___D___。 A.CPU现场 B.进程优先级 C.进程运行状态 D.文件缓冲区 21.PCB就是描述进程状态与特性的数据结构,一个进程___D___。 A.可以有多个PCB   B.可以与其她进程共用一个PCB C.可以没有PCB     D.只能有唯一的PCB 22.进程与程序的本质区别就是___B___。 A.内存与外存 B.动态与静态特征 C.共享与独占使用计算机资源 D.顺序与非顺序执行指令 23.用于解决进程间互斥的方法就是___B___。 信号量及wait、signal操作 加锁与解锁 信箱方式 消息缓冲方式 特权指令方式 A. 、 与 B. 与 C. 与 D. 与 24.在操作系统,每个进程具有独立性,进程之间又具有相互制约性。对于任何两个 并发进程,它们___C___。 A.必定无关 B.必定相关 C.可能相关 D.可能相同 25.进程所请求的一次打印输出结束后,将使进程状态从__D____ A.运行态变为就绪态 B.运行态变为等待态 C.就绪态变为运行态 D.等待态变为就绪态 解析:运行的进程有3种状态:运行状态、就绪状态与等待状态。 运行状态:就是指进程已获得CPU,并且在CPU执行的状态。就绪状态:就是指进程已具备 运行条件,但由于没有获得CPU而不能运行所处的状态,一旦CPU分配给它,
达梦数据库_SQL语言手册.pdf 数据库快照定义语句 数据库快照删除语句 第章数据查询语句和全文检索语句 单表查询 简单查询 带条件查询 集函数 情况表达式 连接查询 子查询 标量子查询 表子查询 派生表子查询 定量比较 带 谓词的子查询 多列表子查询 查询结果的合并 和 子句的使用 子句的使用 子句 选取前儿条数据 选取其屮几条数据 全文检索 层次查询 层次查询子句 层次查询相关伪列 层次查询相关操作符 层次查询相关函数 查看执行计划 第章数据的插入、删除和修改 数据插入语句 数据修改语句 数据删除语句 伪列的使用 和 自增列的使用 自增列定义 属性 第章视图 视图的作用 视图的定义 视图的删除 视图的查询 视图数据的更新 第章嵌入式 前缀和终结符 宿主变量 输入和输出变量 指示符变量 服务器登录与退出 登录服务器 退出服务器 游标的定义与操纵 定义游标语句 打开游标语句 拨动游标语句 关闭游标语句 关于可更新游标 游标定位删除语句 游标定位修改语句 单元组查询语句 动态 立即执行语句 准备语句 执行语句 异常处理 第章函数 数值函数 字符串函数 日期时间函数 空值判断函数 类型转换函数 杂类函数 系统函数 存储加密函数 标记处理函数 备份恢复函数 附加分离数据库 第章一致性和并发性 事务相关语句 事务的开始 事务的结束 保存点相关语句 设置事务隔离级及读写特性 手动上锁语句 第章存储模块 存储模块的定义 存储模块的删除 存储模块的控制语句 语句块 赋值语句 条件语句 循环语句 语句 调用语句 语句 语句 语句 语句 打印语句 存储模块的异常处理 异常变量的说明 异常的抛出 异常处理器 异常处理用法举例 存储模块的语句 游标 动态 游标变量 返回查询结果集 语句应用举例 客户端存储模块 子过程、子函数 子过程 子函数 记录类型 记录类型定义 记录赋值 第章触发器 触发器的定义 触发器类型 触发器激发顺序 新、旧行值的引用 触发器谓词 变异表 设计触发器的原则 触发器的删除 禁止和允许触发器 触发器应用举例 使用触发器实现审计功能 使用触发器维护数据完整性 使用触发器保障数据安全性 使用触发器派生字段值 第章安全管理 创建角色语句 删除角色语句 授权语句数据库权限 授权语句对象权限 授权语句角色权限 回收权限语句数据库权限 回收权限语句对象权限 回收权限语句角色权限 策略与标记管理 创建策略 修改策略 删除策略 安全标记 用户标记设置语句 表标记设置语句 审计设置语句 审计取消语句 审计信息查阅语句 审计分析 创建审计分析规则 删除审计分析规则 加密引擎 创建加密引擎 修改加密引擎 删除加密引擎 第章外部链接 创建外部链接 删除外部链接 使用外部连接进行远程对象操作 第章备份还原 备份数据库 还原数据库 第章包 创建包 创建包规范 创建包主体 删除包 删除包规范 删除包主体 应用实例 第章同义词 创建同义词 删除同义词 附录关键字和保留字 附录 语法描述说明 附录命令参考 附录系统存储过程和函数 附录技术支持 第1章结构化查询语言简介 第章结构化查询语言 简介 结构化查询语言 是在年提出的一种关系数据库语言。 由于语言接近英语的语句结构,方便简洁、使用灵活、功能强人,倍受用户及计算机工业 界的欢迎,被众多计算机公司和数据库厂商所采用,经各公司的不断修改、扩充和完善,语 言最终发展成为关系数据库的标准语言。 的第一个标准是年月由美国国家标准化组织公布的 数据库语言 简称 年国际标准化组织也通过了这一标准。以后通过对 的不断修改和完善,于年第二次公布了标准 年又公布了标准 即 。最新的标准是 (也称 年作为 《信息技术——数据库语言》发布。我国也相继 公布了数据库语言的国家标准。 成为国际标准以后,其影响远远超出了薮据库领域。例如在 软件工程、人工智 能、分布式等领域,人们不仅把作为检索数据的语言规范,而且也把作为检索图形、 图象、声音、文字等信息类型的语言规范。目前,世界上大型的著名数据库管理系统均支持 语言,如 等。在未来相当长的时间里,仍将是数据库领 域以至信息领域数据处理的主流语言之 由于不同的产品,大都按自己产品的特点对语言进行了扩充,很难完全符合 标准。目前在 市场上已将的符合夲作为衡量产品质量的重要指标,并研制成专门的 测试软件,如 目前, 入门级和过渡级的符合率均达到,并且部分支持 更新的 标准。同时还兼容 和 的部分语言特性。本章主要 介绍系统所支持的语言 语 语言的特点 语言符合结构化査询语言标准,是标准的扩充。它集数据定乂、数据査 询、薮据操纵和数据控制于一体,是一种统一的、综合的关系数据库语言。它功能强大,使用简 单方便、容易为用户掌握 语言具有如下特点: 功能一体化 的功能一体化表现在以下两个方面 支持多媒体数据类型,用户在建表时可直接使用。系统在处理常规数据与 多媒体数据时达到了四个一体化:一体化定义、一体化存储、一体化检索、一体化处理,最大限 度地提高了数据库管理系统处理多媒体的能力和速度; 语言集数据库的定义、査询、更新、控制、维护、恢复、安全等一系列操作于 体,每一项操作都只需一种操作符表示,格式规范,风格一致,简单方便,很容易为用户所掌 握 两种用户接口使用统一语法结构的语言 语言既是自含式语言,又是嵌入式语言。作为自含式语言,它能独立运行于联机交 互方式。作为嵌入式语言, 浯句能够嵌入到和语言程序,将高级语言也称主 语言灵活的表达能力、强大的计算功能与 语言的数据处理功能相结合,完成各种复杂 的事务处理。而在这两种不同的使用方式, 语言的语法结构是一致的,从而为用户使 第1章结构化查询语言简介 用提供了极大的方使性和灵活性。 高度非过程化 语言是·种非过程化语言。用户只需指出“做什么”,而不需指出“怎么做”,对数 据存取路径的选择以及 语句功能的实现均由系统自动完成,与用户编制的应用程序与 具体的机器及关系 的实现细节无关,从而方便了用户,提高了应用程序的开发效率,也 增强了数据独立性和应用系统的叮移植性。 面向集合的操作方式 语言采用了集合操作方式。不仅查询结果可以是元组的集合,而且一次插入、删除、 修改操作的对象也可以是元组的集合,相对于面向记录的数据库语言一次只能操作一条记录来 语言的使用简化了用户的处理,提高了应用程序的运行效率 语言简洁,方便易学 语言功能强大,格式规范,表达简洁,接近英语的语法结构,容易为用户所掌握。 保留字与标识符 标识符的语法规则兼容标准 ,标识符分为正规标识符和定界标识符两大类。 正规标识符以字母、、、或汉字开头,后面可以跟随字母、数字、、、或者汉字,正 规标识符的最大长度是个英文字符或个汉字。正规标识符不能是保留字 正规标识符的例子:, 表 定界标识符的标识符体用双引号括起来时,标识符体可以包含任意字符,特别地,其使用 连续两个双引号转义为一个双引号 定界标识符的例子: 保留字的清单参见附录 语言的功能及语句 语言是一种介于关系代数与关系演算之间的语言,其功能主要包括数据定义、查询 操纵和控制四个方面,通过各种不同的语句米实现。按照所实现的功能, 语句分 为以下几种 数据库、登录、用户、模式、基表、视图、索引、序列、全文索引、存储过程和触发器 的定义和删除语句,登录、基表、视图、仝文索引的修改语句,对象的更名语句; 査询(含全文检索)、插入、删除、修改语句; 数据库安全语句。包括创建角色语句、删除角色语句,授权语句、回收权限语句,修改 登录口令语句,审计设置语句、取消审计设置语句等。 在嵌入方式,为了协调 语言与主语言不同的数据处理方式 语言引入 了游标的概念。因此在嵌入方式下,除了数据查询语句一次查询一条记录外,还有几种与游标 有关的语句: 游标的定义、打廾、关闭、拨动语句 游标定位方式的数据修改与删除语句。 为了有效维护数据库的完整性和一致性,支持 的并发控制机制 语言提供 了事务的回滚( )与提交( )语句。同时允许选择实施事务级读一致 性,它保证同一事务内的可重复读,为此提供用户多种手动上锁语句,和设置事务隔离级别 第1章结构化查询语言简介 语句 所支持的数据类型 数据类型是可表示值的集。值的逻辑表示是字值。值的物理表示依赖于实现。系统具 有 的绝大部分数据类型,以及部分 和 的数据类型。 常规数据类型 字符数据类型 类型 语法:长度 功能: 数据类型指定定长字符串。在基表,定义 类型的列时,可以指 定一个不超过的正整数作为字符长度,例如 如果未指定长度,缺省为。 确保存储在该列的所有值都具有这一长度。 数据类型的最大长度由数据库页面大 小决定,字符类型最大长度和页面大小的对应关系请见下表支持按字节存放字符 串 表 数据库页面大 最大长度 类型 语法: 长度 功能:与 相同。 类型 语法: 长度 功能 数据类型指定变长字符串,用法类似 数据类型,可以指定一 个不超过的正整数作为字符长度,例如: 。如果未指定长度,缺省为 在系统, 数据类型的实际最大长度由数据库页面大小决定,具体最 大长度算法如表 的区别在于前者长度不足时,系统自动填充空 格,而后者只占用实际的字节空间。 表 数据库页面大 实际最大长度 注:这个限制长度只针对建表的情况,在定义变量的时候,可以不受这个限制长度的限 制 数值数据类型
以下是一个使用Java多线程实现文件复制的示例程序: ```java import java.io.*; public class FileCopyThread extends Thread { private File sourceFile; private File targetFile; public FileCopyThread(File sourceFile, File targetFile) { this.sourceFile = sourceFile; this.targetFile = targetFile; } public void run() { try { FileInputStream in = new FileInputStream(sourceFile); FileOutputStream out = new FileOutputStream(targetFile); byte[] buffer = new byte[4096]; // 缓冲区大小为4KB int bytesRead; while ((bytesRead = in.read(buffer)) != -1) { out.write(buffer, 0, bytesRead); } in.close(); out.close(); } catch (IOException e) { e.printStackTrace(); } } public static void main(String[] args) { File source = new File("source.txt"); File target = new File("target.txt"); int threadCount = 4; // 创建4个线程 long fileSize = source.length(); long blockSize = fileSize / threadCount; for (int i = 0; i < threadCount; i++) { long start = i * blockSize; long end = (i == threadCount - 1) ? fileSize : (i + 1) * blockSize; new FileCopyThread(source, target, start, end).start(); } } } ``` 该程序使用了多线程来复制文件。首先,将要复制的文件分成若干个块,每个块由一个线程处理。在 `FileCopyThread` 类的构造函数,需要传入源文件和目标文件,以及该线程需要复制的文件块的起始和结束位置。在 `run()` 方法,使用 `FileInputStream` 和 `FileOutputStream` 分别读取和写入文件,每次读取缓冲区大小的数据,并使用 `while` 循环重复读取和写入,直到文件复制完成。 在 `main()` 方法,首先指定源文件和目标文件,然后指定要创建的线程数。接着,计算每个线程需要复制的文件块的起始和结束位置,并创建 `FileCopyThread` 对象,并启动该线程。 需要注意的是,该示例程序没有考虑多线程并发读写文件的问题,可能会产生竞争条件和文件损坏等问题。在实际应用,需要使用线程同步机制来保证线程安全。同时,该程序还没有处理文件不存在等异常情况,也需要根据实际需求进行处理。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Gavi曦

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值