草庐IT

4. 事务和锁

LimeCoder 2023-03-28 原文

事务

  • 原子性

    事务(transaction)中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节。

  • 一致性:

    执行的结果必须是使数据库从一个一致性状态变到另一个一致性状态。因此当数据库只包含成功事务提交的结果时,就说数据库处于一致性状态。

  • 隔离性

    一个事务的执行不能被其他事务干扰。即一个事物内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事物之间不能互相干扰

  • 持久性

    一个事务的执行不能被其他事务干扰。即一个事物内部的操作及使用的数据对并发的其他事务是隔离的,并发执行的各个事物之间不能互相干扰

begin;   #开始事务
...
rollback;  #回滚事务
savepoint 回滚点;  #添加回滚点
rollback to 回滚点; #回滚到指定回滚点
...
commit; #提交事务

并发三大问题

  • 丢失修改:两个都改

    事务T1和事务T2同时读入同一数据进行修改,T1或者T2修改数据丢失的问题

  • 不可重复读:一读一改

    事务T1读取数据后,事务T2修改数据,使得T1两次读取的值不一致的问题

  • 读脏数据的问题:一读一改 + 事务撤销

    事务T2修改数据,事务T1读数据,T2事务撤销,T1读取数据不正确

排他锁和共享锁

  • 排他锁:写锁

    当前事务给数据添加写锁后,其他事务既不能读,也不能写,也不能添加锁。

  • 共享锁:读锁

    当前事务给数据添加读锁后,其他事务仅可以读,当前数据所有读锁释放后,才可以添加写锁。

  • 一级封锁协议

    事务T在修改数据R之前必须获得排他锁(解决丢失修改问题)

  • 二级封锁协议

    一级封锁协议+事务T在读数据R之前必须先对数据添加共享锁,读完立即释放(解决丢失修改和读脏数据的问题)

    因为事务结束前释放了共享锁,因此其他事务可以进行修改数据,因此无法解决不可重复读问题。

  • 三级封锁协议

    一级封锁协议+事务T在读数据R之前必须先对数据添加共享锁,且事务结束后才释放共享锁。

事务隔离

  • 读-未提交(read uncommitted):未上锁

    事务T1可以查看事务T2未提交的数据。(读脏数据的问题)

  • 读-已提交(read committed): 上了写锁,提交后解锁

    事务T1只能读取到T2已经提交后的数据。(不可重复读)

  • 可重复读(repeatable read): 三级封锁协议

    事务T1读取一张表时,事务T2不能修改那张表的数据。(幻读: 数据不真实)

  • 序列化/串行化(serializable)

    并发都没了,事务T1和事务T2不能对同一张表操作。

# 查看事务隔离级别
select @@transaction_isolation;
# 设置事务隔离级别
set global transaction ioslation level 隔离级别

全局锁

全局锁就是对整个数据库实例加锁,即数据库中的所有表都将被加上锁,加锁后整个实例就处于只读状态,后续的DML的写语句,DDL语句,已经更新操作的事务提交语句都将被阻塞。

# 开启全局锁,全局锁只有读
flush tables with read lock;
# 解除全局锁
unlock tables;

表锁

表锁作用于某一张表,是MyISAM和InnoDB存储引擎支持的方式,是MyISAM的默认锁机制。

# 添加写锁/读锁
lock table 表名称 read/write;
# 解锁
unlock tables;

为表添加写锁后,不能读取表中任何数据。

为表添加读锁后,不能修改表中任何数据。

行锁

InnoDB引擎支持行锁

-- 添加读锁(共享锁), 只能在事务中使用
select * from ... lock in share mode;
-- 查询时添加写锁(排他锁)
select * from ... for update;
-- 更新时自动添加写锁
update 表名 set xxx = xxx where xxx

行锁的细分,了解一下就好。

记录锁

(Record Locks)记录锁, 仅仅锁住索引记录的一行,在单条索引记录上加锁。Record lock锁住的永远是索引,而非记录本身,即使该表上没有任何索引,那么InnoDB会在后台创建一个隐藏的聚集主键索引,那么锁住的就是这个隐藏的聚集主键索引。所以说当一条sql没有走任何索引时,那么将会在每一条聚合索引后面加写锁,类似于表锁,但原理上和表锁是完全不同的。

