事务相关
什么是事务?
事务是逻辑上的一组操作,要么都执行,要么都不执行。
四大特性
- 原子性:事务是最小的执行单位,不允许分割。事务的原子性确保动作要么全部执行,要么全部不执行。
- 一致性:执行事务前后,多个事务对同一数据读取的结果是相同的。
- 隔离性:并发访问数据库时,一个用户的事务不被其他事务所干扰,各并发事务之间的数据库是独立的。
- 持久性:一个事务被提交之后,它对数据库中数据的改变是持久的,即使数据库发生故障也不应该对其有影响。
并发事务带来的问题
- 脏读 :当一个事务正在访问数据并且对数据进行了修改,而这种修改还没有提交到数据库中,这时另一个事务也访问了这个数据,并且使用了这个数据,因为这个数据是还没有提交的数据,所以另一个事务读取到的也就是“脏数据”,根据“脏数据”所做的操作可能是不正确的。
- 丢失修改 :多个事务同时访问一条数据,在第一个事务修改了这个数据后,第二个事务也修改了这个数据,就会出现第一个事务内修改的数据丢失,因此称为丢失修改。
- 不可重复读 :一个事务内多次访问一条数据,在这个事务还没有结束时,另一个事务也访问该数据。那么,在第一个事务中两次读数据之间,由于第二个事务的修改导致第一个事务两次读取的数据可能不太一样,这就发生了在一个事务内两次读到的数据不一致,因此称为不可重复读。
- 幻读 :和不可重复读类似,它发生在一个事务读取了几行数据,接着另一个并发事务插入了一些数据。在随后的查询中,第一个事务就会发现多了一些原本不存在的数据,就好像发生了幻觉一样,因此称为幻读。
不可重复读和幻读的区别
前者重点是修改,后者重点是新增或者删除
事务的隔离级别,mysql的默认隔离级别
sql标准定义了四个隔离级别:
- 读取未提交:最低的隔离级别,允许读取尚未提交的数据变更。可能会导致脏读、幻读或不可重复读。
- 读取已提交:允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读和不可重复读仍有可能发生。
- 可重复读:对同一字段的多次读取结果都是一致的,除非数据被本身的事务所修改,可以阻止脏读和不可重复读,但幻读仍可能发生。
- 可串行化:最高的隔离级别,完全服从ACID的隔离级别。所有的事务依次逐个执行,这种事务之间就完全不会产生干扰,也就是说,该级别可以防止脏读,不可重复读以及幻读。
图像说明:
隔离级别 | 脏读 | 不可重复读 | 幻读 |
READ-UNCOMMITTED(读取未提交) | ✅ | ✅ | ✅ |
READ-COMMITTED (读取已提交) | ❌ | ✅ | ✅ |
REPEATABLE-READ(可重复读) | ❌ | ❌ | ✅ |
SERIALIZABLE(可串行化) | ❌ | ❌ | ❌ |
隔离级别越低,事务请求的锁越少,所以大部分数据库系统的隔离级别都是READ-COMMITTED(读取提交内容):,但是你要知道的是InnoDB 存储引擎默认使用 **REPEATABLE-READ(可重读)**并不会有任何性能损失。
InnoDB 存储引擎在 分布式事务 的情况下一般会用到**SERIALIZABLE(可串行化)**隔离级别。
索引相关
为什么索引能够提高查询速度
先从mysql的基本存储结构说起
mysql的基本存储结构是页(记录都存在页里面)
- 各个数据页可以组成一个双向链表
- 每个数据页中的记录又可以组成一个单向链表
- 每个数据页都会为存储在它里面的记录生成一个页目录,在通过主键查找某条记录的时候可以在页目录中使用二分法快速定位到对应的槽,然后再遍历该槽对应分组中的记录即可快速找到指定的目录。
- 以其他列(非主键)作为搜索条件:只能从最小记录开始依次遍历单链表中的每条记录。
所以说,像这种select * from user where name=’xiaobei’ 这种没有进行任何优化的sql语句,默认会这样做:
- 定位到记录所在的页:需要遍历双向链表,找到所在的页。
- 从所在的页内查找相应的记录:由于不是根据主键查询,只能遍历所在页的单链表。
很明显,在数据量很大的情况下这样查找会很慢!这样的时间复杂度O(n)
要查询id = 8 的记录简要步骤
很明显的是:没有用索引我们是需要遍历双向链表来定位对应的页,现在通过 “目录” 就可以很快地定位到对应的页上了!(二分查找,时间复杂度近似为O(logn))
其实底层结构就是B+树,B+树作为树的一种实现,能够让我们很快地查找出对应的记录。
乐观锁和悲观锁的区别
悲观锁
总是假设最坏的情况,每次去拿数据都会认为别人会修改,所以每次在拿数据的时候都是上锁,这样别人想拿到这数据就会阻塞直到它拿到锁(共享资源每次只给一个线程使用,其他线程阻塞,用完之后再把资源转让给其他线程)。传统的关系型数据库就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等都是在操作之前先上锁。Java中synchronized和ReentrantLock等独占锁就是悲观锁思想的实现。
乐观锁
总是假设最好的情况,每次去拿数据都会认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此之前有没有别人去更新这个数据。可以使用版本号机制和CAS算法实现。乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库提供的类似于write_condition机制,其实都是提供的乐观锁。在Java中java.util.concurrent.atomic包下面的原子变量类就是使用了一种乐观锁的一种实现方式CAS实现的。
关于CAS简单说一下
CAS(Compare-and-Swap),即比较并替换,是一种实现并发算法时常用到的技术,Java并发包中的很多类都使用了CAS技术。CAS也是现在面试经常问的问题,本文将深入的介绍CAS的原理。
案例说明:
package com.example.demo;
public class Case {
public static volatile int race;
private static final int THREADS_COUNT = 20;
public static void increase()
{
race++;
}
public static void main(String[] args) {
Thread[] threads = new Thread[THREADS_COUNT];
for (int i = 0; i < THREADS_COUNT; i++) {
threads[i] = new Thread(new Runnable() {
@Override
public void run() {
for (int j = 0; j < 10000; j++) {
increase();
}
}
});
threads[i].start();
}
while (Thread.activeCount() > 1) {
Thread.yield();
}
System.out.println(race);
}
}
运行,结果并不是我们期望的,并且每次都是输出小于200000的不同结果值。这是因为volatile只能保证可见性,无法保证原子性,而自增操作并不是一个原子操作。
解决办法
首先我们想到的就是关键字synchronized来修饰increase()方法。
使用synchronized修饰后,increase方法变成了一个原子操作,因此是肯定能得到正确的结果。但是,我们知道,每次自增都进行加锁,性能可能会稍微差了点。
package com.example.demo;
import java.util.concurrent.atomic.AtomicInteger;
public class Case {
// public static volatile int race;
public static AtomicInteger race = new AtomicInteger(0);
private static final int THREADS_COUNT = 20;
public static void increase()
{
// race++;非原子操作,取值,加1,写值
race.getAndIncrement();//原子操作
}
public static void main(String[] args) {
Thread[] threads = new Thread[THREADS_COUNT];
for (int i = 0; i < THREADS_COUNT; i++) {
threads[i] = new Thread(new Runnable() {
@Override
public void run() {
for (int j = 0; j < 10000; j++) {
increase();
}
}
});
threads[i].start();
}
while (Thread.activeCount() > 1) {
Thread.yield();
}
System.out.println(race);
}
}
CAS的缺点:
CAS虽然很高效的解决了原子操作问题,但是CAS仍然存在三大问题。
循环时间长开销很大。
只能保证一个共享变量的原子操作。
ABA问题。
循环时间长开销很大:
我们可以看到getAndAddInt方法执行时,如果CAS失败,会一直进行尝试。如果CAS长时间一直不成功,可能会给CPU带来很大的开销。
只能保证一个共享变量的原子操作:
当对一个共享变量执行操作时,我们可以使用循环CAS的方式来保证原子操作,但是对多个共享变量操作时,循环CAS就无法保证操作的原子性,这个时候就可以用锁来保证原子性。
本篇参考总结于原博:
https://blog.csdn.net/qq_34337272/article/details/94201189
https://blog.csdn.net/v123411739/article/details/79561458