科大讯飞面经总结

Spring IoC容器结构

1.资源组件:Resource,对资源文件的描述,不同资源文件如xml、properties文件等,格式不同,最终都将被ResourceLoader加载获得相应的Resource对象;

2.资源加载组件:ResourceLoader:加载xml、properties等各类格式文件,解析文件,并生成Resource对象。

3.Bean容器组件:BeanFactory体系:IoC容器的核心,其他组件都是为它工作的(但不是仅仅为其服务).

4.Bean注册组件:SingletonBeanRegister/AliasRegister:将BeanDefinition对象注册到BeanFactory(BeanDefinition Map)中去。

5.Bean描述组件:BeanDefinition体系,Spring内部对Bean描述的基本数据结构

6.Bean构造组件:BeanDefinitionReader体系,读取Resource并将其数据转换成一个个BeanDefinition对象。

数据库SQL索引什么时候会失效

1.对于创建的多列索引(联合索引),不是使用的第一部分就不会使用索引,即未使用最左前缀。

alter table student add index my_index(name, age)   // name左边的列, age 右边的列                                                              

select * from student where name = 'aaa'     // 会用到索引

select * from student where age = 18          //  不会使用索引

2.对于使用 like 查询, 查询如果是 ‘%aaa’ 不会使用索引,而 ‘aaa%’ 会使用到索引。

3.如果条件中有 or, 有条件没有使用索引,即使其中有条件带索引也不会使用,换言之, 就是要求使用的所有字段,都必须单独使用时能使用索引。
or会使索引失效。如果查询字段相同,也可以使用索引。例如where A=a1 or A=a2(生效),where A=a or B=b(失效)

4.如果列类型是字符串,那么一定要在条件中使用引号引用起来,否则不使用索引。例如where A=‘China’,否则索引失效(会进行类型转换);

5.如果mysql认为全表扫面要比使用索引快,则不使用索引。在索引列上的操作,函数(upper()等)、or、!=(<>)、not in等;

如:表里只有一条数据。

线程池创建

https://www.cnblogs.com/superfj/p/7544971.html
四种线程创建方式

在这里插入图片描述

wonrkerCountOf()方法能够取得当前线程池中的线程的总数,取得当前线程数与核心池大小比较,

如果小于,将通过addWorker()方法调度执行。

如果大于核心池大小,那么就提交到等待队列。

如果进入等待队列失败,则会将任务直接提交给线程池。

如果线程数达到最大线程数,那么就提交失败,执行拒绝策略。

常见的四种线程池
newFixedThreadPool

public static ExecutorService newFixedThreadPool(int var0) {
        return new ThreadPoolExecutor(var0, var0, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue());
    }
public static ExecutorService newFixedThreadPool(int var0, ThreadFactory var1) {
    return new ThreadPoolExecutor(var0, var0, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue(), var1);
}

固定大小的线程池,可以指定线程池的大小,该线程池corePoolSize和maximumPoolSize相等,阻塞队列使用的是LinkedBlockingQueue,大小为整数最大值。

该线程池中的线程数量始终不变,当有新任务提交时,线程池中有空闲线程则会立即执行,如果没有,则会暂存到阻塞队列。对于固定大小的线程池,不存在线程数量的变化。同时使用无界的LinkedBlockingQueue来存放执行的任务。当任务提交十分频繁的时候,LinkedBlockingQueue

迅速增大,存在着耗尽系统资源的问题。而且在线程池空闲时,即线程池中没有可运行任务时,它也不会释放工作线程,还会占用一定的系统资源,需要shutdown。

newSingleThreadExecutor

public static ExecutorService newSingleThreadExecutor() {
        return new Executors.FinalizableDelegatedExecutorService(new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue()));
    }

    public static ExecutorService newSingleThreadExecutor(ThreadFactory var0) {
        return new Executors.FinalizableDelegatedExecutorService(new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue(), var0));
    }

单个线程线程池,只有一个线程的线程池,阻塞队列使用的是LinkedBlockingQueue,若有多余的任务提交到线程池中,则会被暂存到阻塞队列,待空闲时再去执行。按照先入先出的顺序执行任务。

newCachedThreadPool

