事务,不只ACID | 京东物流技术团队

1. 什么是事务?

应用在运行时可能会发生数据库、硬件的故障,应用与数据库的网络连接断开或多个客户端端并发修改数据导致预期之外的数据覆盖问题,为了提高应用的可靠性和数据的一致性,事务应运而生。

从概念上讲,事务是应用程序将多个读写操作组合成一个逻辑单元的一种形式,这样其中所有的读写操作都被视为单个操作来执行,要么成功提交,要么失败回滚,不存在任何部分成功和部分失败的情况。现在,几乎所有的关系型数据库和一些非关系型数据库都支持事务。

1.1 ACID

事务通过ACID来保证安全的操作,它们分别是原子性(Atomicity)一致性(Consistency)隔离性(Isolation)持久性(Durability)。ACID的提出旨在为数据库容错机制建立精确的术语,但是它在不同的数据库中实现并不相同,我们来对其逐一的进行解释。

  • 原子性

原子性定义的特征是:一个事务必须被视为一个不可分割的工作单元,整个事务中的所有操作要么全部提交成功,要么全部失败回滚,不可能只执行其中的一部分操作。


  • 一致性

一致性在ACID中是“多余的”存在,它不同于原子性、隔离性和持久性,一致性是应用程序的属性,而其他三者是数据库的属性。应用可能依赖原子性和隔离性来保证一致性,但有时候一致性的保证并不仅仅取决于数据库。

一致性的体现依赖应用程序对数据的约束,比如在会计系统中,所有账户的交易收支一定是平衡的。如果一个事务开始于一个平衡状态,那么在该事务执行完成提交后,那么依然会保持平衡。从概念上来说,一致性是对数据的一组特定约束必须始终成立,这一点由应用程序来保证,因为这些写入和修改的逻辑都是由应用程序决定的,数据库只负责对这些操作进行执行。


  • 隔离性

在实际工作中,大多数数据库都同时被多个客户端访问,这就可能会发生客户端并发修改同一条数据的情况,引发并发问题。如下图中例子所示:

隔离性.png

User1和User2要同时在数据库中操作计数器增长,每个用户都是先读取值,执行加1,然后写入。理论上计数器的值最终应该为44,但是由于并发问题,使得终值为43。

通常来说,隔离性让一个事务所做的修改在最终提交以前,对其他事务不可见,它解决的是并发问题。如果一个事务进行多次写入,则另一个事务要么看到全部写入结果,要么什么都看不到。所以在上述例子中,User2在修改计数器时读取到的值应该是User1修改完之后的结果43,之后执行加1,使得最终结果为44。


  • 持久性

持久性是一个承诺,即事务成功提交,即使发生硬件故障或者数据库崩溃,写入的任何数据都不会丢失,在单节点数据库中,它通常意味着数据已经被写入硬盘或SSD;在多节点数据库中,持久性可能意味着数据已经成功复制到一些节点。但是,如果硬盘和备份被销毁,那么显然没有任何数据库能再找回这些数据,所以完美的持久性并不存在

2. 并发产生数据不一致的问题

往往由于事务之间的操作对象有竞争关系,并且又因为并发事务之间不确定的时序关系,会导致这些所操作的有竞争关系的对象会出现各种奇怪的结果,下面我们就来看看这些常见的问题。

2.1 脏写

两个事务尝试同时更新数据库中相同的对象,如果先前的写入是尚未提交事务的一部分,后面的写入将一个尚未提交的值覆盖掉了,这种情况被称为脏写

2.2 脏读

如果A事务已经将一些数据写入数据库,但是A事务还没有提交或中止,现在开启另一个B事务查询,那么B事务能看到A事务中没有提交的数据,这就是脏读

我们考虑一种情况,一旦A事务发生回滚,B事务很有可能将未提交过的数据提交给数据库,因此造成的问题会让人无从下手去排查。

2.3 不可重复读

我们拿一个例子来说,Alice 在银行有 1000 美元的储蓄,分为两个账户,每个 500 美元。现在有一个事务从她的一个账户转移了 100 美元到另一个账户。如果她在事务处理的过程中查看其账户余额,她可能在发出转账之后看到付款账户的余额为 400 美元,而收款账户的余额仍为 500 美元。对 Alice 来说,现在她的账户看起来总共只有 900 美元,转账的 100 美元似乎凭空消失了,而再对收款账户进行读取时,发现余额变成了 600 美元。

这种情况被称为不可重复读,又被称为读偏差,即对同一数据两次读取的结果不一致。