间隙锁

(Gap Locks)仅仅锁住一个索引区间(开区间,不包括双端端点)。在索引记录之间的间隙中加锁,或者是在某一条索引记录之前或者之后加锁,并不包括该索引记录本身。比如在 1、2中,间隙锁的可能值有 (-∞, 1),(1, 2),(2, +∞),间隙锁可用于防止幻读,保证索引间的不会被插入数据。

临键锁

(Next-Key Locks)Record lock + Gap lock,左开右闭区间。默认情况下,InnoDB正是使用Next-key Locks来锁定记录(如select … for update语句)它可以根据场景进行灵活变换:

场景 转换
使用唯一索引进行精确匹配,但表中不存在记录 自动转换为 Gap Locks
使用唯一索引进行精确匹配,且表中存在记录 自动转换为 Record Locks
使用非唯一索引进行精确匹配 不转换
使用唯一索引进行范围匹配 不转换,但是只锁上界,不锁下界

引擎间的锁机制

使用InnoDB的情况下,在执行更新、删除、插入操作时,数据库也会自动为所涉及的行添加写锁(排他锁),直到事务提交时,才会释放锁,执行普通的查询操作时,不会添加任何锁。而当不使用where语句时,行锁会升级为读锁。

使用MyISAM的情况下,在执行更新、删除、插入操作时,数据库会对涉及的表添加写锁,在执行查询操作时,数据库会对涉及的表添加读锁。