public static ExecutorService newCachedThreadPool() {
        return new ThreadPoolExecutor(0, 2147483647, 60L, TimeUnit.SECONDS, new SynchronousQueue());
    }

    public static ExecutorService newCachedThreadPool(ThreadFactory var0) {
        return new ThreadPoolExecutor(0, 2147483647, 60L, TimeUnit.SECONDS, new SynchronousQueue(), var0);
    }

缓存线程池,缓存的线程默认存活60秒。线程的核心池corePoolSize大小为0,核心池最大为Integer.MAX_VALUE,阻塞队列使用的是SynchronousQueue。是一个直接提交的阻塞队列, 他总会迫使线程池增加新的线程去执行新的任务。在没有任务执行时,当线程的空闲时间超过keepAliveTime(60秒),则工作线程将会终止被回收,当提交新任务时,如果没有空闲线程,则创建新线程执行任务,会导致一定的系统开销。如果同时又大量任务被提交,而且任务执行的时间不是特别快,那么线程池便会新增出等量的线程池处理任务,这很可能会很快耗尽系统的资源。

newScheduledThreadPool

public static ScheduledExecutorService newScheduledThreadPool(int var0) {
        return new ScheduledThreadPoolExecutor(var0);
    }

    public static ScheduledExecutorService newScheduledThreadPool(int var0, ThreadFactory var1) {
        return new ScheduledThreadPoolExecutor(var0, var1);
    }

定时线程池,该线程池可用于周期性地去执行任务,通常用于周期性的同步数据。

scheduleAtFixedRate:是以固定的频率去执行任务,周期是指每次执行任务成功执行之间的间隔。

schedultWithFixedDelay:是以固定的延时去执行任务,延时是指上一次执行成功之后和下一次开始执行的之前的时间。

Java中为什么不建议使用Executors的四种线程池呢?

主要是newFixedThreadPool和newSingleThreadExecutor的等待队列是LinkedBlockingQueue,长度是Integer.MAX_VALUE,,可以认为是无限大的,如果创建的任务特别多,可能会造成内存溢出。

newCachedThreadPool和newScheduledThreadPool的最大线程数是Integer.MAX_VALUE,如果创建的任务过多,可能会导致创建的线程过多,从而导致内存溢出。

Redis 持久化机制(怎么保证 Redis 挂掉之后再重启数据可以进行恢复)

redis 的一种持久化方式叫快照(snapshotting,RDB),另一种方式是只追加文件(append-only file, AOF)
https://www.cnblogs.com/itdragon/p/7906481.html

快照(snapshotting)持久化(RDB)

Redis 可以通过创建快照来获得存储在内存里面的数据在某个时间点上的副本。Redis创建快照之后,可以对快照进行备份,可以将快照复制到其他服务器从而创建具有相同数据的服务器副本(Redis 主从结构,主要用来提高 Redis 性能),还可以将快照留在原地以便重启服务器的时候使用。

RDB 是 Redis 默认的持久化方案。在指定的时间间隔内,执行指定次数的写操作,则会将内存中的数据写入到磁盘中。即在指定目录下生成一个dump.rdb文件。Redis 重启会通过加载dump.rdb文件恢复数据。

触发RDB快照

1 在指定的时间间隔内,执行指定次数的写操作

2 执行save(阻塞, 只管保存快照,其他的等待) 或者是bgsave (异步)命令

3 执行flushall 命令,清空数据库所有数据,意义不大。

4 执行shutdown 命令,保证服务器正常关闭且不丢失任何数据,意义…也不大。

RDB 的优缺点

优点:

1 适合大规模的数据恢复。

2 如果业务对数据完整性和一致性要求不高,RDB是很好的选择。

缺点:

1 数据的完整性和一致性不高,因为RDB可能在最后一次备份时宕机了。

2 备份时占用内存,因为Redis 在备份时会独立创建一个子进程,将数据写入到一个临时文件(此时内存中的数据是原来的两倍哦),最后再将临时文件替换之前的备份文件。

所以Redis 的持久化和数据的恢复要选择在夜深人静的时候执行是比较合理的。

AOF(append-only file)持久化

与快照持久化相比,AOF 持久化的实时性更好,因此已成为主流的持久化方案。默认情况下 Redis 没有开启 AOF(append only file)方式的持久化,可以通过 appendonly 参数开启:

appendonly yes

