学习总结与分享-数据库的事务与锁入门

本文介绍了数据库事务的四大特性(ACID),包括原子性、一致性、隔离性和持久性,并探讨了事务在并发环境中可能遇到的问题及解决方案。文章还详细讲解了数据库的隔离级别,以及如何通过锁机制来保证事务的隔离性,包括读未提交、读已提交、可重复读和串行化。最后,讨论了乐观锁和悲观锁的区别,以及行级锁、表级锁、记录锁、间隙锁等锁类型的应用。

这次的事务和锁研究,主要是MySQL数据库的innodb引擎,说到数据库,就免不了要说事务和锁机制,这是数据库最重要的组成部分。
什么是事务?
事务(Transaction)是并发控制的基本单位。所谓的事务,它是一个操作序列,这些操作要么都执行,要么都不执行,它是一个不可分割的工作单位
数据库事务(简称:事务)是数据库管理系统执行过程中的一个逻辑单位,由一个有限的数据库操作序列构成。(摘自百科)
简单通俗来说,就是将一系列对数据库的操作限定成同一个操作,这个操作的执行一定是同时执行成功或者执行失败。
那为了保证事务的正确执行,所以就有了事务的四大特性(ACID)
事务的四大特性(ACID)
1.原子性(Atomicity):事务作为一个整体被执行,包含在其中的对数据库的操作要么全部被执行,要么都不执行。
通俗来说:指所有在事务中的操作要么都成功,要么都不成功,所有的操作都不可分割,没有中间状态。一旦某一步执行失败,就会全部回滚到初始状态。
2.一致性(Consistency):事务应确保数据库的状态从一个一致状态转变为另一个一致状态。一致状态的含义是数据库中的数据应满足完整性约束。
通俗来说:指的是逻辑上的一致性,即所有操作是符合现实当中的期望的。
3.隔离性(Isolation):多个事务并发执行时,一个事务的执行不应影响其他事务的执行。
通俗来说:指不同事务之间的相互影响和隔离的程度。比如,不同的隔离级别,事务的并发程度也不同,最强的隔离状态是所有的事务都是串行化的(serializable)(即一个事务完成之后才能进行下一个事务),这样并发性也会降到最低,在保证了强一致性的情况下,性能也会受很大影响,所以在实际工程当中,往往会折中一下。
4.持久性(Durability):已被提交的事务对数据库的修改应该永久保存在数据库中。
通俗来说:简单地理解为事务执行完毕后数据不可逆并持久化存储于存储系统当中。
介绍完了事务的四大特性,我们可以发现,原子性、隔离性、持久性,这三个特性,实际中都是为了保证事务的一致性来服务的,那么在并发下什么问题会导致事务没有保持一致性呢,大概有脏读、不可重复读、幻读、丢失更新(由不可重复读这个问题延伸出来),下面举几个例子:
脏读:A事务读取B事务尚未提交的数据并在此基础上操作,而B事务执行回滚,那么A读取到的数据就是脏数据。

时间转账事务A取款事物B
T1开始事务
T2开始事务
T3查询账户余额为1000元
T4取出500元余额修改为500元
T5查询账户余额为500元(脏读)
T6撤销事务余额恢复为1000元
T7汇入100元把余额修改为600元
T8提交事务

不可重复读:事务A重新读取前面读取过的数据,发现该数据已经被另一个已提交的事务B修改过了。

时间转账事务A取款事物B
T1开始事务
T2开始事务
T3查询账户余额为1000元
T4查询账户余额为1000元
T5取出100元修改余额为900元
T6提交事务
T7查询账户余额为900元(不可重复读)

幻读:事务A重新执行一个查询,返回一系列符合查询条件的行,发现其中插入了被事务B提交的行。

时间统计金额事务A取款事物B
T1开始事务
T2开始事务
T3统计所有帐户总存款为10000元
T4新增一个存款账户存入100元
T5提交事务
T6再次统计总存款为10100元(幻读)

丢失更新:事务A覆盖事务B已经提交的数据,造成事务B所做的操作丢失或事务A撤销时,把已经提交的事务B的更新数据覆盖了。总的来说都是由不可重复读延伸出去的其他问题,就举一个代表性的例子来说

时间转账事务A取款事物B
T1开始事务
T2开始事务
T3查询账户余额为1000元
T4查询账户余额为1000元
T5取出100元将余额修改为900元
T6提交事务
T7汇入100元将余额修改为1100元
T8提交事务
T8查询账户余额为1100元(丢失更新)

看完了这些为保持数据一致性存在的问题之后,那么如何解决这些问题呢。这里就涉及到了事务的隔离性,为了保证事务的隔离性,就有了事务的四种隔离级别,其中每种隔离级别都对应的解决了一些问题。

  • 隔离级别(从小到大):
    1. read uncommitted:读未提交
    * 解决的问题:解决了第一种丢失更新:事务A覆盖事务B已经提交的数据,造成事务B所做的操作丢失或事务A撤销时,把已经提交的事务B的更新数据覆盖了。
    * 产生的问题:脏读、不可重复读、幻读
    2. read committed:读已提交 (Oracle)
    * 解决的问题:脏读
    * 产生的问题:不可重复读、幻读
    3. repeatable read:可重复读 (MySQL默认)
    * 解决的问题:不可重复读、第二种丢失更新:事务A撤销时,把已经提交的事务B的更新数据覆盖了
    * 产生的问题:幻读
    4. serializable:串行化
    * 解决了所有会产生的问题。所有的事务都会按照顺序依次执行,但这样会严重影响数据库在并发下的读写性能。