有关4. 事务和锁的更多相关文章

  1. ruby - 我可以使用 aws-sdk-ruby 在 AWS S3 上使用事务性文件删除/上传吗? - 2

    我发现ActiveRecord::Base.transaction在复杂方法中非常有效。我想知道是否可以在如下事务中从AWSS3上传/删除文件:S3Object.transactiondo#writeintofiles#raiseanexceptionend引发异常后,每个操作都应在S3上回滚。S3Object这可能吗?? 最佳答案 虽然S3API具有批量删除功能,但它不支持事务,因为每个删除操作都可以独立于其他操作成功/失败。该API不提供任何批量上传功能(通过PUT或POST),因此每个上传操作都是通过一个独立的API调用完成的

  2. ruby - 分布式事务和队列,ruby,erlang,scala - 2

    我有一个涉及多台机器、消息队列和事务的问题。因此,例如用户点击网页,点击将消息发送到另一台机器,该机器将付款添加到用户的帐户。每秒可能有数千次点击。事务的所有方面都应该是容错的。我以前从未遇到过这样的事情,但一些阅读表明这是一个众所周知的问题。所以我的问题。我假设安全的方法是使用两阶段提交,但协议(protocol)是阻塞的,所以我不会获得所需的性能,我是否正确?我通常写Ruby,但似乎Redis之类的数据库和Rescue、RabbitMQ等消息队列系统对我的帮助不大——即使我实现某种两阶段提交,如果Redis崩溃,数据也会丢失,因为它本质上只是内存。所有这些让我开始关注erlang和

  3. ruby - 如何使用 ruby​​ mysql2 执行事务 - 2

    我已经开始使用mysql2gem。我试图弄清楚一些基本的事情——其中之一是如何明确地执行事务(对于批处理操作,比如多个INSERT/UPDATE查询)。在旧的ruby-mysql中,这是我的方法:client=Mysql.real_connect(...)inserts=["INSERTINTO...","UPDATE..WHEREid=..",#etc]client.autocommit(false)inserts.eachdo|ins|beginclient.query(ins)rescue#handleerrorsorabortentirelyendendclient.commi

  4. ruby-on-rails - 在 rails 中提交后回滚事务 - 2

    保存成功后可以回滚吗?让我有一个带有属性名称、电子邮件等的用户模型。例如u=User.newu.name="test_name"u.email="test@email.com"u.save现在记录将成功保存在数据库中,之后我想回滚我的事务(不是销毁或删除)。有什么想法吗? 最佳答案 您可以通过交易来做到这一点,请参阅http://markdaggett.com/blog/2011/12/01/transactions-in-rails/例子:User.transactiondoUser.create(:username=>'Nemu

  5. ruby - 如何使用 PG Ruby Gem 有条件地回滚事务 - 2

    我目前正在上一门数据库类(class),其中一个实验室问题让我困惑于如何实现上述内容,事实上,如果可能的话。我试过搜索docs但是定义的交易方式比较模糊。这是我第一次尝试在没有Rails的情况下进行任何数据库操作,所以我有点迷茫。我已经成功地创建了一个到我的postgresql数据库的连接并且可以执行语句,我需要做的最后一件事是根据一个简单的条件回滚一个事务。请允许我向您展示代码:require'pg'@conn=PG::Connection.open(:dbname=>'db_15_11_labs')@conn.prepare('insert','INSERTINTOhouse(ho

  6. ruby-on-rails - 在 Rails 的一个 ActiveRecord 事务中更新多条记录 - 2

    如何使用Rails中的事务block一次性更新/保存模型的多个实例?我想更新数百条记录的值;每条记录的值都不同。这不是一个属性的批量更新情况。Model.update_all(attr:value)在这里不合适。MyModel.transactiondothings_to_update.eachdo|thing|thing.score=rand(100)+rand(100)thing.saveendendsave似乎发布了它自己的事务,而不是将更新分批处理到周围的事务中。我希望所有更新都在一次大交易中进行。我怎样才能做到这一点? 最佳答案

  7. ruby-on-rails - Rails Controller 操作是否隐式定义事务绑定(bind)? - 2

    给定以下代码:defcreate@something=Something.new(params[:something])thing=@something.thing#anothermodel#modificationofattributesonboth'something'and'thing'omitted#doIneedtowrapitinsideatransactionblock?@something.savething.saveendcreate方法是隐式包装在ActiveRecord事务中,还是需要将其包装到事务block中?如果我确实需要包装它,这是最好的方法吗?

  8. ruby-on-rails - TimeOut 与事务 block rails - 2

    据我了解,如果我们在此交易中有任何代码,并且当它发生任何错误时(保存!,...)在该block中,整个代码将恢复,这里的问题是是否有任何超时(racktimeout=12)发生在这个block中。defcreateActiveRecord::Base.transactiondo//timeouthappensendend当Rack::Timeout发生时,我们如何使用事务回滚代码? 最佳答案 当发生Rack超时时,任何正在进行的事务都将被回滚,但是已经提交的事务当然会保持提交状态。您不必为此担心。一旦启动数据库事务,它最终将被提交或

  9. ruby-on-rails - 是否可以让数据库事务跨越 Rails 中的多个请求? - 2

    我有一个跨越多个页面的表单。现在的设置方式并不理想,因为它会在提交时保存(到数据库)每个页面。因此,如果用户未在所有页面上完成表单,则数据库中将保存不完整的用户注册信息。如果用户没有完全填写表格,我想“回滚”保存。那么有没有一种方法可以设置一个事务,该事务在用户填写第一个表单时开始,在用户完成最后一页时结束? 最佳答案 您正在寻找的是acts_as_state_machinegem.如果您不熟悉状态机,请查看here. 关于ruby-on-rails-是否可以让数据库事务跨越Rails中

  10. sql - 将所有 Controller 操作包装在 Rails 中的事务中 - 2

    是否可以设置一个Rails应用程序,以便所有Controller操作都自动包装在一个事务中,并在出现未挽救的异常时自动回滚?我正在开发一个Rails3应用程序,目前正在执行一项相当棘手的操作,该操作会进行大量数据库更改。而且我一直弄错了很多次!一段时间后,我意识到我的代码无法正常工作,因为我最终在数据库中得到了不一致的数据。我可以很容易地用一个事务来包装它(这是一个明显需要的实例!)。然而,这让我想到,至少在开发过程中,将这个想法应用于每个Controller操作会很有用。假设这是可能的,这有什么缺点吗? 最佳答案 有关信息,我在我

随机推荐