目录
java
1、Java语言特点
2、JVM vs JDK vs JRE
3、基本数据类型 8种
4、重载和重写区别
5、构造方法有哪些特点?是否可以override
6、 接口和抽象类共同点和区别
7、== 和 equals 区别
8、 String、StringBuffer、StringBuilder区别
9、字符串常量池
10、Exception和Error区别
11、检查型异常 和 非检查型异常
12、finally中的代码一定会被执行吗
13、什么是泛型,作用?
14、反射
15、序列化和反序列化
16、I/O流
17、Hashmap、ArrayList、LinkedList区别
18、 JVM
19、设计模式
20、线程池
数据库
1、MySQL存储引擎
2、MySQL索引
3、MySQL事务
4、并发事务带来哪些问题(脏/幻/不可重复)
5、并发事务的控制方式有哪些
6、SQL标准定义了哪些事务隔离级别
7、表级锁和行级锁区别
8、Redis为什么快?
9、为什么要用Redis/缓存
10、Redis数据结构
常用框架
1、Spring IOC
2、DI
3、AOP
简单易学;面向对象(封装、继承、多态);
平台无关性(Java虚拟机实现平台无关性);
支持多线程(Java语言提供了多线程的支持);
可靠性(具备异常处理和字段内存管理机制);
安全性(Java语言提供了多重安全防护机制,如访问权限修饰符、限制程序直接访问操作系统资源);
Java语言支持网络编程并且很方便
封装:把一个对象的状态信息隐藏在对象内部,不允许外部对象直接访问对象的内部信息。但是可以提供一些可以被外界访问的方法来操作属性。就好比我们看不到空调的内部的零件信息(属性),但是可以通过遥控器来控制空调。
继承:继承时使用已存在的类的定义作为基础建立新类的技术,新类的定义可以增加新的数据或新的功能,也可以用父类的功能,但不能选择性地继承父类。通过使用继承,可以快速地创建新的类,提高代码的复用,程序的可维护性,节省了大量创建新类的时间。
多态:一个对象具有多种的状态。多态存在的三个必要条件:1、继承或实现:在多态中必须存在有继承或实现关系的子类和父类。2、方法的重写:子类对父类中的某些方法进行重新定义(重写,使用@Overrode注解进行重写)。3、基类引用指向派生类对象,即父类引用指向子类对象,父类类型:指子类对象继承的父类类型,或实现的父接口类型。
Java虚拟机(JVM)是运行Java字节码的虚拟机。 针对不同的系统有不同的实现,目的是使用相同的字节码。
JDK 是提供给开发者使用的,能够创建和编译Java程序。包含了JRE,同时包含了编译javac以及一些其他工具。
- 4种整数型:byte(1)、short(2)、int(4)、long(8)
- 2种浮点型:float(4)、double(8)
- 1种字符类型:char(2)
- 1种布尔型:boolean(4)
1、重载就是同样的一个方法能够根据输入数据的不同,做出不同的处理。
方法名必须相同,参数类型 / 个数 / 顺序不同,方法返回值和访问修饰符可以不同。
2、重写就是当子类继承父类的相同方法,输入数据一样,但要做出有别于父类的响应时,你就要覆盖父类方法 。
方法名、参数必须相同,子类返回的方法应该比父类方法返回值类型更小或相等,抛出的异常范围小于等于父类,访问修饰符范围大于等于父类
特点:名字与类名相同;没有返回值,但不能用void声明构造函数;生成类的对象时自动执行,无需调用。
构造方法不能被重写,但可以被重载
共同点:
- 都不能被实例化
- 都可以包含抽象方法
- 都可以有默认实现的方法(Java8可以用default关键字在接口中定义默认方法)
区别:
- 接口是行为的抽象,是一种行为的规范,是like a的关系;抽象是对类的抽象,抽象类是is a的关系。
- 接口没有构造方法,抽象类有,方法一般给子类使用
- 接口只有定义,不能有方法的实现,Java1.8中可以定义default方法体,而抽象类可以有定义与实现,方法可以在抽象类中实现
- 抽象体现了继承关系,继承只能单继承。接口体现了实现的关系,实现可以多实现。接口强调特定功能的实现,而抽象类强调所属关系。
- 接口成员变量默认是public static final,必须赋初值,不能被修改;所有的成员方法都是public abstract的。抽象类中成员默认default,可在子类中被重新定义,也可被重新赋值;抽象方法被abstract修饰,不能被private、static、synchronized和native等修饰,必须以分号结尾,不带花括号。
1、==是一个运算符,equals是Object类的一个方法。因此基本数据类型不能使用equals,只有引用类型可以使用equals
2、==两边是基本数据类型,比较的是值,==两边是引用类型比较的是地址;equals从源码上看,如果不重写的话就相当于==号,也就是说比较的是地址,重写后则可以根据自己的规则去定义两个对象之间是否相等。
- equals方法判断两个对象是相等的,那这两个对象的hashcode值也要相等
- 两个对象有相同的 hashcode 值,它们一定相等的。
String:不可变,因为value数组是final类型。因为不可变,所以每次生成新对象。线程安全,是final类型。
StringBuffer:可变,父类(AbstractStringBuilder)的value数组不是final类型。线程安全,原因:方法都用了synchronized。
StringBuiler:可变,因为父类(同上)的value数组不是final类型。线程不安全的,单线程的时候建议使用,因为没加锁,速度快
字符串常量池:是JVM为了提升性能和减少内存消耗,针对字符串(String类)专门开辟的一块区域,主要目的是为了避免字符串的重复创建。
在java中,所有的异常都有一个共同的祖先 java.lang 包中的 Throwable 类。Throwable 类中有两个重要的子类:
检查型异常:Java代码在编译过程中,如果受检查异常没有被 catch 或 throws 关键字处理的话,就没办法通过编译。比如:classNotFoundException、SQLException
非检查型异常:Java代码在编译过程中,我们即使不处理,也可以正常通过编译,比如:NullPointerException(空指针错误)、参数错误、数组越界错误、类型转换错误等
不一定!比如 finally 之前虚拟机被终止运行的话,finally 中的代码就不会被执行。
程序所在线程死亡; 关闭CPU的情况 下都不会被执行。
Java泛型是JDK5中引入的一个新特性。使用泛型参数,可以增强代码的可读性、稳定性。
编译器可以对泛型参数进行检测,并且通过泛型参数可以指定传入的对象类型。
比如:ArrayList
persons = new ArrayList ()这行代码就指明了该ArrayList ()这行代码就只能传入Person对象,如果传入其他类型的对象就会报错。 ArrayList
extends AbstractList 并且,原生 List 返回类型是 Object,需要手动转换类型才能使用,使用泛型后编译器自动转换。
泛型的使用方式:
1、泛型类 2、泛型接口 3、泛型方法
通过反射可以获取任意一个类的所有属性和方法,还调用这些方法和属性。
优点: 可以让我们的代码更加灵活,为各种框架提供开箱即用的功能提供了遍历。
缺点:增加了安全问题,比如可以无视泛型参数的安全检查。另外,反射的性能也要稍微差点,不过,对于框架来说实际是影响不大的。
应用场景:平时大部分时候在写业务代码,很少会接触到直接使用反射机制的场景。但是,这并不代表反射没有用。正式因为反射,才能轻松的使用各种框架,像Spring/Spring Boot、MyBatis等等都大量使用了。
这些框架中也大量使用了动态代理,而动态代理的实现也依赖反射。
序列化:将数据结构或对象转换成二进制字节流的过程
反序列化:将在序列化过程中所生成的二进制字节流转换成数据结构或者对象的过程。
序列化的主要目的是通过网络传输对象或者说是将对象存储到文件系统、数据库、内存中。
- InputStream / Reader:所有的输入流的基类,前者是字节输入流,后者是字符输入流
- OutputStream / Writer
Arraylist实现了Collection下的接口,是基于数组的结构,因为地址的连续性,导致长度固定,类型固定,灵活性不足,删除和插入性能降低。内存不足要动态扩容,每次是原来的1.5倍。
arraylist最最主要的一个特点就是自动扩容,自动扩容的过程就是当你新增数据达到了它底层数组长度的最大长度之后,进行自动扩容,扩容的是1.5倍。扩容之后,是否能容纳你存储的这个数据+你原本数据的容量。扩容之后,如果这个长度还是不能完全存储的话,它就会创建一个Integer.valuemax(Integer最大长度)-8。为什么是Integer-8,因为在arraylist底层它存储的过程中,有一个8个字节的,用来存储arraylist一些数据之类的。
这就是扩容过程中一些参数的变化和数值的调整。
Linkedlist实现了Collectionxia的list和Queue接口,基于双向链表的结构,由于链表的结构,导致查询慢,插入删除快
HashMap实现了Map接口,本质是一个数组,然后通过计算获得其应该存放的下标位置。如下标位置重复,<7存放在链表结构里,>=7存放在树结构中。Key-value映射关系,可以通过key快速查找定位相对于的value值。但是相同的key值也会映射同一下标,导致HashMap结构复杂。
HashMap底层采用数组+链表(JDK1.7),采用数组+链表+红黑树(JDK1.8)。线程不安全
容器:HashMap默认容器长度为16,扩容因子为0.75,以2的n次方扩容,最高可扩容30次。
扩容机制JDK1.8:1、先生成新数组2、遍历老数组中的每个位置上的链表或红黑树(链表元素个数大于8,数组的长度大于等于64,则生成一个新的红黑)3、如果是链表,则直接将链表中的每个元素重新计算下标,并添加到新数组中4、如果是红黑树,先遍历红黑树,先计算出红黑树中每个元素对应在新数组中的下标位置5、所有元素转移完了之后,将新数组赋值给HashMap对象的table属性。
JV M
一次编写,到处运行;自动内存管理,垃圾回收机制
【精选】七种常用的设计模式-CSDN博客
【精选】Java 多线程:彻底搞懂线程池_多线程线程池-CSDN博客
优势:
1、降低资源消耗,通过重复利用已创建的线程降低线程创建和销毁造成的消耗;
2、提高响应速度,当任务到达时,任务可以不需要等到线程创建就能立即执行;
3、提高线程的可管理性,线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。
创建线程池:
- newCachedThreadPool:缓存线程池(无线大)
- newFixedThreadPool:固定大小的线程池
- newScheduledThreadPool:定时任务线程池
- newSingleThreadExecutor:单线程化的线程池
七个参数:
- corePoolSize:核心线程数
- maximumPoolSize:最大线程数量
- keepAliveTime:存活时机
- TimeUnitunit:时间单位
- workQueue:任务队列
- threadFactory:线程的创建工厂
- RejectedExecutionHandler handler:拒绝策略
1、数组、链表(Array、Linked List)
Array:在内存中,数组是一块连续的区域
Linked List:链表在内存中可以存放在任何地方,不要求连接
2、各自的优缺点
数组的优点:随机访问性强;查找速度快
缺点:插入和删除效率低;可能浪费内存;内存空间要求高,必须有足够的连续内存空间;数组大小固定,不能动态拓展。
链表的优点:插入删除速度快;内存利用率高,不会浪费内存;大小没有固定,扩展很灵活。
缺点:不能随机查找,必须从第一个开始遍历,查找效率低
1、strcpy()可以对一个字符串进行复制,并返回复制的字符串
2、strcmp()可以对两个字符串大小进行比较
3、strcat()可以在一个字符串后添加另外一个字符串
4、strlen()可以将一个字符串长度进行返回
5、toLowerCase()转为小写 toUpperCase()转为大写
主要分为 Collection 和 map 类
collection 下一个是线性的,一个是树形结构的
map 下是key-value 底层树形结构
collection分为list和set。list是一个线性的,有序的索引;set是一个无序的,但是它的底层是一个map,可以进行自动排序。
map下用的最多的是hashmap
List :底层是数组,不能动态扩容。元素按进入先后有序保存,可重复
LinkedList 接口实现类,链表,插入删除,没有同步,线程不安全。动态扩容就是像链表中插入数据。
ArrayList 接口实现类,数组,随机访问,没有同步,线程不安全。arraylist在jdk8之前,他有个默认的值等于10,在初始化的时候,会给ArrayList底层的数组赋一个默认值为10的一个初始值容量,之后进行新增,然后扩容。在jdk8之后,创建这个构造方法的时候,先创建一个默认的空的数组,之后在新增数据的时候才会赋这个默认值10,如果传入的是一个空值,它会赋一个默认值10的数组长度,当传入数据的时候,就会赋一个你传入这个数据的初始化长度。
arraylist最最主要的一个特点就是自动扩容,自动扩容的过程就是当你新增数据达到了它底层数组长度的最大长度之后,进行自动扩容,扩容的是1.5倍。扩容之后,是否能容纳你存储的这个数据+你原本数据的容量。扩容之后,如果这个长度还是不能完全存储的话,它就会创建一个Integer.valuemax(Integer最大长度)-8。为什么是Integer-8,因为在arraylist底层它存储的过程中,有一个8个字节的,用来存储arraylist一些数据之类的。
这就是扩容过程中一些参数的变化和数值的调整。
Vector:接口实现类 数组,同步,线程安全
stack:是Vector类的实现类
Set :仅接受一次,不可重复,并做内部排序
HashSet使用hash表(数组)存储元素
LinkedHashSet 链表维护元素的插入次序
TreeSet 底层实现为二叉树,元素排好序。
Map:键值对的集合(双列集合)
Hashtable 接口实现类,同步,线程安全
HashMap 接口实现类,没有同步,线程不安全,使用比较多。jdk8之前是数组+链表实现的,当你存储数据的时候,不等于key-value,先对这个key进行hash方法处理,有一个hashcode值,用hash值进行排序,判断存储在这个hashmap数组中的哪一个位置,再进行传输,如果判断这个位置是空的,则之间存储,如果是有数据的,此时就会产生hash冲突,然后就会给这个数组创建一个链表,用链表去存储。hashmap在8及之后的话,它是数组+链表+红黑树。使用红黑树,是因为它的一个特点是它的节点的高度会更低一点,会加快查询的效率。当插入的数据给hashmap链表中插入数据。当数组的长度等于64并且当前链表的长度大于8的时候,它会将链表转化为红黑树。
因为hashmap是线程不安全的,它有一个线程安全的hashmap,叫concurrentHashMap。他们的区别是在jdk8之前,concurrenHashMap是
LinkedHashMap 双向链表和哈希表实现
TreeMap 红黑树对所有的key进行排序 ,是线程安全的,效率比较低
进程是操作系统进行资源分配的最小单元。线程是操作系统进行运算调度的最小单元。
- 地址空间:进程有独立的地址空间,线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间;
- 开销:进程和线程切换时,需要切换进程和线程的上下文,进程的上下文切换时间开销远远大于线程上下文切换时间,耗费资源较大,效率要差一些;上下文切换:指的是内核(操作系统的核心)在CPU上对进程或者线程进行切换。
- 并发性:进程的并发性较低,线程的并发性较高;
- 是否能独立执行:每个独立的进程有一个程序运行的入口、顺序执行序列和程序的出口。线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制;
- 内存空间:系统在运行的时候会为每个进程分配不同的内存空间;而对线程而言,除了 CPU 外,系统不会为线程分配内存(线程所使用的资源来自其所属进程的资源),线程组之间只能共享资源;
- 健壮性:一个进程崩溃后,在保护模式下不会对其他进程产生影响,但是一个线程崩溃整个进程都死掉。所以多进程要比多线程健壮。
1、继承Thread类,重写run() 方法
优点:编码简单 缺点:线程类已经继承Thread,无法继承其他类,不利于扩展
2、实现Runnable接口,调用start() 方法启动线程
优点:线程任务类知识实现接口,可以继续继承类和实现接口,扩展性强
缺点:编程多一层对象包装,如果线程有执行结果是不可以直接返回的
3、实现Callable接口
优点:线程任务类只是实现接口,可以继续继承类和实现接口,扩展性强
缺点:编码复杂一点
4、利用线程池来创建线程,采用复用线程的技术
为什么使用线程池?
1、资源管理:线程池可以限制同时执行的线程数量,避免创建过多的线程导致资源耗尽。
2、提高性能:线程池可以减少线程的创建和销毁开销
3、提高响应性:可以更快地响应任务请求,提高系统的响应性能。线程池可以降低任务的执行延迟。当有任务提交到线程池时,线程池中的线程可以立即处理任务,而不需要等待新线程的创建。
4、控制并发度:线程池可以限制同时执行的线程数量,从而控制并发度。
5、提供任务队列:线程池通常会提供一个任务队列,用于存储待执行的任务。
得到线程池对象的方式:
方式一:使用ExecutorService的实现类ThreadPoolExecutor自创建一个线程池对象
方式二:使用Executors(线程池的工具类)调用方法返回不同特点的线程池对象。
ThreadPoolExcutor 构造器的参数说明:
int corePoolSize 指定线程池的线程数量(核心线程),不能小于0
int maxinumPoolSize 指定线程池可支持的最大线程数,最大数量>=核心线程数量
long keepAliveTime 指定临时线程的最大存活时间,不能小于0
TimeUnit unit 指定存活时间的单位(秒、分、时、天)
BlockingQueue workQueue 指定任务队列,不能为null
ThreadFactory threadFactory 指定用哪个线程工厂创建线程,一般使用默认的,不能为null
RejectedExecutionHandler handler 指定线程忙,任务满的时候,新任务来了怎么办,不能为null(执行条件:核心线程和临时线程都在忙,任务队列也满了,新的任务过来的时候才会开始任务拒绝)
线程:是操作系统调度的基本单位,没有独立的地址空间和内存空间(只有自己的堆栈和局部变量,只能共享所在进程的内存空间),线程之间可以共享进程内的资源,上下文切换快,并发高,不能独立执行(程序控制多线程执行,进程通过管理线程优先级间接控制线程执行),不健壮(因为一个线程崩溃会导致整个进程崩溃)。
多线程的好处:当一个线程进入阻塞或者等待状态时,其他的线程可以获取CPU的执行权,提高了CPU的利用率。多线程的缺点:
- 死锁:多个进程或线程相互等待对方释放所持有的资源,从而无法继续执行的情况。若无外力作用,它们都将无法推进下去。死锁用占用CPU、内存等系统资源,导致资源浪费,死锁会导致程序无法正常退出,导致系统性能差。
- 上下文频繁切换:频繁的上下文切换可能会造成资源的浪费;
- 串行:如果因为资源的限制,多线程串行执行,可能速度会比单线程更慢。
1、wait 用于等待另一个进程的结束,而sleep用于暂停程序的执行。
2、sleep是Thread类的方法,wait属于Object类的方法
3、sleep适用于TIMED_WAITING,时间到了会自动唤醒,wait 属于waiting,需要手动唤醒。
4、sleep没有释放锁资源,wait释放了锁资源
5、wait唤醒条件:调用对象的notify()或者notifyAll()方法;sleep超时或者调用interrupt()方法体。
4、sleep是静态方法,wait是实例方法
wait()方法用于线程间通信,如果等待条件为真且其他线程被唤醒时,它会释放锁,而sleep()方法仅仅释放CPU资源或者让当前线程停止执行一段时间,但不会释放锁。
主要作用是同步,它可以使得线程之间的并行执行变为串行执行。在A线程中调用了B线程的join()方法时,表示只有当B线程执行完毕时,A线程才能继续执行。调用这个方法的线程将被阻塞。
方法join(long)的功能在内部是使用wait(long)方法来实现的,所以join(long)方法具有释放锁的特点。但是sleep(long)不释放锁。
1、synchronized是独占锁,加锁和解锁的过程自动进行,易于操作,但不够灵活;ReetrantLock也是独占锁,加锁和解锁的过程需要手动进行,不易操作,但非常灵活。
2、Synchronized可重入,因为加锁和解锁自动进行,不必担心最后是否释放锁;ReentrantLock也可重入,但加锁和解锁需要手动进行,且次数需一样,否则其他线程无法获得锁。
3、Synchronized不可响应中断,一个线程获取不到锁就一直等着;ReentrantLock可以响应中断
4、ReetrantLock可以实现公平锁机制,也就是等待时间最长的线程将获取锁的使用权。
CAS: 不断对变量进行原子性比较和交换,从而解决单个变量的线程安全问题。比较内存中值和预期值,如果相等则交换,如果不相等就代表被其他线程改了则重试。
乐观锁:CAS属于乐观锁,乐观地认为并发不高,所以让线程不断去重试交换。
自旋:CAS本身是不自旋的,线程失败后不再重试。一般使用时,会通过while让CAS自旋,线程失败后不断自旋重试。
优点:不使用锁,所以性能高。CAS在不使用锁的情况下通过原子性操作变量,实现多线程时的变量同步。
使用场景:AQS、并发容器、原子类、synchronized的偏向锁和轻量级锁
线程安全需要保证几个基本特征:
1、原子性:简单说就是相关操作不会中途被其他线程干扰,一般通过同步机制实现。
2、可见性:是一个线程修改了某个共享变量,其状态能够立即被其他线程知晓,通常被解释为将线程本地状态反映到主内存上,volatile就是负责保证可见性的
3、有序性:是保证线程内串行语义,避免指令重排等
- 接口和关键字。Lock是一个接口,而synchronized是Java中的关键字,synchronized是内置的语言实现;
- 死锁问题。synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而Lock在发生异常时,如果没有主动通过unLock()去释放锁,则很可能造成死锁现象,因此使用Lock时需要在finally块中释放锁;
- 让等待锁的线程响应中断。Lock可以可以通过lockInterruptibly()获取锁的方法让等待锁的线程响应中断。而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断;
- 得知是否成功获取锁。通过Lock可以通过tryLock()知道有没有成功获取锁,而synchronized却无法办到。
- 性能对比。两者性能差不多。JDK6之前synchronized没有锁升级,线程竞争非常激烈时Lock的性能要远远优于synchronized;而JDK6引入锁升级后,线程竞争激烈时候两者性能也相差无几。
- 可见性:对一个volatile变量的读,总是能看到(任意线程)对这个volatile变量最后的写入
- 原子性:对单个volatile变量的读写具有原子性
当一个操作具有原子性时,就意味着该操作在同一时刻只能被一个线程访问和修改,其他线程访问时只能获得之前的结果或等待其操作完成后再访问。
New:初始状态,线程被创建,但是还没有调用start方法
RUNNABLE:可运行状态
BLOCKED:阻塞状态
WAITING:等待状态
TIMED_WAITING:超时等待状态
TERMINATED:终止状态
默认存储引擎是InnoDB。并且,所有存储引擎中只有InnoDB是事务性存储引擎,也就说只有InnoDB支持事务。
- InnoDB提供事务支持,实现了SQL标准定义了四个隔离级别,具有提交(commit)和回滚(rollback)的能力。
- 并且,InnoDB默认使用的 可重读 隔离级别可以解决幻读问题发生。
- InnoDB支持外键。外键对于维护数据一致性非常有帮助,但是对性能有一定损耗。
- 使用InnoDB的数据库在异常崩溃后,数据库重新启动的时候会保证数据库恢复到崩溃前的状态。这个恢复的过程依赖于 redo log
MySQL存储引擎采用的是插件式架构,支持多种存储引擎
索引类型
按照数据结构维度划分:
- B树索引:MySQL里默认和最常用的索引类型。只有叶子节点存储value,非叶子节点只有指针和key
- 哈希索引:类似键值对的形式,一次即可定位
- 全文索引:对文本的内容进行分词,进行搜索。目前只有CHAR、VARCHAR、TEXT列上可以创建全文索引。一般不会使用,效率较低。
按照底层存储方式角度划分:
- 聚簇索引:索引结构和数据一起存放的索引,InnoDB中的主键索引就属于聚簇索引。
优点:查询速度非常快,因为整个B+树本身就是一颗多叉平衡树,叶子节点也都是有序的,定位到索引的节点,就相当于定位到了数据。对排序查找和范围查找优化:聚簇索引对于主键的
- 非聚簇索引:索引结构和数据分开存放的索引,二级索引就属于非聚簇索引。
按照应用维度划分:
- 主键索引:加速查询+列表唯一+表中只有一个
- 普通索引:仅加速查询
- 唯一索引:一个索引包含所有需要查询的字段的值
- 联合索引:多列值组成一个索引,专门用于组合搜索。
- 全文索引
当我们需要插入多条相关联的数据到数据库,可能会因数据库中途突然因为某些原因挂掉;客户端突然因为网络原因连接不上数据库了;并发访问数据库时,多个线程同时写入数据库,覆盖了彼此的更改……这个问题都会导致数据的的不一致性。
事务就是逻辑上的一组操作,要么都执行,要么都不执行。
# 开启一个事务 START TRANSACTION; # 多条 SQL 语句 SQL1,SQL2…… # 提交事务 Commit;
关系型数据库事务都有ACID特性(原子性、一致性、隔离性、持久性)
- 原子性(Atomicity):事务是最小的执行单位,不允许分割。事务的原子性确保动作要么全部完成,要么完全不起作用。
- 一致性(Consistency):执行事务前后,数据保持一致,例如转账业务中,无论事务是否成功,转账者和收款人的总额应该是不变;
- 隔离性(Isolation):并发访问数据库时,一个用户的事务不被其他事务所干扰,各并发事务之间数据库是独立的;
- 持久性(Durability):一个事务被提交之后。他对数据库中数据的改变是持久的,即使数据库发生故障也不应该对其有任何影响。
ISAM和InnoDB
1、InnoDB支持事务,ISAM不支持事务
2、InnoDB支持行级锁,ISAM不支持行级锁
按数据库(全局锁)分:防止数据库备份的过程中,插入、删除、修改。但是结果与备份不一致。添加全局锁,锁住数据库,让他不能操作增删改。
按粒度分:主要分为表级锁、行级锁、页级锁
表级锁:InnoDB在使用过程中只要不通过索引检索数据时,全部是表锁。
开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低行级锁:InnoDB行锁是通过索引上的索引项加锁来实现的。行锁的劣势:开销大、加速慢、会出现死锁。优势:锁的粒度小,发生锁冲突的概率低,处理并发的能力强。
页级锁:页级表是MySQL中比较独特的一种锁定级别,在其他数据库管理软件中不常见。页级锁的颗粒度介于行级锁与表级锁之间,所以获取锁定所需要的资源开销,以及所能提供的并发处理能力同样也是介于上面二者之间。另外,页级锁和行级锁一样,会发生死锁。
锁级别划分:共享锁(Share lock,S锁)、排他锁(exclusive lock,X锁)、意向锁
共享锁:又称读锁,允许一个事务去读取一行,若事务T对数据对象A加上S锁,则事务T可以读A,但不能修改A,其他事务只能对在对A加S锁,而不能加X锁,直到T释放A上的锁,这保证了其他事务可以读A,但在释放A上的S锁之前不能对A做任何修改。
排他锁:又称写锁,允许获取排他锁的事务更新数据。若事务T对数据对象A加上X锁,事务T可以读A也可以修改A,其他事务不能再对A加任何锁,直到T释放A上的锁。
意向锁:当一个事务在需要获取资源的锁定时,如果该资源已经被排他锁占用,则数据库会自动给该事务申请一个该表的意向锁。如果自己需要一个共享锁定,就申请一个意向共享锁。如果需要的是某行(或者某些行)的排他锁定,则申请一个意向排他锁。
事务B对一行数据使用行锁,当有另一个事务A对这个表使用了表锁,这个行锁就会升级为表锁,事务A在申请行锁之前,数据库会自动先给事务A申请表的意向排他锁。当事务B去申请表的写锁时就会失败,因为表上有意向排他锁之后,事务B申请表的写锁会被阻塞。
锁的使用方式分类:乐观锁(Optimistic Lock)、悲观锁(Pessimistic Lock)
乐观锁:乐观锁的特点先进行业务操作,不到万不得已不去拿锁。所以在进行完业务操作,需要实际更新数据的最后一步再去拿一下锁就好。
乐观锁是否在事务中其实都是无所谓的,其底层机制是这样:在数据库内部update同一行的时候是不允许并发的,即数据库每次执行一条update语句时会获取被update行的写锁,直到这一行被成功更新后才释放。因此在业务操作进行前获取需要锁的数据的当前版本号,然后实际更新数据时再次对比版本号确认与之前获取的相同,并更新版本号,即可确认这之间没有发生并发的修改。如果更新失败即可认为老版本的数据已经被并发修改掉而不存在了,此时认为获取锁失败,需要回滚整个业务操作并可根据需要重试整个过程。
悲观锁:悲观锁的特点是先获取锁,再进行业务操作,即“悲观”的认为获取锁是非常有可能失败的,因此要先确保获取锁成功再进行业务操作。通常来讲在数据库上的悲观锁需要数据库本身提供支持,即通过常用的select … for update操作来实现悲观锁。
表的设计优化
- 设置合适的数值,根据实际情况而定
- 设置合适的字符串类型(char 和 varchar)
- 尽量使用数值代替字符串类型,比如主键优先使用int类型,性别、状态,使用1,0替代
SQL语句优化
- 避免使用select*,务必指明字段名称
- SQL语句要避免索引失效的写法
- 尽量使用union all代替union,union会多一次过滤,效率低
- 避免在where子句中对字段进行表达式操作
- 能用inner join就用inner join,如果必须使用其他的必须以小表驱动,内连接会自己对两个表进行优化,优先将小表放在外面。而left join和right join不会自动优化。
脏读:一个事务读取数据并且对数据进行了修改,这个修改对其他事务来说是可见的,即使当前事务还没有提交。这时另一个事务读取了这个还未提交的数据,但第一个事务突然回滚,导致数据并没有被提交到数据库,那第二个事务读取到的就是脏数据。
不可重复读:指在一个事务内多次读同一数据。在这个事务还没有结束时,另一个事务也访问该数据。那么在第一个事务中的两次读数据之间,由于第二个事务的修改导致第一个事务两次读取的数据可能不太一样。这就发生了在一个事务内两次读到的数据是不一样的情况,因此称为不可重复读。
幻读:幻读与不可重复读类似。它发生在一个事务读取了几行数据,接着另一个并发事务插入了一些数据时。在随后的查询中,第一个事务就会发现多了一些原本不存在的记录,就好像发生了幻觉一样,所以称为幻读。
MySQL中并发事务的控制方式无非两种:锁 和 MVCC。锁可以看作是悲观控制的模式,多版本并发控制(MVCC,Multiversion concurrency control)可以看作是乐观控制的模式。
锁 控制方式下会通过锁来显示控制共享资源而不是通过调度手段,MySQL中主要是通过读写锁来实现并发控制。
- 共享锁(s锁):又称读锁,事务在读取记录的时候获取共享锁,允许多个事务同时获取(锁兼容)
- 排他锁(X锁):又称写锁/独占锁,事务在修改记录的时候获取排他锁,不允许多个事务同时获取。如果一个记录已经被加了排他锁,那其他事务不能再对这条记录加任何类型的锁(锁不兼容)。
- READ-UNCOMMITTED(读未提交):最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读、不可重复读
- READ-COMMITTED(读已提交):允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生。
- REPEATABLE-READ(可重复读):对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。
- SERIALIZABLE(可串行化):最高的隔离级别,完全服从ACID的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。
- 表级锁: MySQL 中锁定粒度最大的一种锁(全局锁除外),是针对非索引字段加的锁,对当前操作的整张表加锁,实现简单,资源消耗也比较少,加锁快,不会出现死锁。不过,触发锁冲突的概率最高,高并发下效率极低。
- 行级锁: MySQL 中锁定粒度最小的一种锁,是 针对索引字段加的锁 ,只针对当前操作的行记录进行加锁。 行级锁能大大减少数据库操作的冲突。其加锁粒度最小,并发度高,但加锁的开销也最大,加锁慢,会出现死锁。行级锁和存储引擎有关,是在存储引擎层面实现的。
慢查询,就是执行很慢的查询。超过 long_query_time 参数设定的时间阈值(默认10s),就被认为是慢的,是需要优化的。慢查询被记录在慢查询日志里。
慢查询日志默认是不开启的。如果你需要优化SQL语句,就可以开启这个功能,它可以让你很容易地知道哪些语句是需要优化的(想想一个SQL要10s就可怕)。
SQL优化:
Redis学习-CSDN博客
1、Redis基于内存,内存的访问速度是磁盘的上千倍;
2、Redis主要是单线程事件循环和 IO多路复用
3、Redis内置了多种优化过后的数据类型/结构实现,性能非常高。
1、高性能:用户下一次在访问这些数据的时候就可以直接从缓存种获取了。操作缓存就是操作内存,速度非常快。
2、高并发:操作缓存能够承受的数据库请求数量是远远大于直接访问数据库的,所以我们可以考虑把数据库中的部分数据转移到缓存中去,这样用户的一部分请求会直接到缓存这里而不用经过数据库。进而,我们也就提高了系统整体的并发。
1、String:最简单、最常用的一个数据类型。使用场景:存放用户(登录)信息;存放文章详情和列表信息;存放和累计网页的统计信息。
2、Set:存放不能重复的场景:文章点赞、动态点赞等等。获取多个数据源交集、并集和差集的场景:共同好友(交集)、共同粉丝(交集)、共同关注(交集)、好友推荐(差集)等;随机获取数据源中的元素的场景:抽奖系统、随机点名等。
3、Hash:相当于Java中的HashMap,内部存储了很多键值对。实现结构和Java的HashMap是一样的,都是数组+链表。
4、List:相当于LinkedList,它是链表而不是数组,插入删除操作很快。
5、Zset:最具特色的数据结构,类似于SortedSet和HashMap的集合体,一方面它是一个set,保证了内部的唯一性,另一方面它可以给每个value赋予一个score,代表这个value的排序权重。Zset最后一个value被移除后,数据结构被自动删除,内存被回收;Redis有序集合非常适合那些有序无重复数据的存储,例如:排行榜、经验榜等
IOC,控制反转,指将对象的控制权转移给Spring框架,由Spring来负责控制对象的生命周期(比如创建、销毁)和对象间的依赖关系。
最直观的表达就是,从前创建对象的时机和主动权都是由自己把握的,如果在一个对象中使用另外的对象,就必须主动通过new指令去创建依赖对象,使用完后还需要销毁。而在IOC中,所有的对象都被Spring控制,控制对象生命周期的不在是引用对象的生命周期,而在IOC中,所有的对象都被Spring控制,控制对象生命周期的是Spring容器,由Spring容器帮我们创建、查找、注入依赖对象,而引用对象只是被动的接收依赖对象。
IoC的一个重点就是在程序运行时,动态的向某个对象提供它所需要的其他对象,这一点是通过DI(Dependency Injection。依赖注入)来实现的,即应用程序在运行时依赖IoC容器来动态注入对象所需要的外部依赖。而Spring的DI具体就是通过反射实现注入的,反射允许程序在运行的时候动态生成对象、执行对象的方法、改变对象的属性。
原理:Spring的IoC的实现原理就是工程加工模式加反射机制。
AOP,为面向切面,用于那些与业务无关,但却对多个对象产生影响的公共行为和逻辑,抽取并封装为一个可重用的模块。
几个名词:
1、连接点:程序运行过程中所执行的方法。在SpringAOP中,一个连接点总代表一个方法的执行。
2、切面:被抽取出来的公共模块,可以用来横切多个对象。Aspect切面可以看成Pointcut切点 和 Advice通知 的结合,一个切面可以由多个切点和通知组成。
3、切点:切点用于定义,要对哪些Join point进行拦截
4、通知:要在连接点上执行的动作
5、目标对象:包含连接点的对象,也被称作通知(Advice)的对象。
6、织入:通过动态代理,在目标对象的方法中执行增强逻辑的过程
7、引入:添加额外的方法或者字段到被通知的类。