2.4 丢失更新

两个事务同时执行读取-修改-写入序列,其中一个写操作在没有合并另一个写操作变更的情况下,直接覆盖了另一个写操作的结果,导致了数据的丢失,这种情况被称为丢失更新

比较直接的避免丢失更新的方法是不使用读取-修改-写入这一系列操作,而是进行原子更新,以计数器为例,SQL如下。它的原理通常是获取要读取对象的排他锁,使得事务在修改同一数据时依次执行。

update counters set value = value + 1 where key = 1;

如果不能避免读取-修改-写入这一系列操作,那么可以通过显式加锁(FOR UPDATE)的方式来避免丢失更新,使得任何其他想要读取同一对象的事务被阻塞,直到第一个获取到该锁的事务执行完毕。

BEGIN TRANSACTION;SELECT * FROM xxx FOR UPDATE;-- 执行业务逻辑
UPDATE xxx SET ...;COMMIT;

比较并设置(CAS)是一种比较常见的乐观的避免丢失更新的操作。当对数据更新时,会将数据表中的值和读取值进行对比,只有在没有发生改变的情况下才允许更新,否则需要重试这个事务。一般在工作中会采用在数据表中添加时间戳列的方式来实现CAS。

2.5 幻读和写入偏差问题

幻读用一句话来概括就是:一个事务的写入改变了另一个事务的搜索查询结果

A事务的select查询出符合条件的数据,并检查是否符合业务要求,根据检查结果决定业务是否继续执行。如果此时B事务对数据进行修改,并符合A事务select的查询条件,那么A事务在执行完写入操作后,再次执行select查询会发现不同的结果,这很可能会导致写入偏差问题。

写入偏差问题是两个事务读取相同的对象,然后更新其中一些对象时发生了预期之外的异常情况。它区别于脏写和丢失更新,因为它是两个事务正在更新两个不同的对象。如下面这个例子所示,Alice 和 Bob 是两位值班医生,两人都感到不适,所以他们都决定请假。不幸的是,他们恰好在同一时间点击按钮下班:

幻读例子.png

这导致了没有医生值班,违反了至少有一名医生值班的业务要求。

解决写入偏差问题比较麻烦,因为它涉及多个对象,采用单对象原子操作的方法不能解决。通常情况下会采用更改隔离级别为可串行化或通过加锁的方式来解决。

但是,加锁的方式并不是在所有情况下都适用。比如,多人预定同一时段的会议室,因为该时段的会议室预定记录还没有生成,导致多人读取预定纪录时都没有读到对应的结果值,所以就无从加锁,那么此时将会造成多人预定同一时段会议室的结果。为了解决这种情况,可以再创建一张数据表管理会议室的时间段,当有人想预定某时段会议室时,会将该时段的数据进行加锁,那么这时再有其他用户来查询时,将会被阻塞,这种方法被称为物化冲突

3. 隔离级别

数据库一直试图通过事务隔离解决并发问题。可串行化隔离级别能保证事务串行执行,这意味着不会发生并发问题。但是在实际生产中为了保证系统的性能,往往不会采用该隔离级别,而是会采用一些较弱的隔离级别,它们可能在某些情况下不能保证数据的一致性,但是能够让系统的性能更好。下面我们对这些隔离级别进行介绍:

3.1 读未提交

该隔离级别相对更弱,只能避免脏写

3.2 读已提交(Read Committed)

这种隔离级别非常流行,它能够避免脏读脏写

最常见的情况是使用行锁来防止脏写:当事务想要修改同一个对象时,则必须等到第一个事务提交或回滚后才能获取该行的锁继续。

脏读也可以通过加读锁来避免,但是这种方式会导致在有长时间的写入事务持有要读数据的锁时,读请求被阻塞,所以这种方式在实践中的效果并不好。另一种避免方式是数据库将已经写入的旧值记住,即使发生新的写入事务且并没有执行完时,读请求读取到的都是这个旧值,只有当该写事务提交时才能读取到新值。

3.3 可重复读

可重复读能够避免脏写、脏读、不可重复读和只读查询中的幻读,快照隔离是实现可重复读的常见解决方案。每个事务都从数据库的一致性快照中进行读取,那么这也就意味着该事务能看到事务开始时在数据库中提交的所有数据。即使这些数据随后被新的事务更改,该事务还仍然读取的是在事务开始时的旧数据。这种办法对长时间运行的只读查询非常有用,因为如果在查询过程中数据不断的变化,那么没有办法对数据进行分析。