开启 AOF 持久化后每执行一条会更改 Redis 中的数据的命令,Redis 就会将该命令写入到内存缓存 server.aof_buf 中,然后再根据 appendfsync 配置来决定何时将其同步到硬盘中的 AOF 文件。

AOF 文件的保存位置和 RDB 文件的位置相同,都是通过 dir 参数设置的,默认的文件名是 appendonly.aof

在 Redis 的配置文件中存在三种不同的 AOF 持久化方式,它们分别是:

appendfsync always    #每次有数据修改发生时都会写入AOF文件,这样会严重降低Redis的速度
appendfsync everysec  #每秒钟同步一次,显示地将多个写命令同步到硬盘
appendfsync no        #让操作系统决定何时进行同步

为了兼顾数据和写入性能,用户可以考虑 appendfsync everysec 选项 ,让 Redis 每秒同步一次 AOF 文件,Redis 性能几乎没受到任何影响。而且这样即使出现系统崩溃,用户最多只会丢失一秒之内产生的数据。当硬盘忙于执行写入操作的时候,Redis 还会优雅的放慢自己的速度以便适应硬盘的最大写入速度。

补充内容:AOF 重写

AOF 重写可以产生一个新的 AOF 文件,这个新的 AOF 文件和原有的 AOF 文件所保存的数据库状态一样,但体积更小。

AOF 重写是一个有歧义的名字,该功能是通过读取数据库中的键值对来实现的,程序无须对现有 AOF 文件进行任何读入、分析或者写入操作。

在执行 BGREWRITEAOF 命令时,Redis 服务器会维护一个 AOF 重写缓冲区,该缓冲区会在子进程创建新 AOF 文件期间,记录服务器执行的所有写命令。当子进程完成创建新 AOF 文件的工作之后,服务器会将重写缓冲区中的所有内容追加到新 AOF 文件的末尾,使得新旧两个 AOF 文件所保存的数据库状态一致。最后,服务器用新的 AOF 文件替换旧的 AOF 文件,以此来完成 AOF 文件重写操作。

Redis 事务

redis 可以通过 MULTI,EXEC,DISCARDWATCH 等命令来实现事务(transaction)功能。

使用 MULTI 命令后可以输入多个命令。Redis 不会立即执行这些命令,而是将它们放到队列,当调用了 EXEC 命令将执行所有命令。

这个过程是这样的:

开始事务(MULTI)。

命令入队(批量操作 Redis 的命令,先进先出(FIFO)的顺序执行)。

执行事务(EXEC)。

你也可以通过 DISCARD 命令取消一个事务,它会清空事务队列中保存的所有命令。

> MULTI
OK
> SET USER "Guide哥"
QUEUED
> GET USER
QUEUED
> DISCARD
OK

WATCH 命令用于监听指定的键,当调用 EXEC 命令执行事务时,如果一个被 WATCH 命令监视的键被修改的话,整个事务都不会执行,直接返回失败。

> WATCH USER
OK
> MULTI
> SET USER "Guide哥"
OK
> GET USER
Guide哥
> EXEC
ERR EXEC without MULTI

Redis 是不支持 roll back 的,因而不满足原子性的(而且不满足持久性)。你可以将 Redis 中的事务就理解为 :Redis事务提供了一种将多个命令请求打包的功能。然后,再按顺序执行打包的所有命令,并且不会被中途打断。

跳跃表什么时候采用ziplist编码,什么时候采用skiplist编码

有序集合对象同时满足以下2个条件时,对象使用ziplist编码,否则使用skiplist编码。

有序集合保存的元素数量小于128个
有序集合保存的所有元素成员的长度都小于64字节

jdk代理

package nuc.ss.demo03;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

//等会我门会用这个类,自动生成代理类
public class ProxyInvocationHandler implements InvocationHandler {

    //被代理的接口
    private Rent rent;

    public void setRent(Rent rent) {
        this.rent = rent;
    }

    //生成代理对象
    public Object getProxy() {
        return Proxy.newProxyInstance(this.getClass().getClassLoader(),rent.getClass().getInterfaces(),this);
    }

    //这个invoke方法就相当于Spring的Aop,或者说是前面静态代理中中介所做的事情
    //就是比之前多了一个代理生成类,以前的代理类我们写死了,现在可以丢不同的host里面去生成不同的代理类,然后代理类去处理各种方法
    //处理代理实例,并返回结果
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //动态代理的本质,就是使用反射机制
        seeHouse();
        Object result = method.invoke(rent, args);
        fare();
        return result;
    }

    public void seeHouse() {
        System.out.println("中介带看房子");
    }

    public void fare() {
        System.out.println("收中介费");
    }
}

