String s = new String(“abc”)创建了几个字符串对象
Linux显示磁盘的空间使用情况及挂载点的命令
Linux下tomcat配置jvm内存在哪个文件
MySQL中innoDB存储引擎使用的数据结构是哪种
JVM常用调试工具,能得到运行Java程序的Java stack和native stack 的信息,可以轻松得到当前线程的运行情况的是哪个
事务的四大特性
1.原子性(Atomicity): 事务是最小的执行单位,不允许分割。事务的原子性确保动作要么全部完成,要么完全不起作用;
2.隔离性(Isolation): 并发访问数据库时,一个用户的事务不被其他事务所干扰,各并发事务之间数据库是独立的;
3.持久性(Durability): 一个事务被提交之后。它对数据库中数据的改变是持久的,即使数据库发生故障也不应该对其有任何影响。
4.一致性(Consistency): 执行事务前后,数据保持一致,多个事务对同一个数据读取的结果是相同的;
描述一下JVM加载class文件的原理机制
什么是XSS攻击?什么是SQL注入攻击?什么是CSRF攻击?
简单介绍三种以上的设计模式,说明使用场景和使用原因
用Java写一个单例类
简单介绍一下MySQL的复合索引和使用条件
MySQL查询过程。从客户端发送查询请求,到返回结果,数据库执行的查询过程有哪些步骤
有长数组A和短数组B,都存储的是正整数,不适用额外的数据结构,查询两个数组中重复的元素并列出
请说明Linux如下脚本的作用,find/APP/ido/xu/ -mtime +7 -name “isTester.tar.*” -exec rm -rf {}\
TinyURL是一个可以缩短URL的服务,例如:当您输入一个比较长的URL时,https://lectcode.com/problems/design-tinyurl,这个服务会返回一个比较短的URL,http://tinyurl.com/4e9iAk。请为TinyURL服务设计一个encode和decode的方法。
Spring的事务传播机制
Linux查看当前用户
Java八大基本类型
JVM SQL优化
PostgreSQL索引建立方式
PostgreSQL主主复制
Linux常用命令
Linux安装Tomcat
什么是死锁?如何避免死锁?
接口和抽象类的区别
什么是线程安全
#{}和${}的区别
#{}相对安全,它的sql是预处理,不会存在sql注入的问题,而KaTeX parse error: Expected 'EOF', got '#' at position 21: …在sql注入的安全性问题的; #̲{}在替换字符串参数时会加上单…{}它能使用SQL所有的替换。
List、Set、Map的区别
Spring的IOC、AOP以及IOC的好处AOP的实现原理和实现方法
给定一个字符串,有几种方法反转输出
Nginx负载均衡
List的实现以及它们的区别
消息队列的应用场景
autowired和resource区别
1、 @Autowired与@Resource都可以用来装配bean. 都可以写在字段上,或写在setter方法上。
2、 @Autowired默认按类型装配(这个注解是属业spring的),默认情况下必须要求依赖对象必须存在,如果要允许null值,可以设置它的required属性为false,如:@Autowired(required=false) ,如果我们想使用名称装配可以结合@Qualifier注解进行使用,如下:
@Autowired () @Qualifier ( "baseDao" )
private BaseDao baseDao;
3、@Resource(这个注解属于J2EE的),默认按照名称进行装配,名称可以通过name属性进行指定,如果没有指定name属性,当注解写在字段上时,默认取字段名进行安装名称查找,如果注解写在setter方法上默认取属性名进行装配。当找不到与名称匹配的bean时才按照类型进行装配。但是需要注意的是,如果name属性一旦指定,就只会按照名称进行装配。
@Resource装配顺序
1. 如果同时指定了name和type,则从Spring上下文中找到唯一匹配的bean进行装配,找不到则抛出异常
2. 如果指定了name,则从上下文中查找名称(id)匹配的bean进行装配,找不到则抛出异常
3. 如果指定了type,则从上下文中找到类型匹配的唯一bean进行装配,找不到或者找到多个,都会抛出异常
4. 如果既没有指定name,又没有指定type,则自动按照byName方式进行装配;如果没有匹配,则回退为一个原始类型进行匹配,如果匹配则自动装配;
数据库隔离级别
Read Uncommitted,顾名思义,就是可以读取到一个事务未提交的数据,我们称为脏数据。
Read committed, 读提交,如果一个事务正在访问一条数据,另一个事务也要访问,当且仅当第一条事务提交后才能访问,否则,只能阻塞等待,可以防止脏读。但是这种情况会出现不可重复读,比如,开启一个事务A,读取到卡内余额为1000元,还未提交,准备取款,这时另一个事务B,开始读取同一张卡的余额1000,并将这1000元转到其他账户,然后提交,事务A开始取款,读取到了B已经提交的事务,发现余额为0,取款失败。
Repeatable Read,可重复读。该隔离级别可以防止读提交中的不可重复读问题,当时不可预防幻读(Phantom Read),如事务A从一个表中读取了数据,然后事务B在该表中插入了一些新数据,导致A再次读取同一个表, 就会多出几行。
Serializable,串行化,属于最高的隔离级别,可以防止上述的脏读,不可重复读和幻读,最安全的同时并发也最低。
并行和并发的区别
并行指在同一时刻,有多条指令在多个处理器上同时执行;无论从微观还是从宏观来看,二者都是一起执行的。并发指在同一时刻只能有一条指令执行,但多个进程指令被快速的轮换执行,使得在宏观上具有多个进程同时执行的效果,但在微观上并不是同时执行的。
事务传播行为
REQUIRED(0)
需要事务,它是默认传播行为,如果当前存在事务,就沿用当前事务,否则新建一个事务运行子方法。
SUPPORTS(1)
支持事务,如果当前存在事务,就沿用当前事务,如果不存在,则继续采用无事务的方式运行子方法。
MANDATORY(2)
必须使用事务,如果当前没有事务,则会抛出异常,如果存在当前事务,就沿用当前事务。
REQUIRES_NEW(3)
无论当前事务是否存在,都会创建新事务运行方法,这样新事务就可以拥有新的锁和隔离级别等特性,与当前事务相互独立。
NOT_SUPPORTED(4)
不支持事务,当前存在事务时,将挂起事务,运行方法。
NEVER(5)
不支持事务,如果当前方法存在事务,则抛出异常,否则继续使用无事务机制运行。
NESTED(6)
在当前方法调用子方法时,如果子方法发生异常,只回滚子方法执行过的SQL,而不回滚当前方法的事务。
什么情况下不建议使用索引
1.在where条件中(包括group by以及order by)里用不到的字段不需要创建索引,索引的价值是快速定位,如果起不到定位的字段通常是不需要创建索引的。
2.数据量小的表最好不要使用索引
如果表中记录太少,比如少于1000个,那么是不需要创建索引的
3.有大量重复数据的列上不要建立索引
字段中如果有大量重复数据,也不用创建索引
4.避免对经常更新的表创建过多的索引
更新数据的时候,也需要更新索引,如果索引太多,在更新索引的时候会造成负担,从而影响效率。
5.不建议使用无序的值作为索引
sql执行时间太长如何优化
1、查看sql是否涉及多表的联表或者子查询,如果有,看是否能进行业务拆分,相关字段冗余或者合并成临时表(业务和算法的优化)
2、涉及链表的查询,是否能进行分表查询,单表查询之后的结果进行字段整合
3、如果以上两种都不能操作,非要链表查询,那么考虑对相对应的查询条件做索引。加快查询速度
4、针对数量大的表进行历史表分离(如交易流水表)
5、数据库主从分离,读写分离,降低读写针对同一表同时的压力,至于主从同步,mysql有自带的binlog实现 主从同步
6、explain分析sql语句,查看执行计划,分析索引是否用上,分析扫描行数等等
7、查看mysql执行日志,看看是否有其他方面的问题
创建索引的原则
1、表的主键、外键必须有索引;
2、数据量超过300的表应该有索引;
3、经常与其他表进行连接的表,在连接字段上应该建立索引;
4、经常出现在Where子句中的字段,特别是大表的字段,应该建立索引;
5、索引应该建在选择性高的字段上;
6、索引应该建在小字段上,对于大的文本字段甚至超长字段,不要建索引;
7、频繁进行数据操作的表,不要建立太多的索引;
8、删除无用的索引,避免对执行计划造成负面影响;
索引失效的情况
1、对索引进行%在前的模糊查询
2、使用or的时候,or前面索引有效,后面无效
3、对索引进行 ±*/ 、不等于、not in 、not exits、函数等操作时 也会失效
4、存在is null 或者is not null时
5、数据库全局搜索比索引快的时候
6、字符没有用单引号时
springboot自动配置原理
Spring Boot通过@EnableAutoConfiguration注解开启自动配置,对jar包下的spring.factories文件进行扫描,这个文件中包含了可以进行自动配置的类,当满足@Condition注解指定的条件时,便在依赖的支持下进行实例化,注册到Spring容器中。
通俗的来讲,我们之前在写ssm项目时候,配置了大量坐标和配置内容,搭环境的过程在项目开发中占据了大量时间,Spring Boot的最大的特点就是简化了各种xml配置内容,所以springboot的自动配置就是用注解来对一些常规的配置做默认配置,简化xml配置内容,使你的项目能够快速运行。
Spring注入Bean的几种方式
通过注解注入Bean
通过构造方法注入Bean
通过set方法注入Bean
通过属性去注入Bean
通过List注入Bean
通过Map去注入Bean
http中post 和 get 请求方法区别
1、GET请求,请求的数据会附加在URL之后,以?分割URL和传输数据,多个参数用&连接。URL的编码格式采用的是ASCII编码,而不是uniclde,即是说所有的非ASCII字符都要编码之后再传输。
POST请求:POST请求会把请求的数据放置在HTTP请求包的包体中。上面的item=bandsaw就是实际的传输数据。
因此,GET请求的数据会暴露在地址栏中,而POST请求则不会。
2、传输数据的大小
在HTTP规范中,没有对URL的长度和传输的数据大小进行限制。但是在实际开发过程中,对于GET,特定的浏览器和服务器对URL的长度有限制。因此,在使用GET请求时,传输数据会受到URL长度的限制。
对于POST,由于不是URL传值,理论上是不会受限制的,但是实际上各个服务器会规定对POST提交数据大小进行限制,Apache、IIS都有各自的配置。
3、安全性
POST的安全性比GET的高。这里的安全是指真正的安全,而不同于上面GET提到的安全方法中的安全,上面提到的安全仅仅是不修改服务器的数据。比如,在进行登录操作,通过GET请求,用户名和密码都会暴露再URL上,因为登录页面有可能被浏览器缓存以及其他人查看浏览器的历史记录的原因,此时的用户名和密码就很容易被他人拿到了。除此之外,GET请求提交的数据还可能会造成Cross-site request frogery攻击
mysql怎么防止索引失效
1.尽量使用覆盖索引(之访问索引列的查询),减少 select * 覆盖索引能减少回表次数;
2.使用组合索引时,需要遵循“最左前缀”原则;
3.不在索引列上做任何操作,例如计算、函数、类型转换,会导致索引失效而转向全表扫描;
4.LIKE以通配符开头(%abc)MySQL索引会失效变成全表扫描的操作;
5.少用or,用它来连接时会索引失效。
6.字符串不加单引号会导致索引失效(可能发生了索引列的隐式转换);
7.MySQL在使用不等于(!=或者<>)的时候无法使用索引会导致全表扫描;
重载和重写的区别
重载: 发生在同一类中,函数名必须一样,参数类型、参数个数、参数顺序、返回值、修饰符可以不一样。
重写: 发生在父子类中,函数名、参数、返回值必须一样,访问修饰符必须大于等于父类,异常要小于等于父类,父类方法是private不能重写。
jdk1.8新特性
1.Lambda表达式
2.接口中的静态方法和默认方法
3.方法引用
4.函数式接口
5.Stream流
redis持久化两种方式RDB和AOF原理以及优缺点
Redis的数据结构
字符串string
哈希hash
列表list
集合set
有序集合zset
一级缓存二级缓存的区别
一级缓存基于sqlSession默认开启,在操作数据库时需要构造SqlSession对象,在对象中有一个HashMap用于存储缓存数据。不同的SqlSession之间的缓存数据区域是互相不影响的。
一级缓存的作用域是SqlSession范围的,当在同一个sqlSession中执行两次相同的sql语句时,第一次执行完毕会将数据库中查询的数据写到缓存(内存),
第二次查询时会从缓存中获取数据,不再去底层数据库查询,从而提高查询效率。
需要注意的是,如果SqlSession执行了DML操作(增删改),并且提交到数据库,MyBatis则会清空SqlSession中的一级缓存,这样做的目的是为了保证缓存中存储的是最新的信息,避免出现脏读现象。
当一个SqlSession结束后该SqlSession中的一级缓存也就不存在了。
关闭一级缓存后,再次访问,需要再次获取一级缓存,然后才能查找数据,否则会抛出异常。
二级缓存是mapper级别的缓存。使用二级缓存时,多个SqlSession使用同一个Mapper的sql语句去操作数据库,得到的数据会存在二级缓存区域,它同样是使用HashMap进行数据存储。相比一级缓存SqlSession,二级缓存的范围更大,多个Sqlsession可以共用二级缓存,二级缓存是跨SqlSession的。
二级缓存的作用域是mapper的同一个namespace。不同的sqlSession两次执行相同的namespace下的sql语句,且向sql中传递的参数也相同,即最终执行相同的sql语句,则第一次执行完毕会将数据库中查询的数据写到缓存,第二次查询会从缓存中获取数据,不再去底层数据库查询,从而提高效率。
union和union all的区别
一、区别1:取结果的并集
1、union: 对两个结果集进行并集操作, 不包括重复行,相当于distinct, 同时进行默认规则的排序;
2、union all: 对两个结果集进行并集操作, 包括重复行, 即所有的结果全部显示, 不管是不是重复;
二、区别2:获取结果后的操作
1、union: 会对获取的结果进行排序操作
2、union all: 不会对获取的结果进行排序操作
union all只是合并查询结果,并不会进行去重和排序操作,在没有去重的前提下,使用union all的执行效率要比union高
cookie和session区别
1、Cookie是服务器生成,储存在浏览器的一小段文本信息。
2、cookie的特点:
1⃣以键值对进行存储
2⃣通过浏览器访问一个网站时,会将浏览器存储的跟网站相关的所有cookie信息发送给该网站的服务器。
3⃣cookie是基于域名安全的。
4⃣cookie是有过期时间的,如果不指定,默认关闭浏览器之后cookie就会过期
1、Session的特点:
1⃣session是以键值对进行存储的。
2⃣session依赖于cookie。唯一的标识码保存在sessionid cookie中。
3⃣session也是有过期时间,如果不指定,默认两周就会过期。
2、记住用户登陆状态案例
hashmap和hashtable的区别
1 HashMap不是线程安全的
HashMap是map接口的子类,是将键映射到值的对象,其中键和值都是对象,并且不能包含重复键,但可以包含重复值。HashMap允许null key和null value,而hashtable不允许。
2 HashTable是线程安全。
HashMap是Hashtable的轻量级实现(非线程安全的实现),他们都完成了Map接口,主要区别在于HashMap允许空(null)键值(key),由于非线程安全,效率上可能高于Hashtable。
HashMap允许将null作为一个entry的key或者value,而Hashtable不允许。 HashMap把Hashtable的contains方法去掉了,改成containsvalue和containsKey。因为contains方法容易让人引起误解。 Hashtable继承自Dictionary类,而HashMap是Java1.2引进的Map interface的一个实现。 最大的不同是,Hashtable的方法是Synchronize的,而HashMap不是,在多个线程访问Hashtable时,不需要自己为它的方法实现同步,而HashMap 就必须为之提供外同步。 Hashtable和HashMap采用的hash/rehash算法都大概一样,所以性能不会有很大的差
Arraylist和Linkedlist的区别
ArrayList:
线程不同步
优点:集合底层采用数组数据结构来保存对象,因为数组有索引,所以查询速度比较快,它在内存中分配的空间是连续的;
缺点:这种方式将对象放在连续的位置中,所以增删时非常麻烦,速度比较慢。
LinkedList:
```java
线程同步
优点:集合的底层采用的是链表数据结构来保存对象,将对象存放在独立的空间中,而且在每个空间中还保存下一个链接的索引,
增删速度比较快;
缺点:查找时非常麻烦,需要丛第一个索引开始查找,所以查询速度比较慢。
springmvc执行流程
1、用户发送请求到前端控制器(DispatcherServlet)。
2、前端控制器请求处理器映射器(HandlerMapping)去查找处理器(Handler)。
3、找到以后处理器映射器(HandlerMappering)向前端控制器返回执行链(HandlerExecutionChain)。
4、前端控制器(DispatcherServlet)调用处理器适配器(HandlerAdapter)去执行处理器(Handler)。
5、处理器适配器去执行Handler。
6、处理器执行完给处理器适配器返回ModelAndView。
7、处理器适配器向前端控制器返回ModelAndView。
8、前端控制器请求视图解析器(ViewResolver)去进行视图解析。
9、视图解析器向前端控制器返回View。
10、前端控制器对视图进行渲染。
11、前端控制器向用户响应结果。
springboot启动原理
spring事务失效的几种场景
1.数据库存储引擎不支持事务
以 MySQL 为例,其 MyISAM 引擎是不支持事务操作的,InnoDB 才是支持事务的引擎,一般要支持事务都会使用 InnoDB。
2.没有被spring管理
如下面例子:
// @Service
public class OrderServiceImpl implements OrderService {
@Transactional
public void updateOrder(Order order) {
// update order
}
如果此时把 @Service 注解注释掉,这个类就不会被加载成一个 Bean,那这个类就不会被 Spring 管理了,事务自然就失效了。
3.方法不是 public 的
大概意思就是 @Transactional 只能用于 public 的方法上,否则事务不会生效,如果要用在非 public 方法上,可以开启 AspectJ 代理模式。
4.自身调用问题
@Service
public class OrderServiceImpl implements OrderService {
public void update(Order order) {
this.updateOrder(order);
}
@Transactional
public void updateOrder(Order order) {
// update order
}
发生了自身调用,就调该类自己的方法,而没有经过 Spring 的代理类,默认只有在外部调用事务才会生效,this是一个实际的对象,事务调用者为动态代理对象才生效
5.异常被处理掉了
@Service
public class OrderServiceImpl implements OrderService {
@Transactional
public void updateOrder(Order order) {
try {
// update order
} catch {
}
}
}
把异常吃了,然后又不抛出来,事务怎么回滚吧!
6.异常类型错误
@Service
public class OrderServiceImpl implements OrderService {
@Transactional
public void updateOrder(Order order) {
try {
// update order
} catch {
throw new Exception("更新错误");
}
}
}
这样事务也是不生效的,因为默认回滚的是:RuntimeException,如果你想触发其他异常的回滚,需要在注解上配置一下,如:
@Transactional(rollbackFor = Exception.class)
这个配置仅限于 Throwable 异常类及其子类。
7.@Transactional标记在了非接口实现的方法上
```java
```java
@Resource
private INoWorkingService noWorkingService;
@Test
void noWorkModel1(){
noWorkingService.noWorkModel1();
@Override
public void noWorkModel1() {
noWorkModel1_1();
}
@Transactional
public void noWorkModel1_1(){
User user = new User();
user.setName("noWorkModel1_1");
user.setAge(777);
userDao.save(user);
int q = 1/0;
}
由于业务开发中通常使用接口作为调用对象,如果注解标记在了接口方法调用的方法中,则事务无效
jvm调优
docker常用命令
Docker系统信息:
docker info:查看Docker系统信息。
docker version:查看Docker版本信息。
镜像操作:
docker pull:从镜像仓库中拉取或者更新指定镜像。
docker build:使用Dockerfile构建新的镜像。
docker images:列出本地镜像。
docker rmi:删除一个或多个镜像。
容器操作:
docker run:创建一个新容器并运行一个命令。
docker ps:列出当前主机正在运行的容器列表。
docker start:启动一个或多个已停止的容器。
docker stop:停止一个或多个运行中的容器。
docker restart:重启一个运行中的容器。
docker kill:杀掉一个运行中的容器。
docker rm:删除一个或多个容器。
docker exec:在运行的容器中执行命令。
docker pause:暂停容器中所有的进程。
docker unpause:恢复容器中所有的进程。
docker inspect:获取容器/镜像的详细信息。
宿主机与容器交互:
docker cp:容器与主机之间的数据拷贝。
docker port:列出指定的容器的端口映射,或者查找将PRIVATE_PORT NAT到面向公众的端口。
网络连接:
docker network create:创建一个网络。
docker network connect:将容器连接到一个网络。
docker network disconnect:将容器从一个网络断开。
docker network ls:列出网络。
docker network rm:删除一个网络。
数据管理:
docker volume create:创建一个数据卷。
docker volume ls:列出数据卷。
docker volume inspect:显示一个数据卷的详细信息。
docker volume rm:删除一个数据卷。
登录和登出:
docker login:登录到一个Docker镜像仓库。
docker logout:从Docker镜像仓库登出。
脏读、幻读、不可重复读的区别是什么
-
脏读 :脏读就是指当一个事务正在访问数据,并且对数据进行了修改,而这种修改还没有提交到数据库中,这时,另外一个事务也访问 这个数据,然后使用了这个数据。
-
不可重复读 :是指在一个事务内,多次读同一数据。在这个事务还没有结束时,另外一个事务也访问该同一数据。那么,在第一个事务中的两 次读数据之间,由于第二个事务的修改,那么第一个事务两次读到的的数据可能是不一样的。这样就发生了在一个事务内两次读到的数据是不一样的,因此称为是不 可重复读。例如,一个编辑人员两次读取同一文档,但在两次读取之间,作者重写了该文档。当编辑人员第二次读取文档时,文档已更改。原始读取不可重复。如果 只有在作者全部完成编写后编辑人员才可以读取文档,则可以避免该问题。
-
幻读 : 是指当事务不是独立执行时发生的一种现象,例如第一个事务对一个表中的数据进行了修改,这种修改涉及到表中的全部数据行。 同时,第二个事务也修改这个表中的数据,这种修改是向表中插入一行新数据。那么,以后就会发生操作第一个事务的用户发现表中还有没有修改的数据行,就好象 发生了幻觉一样。例如,一个编辑人员更改作者提交的文档,但当生产部门将其更改内容合并到该文档的主复本时,发现作者已将未编辑的新材料添加到该文档中。 如果在编辑人员和生产部门完成对原始文档的处理之前,任何人都不能将新材料添加到文档中,则可以避免该问题。
oracle锁机制之悲观锁与乐观锁以及for update用法
1 悲观锁
所谓的悲观锁:顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次拿数据的时候都会上锁。这样别人拿数据的时候就要等待直到锁的释放。
数据库行级锁,目的是让数据被查出来的时候就加上锁,然后再执行下面的程序逻辑,这样后面为了操作相同数据而进来的请求,就会在一开始就被拦住(这种效果千万不要以为可以做防重复提交)
在操作DML(insert,update,delete)语句时,oracle会自动加上行级锁,在select * from table for update 【of column】【nowait|wait 3】时,oracle也会自动加锁
1.1 单表 for update
一般在for update 时加nowait,这样就不用等待其他事务执行了,一判断有事务,立马抛出错误。
下面简单说一下 for update的四种情况:
select * from table where id = '1001' for update 锁住了这条数据,那么另外一个人对该笔数据进行DML操作或者也执行同样的for update操作时,会检测到这笔数据上有行级锁,那么就会等待着锁释放;这样就会出现一个问题:其他的程序如果需要对这笔数据操作,就需要等,至于等多久要看锁什么时候释放!
select * from table where id = '1001' for update nowait,意思就是如果这笔数据上本身加了锁,另外一个人去执行这句SQL的时候,发现加了锁,就会直接抛出异常(ORA-00054:资源正忙),不会等待这笔数据的锁释放。
select * from table where id = '1001' for update wait 5;意思就是如果这笔数据被锁住,另外一个人如果执行这句SQL后,会等待5秒,如果5秒后这句SQL还没有得到这笔数据的锁,就会抛出异常(ORA-00054:资源正忙)
先执行 A语句:select * from table where id = '1001' for update 把1001加上锁,然后再执行 B语句:select * from table where id = '1001' and id ='1002' for update;这时候肯定查不出来,因为A已经把B要加锁的数据锁了,这样B连1002的数据都查不出来。解决方案:skip locked。如果把B语句改为:select * from table where id = '1001' and id ='1002' for update skip locked;意思就是执行的时候如果发现要查询的数据有锁,就把加了锁的数据排除,把剩下的数据加锁,然后查出来!
上面讲到了 for update 的四种方式,实际情况如何选择呢?
关于NOWAIT,当有LOCK冲突时会提示错误并结束STATEMENT而不是在那里等待(比如:要查的行已经被其它事务锁了,当前的锁事务与之冲突,加上nowait,当前的事务会结束会提示错误并立即结束 STATEMENT而不再等待).
WAIT 子句指定等待其他用户释放锁的秒数,防止无限期的等待。
“使用FOR UPDATE WAIT”子句的优点如下:
防止无限期地等待被锁定的行;
允许应用程序中对锁的等待时间进行更多的控制。
对于交互式应用程序非常有用,因为这些用户不能等待不确定
若使用了skip locked,则可以越过锁定的行,不会报告由wait n 引发的‘资源忙’异常报告
1.2 关联表for update
现在大部分业务都是联表查询,如果用for update 的话,就会把所有关联表查询出来的列所在的行全部加锁,那这个锁可就重了,比如:
select * from t1,t2 where t1.id = t2.id and t1.age = '20' for update
就会把T1和T2两个表中符合条件的行锁定;如果上述SQL我只想对T1表的结果集加锁,怎么办?答案:of column_name
例子:
select * from t1,t2 where t1.id = t2.id and t1.age = '20' for update of t1.id;
这样就会只把T1表中的符合条件的行加锁,T2表中符合条件的行不会加锁。
PS:如果单表for update of column_name查询,其实和 for update操作是一样的!
1.3 解除for update 锁的占用
如果在为了方便修改表时,使用了for update 就容易把表锁住,如果这时候及时的commit或者rollback还能释放锁
如果,在提交或者释放之前这个plsql会话连接断了,或者别人占有锁而自己也想操作锁,可以如下操作:
查看锁表进程
select
t2.username,t2.sid,t2.serial#,t2.logon_time
from
v$locked_object t1,v$session t2
where t1.session_id=t2.sid ;
解锁杀死锁表进程
// kill的数字即是,锁表进程中的SID和SERIAL字段的值,把所有的值全部杀掉即可
alter system kill session '1155,39095';
1.4 悲观锁缺点
虽然悲观锁应用起来很简单并且十分安全,与此同时却有两大问题:
锁定:应用的使用者选择一个记录进行更新,然后去吃午饭,但是没有结束或者丢弃该事务。这样其他所有需要更新该记录的用户就必须等待正在进行实务操作的用户回来并且完成该事务或者直到DBA杀掉该不愉快的事务并且释放锁。
死锁:用户A和B同时更新数据库。用户A锁定了一条记录并且试图请求用户B持有的锁,同时用户B也在等待获取用户A持有的锁。两个事务同时进入了无限等待状态即进入死锁状态。
2 乐观锁
所谓的乐观锁:就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据。oracle默认使用乐观锁
在乐观锁中,我们有3种常用的做法来实现:
2.1 比对法
第一种就是在数据取得的时候把整个数据都copy到应用中,在进行提交的时候比对当前数据库中的数据和开始的时候更新前取得的数据。当发现两个数据一模一样以后,就表示没有冲突可以提交,否则则是并发冲突,需要去用业务逻辑进行解决。
2.2 版本戳
第二种乐观锁的做法就是采用版本戳,这个在Hibernate中得到了使用。采用版本戳的话,首先需要在你有乐观锁的数据库table上建立一个新的column,比如为number型,当你数据每更新一次的时候,版本数就会往上增加1。比如同样有2个session同样对某条数据进行操作。两者都取到当前的数据的版本号为1,当第一个session进行数据更新后,在提交的时候查看到当前数据的版本还为1,和自己一开始取到的版本相同。就正式提交,然后把版本号增加1,这个时候当前数据的版本为2。当第二个session也更新了数据提交的时候,发现数据库中版本为2,和一开始这个session取到的版本号不一致,就知道别人更新过此条数据,这个时候再进行业务处理,比如整个Transaction都Rollback等等操作。在用版本戳的时候,可以在应用程序侧使用版本戳的验证,也可以在数据库侧采用Trigger(触发器)来进行验证。不过数据库的Trigger的性能开销还是比较的大,所以能在应用侧进行验证的话还是推荐不用Trigger。
2.3 timestamp型
第三种做法和第二种做法有点类似,就是也新增一个Table的Column,不过这次这个column是采用timestamp型,存储数据最后更新的时间。在Oracle9i以后可以采用新的数据类型,也就是timestamp with time zone类型来做时间戳。这种Timestamp的数据精度在Oracle的时间类型中是最高的,精确到微秒(还没与到纳秒的级别),一般来说,加上数据库处理时间和人的思考动作时间,微秒级别是非常非常够了,其实只要精确到毫秒甚至秒都应该没有什么问题。和刚才的版本戳类似,也是在更新提交的时候检查当前数据库中数据的时间戳和自己更新前取到的时间戳进行对比,如果一致则OK,否则就是版本冲突。如果不想把代码写在程序中或者由于别的原因无法把代码写在现有的程序中,也可以把这个时间戳乐观锁逻辑写在Trigger或者存储过程中。
2.4 例子Demo
说明:小明成绩错了,要改成绩。班主任能改,年级主任也能改!
//先查出来小明的成绩
select t.id,t.result from T t where t.id='10001';---10001,59
//更新成绩,改为60
update T t set t.result =‘60’ where t.id='10001' and t.result = '59'
//加上and t.result = ‘59’ 这个条件的目的就是为了验证,数据库里10001的成绩在此期间有没有被其他人改过,如果改过,那就更新条数为0(因为找不到符合条件的数据);
PS:没有找到数据,所以没更新10001这笔数据,最好是程序返回一个没有更新到这笔数据的提示,如果不加任何提示,前端就会认为更新成功了!
1.利用数据库中的数据和已经取出的数据的一致性做为“锁”,与for update相比,乐观锁机制是等到更改数据的时候才去校验,悲观锁是读取数据就开始做了校验,从这个角度来看,乐观锁是对数据库没有额外开销,那么效率相对是高的。
- 需要更改的字段可以作为乐观锁的验证字段;或者表里建立version版本号,每更新一次数据版本号+1;或者加lastupdatedate(最后更新时间),同理:数据更改的同时lastupdatedate也跟着变更!
3.其实乐观锁存在一个很致命的问题:
场景: 已上述小明改成绩为例,假设班主任改的同时,年级主任也改,两个请求几乎同时执行了查询:
select t.id,t.result from T t where t.id='10001';---10001,59
都查出来是59分!!
然后几乎同时执行了改成绩,班主任改成60分,年级主任改成了80分,关键是还都update到10001了班主任:
update T t set t.result =‘60’ where t.id='10001' --and t.result = '59' ;
年级主任:
update T t set t.result =‘80’ where t.id='10001' --and t.result = '59' ;
这时候班主任事务先提交,数据库小明成绩改成了60,年级主任事务紧接着提交,小明的成绩又从60改成了80,那么对于班主任来说,他的数据就是更新丢失!!!所以大家使用起来要注意并发的情况!!