不提供快照隔离的读已提交不能实现可重复读,因为它只记住了数据的两个版本

快照隔离也是通过写锁的方式来避免脏写,而避免脏读的方式无需加锁,而是通过读取数据库中维护的对应版本的数据对象,它的关键原则是读不阻塞写,写不阻塞读。这也就意味着数据库在处理一致性快照上的长时间查询时,能够同时处理写入,而不会发生锁的争用。


使用InnoDB引擎的MySQL对快照隔离的实现方法是MVCC多版本并发控制,它会同时维护单个对象的多个版本,以提供多个不同时间节点的数据状态,我们下面来简单地看一下它的实现原理。

对于InnoDB引擎的表来说,它的聚簇索引记录中都包含两个必要的隐藏列:trx_idroll_pointer

  • trx_id: 事务每次对某条聚簇索引记录进行改动时,都会把该事务的事务ID赋值给 trx_id
  • roll_pointer: 每次对某条聚簇索引记录进行改动时,都会把旧的版本写入到 undo 日志中, 这个隐藏列相当于一个指针,可以通过它来找到该记录修改前的信息。我们举个例子来理解它,假设表 hero 中只包含一条记录:
mysql> select * from hero;
+-----+-----+
|id   |name |
+-----+-----+
|1    |刘备 |
+-----+-----+

指定插入该记录的事务ID为80,此时再开启两个事务对这条记录进行修改,每次修改都会生成一条 undo log,每条日志也都有 trx_id 属性和 roll_pointer 属性。通过 roll_pointer 属性可以将多条 undo log 连接成一条链表,如下图所示:

roll_pointer.png

这个链表被称为版本链,版本链的头节点是当前最新的记录,利用这个记录的版本链可以来控制并发事务访问相同记录时的行为,这种方式被称为多版本并发控制

事务在执行第一次查询的时候会生成一致性快照(Read View),通过它来判断版本链中的哪个版本对当前事务是可见的。Read View 中包含4个比较重要的内容如下:

  • m_ids: 生成 Read View 时,当前系统中活跃的读写事务的 id 列表,它用来保证即使活跃的这些事务被提交,它们的写入也会被当前事务忽略
  • min_trx_id: 当前系统中活跃的读写事务的最小事务 id
  • max_trx_id: 系统应该分配给下一个事务的事务 id
  • creator_id: 生成该 Read View 的事务的事务 id

有了 Read View,只需要按照下面的步骤去判断记录中的某个版本是否可见:

  • 如果被访问版本的 trx_id 属性值与 Read View 中的 creator 中的 creator_trx_id 值相同,则意味着当前事务在访问自己修改过的内容,该版本能够被当前事务访问
  • 如果被访问的版本的 trx_id 属性值小于 Read View 中的 min_trx_id 值,表明生成该版本的事务在当前事务生成 Read View 前已经提交,这些版本能够被当前事务访问
  • 如果被访问的版本的 trx_id 属性大于或等于 Read View 中的 max_trx_id 值,表明生成该版本的事务在当前事务生成 Read View 之后才开启,那么该版本不能被当前事务访问
  • 如果被访问的版本的 trx_id 属性值在 Read View 的 min_trx_id 和 max_trx_id 之间,则需要判断 trx_id 是否在 m_ids 列表中。如果在,说明创建该 Read View 时生成该版本的事务还是活跃的,所以该版本不可见;如果不在,说明创建该 Read View 时生成该版本的事务已经被提交,该版本可以被访问

也就是说,想要满足记录对当前读事务可见,需要创建该记录的事务在当前读事务开启前已经提交

3.4 可串行化

可串行化通常被认为是最强的隔离级别,能够避免我们上诉所有数据不一致问题。它能保证即使事务可以并行执行,但最终的结果也是一样的,就好像它们没有任何并发性,连续挨个执行一样。也就是说,数据库可以防止所有可能的竞争条件。

可串行化的实现技术大多采用如下3种方式之一:串行化执行事务两阶段锁定可串行化快照隔离

串行化执行事务

使用这种技术实现必须要求每个事务小而快,如果其中有一个缓慢的事务,那么自然会将其他事务拖慢。除此之外,这种方式限于活跃数据集可以放入内存的情况,如果需要在事务中访问磁盘中的数据,那么系统也会变得非常慢。写入吞吐量必须低到在单个CPU核上处理,如若不然,事务需要能划分至单个分区,且不需要跨分区协调。当然跨分区事务可以实现,但是它的执行效率会非常低。所以,串行化执行事务的伸缩性较差。