cglib基本使用

为什么使用CGLIB
CGLIB代理主要通过对字节码的操作,为对象引入间接级别,以控制对象的访问。我们知道Java中有一个动态代理也是做这个事情的,那我们为什么不直接使用Java动态代理,而要使用CGLIB呢?答案是CGLIB相比于JDK动态代理更加强大,JDK动态代理虽然简单易用,但是其有一个致命缺陷是,只能对接口进行代理如果要代理的类为一个普通类、没有接口,那么Java动态代理就没法使用了。关于Java动态代理,可以参者这里Java动态代理分析

Cglib及其基本使用
对类做代理步骤:
1.创建被代理的类,文中给的是Dao.class
2.创建一个Dao代理(DaoProxy),实现MethodInterceptor接口,控制被拦截的对象。

/**
*Object表示要进行增强的对象
*Method表示拦截的方法
*Object[]数组表示参数列表,基本数据类型需要传入其包装类型,如int-->Integer、long-Long、double-->Double
*MethodProxy表示对方法的代理,invokeSuper方法表示对被代理对象方法的调用
*/
public class DaoProxy implements MethodInterceptor {

    @Override
    public Object intercept(Object object, Method method, Object[] objects, MethodProxy proxy) throws Throwable {
        System.out.println("Before Method Invoke");
        proxy.invokeSuper(object, objects);
        System.out.println("After Method Invoke");
        
        return object;
    }
    
}

3.测试类代码如下:

public class CglibTest {

    @Test
    public void testCglib() {
        DaoProxy daoProxy = new DaoProxy();
        
        Enhancer enhancer = new Enhancer();
        //将dao类设置为父类,cglib的动态代理增强的原理是:子类增强父类。
        enhancer.setSuperclass(Dao.class);
        //设置daoProxy为回调函数,当调用Dao对象时执行该回调接口对象,即执行代理。
        enhancer.setCallback(daoProxy);
        //用enhancer创建一个Dao的子类对象。在spring中此步骤应该由bean容器完成
        Dao dao = (Dao)enhancer.create();
        dao.update();
        dao.select();
    }
    
}

使用Cglib定义不同的拦截策略步骤:

1.再定义一个代理类DaoAnotherProxy.class
2.实现CallbackFilter,CallbackFilter的accept方法返回的数值表示的是顺序,这里表示方法名为"select"的方法返回的顺序为0,不是则返回1。

public class DaoFilter implements CallbackFilter {

    @Override
    public int accept(Method method) {
        if ("select".equals(method.getName())) {
            return 0;
        }
        return 1;
    }
    
}

3.测试类代码如下

public class CglibTest {

    @Test
    public void testCglib() {
        DaoProxy daoProxy = new DaoProxy();
        DaoAnotherProxy daoAnotherProxy = new DaoAnotherProxy();
        
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(Dao.class);
        enhancer.setCallbacks(new Callback[]{daoProxy, daoAnotherProxy, NoOp.INSTANCE});
        enhancer.setCallbackFilter(new DaoFilter());
        
        Dao dao = (Dao)enhancer.create();
        dao.update();
        dao.select();
    }
    
}

原理:
Enhancer介绍
Enhancer:
Enhancer既能够代理普通的class,也能够代理接口。
Enhancer创建一个被代理对象的子类并且拦截所有的方法调用(包括从Object中继承的toString和hashCode方法)。
Enhancer不能够拦截final类与方法。

Enhancer.setSuperclass(Class superclass);
用来设置父类型

Enhancer.setCallback(Callback callback);
Enhancer.setCallback(new InvocationHandler(){});
Enhancer.setCallback(new MethodInterceptor(){});
增强
Enhancer.create(Class type, Callback callback);
Enhancer.create(Class superclass, Class[] interfaces, Callback callback);
Enhancer.create(Class[] argumentTypes, Object[] arguments);
方法是用来创建代理对象,其提供了很多不同参数的方法用来匹配被增强类的不同构造方法。

Callback

Callback是一个空的接口,在Cglib中它的实现类有以下几种:
MethodInterceptor
NoOp
LazyLoader
Dispatcher
InvocationHandler
FixedValue

