目录
事务(Transaction)
乐观锁(Optimistic Lock)
悲观锁(Pessimistic Lock)
封锁(数据库机制锁)
封锁协议
死锁(Deadlock)
MySQL
概念定义:事务是恢复和并发控制的基本单位
基本特性:ACID(原子性,一致性,隔离性,持久性)
原子性和一致性差不多,意思是要么全部成功,要么就全部失败;
一致性是说,从一个一致性状态到另一个一致性状态,即状态的一致性;
隔离性是说一个事务执行的过程中不能被另一个事务干扰,我理解为概率事件中的独立性;
持久性也就是事务一旦提交,他对数据库中数据的改变就应该是永久的
事务并发:导致数据不一致性
概念定义:每次获取数据的时候都不会进行加锁,但是在更新数据前需要判断该数据是否被别人修改过。如果数据被其他线程修改,则不进行数据更新,如果数据没有被其他线程修改,则进行数据更新。由于数据没有进行加锁,期间该数据可以被其他线程进行读写操作。
应用场景:比较适合读取操作比较频繁且并发量较大的场景,可以提高系统的吞吐量;反之如果出现大量的写入操作,数据发生冲突的可能性就会增大,为了保证数据的一致性,应用层需要不断的重新获取数据,这样会增加大量的查询操作,反而降低了系统的吞吐量。
实现方式:
问题思考:它如何保证检查版本,提交和修改版本是一个原子操作? 也就是如何保证在检查版本的期间,没有其他事务对其进行操作?
解决方案: 将比较版本号和更新操作写入到同一条SQL语句中可以解决该问题,
update table1 set a=1, b=2, version = version +1 where version = 1;
mysql 自己能够保障单条SQL语句的原子操作性。
如果是多条SQL语句,就需要mySQL的事务通过锁机制来保障了
atomic
包下面的原子变量类就是采用这种方式实现的。CAS算法涉及到三个操作数:需要读写的数V(第一次取出)、进行比较的数A(提交前进行比较,再次取出的数)、修改提交的新值B,当且仅当 V 的值等于 A时,CAS通过原子方式用新值B来更新V的值,否则不会执行任何操作(比较和替换是一个原子操作)。一般情况下是一个自旋操作,即不断的重试。CAS带来的问题又有哪些?
- ABA 问题(理解ABA)
如果一个变量V初次读取的时候是A值,并且在准备赋值的时候检查到它仍然是A值,那我们就能说明它的值没有被其他线程修改过了吗?很明显是不能的,因为在这段时间它的值可能被改为其他值,然后又改回A,那CAS操作就会误认为它从来没有被修改过。这个问题被称为CAS操作的 "ABA"问题。
解决:1、JDK 1.5 以后的 AtomicStampedReference 类就提供了此种能力,其中的 compareAndSet 方法就是首先检查当前引用是否等于预期引用,并且当前标志是否等于预期标志,如果全部相等,则以原子方式将该引用和该标志的值设置为给定的更新值。2、互斥同步锁synchronized。3、如果业务逻辑不是过于复杂即只在乎数值的正确性的话,可能不会影响并发正确性。(参考:ABA解决)
- 循环时间长开销大
自旋CAS(也就是不成功就一直循环执行直到成功)如果长时间不成功,会给CPU带来非常大的执行开销。
- 只能保证一个共享变量的原子操作
CAS 只对单个共享变量有效,当操作涉及跨多个共享变量时 CAS 无效。但是从 JDK 1.5开始,提供了Atomic Reference类来保证引用对象之间的原子性,你可以把多个变量放在一个对象里来进行 CAS 操作.所以我们可以使用锁或者利用AtomicReference类把多个共享变量合并成一个共享变量来操作。
CAS与Synchronized的使用场景?
- CAS适用于写比较少的情况下(多读场景,冲突一般较少)
- Synchronized适用于写比较多的情况下(多写场景,冲突一般较多)
解释:对于资源竞争较少(线程冲突较轻)的情况,使用synchronized同步锁进行线程阻塞和唤醒切换以及用户态内核态间的切换操作额外浪费消耗cpu资源;而CAS基于硬件实现,不需要进入内核,不需要切换线程,操作自旋几率较少,因此可以获得更高的性能。对于资源竞争严重(线程冲突严重)的情况,CAS自旋的概率会比较大,从而浪费更多的CPU资源,效率低于synchronized。参考(乐观悲观比较)
概念定义:每次获取数据的时候都会进行加锁,确保在自己使用的过程中数据不会被别人修改,使用完成后进行数据解锁。由于数据进行加锁,期间对该数据进行读写的其他线程都会进行等待。
应用场景:比较适合写入操作比较频繁或者并发量较小的场景,如果出现大量的读取操作,每次读取的时候都会进行加锁,这样会增加大量的锁的开销,降低了系统的吞吐量。
共享锁(share lock 读锁):如果事务T对数据对象A加上读锁,T可以读A但不能写A,其他事务可以加读锁,但是不能加写锁,直到释放该数据上所有的读锁。通俗来说就是多个用户可以同时读,但是不能有人修改。
排它锁(exclusive lock 写锁):如果事务T对数据对象A加上写锁,那么只允许事务T读取和修改A,其他任何事务不能再对A加任何类型的锁,直到T释放A上的锁为止。通俗来说就是只有一个用户来写,其他用户不能读也不能写。
一级封锁协议:修改数据之前加写锁,事务结束释放(包括正常结束commit和回滚rollback)。可能会读脏数据,不支持重复读
二级封锁协议:在一的基础上,读数据之前加读锁,读完后释放。不保证可重复度
三级封锁协议:在一的基础上,读数据之前加读锁,直到事务结束才释放(比如事务中有验算过程,需要重复读一个数据)
概念定义:死锁是指一个进程(或者线程)无限期的等待另一个进程所占有的永远不会释放的资源;
产生原因:一种是竞争资源,共享的资源不足引起竞争而产生死锁,另一种是进程推进顺序不当,请求和释放的顺序不当引起的死锁;
必要条件:互斥、请求和保持、不剥夺、环路等待;
解决办法:有预防、避免、检测和解除四个方面,预防就是破坏死锁产生的四个必要条件,但会降低系统的资源利用率,避免的话就是不破坏条件,而是在资源动态分配的过程中防止系统进入不安全的状态,比如经典的银行家算法,检测和解除是配套的一种设施,就是通过检测机构确定死锁的进程或者资源然后再予以解除,常用的就是挂起或者撤销一些进程,从而释放一些资源。
数据结构:B+树
数据库引擎:
加锁粒度:(experience)
隔离级别:MySQL默认可重复读
隔离级别 | 脏读(Dirty Read) | 不可重复读(NonRepeatable Read) | 幻读(Phantom Read) |
---|---|---|---|
未提交读(Read uncommitted) | 可能 | 可能 | 可能 |
已提交读(Read committed) | 不可能 | 可能 | 可能 |
可重复读(Repeatable read) | 不可能 | 不可能 | 可能 |
可串行化(Serializable ) | 不可能 | 不可能 | 不可能 |
加锁协议:
1. 加锁阶段:对任何数据进行读写操作之前,首先要申请并获得对该数据的封锁;
2. 解锁阶段:释放了一个封锁以后,事务进入解锁阶段,不再申请和获得任何其他锁。
例如:事务提交时(commit) 和事务回滚时(rollback)会自动的同时释放该事务所加的insert、update、delete对应的锁。