两阶段锁定(2PL, two pahse locking)

两阶段提交的含义是:第一阶段事务执行时获取锁(共享锁/排他锁),第二阶段在事务执行完成时释放锁。它要求没有写入时多个事务都可以读取同一个对象,但是只要有写入就会独占访问读阻塞写,写也会阻塞读,与快照隔离不同,因此两阶段锁定可以避免竞争条件而实现可串行化。

Mysql的InnoDB引擎实现可串行化隔离级别采用的就是2PL机制。

两阶段锁定的性能很差,不仅是因为它获取和释放锁的开销,而且还包括并发性的降低,因为如果两个事务修改同一个对象时,第二个事务必须要等待第一个事务执行完为止。除此之外,2PL实现的可串行化隔离出现死锁的情况也比较频繁。

可串行化快照隔离(SSI, serializable snapshot isolation)

可串行化快照隔离是一种乐观的并发控制技术,它在快照隔离的基础上,添加了一种算法来检测写入之间的串行化冲突,并确定要终止哪些事务。

乐观意味着如果存在潜在的危险也不阻止事务,而是继续执行事务,希望一切都会好起来。当一个事务想要提交时,数据库检查是否有什么不好的事情发生(即隔离是否被违反),如果是的话,事务将被中止,并且必须重试。在争用不是很高时,乐观的并发控制往往比悲观的并发控制性能要好。

事务从数据库中读取一些数据,并根据这些数据进行条件判断执行业务逻辑时,在快照隔离的条件下,往往先前的查询结果不是最新的,因为在数据查询之后,该数据可能会被修改,所以执行的业务逻辑可能会出现异常。因此在事务提交时判断先前读的数据是否发生改变就需要两方面的校验:

  1. 检查是否存在读之前未提交的写入
  2. 检查读之后的写入

只有通过这些校验后才能保证事务提交时使用的数据是新的。

可串行化快照隔离与串行执行相比,可串行化快照隔离并不局限于单个 CPU 核的吞吐量,所以它的伸缩性更好;可串行化快照隔离与两阶段锁定相比,它的最大优点是一个事务不需要阻塞等待另一个事务所持有的锁,就像在快照隔离下一样,读不阻塞写,写也不阻塞读,这对于读取较多的业务场景非常友好。

可串行化快照隔离的性能表现在中止率上,如果长时间的读写事务较多,很可能会经常发生冲突导致事务中止。因此在事务比较短小的情况下,可串行化快照隔离的表现更好。

4. 及时性与完整性

ACID事务通常能保证强一致性,也就是说,写入者会等到事务提交,而且在写入完成后,写入结果对所有读取者可见。在强一致性这个语义中,包含两个特别值得考虑的方面:

  • 及时性:这意味着确保用户观察到系统的最新状态。如果不是强一致性而是最终一致性的情况,那么用户可能会读取到陈旧的数据,但这种不一致是暂时的,最终都会通过等待与简单地重试得到解决
  • 完整性:完整性代表数据没有丢失、矛盾或错误,即没有损坏。尤其是某些衍生数据集(缓存、搜索索引等),它们一定要与底层数据库保持一致。在ACID事务中,原子性和持久性是保证完整性的重要原则

有意思的是:基于异步流处理系统实现的分布式事务,它能够将及时性与完整性分开,只保证完整性,而不保证及时性,除非我们显示地构建一个在事务提交返回结果之前明确等待特定消息到达的消费者。

下面我们来看一个基于流处理系统实现分布式事务的例子,来加深对及时性和完整性的理解。

4.1 使用基于日志的消息队列保证完整性

我们以转账为例,比如有三个分区:一个包含请求ID,一个包含收款人账户,另一个包含付款人账户。如果在数据库传统的方法中,执行此事务需要跨三个分区进行原子提交,这样就需要协调分布式事务,因此吞吐量很可能会受到影响。但事实上使用基于日志的消息队列实现的流处理系统,可以达到等价的数据完整性而不需要原子提交。例子执行过程如下:

  1. 从账户 A 向账户 B 转账的请求由客户端提供一个唯一的请求 ID,并按请求 ID 追加写入相应的消息队列,并对该消息进行持久化
  2. 消费者读取请求日志。对于每个请求消息,它向输出流发出两条消息:付款人的借记指令(A分区),收款人的贷记指令(B分区),发出的消息中会携带原始的请求ID
  3. 后续消费者消费借记和贷记指令,按照ID除重,并将变更应用到账户的余额