MethodInterceptor:

它可以实现类似于AOP编程中的环绕增强(around-advice)。

它只有一个方法:
public Object intercept(Object obj, java.lang.reflect.Method method, Object[] args, MethodProxy proxy)

代理类的所有方法调用都会转而执行这个接口中的intercept方法而不是原方法
如果需要在intercept方法中执行原方法可以使用参数method进行反射调用,
或者使用参数proxy 一 proxy.invokeSuper(obj, args);
后者会快一些(反射调用比正常的方法调用的速度慢很多)。
MethodInterceptor允许我们完全控制被拦截的方法,并且提供了手段对原方法进行调用,
因为 MethodInterceptor的效率不高,它需要产生不同类型的字节码,
并且需要生成一些运行时对象(InvocationHandler就不需要),所以Cglib提供了其它的接口供我们选择。

InvocationHandler:
它的使用方式和MethodInterceptor差不多。
需要注意的一点是,所有对invoke()方法的参数proxy对象的方法调用都会被委托给同一个InvocationHandler,所以可能会导致无限循环。
死循环状况如下:

public class TracyEminem<T> implements InvocationHandler {
    private T delegate; //被代理的对象
 
    public TracyEminem(T delegate) {
        this.delegate = delegate;
    }
 
 
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("代理对象执行的操作"+method.getName());
        //此处的invoke方法的第一个参数不能使用方法内传入的proxy这个对象,要使用外部传入的delegate
        //对象,否则会出现之前所说的循环调用的现象
        Object result = method.invoke(delegate,args);
        return result;
    }
}

java中可重入锁要怎样进行实现

JAVA可重入锁与不可重入锁

JAVA 中可重入锁的理解

可重入锁,指的是以线程为单位,当一个线程获取对象锁之后,这个线程可以再次获取本对象上的锁,而其他的线程是不可以的。
.
可重入锁的意义之一在于防止死锁。

实现原理实现是通过为每个锁关联一个请求计数器和一个占有它的线程。当计数为0时,认为锁是未被占有的;线程请求一个未被占有的锁时,JVM将记录锁的占有者,并且将请求计数器置为1 。

如果同一个线程再次请求这个锁,计数器将递增;

每次占用线程退出同步块,计数器值将递减。直到计数器为0,锁被释放。


sync和reentrantlock的效率

synchronized和ReentrantLock性能瓶颈及实现


redis为什么快?nio的原理,详细说一说?java里面哪些用到了nio?

redis为什么快

1.采用了多路复用io阻塞机制
2.数据结构简单,操作节省时间
3.运行在内存中,自然速度快

nio和bio

参考:
IO多路复用机制详解
关于BIO和NIO的理解

BIO:同步阻塞式IO,服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销,当然可以通过线程池机制改善。
在while循环中服务端会调用accept方法等待接收客户端的连接请求,一旦接收到一个连接请求,就可以建立通信套接字在这个通信套接字上进行读写操作,此时不能再接收其他客户端连接请求,只能等待同当前连接的客户端的操作执行完成。
如果BIO要能够同时处理多个客户端请求,就必须使用多线程,即每次accept阻塞等待来自客户端请求,一旦受到连接请求就建立通信套接字同时开启一个新的线程来处理这个套接字的数据读写请求,然后立刻又继续accept等待其他客户端连接请求,即为每一个客户端连接请求都创建一个线程来单独处理。
在这里插入图片描述

注:这里的用户线程只是指用户级线程,用来开一个用户线程来监听来自客户端的请求的。
伪代码:

{
 
    read(socket, buffer);
 
    process(buffer);
 
}

即用户需要等待read将socket中的数据读取到buffer后,才继续处理接收的数据。整个IO请求的过程中,用户线程是被阻塞的,这导致用户在发起IO请求时,不能做任何事情,对CPU的资源利用率不够。

NIO:同步非阻塞式IO,服务器实现模式为多个请求一个线程,即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O请求时才启动一个线程进行处理。

在这里插入图片描述

同步非阻塞式IO,关键是采用了事件驱动的思想来实现了一个多路转换器(在java中是select())。
用户线程使用select函数的伪代码描述为:

{
 
    select(socket);
 
    while(1) 
    {
 
        sockets = select();
 
        for(socket in sockets) 
        {
 
            if(can_read(socket)) 
            {
 
                read(socket, buffer);
 
                process(buffer);
 
            }
 
        }
    }
 
}

多路复用器,可以监听来自多个客户端的IO事件:

A. 若服务端监听到客户端连接请求,便为其建立通信套接字(java中就是通道),然后返回继续监听,若同时有多个客户端连接请求到来也可以全部收到,依次为它们都建立通信套接字。
B. 若服务端监听到来自已经创建了通信套接字的客户端发送来的数据,就会调用对应接口处理接收到的数据,若同时有多个客户端发来数据也可以依次进行处理。
C. 监听多个客户端的连接请求和接收数据请求同时还能监听自己时候有数据要发送。

虽然上述方式允许单线程内处理多个IO请求,但是每个IO请求的过程还是阻塞的(在select函数上阻塞),如果用户线程只注册自己感兴趣的socket或者IO请求,然后去做自己的事情,等到数据到来时再进行处理,则可以提高CPU的利用率。

IO多路复用模型使用了Reactor设计模式实现了这一机制。
在这里插入图片描述
如图5所示,通过Reactor的方式,可以将用户线程轮询IO操作状态的工作统一交给handle_events事件循环进行处理。用户线程注册事件处理器之后可以继续执行做其他的工作(异步)而Reactor线程负责调用内核的select函数检查socket状态当有socket被激活时,则通知相应的用户线程(或执行用户线程的回调函数),执行handle_event进行数据读取、处理的工作

线程轮询及回调见:
线程轮询和线程回调
java多线程回调方法的理解

java里面哪些用到了nio

Java NIO使用详解

正如前面提到的,原来的 I/O 以流的方式处理数据而 NIO 以块的方式处理数据

面向流 的 I/O 系统一次一个字节地处理数据。一个输入流产生一个字节的数据,一个输出流消费一个字节的数据。为流式数据创建过滤器非常容易。链接几个过滤器,以便每个过滤器只负责单个复杂处理机制的一部分,这样也是相对简单的。不利的一面是,面向流的 I/O 通常相当慢。

一个 面向块 的 I/O 系统以块的形式处理数据。每一个操作都在一步中产生或者消费一个数据块。按块处理数据比按(流式的)字节处理数据要快得多。但是面向块的 I/O 缺少一些面向流的 I/O 所具有的优雅性和简单性。

从一个文件中读取一些数据,如果使用原来的 I/O,那么我们只需创建一个 FileInputStream 并从它那里读取。而在 NIO 中,情况稍有不同:我们首先从 FileInputStream 获取一个 Channel 对象,然后使用这个通道来读取数据

因此读取文件涉及三个步骤:(1) 从 FileInputStream 获取 Channel,(2) 创建 Buffer,(3) 将数据从 Channel 读到 Buffer 中。

现在,让我们看一下这个过程。

第一步是获取通道。我们从 FileInputStream 获取通道:

FileInputStream fin = newFileInputStream( "readandshow.txt");

FileChannel fc = fin.getChannel();

下一步是创建缓冲区:

ByteBuffer buffer = ByteBuffer.allocate( 1024);

最后,需要将数据从通道读到缓冲区中,如下所示:

fc.read( buffer );

设计模式

设计模式分类(创建型模式、结构型模式、行为模式)

mysql怎么避免表锁

MySQL 避免行锁升级为表锁
在 select,update 和 delete 的时候,where 条件如果不存在索引字段,那么这个事务会导致表锁当“值重复率”低时,甚至接近主键或者唯一索引的效果,“普通索引”依然是行锁当“值重复率”高时,MySQL 不会把这个“普通索引”当做索引,即造成了一个没有索引的 SQL(应该是mysql判断此时全包查询的效率高于索引个,所以抛弃索引),此时引发表锁。)

主键和唯一索引是行锁,其他索引并不一定是行锁,很可能是表锁,操作能使用主键尽量使用主键。

Mybatis #和$区别以及原理

Mybatis #和$区别以及原理

mybatis中#{}和${}的区别

#{}可以防止Sql 注入,它会将所有传入的参数作为一个字符串来处理。
$ {} 则将传入的参数拼接到Sql上去执行,一般用于表名和字段名参数,$ 所对应的参数应该由服务器端提供,前端可以用参数进行选择,避免 Sql 注入的风险

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值