那么数据库又是怎么样保证数据的隔离性的呢,就有了锁机制,由数据库为各条记录或者各个表来加锁,从而完成事务的隔离。那数据库又是怎么确定是哪条记录需要加锁呢?之前我们研究数据库底层的存储结构了解到数据库的innodb引擎是由B+树的结构来存储的,子节点里存储的都是索引与指针,而数据则都存储在叶子节点中,那么由此可见,锁是基于索引来实现的,那么什么是锁呢,锁分别又有哪些种类?
什么是数据库锁?
锁是网络数据库中的一个非常重要的概念,当多个用户同时对数据库并发操作时,会带来数据不一致的问题,所以,锁主要用于多用户环境下保证数据库完整性和一致性。
锁都有哪些?
锁根据具体业务、概念、操作的不同也分有很多种,有的是锁的概念,有的是锁的具体体现,也有的是锁的用法,大概有乐观锁、悲观锁、行级锁、表级锁、记录锁、间隙锁、临建锁、共享锁、排他锁、意向共享锁、意向排他锁等等。。

乐观锁和悲观锁:
乐观锁:
总是认为不会产生并发问题,每次去取数据的时候总认为不会有其他线程对数据进行修改,因此不会上锁,但是在更新时会判断其他线程在这之前有没有对数据进行修改,一般会使用版本号机制或CAS操作实现。
version方式:一般是在数据表中加上一个数据版本号version字段,表示数据被修改的次数,当数据被修改时,version值会加一。当线程A要更新数据值时,在读取数据的同时也会读取version值,在提交更新时,若刚才读取到的version值为当前数据库中的version值相等时才更新,否则重试更新操作,直到更新成功。
核心SQL代码:

update table set x=x+1, version=version+1 where id=#{id} and version=#{version};

乐观锁机制避免了长事务中的数据库加锁开销,大大提升了大并发量下的系统整体性能表现。
例子:
通过版本号来控制,数据版本是1,我和小红同时对数据进行修改,在事务提交前,我和小红的程序内部获取的数据版本都是1,所以提交的时候,版本是2.修改后,我先提交,数据版本为2,小红提交时想把数据版本变成2,但是不好意思,只有数据版本只能是3.所以,小红的提交无效。

悲观锁:
总是假设最坏的情况,每次取数据时都认为其他线程会去修改,所以都会加锁(读锁、写锁、行锁等),当其他线程想要访问数据时,都需要阻塞挂起等待。可以依靠数据库实现,如行锁、读锁和写锁等,都是在操作之前加锁,在Java中,synchronized(同步锁)的思想也是悲观锁。

行级锁和表级锁:
表级锁:
表级别的锁定是MySQL各存储引擎中最大颗粒度的锁定机制。该锁定机制最大的特点是实现逻辑非常简单,带来的系统负面影响最小。所以获取锁和释放锁的速度很快。由于表级锁一次会将整个表锁定,所以可以很好的避免困扰我们的死锁问题。

行级锁:
行级锁定最大的特点就是锁定对象的颗粒度很小,也是目前各大数据库管理软件所实现的锁定颗粒度最小的。由于锁定颗粒度很小,所以发生锁定资源争用的概率也最小,能够给予应用程序尽可能大的并发处理能力而提高一些需要高并发应用系统的整体性能。

表级锁:开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低;
行级锁:开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高;

记录锁、间隙锁、临建锁:
这些锁的前提条件是要命中索引,属于行级锁
记录锁:
记录锁很好理解,一般要通过主键或唯一索引加锁,就可以较好的实现。记录锁就是为某行记录加锁,它封锁该行的索引记录:
– id 列为主键列或唯一索引列
SELECT * FROM table WHERE id = 1 FOR UPDATE;
id 为 1 的记录行会被锁住。id 列必须为唯一索引列或主键列,否则上述语句加的锁就会变成临键锁。同时查询语句必须为精准匹配(=),不能为 >、<、like等,否则也会退化成临键锁

间隙锁:
间隙锁基于非唯一索引,它锁定一段范围内的索引记录。间隙锁基于下面将会提到的Next-Key Locking 算法,请务必牢记:使用间隙锁锁住的是一个区间,而不仅仅是这个区间中的每一条数据。
SELECT * FROM table WHERE id BETWEN 1 AND 10 FOR UPDATE;
即所有在(1,10)区间内的记录行都会被锁住,所有id 为 2、3、4、5、6、7、8、9 的数据行的插入会被阻塞,但是 1 和 10 两条记录行并不会被锁住。

临建锁:
通过临建锁可以解决幻读的问题。 每个数据行上的非唯一索引列上都会存在一把临键锁,当某个事务持有该数据行的临键锁时,会锁住一段左开右闭区间的数据。需要强调的一点是,InnoDB 中行级锁是基于索引实现的,临键锁只与非唯一索引列有关,在唯一索引列(包括主键列)上不存在临键锁。

共享锁和排他锁:
共享锁也叫写锁,排他锁也叫读锁。
共享锁(写锁):
共享锁用于不更改或不更新数据的操作(只读操作),如 SELECT 语句。
也就是说如果事务1对数据A加上共享锁后,则其他事务只能对A再加共享锁,不能加排他锁。获准共享锁的事务只能读数据,不能修改数据。

排他锁(读锁):
用于数据修改操作,例如 INSERT、UPDATE 或 DELETE。确保不会同时同一资源进行多重更新。
如果事务1对数据A加上排他锁后,则其他事务不能再对A加任任何类型的封锁。获准排他锁的事务既能读数据,又能修改数据。
我们在操作数据库的时候,可能会由于并发问题而引起的数据的不一致性(数据冲突)。

还有很多锁就不一一介绍了,数据库锁的目的就是为了保证事务的隔离性,各个事务之间互不影响,但是在实际业务操作中,我们要根据锁的机制与事务的隔离性来编写代码,从而避免有长事务或死锁的产生。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值