为了在多分区间保证数据完整性而且还要避免对分布式事务的协调(2PC等协议),我们首先需要将这个事务所要做的事情持久化为单条记录,然后从这条消息记录中衍生出贷记指令和借记指令。在几乎所有的数据系统中,单对象的写入都是原子性的:即请求要么出现在日志中,要么都不出现。

如果流处理在步骤2崩溃,则它会从上一个存档点恢复处理,这样它就不会跳过任何消息,但可能会生成多条重复的借记/贷记指令,不过由于它是确定性的,因此它生成的只是相同的指令,在步骤3中的处理器可以通过ID值轻松地去重。

在上述例子中,我们把一个操作拆分为跨越多个阶段的流处理器,消息记录的消费是异步的,发送者不会等其消息被消费处理完,而且这个消息与消息的处理结果被解耦,所以我们没有对及时性进行保证,只是保证了完整性。

一般地,我们在借助可靠的流处理系统时无需再协调分布式事务或采用其他原子提交协议就能保证完整性,其中所包含的机制如下:

  • 将写入操作的内容表示为单条消息,这样就保证了写入的原子性
  • 从这一消息中衍生出其他所需要的状态变更
  • 将客户端生成的请求ID传递通过所有的处理层,从而能达到去重和保证幂等性的目的
  • 保证消息不可变,并允许衍生数据能被随时重新处理,这使从错误中恢复更加容易

4.2 完整性的重要性

不论是ACID事务还是基于流处理系统的分布式事务,它们都保证数据的完整性。因为违反及时性可能会令人困惑,不过这只是暂时的,但是如果违反完整性,那么它的结果可能是灾难性的。违反一致性,最终一致性;违反完整性,永无一致性,是最好的概括。


巨人的肩膀

  • 《数据密集型应用系统设计》:第七章 事务、第十二章 数据系统的未来
  • Replication(下):事务,一致性与共识
  • 《MySQL是怎样运行的》第二十一章
  • 《高性能MySQL 第四版》第一章

作者:京东物流 王奕龙

来源:京东云开发者社区

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/30391.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

用C语言高效地打印杨辉三角

假设杨辉三角的通项公式为a(n),则打印形式如下: 然而我们知道,它应该是这样的: 三角形两边的值都为1,且每个元素的值都为该元素正上方和其正上方前面的元素的值之和。 为了实现这个代码,我们需要知道每行首…

最新SQLMap进阶技术

点击星标,即时接收最新推文 本文选自《web安全攻防渗透测试实战指南(第2版)》 五折购买链接:u.jd.com/3ibjeF6 SQLMap进阶:参数讲解 (1)--level 5:探测等级。 参数“--level 5”指需…

数据库签名的那些事儿

写在前面,关于签名的应用场景 除了我们后端经常使用的接口签名来校验数据这些常见的场景,对于数据安全性要求比较严格的业务来说,大部分落库的核心数据 也都需要签名,为啥? 因为怕数据库的数据被篡改数据或者被攻击了&#xff0c…

vue2.7如何使用vue-i18n

版本: vue:2.7.0 vue-i18n:8.28.2 一、下载 npm i vue-i18n8.28.2二、新建 新建一个文件,例如:lang,项目结构如下: index.js: import Vue from vue import VueI18n from vue-i18n…

Go Windows下开发环境配置(图文)

Go Windows下开发环境配置 下载 安装 点击下载的安装包进行安装。安装路径可以选择到自己的目录。 环境变量配置 GOROOT:(指定到安装目录下) GOPATH:(是工作空间) path:在安装时已经添加了…

go-admin 使用开发

