如何通过变更让 PostgreSQL 翻车
在开发应用程序和维护其后台数据库集群的过程中,我们经常会遇到实践与理论、开发环境与生产环境之间的差异。其中一个典型的例子就是变更数据库中的列类型。
对于在 PostgreSQL(及其他符合 SQL 标准的系统)中变更列类型的常规操作,大多数人都认为有一个标准的方法,即:
ALTER TABLE table_name
ALTER COLUMN column_name
[SET DATA] TYPE new_data_type
这种方法在语义上无可挑剔,但在特定情况下,它可能带来一些意想不到的困扰。
问题
让我们来创建一个示例表,并展示一些你可能会遇到的特定行为。从 1000 万条数据开始说起 —— 虽然听起来很多,但在数据世界里,只是沧海一粟。
-- create very simple table
CREATE TABLE sample_table (id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,label TEXT,created_at TIMESTAMP WITHOUT TIME ZONE DEFAULT NOW(),updated_at TIMESTAMP WITHOUT TIME ZONE DEFAULT NOW()
);-- populate with 10m records
INSERT INTO sample_table (label)
SELECT 'hash: ' || md5(random()::text)
FROM generate_series(1, 7000000);
将 id 的数据类型 从 INT 变更为 BIGINT。
alter_type_demo=# ALTER TABLE sample_table ALTER COLUMN id TYPE bigint;
ALTER TABLE
Time: 21592.190 ms (00:21.592)
然后 …… 21 秒后,数据变更完成。但请注意,这只是一个含有大约 600 MB 数据的小型数据表。如果你要处理的数据量是这个的 100 倍怎么办?让我们来看看背后发生了什么。
PostgreSQL 必须执行的操作
变更数据类型以及其他许多操作都不是简单的任务,PostgreSQL 引擎需要执行以下几项关键任务:
- 重写整个表是最直接的必要步骤。将一个列从 INT 类型变更为 BIGINT 类型,需要为每个数据行额外分配 4 字节。原有的表结构要求数据以固定的字节大小存储,因此系统会以最有效的方式进行存储。在我们的例子中,这意味着需要读取并重新写入所有的 1000 万数据行,以符合新的数据行大小要求。
- 锁处理。虽然在我们这个假设的示例中加锁似乎不是问题,但如果你在实际的生产环境中,同时处理数百甚至数千个并发查询时执行 ALTER 命令,你可能需要先等待所有这些查询释放它们的锁。
- 索引和约束的重建。如果变更的列有索引或约束,这些索引和约束需要被重建或重新验证,这会带来额外的开销。
- 事务处理和预写日志(WAL,Write-Ahead Log)。为了保证数据的持久性 —— 即 ACID 原则中的「持久性」(ACID 中的 Durability),PostgreSQL 必须在 WAL 文件中记录每次变更。这样,即使数据库发生崩溃,系统也可以通过重放 WAL 文件来恢复自上一次检查点以来的所有修改。
如你所见,在进行看似常规的表格维护时,实际涉及的因素远比想象的多。被修改数据的大小、磁盘输入 / 输出和容量以及系统的总体拥堵状态,都是必须考虑的关键点。
然而,这些还只是问题的一部分。在进行严肃的生产环境部署时,还有更多需要考虑的方面:
- 物理和逻辑实时复制:这又增加了一层复杂性。对于只读副本来说,系统默认通过同步提交来保持数据库集群的数据一致性。这种配置确保了只有在所有备份副本都确认接收到修改后,事务才会被最终确认。但这也带来了新的挑战,因为系统的表现现在还依赖于网络的传输效率,包括可能的网络拥堵,以及备份节点的延迟和输入 / 输出性能。
- 恢复和备份是另一个需要考虑的重要方面。虽然常规备份的大小可能不会有太大变化,但在变更前的最后一次备份与下一次备份之间所发生的所有事项都必须被考虑进去,确保能够做到时间点的一致性。
- 异步复制或为逻辑复制预留插槽(reserved slot)可能不太常见,但也不是没有。产生大量变更(以及 WAL 文件)可能会让性能较差(或不频繁)的复制系统落后相当长的时间。虽然这种情况可以接受,但必须确保源系统有足够的磁盘空间来长时间保存 WAL 文件。
如你所见,变更列数据类型并不像看起来那么简单。目前的 CI/CD 实践使得软件开发人员很容易就能提交并在生产环境中部署数据库 schema 变更,但这往往导致他们在几分钟后就面临生产环境的突发事件。虽然使用预发布环境可能有所帮助,但由于负载水平或成本限制,这样的环境并不能完全模拟生产环境的情况。
因此,问题的核心(我要再次强调)在于数据修改的规模、系统的整体拥堵状况、输入 / 输出性能以及目标数据库表在应用程序设计中的重要性。
最终,这些因素共同决定了完成 schema 变更所需的总时间,以及你的业务可能面临的独特限制 —— 这些限制你的业务可能承担得起,也可能承担不起。解决这一问题的最简单方法是在流量较低的时段安排计划内的维护工作,并确保其顺利完成。
如何安全地变更 PostgreSQL 列类型
如果你面临需要重写数百 GB 甚至 TB 的数据,并且几乎不能承受任何停机时间的情况,该如何安全地变更列类型呢?以下是一些步骤和策略。
让我们从坏消息开始:您无法避免重写整个表,这将在重写过程中产生大量 WAL 文件。这是必然的,你必须计划好如何管理它。
好消息是,你可以将可能的停机时间分散到一个比数据处理本身更长的时间段。具体的需求和限制会根据各个业务的需要而有所不同,因此需要仔细规划。
完整的 schema 过程可以总结为以下几个步骤:
- 在目标表中添加一个新的列,并确保其类型正确。确保该列可为空且没有默认值,以避免全表重写。(更正:自 PostgreSQL 11 起,实际上无需重写整个表即可实现这一功能 )例如,如果需要增加 order_id 的 ID,你应添加一个名为 new_order_id 的新列。
- 设置一个触发器,以便在新数据进入时更新这一新列,确保在 schema 变更期间所有新数据都能及时填充到新列中。
- 设计一个批处理变更逻辑,从旧列逐步变更到新列。批量大小和执行时间应根据业务或环境的实际运营限制进行调整。
- 变更旧值:根据你的数据量、输入 / 输出能力及其他约束,旧数据的变更可能需要几小时到几周不等。对于较短的变更,你可能只需要在终端会话中运行一个 SQL 或 PL/pgSQL 函数(可以考虑使用 tmux);而对于更长时间的变更,则可能需要更复杂的方法。
- 变更完成后,为新列创建相应的约束和索引,并注意任何可能的锁定问题,尤其是当新字段作为外键的一部分时。
此时,你已准备好进行实际的切换操作。一旦确认所有数据行都已正确变更到新列,就到了面对最为复杂部分的时候了。如果可能的话,尽量在一次事务处理中完成,或安排在较短的计划停机期间进行。
- 删除原有的旧列。这个操作通常只会让表短暂地锁定。
- 在删除旧列之后,将新列重命名。这一步基本上标志着 schema 变更过程的完成。
考虑重新启动依赖于已更改表的所有应用程序是一种很好的做法,因为某些工具(ORM...... 说的就是你)可能会缓存 OID,无法从容应对更改。
然而,问题并未完全解决。仅仅删除列,只是移除了引用,而数据本身还会物理存留在磁盘上。在这种情况下,你可能需要执行一个全表清理(VACUUM FULL),这个操作可能会锁住整个表并完全重写数据,这有可能违背了进行在线 schema 变更的初衷。这时就需要「膨胀克星:pg_repack 与 pg_squeeze」出马了 —— 了解和准备这些工具是非常必要的。
结论
在 PostgreSQL 中变更列类型的操作可能看起来简单 —— 仅需执行一个 ALTER TABLE 命令。然而,对于涉及的每一个人来说,理解这一操作的复杂性至关重要。无论你是提出变更请求的软件开发者、负责审查此变更的人,还是在这类更改部署到生产环境后负责解决相关问题的技术人员,深刻理解这个过程都非常关键。此外,了解这种具体的变更还可以帮助你更容易地对其他可能代价高昂的操作有所预见。
相关文章:
如何通过变更让 PostgreSQL 翻车
在开发应用程序和维护其后台数据库集群的过程中,我们经常会遇到实践与理论、开发环境与生产环境之间的差异。其中一个典型的例子就是变更数据库中的列类型。 对于在 PostgreSQL(及其他符合 SQL 标准的系统)中变更列类型的常规操作࿰…...
MySQL表涉及规范
MySQL表设计规范是为了确保数据库表结构的合理性、可读性和可维护性。以下是一些建议和规范: 1.使用InnoDB存储引擎:InnoDB存储引擎提供了事务支持、行级锁定和外键约束,有助于提高数据的完整性和性能。 2.表名和字段名命名规范:…...
服务器Ubuntu22.04系统 使用dcocker部署安装ollama和搭配open_webui使用
服务器Ubuntu22.04系统 使用dcocker部署安装ollama和搭配open_webui使用 一、ubuntu和docker基本环境配置 1.更新包列表: 打开终端,输入以下命令: sudo apt-get updatesudo apt upgrade更新时间较长,请耐心等待 2. 安装docke…...
代理模式Proxy
一、代理模式(Proxy) 1.代理模式的定义 代理模式给某一个对象提供一个代理对象,并由代理对象控制对真实对象的访问,起到对代理对象已有功能的增强 通俗的来讲代理模式就是我们生活中常见的中介。 2.作用 中介隔离作用&#x…...
C++ 设计模式——抽象工厂模式
抽象工厂模式 抽象工厂模式 抽象工厂模式主要组成部分代码实现抽象工厂模式模式的 UML 图抽象工厂模式 UML 图解析优点和缺点适用场景 抽象工厂模式提供一个接口,用于创建一系列相关或相互依赖的对象,而无需指定它们的具体类。它通常用于需要创建多个产品…...
《亿级流量系统架构设计与实战》第十一章 Timeline Feed服务
Timeline Feed服务 一、概述1、分类2、功能 二、设计原理1、拉模式与用户发件箱2、推模式与用户收件箱3、推拉模式结合 三、关键技术1、内容与用户收件箱的交互(推模式)2、推送拆分子任务3、收件箱模型设计 内容总结自《亿级流量系统架构设计与实战》 一…...
氙灯老化试验箱试验机
氙灯老化试验箱,采用6.5KW大功率的精密水冷式氙灯,曝晒面积达到了6500cm2 功能强大,测试结果可靠 ◆ 满足国内外所有氙灯测试标准要求。 ◆ 采用氙灯灯管及滤光器组件,保证试验数据的可比性和重现性。 ◆ 自动旋转式三层鼓型样板架…...
【Qt】常用控件QRadioButton
常用控件QRadioButton QRadioButton是单选按钮,可以在多个选项中选择一个。 作为QAbstractButton和QWidget的子类,其属性和用法,对于QRadioButton同样适用。 属性说明 checkable 是否能选中 checked 是否已经被选中. checkable 是 checked…...
Mysql 离线版下载安装-(详细版)
Mysql 离线版下载安装-(详细版) 文章目录 Mysql 离线版下载安装-(详细版)1.0 下载地址2.0 解压到本地2.0.1 配置环境变量2.0.2 新建mysql配置文件ini2.0.3使用管理员启动 cmd 3.0 初始化密码忘记了4.0 修改初始化密码5.0 使用可视化工具登录Mysql 1.0 下载地址 地址࿱…...
Spring Boot和OCR构建车牌识别系统
博客主页: 南来_北往 系列专栏:Spring Boot实战 OCR介绍 OCR(Optical Character Recognition)是光学字符识别技术的缩写,它能够将图像中的文本转换为机器可读和编辑的数字文本格式。这种技术广泛应用于数据输入、文档管理…...
Java-自定义注解中成员变量是Class<?>
在Java中,自定义注解可以包含各种类型的成员变量,包括 Class<?> 类型。这种类型的成员变量 通常用于表示某个类的类型信息。下面我将详细介绍如何定义一个包含 Class<?> 类型成员变量的 自定义注解,并给出一些示例代码。 1. 定义自定义注解 定义一个自定义…...
SX_UNIX套接字通信_15
UNIX套接字通信的优势: UNIX套接字通信常用于一个项目中的进程之间通信,UNIX提供了与网络套接字相似的特性,但是避免了网络延迟,提高了性能,但是它只能在同一台机器上使用,无法跨越网络的进程间通信 实例&…...
JS模块化总结 | CommonJS、ES6
BV13W42197jR 个人笔记 目录 JS模块化基础知识1. 概述1.1 什么是模块化1.2 为什么需要模块化? 2 模块化规范3 导入&导出4 CommonJS规范4.1 初步体验4.2 导出数据4.3 导入数据4.4 扩展理解4.5 浏览器端运行 5 ES6模块化规范5.1 初步体验5.2 Node中运行ES65.3 导出数据①分别…...
25考研计算机组成原理复习·3.5高速缓冲存储器
高速缓冲存储器Cache 工作原理:将某些主存块复制到Cache中,缓和CPU与主存之间的速度矛盾局部性原理 时间局部性:现在访问的地址,不久之后也很可能被再次访问空间局部性:现在访问的地址,其附近的地址也很可…...
餐厅管理系统
目录 一、 系统简介 1.1需求分析 1.2 编程环境与工具 二、 系统总体设计 2.1 系统的功能模块图。 2.2 各功能模块简介。 三、 主要业务流程 (1)用户及管理员登录流程图 (2)信息添加流程 (3…...
杭州百腾教育科技 TiDB 6.5 to 7.5 升级记录
作者: reAsOn2010 原文来源: https://tidb.net/blog/612103f3 背景 使用 TiDB 作为我们的全量数据库已经有六七年了,当时还是 2.0 版本。早期TiDB的迭代和新特性的发布对于实际使用的影响还是很大的,所以从那个时候开始就有每…...
Redis的缓存穿透、击穿、雪崩
目录 缓存穿透 定义: 解决方法: 缓存击穿 定义: 解决方案: 缓存雪崩 定义: 解决方案: 缓存穿透、缓存击穿和缓存雪崩的区别 缓存穿透 定义: 查询一个不存在的数据,数据库未…...
【Django开发】前后端分离django美多商城项目第1篇:欢迎来到美多 项目主要页面介绍【附代码文档】
本教程的知识点为: 项目准备 项目准备 配置 1. 修改settings/dev.py 文件中的路径信息 2. INSTALLED_APPS 3. 数据库 用户部分 图片 1. 后端接口设计: 视图原型 2. 具体视图实现 用户部分 使用Celery完成发送 判断帐号是否存在 1. 判断用户名是否存在 后…...
【软件造价咨询】信息化项目预算评审看什么?
在信息化项目预算评审中,各方往往只重视预算金额部分,而忽视了项目建设的全局性和整体性把关,导致信息系统的重复建设、分散建设、业务和系统两张皮、重功能轻数据、重投资轻方案等问题频出,从而大幅降低财政投资效益。 例如&…...
第37讲:Cephfs文件系统的正确使用姿势
文章目录 1.Cephfs文件系统简介2.Cephfs文件系统细节介绍2.1.Cephfs文件系统多客户端隔离挂载2.2.Ceph集群中多个Cephfs如何单独使用 3.挂载多个Cephfs文件系统4.Cephfs文件系统多客户端隔离挂载实战4.1.创建一个Cephfs文件系统4.2.将Cephfs文件系统挂载到本地路径4.3.在Cephfs…...
单片机烧录
在设计芯片的时候,关于烧录的环节是一个不得不考虑的问题。 我们首先排除掉,由外部直接硬件操控FLASH 的方案,这个方案有很多缺点。 1、每个IC使用的FLASH型号是各不相同的,每种型号的FLASH的烧录命令和流程都有差别,这…...
mysql实现分布式锁
利用数据库的悲观锁实现分布式锁,实际应用中要考虑mysql的高可用。 DistributedLock.h #ifndef DistributedLock_H_ #define DistributedLock_H_// // DistributedLock.h: // #include "base/MySQLDriver.h" class CDistributedLock { public://// Construction/D…...
MySQL快速使用
关系型数据库(RDBMS):建立在关系模型基础上,由多张相互连接的二维表组成的数据库 特点:使用表存储数据,格式统一方便维护;使用SQL语言操作,标准统一使用方便 通用语法: …...
LeetCode41.缺失的第一个正数
1. 题目大意 给你一个未排序的整数数组 nums ,请你找出其中没有出现的最小的正整数。 请你实现时间复杂度为 O(n) 并且只使用常数级别额外空间的解决方案。 2. 思路分析 示例 1: 输入:nums [3,4,-1,1] 输出:2 解释࿱…...
ee trade:黄金投资与股票投资的区别
黄金和股票, 是金融市场中两种常见的投资工具, 它们拥有截然不同的特点和风险, 了解它们的差异, 可以帮助投资者制定更合理的投资策略。 一、 投资性质: 避险与成长, 两种投资方向 黄金: 被视…...
AI视频创作原理
重磅推荐专栏: 《大模型AIGC》 《课程大纲》 《知识星球》 本专栏致力于探索和讨论当今最前沿的技术趋势和应用领域,包括但不限于ChatGPT和Stable Diffusion等。我们将深入研究大型模型的开发和应用,以及与之相关的人工智能生成内容(AIGC)技术。通过深入的技术解析和实践经…...
idea vue项目删除node_modules时报文件损坏且无法读取,导致删除失败
解决办法,查看node_modules所在盘,右击点击属性-工具,点击检查驱动,查完后修复即可, 就能够成功删除损坏的文件了...
Linux下编译安装-单机模式
1.1 Linux下编译安装-单机模式 1.1.1 安装 (1).把安装包放在Linux文件系统下 (2).解压缩 tar -zxf redis-4.0.2.tar.gz (3).切换到解压后的目录 cd redis-4.0.2(4).编译 make(5).进入到src目录 cd src(6).执行安装 make install(7) .返回上级目录 cd .. (8) .修改配置&…...
RSSI定位算法
文章目录 一、定位算法简介1.1. 定位技术原理1.2. 定位算法二、RSSI测距原理2.1. 建模与测量终端到基站的距离三、定位3.1. 三边定位算法3.2. 加权三边定位算法3.3. 加权三角形质心定位算法3.4. 程序定位算法的执行流程一、定位算法简介 1.1. 定位技术原理 定位终端接收到iBe…...
布局管理(Layouts)-Qt-思维导图-学习笔记
布局管理(Layouts) Qt 提供了非常丰富的布局类,主要包括以下基本布局管理类 QBoxLayout 提供了水平和垂直的布局管理,可以将子部件按行或列排列。根据排列方向的不同,QBoxLayout 分为 QHBoxLayout(水平布局)和 QVBox…...
《区块链赋能游戏业:破解虚拟资产交易与确权难题》
在当今数字化的时代,游戏行业正以前所未有的速度发展,虚拟资产在游戏中的重要性日益凸显。然而,虚拟资产的交易和确权问题一直困扰着游戏开发者和玩家。随着区块链技术的引入,为解决这些问题带来了新的曙光。 首先,我…...
机器学习第十一章-特征选择与稀疏学习
11.1子集收集与评价 属性称为"特征" ,对当前学习任务有用的属性称为"相关特征" 、没什么用的属性称为"无关特 征" . 从给定的特征集合中选择出相关特征于集的过程,称为"特征选择"。 特征选择是一个重要的"…...
C#中客户端直接引用服务端Proto文件
gRPC 客户端是从 .proto 文件生成的具体客户端类型。 具体 gRPC 客户端具有转换为 .proto 文件中 gRPC 服务的方法。 下一步打开【服务引用】 控制面板 选择grpc选项,然后继续 到此配置完成,然后就和服务共用一份protocol文件...
SiLM5932SHO系列SiLM5932SHOCG-DG 12A/12A强劲驱动电流能力 支持主动短路保护功能(ASC)单通道隔离门极驱动器
SiLM5932SHO系列是一款单通道隔离驱动器,提供12A源电流和12A灌电流。主动保护功能包括退饱和过流检测、UVLO、隔离故障报警和 4A 米勒钳位。输入侧电源的工作电压为3V至5.5V,输出侧电源的工作电压范围为13V至30V。所有电源电压引脚都有欠压锁定 (UVLO) 保…...
本地项目上传github
一、先在github(GitHub: Let’s build from here GitHub)上创建仓库 1,登录github后,点击右上角头像,点击 Your repositories 2,点击new 3,填写仓库名,假设命名 testhub࿰…...
使用zip包来安装mysql
下载 下载地址mysql,使用5.7.23 配置环境变量 添加到系统变量中 C:\Users\Admin\Downloads\mysql-5.7.23-win32\bin 添加my.ini配置文件 在C:\Users\Admin\Downloads\mysql-5.7.23-win32目录下添加my.ini [mysqld] # 设置3306端口 port3306# 自定义设置mysql的安装目录 b…...
嵌入式面经篇十——驱动开发
文章目录 前言一、驱动开发1、Linux 驱动程序的功能是什么?2、内核程序中申请内存使用什么函数?3、内核程序中申请内存和应用程序时申请内存有什么区别?4、自旋锁和信号量在互斥使用时需要注意什么?在中断服务程序里面的互斥是使用…...
MySQL(四)——常用函数
文章目录 函数字符串函数数值函数日期函数流程函数 函数 函数,是指一段可以直接被另一段程序调用的程序或代码。 MySQL中内置了许多函数,我们只需在合适的场景下调用它们即可,调用函数查询结果直接使用SELECT即可,并且可以嵌套使…...
C++ //练习 17.38 扩展上一题中你的程序,将读入的每个单词打印到它所在的行。
C Primer(第5版) 练习 17.38 练习 17.38 扩展上一题中你的程序,将读入的每个单词打印到它所在的行。 环境:Linux Ubuntu(云服务器) 工具:vim 代码块 #include<iostream> #include<…...
NC 丑数
系列文章目录 文章目录 系列文章目录前言 前言 前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站,这篇文章男女通用,看懂了就去分享给你的码吧。 描述 把只包含质因…...
Spring Boot 整合 Spring AI 实现项目接入ChatGPT(OpenAl的调用)
当前各种AI项目层出不穷,但绝大多数都是用python写的,现在Spring开源了Spring AI项目,让Java开发者也可以轻松给自己的springboot项目集成AI能力。目前spring AI正式版本为0.8.1,支持接入openAI、Ollama、Azure openAI、Huggingfa…...
react中 useContext 和useReducer的使用
在React中,useContext 和 useReducer 是两个非常有用的Hooks,它们分别用于管理跨组件的状态和复杂的状态逻辑。下面将分别介绍这两个Hooks的使用方式及其结合使用的场景。 1. useContext useContext 允许你订阅React的Context变化。Context提供了一种在…...
Android:动态更新app启动图标和应用名
一、需求背景 每逢重要佳节,很多应用启动图标会自动更新为对应佳节的图标,应用无需更新。 二、效果图 更新后的启动图标和应用名称 三、实现流程 Android app只能替换内置的icon,因此需要提前将logo图标放入App资源文件件里 实际项目App更新…...
深入探讨 ElementUI 动态渲染 el-table
在前端开发中,表格是不可或缺的一部分。无论是数据展示、数据录入,还是数据分析,表格都扮演着重要的角色。而在 Vue.js 生态系统中,ElementUI 提供了一个强大且灵活的表格组件——el-table。本文将带你深入了解如何使用 ElementUI…...
数据炼金术:用Python爬虫精炼信息
标题:数据炼金术:用Python爬虫精炼信息 在数据泛滥的互联网时代,Python爬虫不仅是搜集信息的利器,更是清洗和格式化数据的炼金术。本文将带你走进数据清洗和格式化的世界,展示如何使用Python爬虫从海量网络信息中提取…...
C++第三十八弹---一万六千字使用红黑树封装set和map
✨个人主页: 熬夜学编程的小林 💗系列专栏: 【C语言详解】 【数据结构详解】【C详解】 目录 1、set/map基本结构 2、红黑树基本结构改造 3、红黑树的迭代器 4、set的模拟实现 5、map的模拟实现 6、完整代码 1、set/map基本结构 在封装…...
★ C++基础篇 ★ vector 类
Ciallo~(∠・ω< )⌒☆ ~ 今天,我将继续和大家一起学习C基础篇第六章----vector类 ~ 目录 一 vector的介绍及使用 1.1 vector的介绍 1.2 vector的使用 1.2.1 vector的定义 1.2.2 vector iterator 的使用 1.2.3 vector 空间增长问题 1.2.4 vecto…...
原生js用Export2Excel导出excel单级表头和多级表头数据方式实现
原生js用Export2Excel导出excel单级表头和多级表头数据方式实现 原生js用Export2Excel导出excel单级表头和多级表头数据方式实现HTML文件导入需要的文件HTML文件中实现导出函数HTML总代码实现汇总(直接复制代码,注意js引入路径) 原生js用Expo…...
急需翻译PDF文件怎么办?pdf翻译在线快速帮你解决
面对满屏幕密密麻麻的pdf文件,我常常感到头疼! 语言障碍让我在获取信息的道路上踌躇不前,但自从我发现了pdf在线翻译成中文的神奇工具,一切问题似乎都迎刃而解。 这些软件不仅让我能够快速跨过语言壁垒,还让我在学术…...
线程安全的集合类和并发数据结构
在Java中,线程安全的集合类和并发数据结构对于处理多线程环境下的数据共享和同步至关重要。这些集合和数据结构通过不同的机制来确保在多线程环境下数据的一致性和完整性。以下是一些常见的线程安全的集合类和并发数据结构: 线程安全的集合类 Vector 描…...