在项目中使用redis 作为数据缓存:首先引入该包 “github.com/go-redis/redis/v8” client : redis.NewClient(&redis.Options{Addr: config.QueueConfig.Redis.Addr, // Redis 服务器地址Password: config.QueueConfig.Redis.Password, // Redis 密码&…

UNIX 系统概要

UNIX 家族UNIX 家谱家族后起之秀 LinuxUNIX vs LinuxUNIX/Linux 应用领域 UNIX 操作系统诞生与发展UNIX 操作系统概要内核常驻模块shell虚拟计算机特性 其他操作系统 LinuxRichard StallmanGNU 项目FSF 组织GPL 协议Linus Torvalds UNIX 家族 有人说,这个世界上只有…

网络安全设备及部署

什么是等保定级? 之前了解了下等保定级,接下里做更加深入的探讨 文章目录 一、网路安全大事件1.1 震网病毒1.2 海康威视弱口令1.3 物联网Mirai病毒1.4 专网 黑天安 事件1.5 乌克兰停电1.6 委内瑞拉电网1.7 棱镜门事件1.8 熊猫烧香 二、法律法规解读三、安…

OpenCV 中的光流 (C++/Python)

什么是光流? 光流是一项视频中两个连续帧之间每像素运动估计的任务。基本上,光流任务意味着计算像素的位移矢量作为两个相邻图像之间的对象位移差。光流的主要思想是估计物体由其运动或相机运动引起的位移矢量。 理论基础 假设我们有一个灰度图像——具有像素强度的矩阵。我…

【知网检索稳定】第五届经济管理与文化产业国际学术会议(ICEMCI 2023)

抓住数字经济的发展机遇,推动当前文化旅游产业与经济的深度融合才能不断推进经济大格局。第五届经济管理与文化产业国际学术会议(ICEMCI 2023)将继续围绕“经济管理”与“文化产业”两大研究领域展开讨论,旨在为相关研究方向的专家…

Talk | 清华大学交叉信息研究院助理教授许华哲:具身控制中的泛化能力

本期为TechBeat人工智能社区第520期线上Talk! 北京时间8月9日(周三)20:00,清华大学交叉信息研究院助理教授—许华哲的Talk已准时在TechBeat人工智能社区开播! 他与大家分享的主题是: “具身控制中的泛化能力”,从具身控制中视觉外…

手机app测试

一、安装、卸载、更新、运行 1.安装、卸载 应用是否可以正常安装(命令行安装;apk/ipa安装包安装)(有网,无网是否都正常)卸载过程中出现死机,断电,重启等意外的情况&…

小程序自动化测试的示例代码

目录 背景 自动化 SDK 还原用户行为 总结 背景 上述描述看似简单,但是中间还是有些难点的,第一个难点就是如何在业务人员操作小程序的时候记录操作路径,第二个难点就是如何将记录的操作路径进行还原。 自动化 SDK 如何将操作路径还原这…

哪个类包含clone方法?是Cloneable还是Object?

在Java中,clone方法是定义在Object类中的。所有的Java类都继承自Object类,因此每个Java对象都继承了clone方法。然而,要成功地使用clone方法,需要满足一些条件,其中之一是被克隆的类必须实现Cloneable接口。 虽然clone…

[C#] 简单的俄罗斯方块实现

一个控制台俄罗斯方块游戏的简单实现. 已在 github.com/SlimeNull/Tetris 开源. 思路 很简单, 一个二维数组存储当前游戏的方块地图, 用 bool 即可, true 表示当前块被填充, false 表示没有. 然后, 抽一个 “形状” 类, 形状表示当前玩家正在操作的一个形状, 例如方块, 直线…

OCR让纸质文档秒变电子文档,让自动驾驶成为现实

OCR文字识别具有广泛的应用范围,以下是一些常见和广泛应用的领域: 1. 文档数字化:OCR可以将印刷的文档、书籍、报纸等纸质文档转换为可编辑和可搜索的电子文本,从而实现文档的数字化存储和管理。这在图书馆、档案馆、企业和政府机…

关于eclipse导入部署具有增删改查的项目

目录 前言:当我们刚刚进入公司的第一步就是去部署当前公司的项目,本博客就是详细介绍怎么去部署当前公司的项目。 一,开发工具: 二,具体步骤: 2.1导入公司的项目 打开eclipse开发工具 2.2配置当前的环…

Django实现音乐网站 ⑺

使用Python Django框架制作一个音乐网站, 本篇主要是后台对歌手原有实现功能的基础上进行优化处理。 目录 新增编辑 表字段名称修改 隐藏单曲、专辑数 姓名首字母 安装xpinyin 获取姓名首字母 重写保存方法 列表显示 图片显示处理 引入函数 路径改为显示…

oracle sql developer批量删除某个用户

随着navicate收费,还得破解,pl/sql developer配置麻烦,最近使用oracle sql developer来试试oracle的操作如何; 用着还行,没有卡顿现象, 最近要oracle sql developer批量删除某个用户下所有的表&#xff0…

如何恢复已删除的 PDF 文件 - Windows 11、10

在传输数据或共享专业文档时,大多数人依赖PDF文件格式,但很少知道如何恢复意外删除或丢失的PDF文件。这篇文章旨在解释如何有效地恢复 PDF 文件。如果您身边有合适的数据恢复工具,PDF 恢复并不像看起来那么复杂。 便携式文档格式&#xff08…