Java程序员面试笔试宝典笔记

第四章 JAVA基础知识

4.3 关键字

4.3.1 命名规则

       标识符的第一个字符必须是字母,下划线,$

4.3.3 final,finally,finalize

     final: 被final修饰的变量不可变(引用不可变,即它只能指向初始时指向的那个对象,而不关心对象内容的变化),被final修饰的变量必须初始化

    final方法: 当一个方法修饰为final,该方法不允许被任何子类重写,但子类仍然可以用这个方法

    final参数: 这个参数在这个函数内不允许被修改

    final类: 此类不能被继承,所有方法不能被重写,但是可以被重载。

    一个类不能既被声明为abstract,又被声明为final。abstract抽象类不能被实例化,也就是不能被new

 finally: 作为异常的一部分,它只能用在try/catch中,并且附带一个语句块,表示这段语句最终一定被执行,经常用在需要释放资源的情况下。

 finalize: 在垃圾回收器执行时会调用被回收对象的finalize()方法。

  • 哪些变量不能被继承?    final关键字修饰的类,String,StringBuffer

4.3.5  static(静态)作用

1.通过static关键字达到全局的效果;实现单例模式

2.static的方法是类的方法,不需要创建对象就可以被调用;

3.不能在成员变量内部定义static变量

4.static方法中不能使用this和super关键字

5.变量用static final修饰,表示一旦赋值,就不可以修改

4.3.7  volatile

volatile被设计来修饰不同线程访问和修改的变量。被volatile定义的变量,系统每次用到它时都是直接从对应的内存中提取,而不会利用缓存。

4.4 基本类型与运算

4.4.1 基本数据类型:

 1字节:byte      boolean

 2字节:char       short

 4字节: int         float

 8字节:long       double

封装类:由于java是面向对象的,而基本类型不具有对象的性质,为了让其具有对象的的特征,将其封装。在ArrayList,HashMap中用到。

4.4.2 不可变类

   所有基本类型的包装类都是不可变类,例如Integer,Float。此外,String也是不可变类
4.7Java IO流的实现机制是什么?

 - 流分为字节流和字符流,其中字节流继承于InputStream和OutputStream,字符流继承于Reader和Writer。流的主要作用主要是为了改善程序性能并且使用方便
 - 字节流(8bit)和字符流(16bit)区别:字节流在处理输入输出时不会用到缓存,而字符流用到了缓存。
 - Java IO类采用了装饰器模式,好处在于可以在运行时动态地为对象添加一些额外的指责,很灵活
 - Java IO(非阻塞IO),与传统的Socket(套接字)数据交换的方式相比,在处理大量并发请求时,使用NIO要比使用Socket效率高

4.5.1  字符串创建

   new String("abc")创建了几个对象?    一个或者两个,如果常量池中原来有abc,那么只创建一个对象;如果常量池中原来没有字符串abc,那么创建两个对象。

4.5.2 ==和equal

==:比较变量对应的内存中存储的数值是否相同,要比较两个基本类型的数据或两个引用变量是否相等,只能用==;对于指向对象类型的变量,如果要比较两个对象是否指向同一块存储空间,用==。

equals:对于对象类型的变量,要比较两个对象的内容是否相等,用equals

如果一个类没有自己定义equals()方法,它默认使用从Object类继承的==,此时equals和==是一样的效果,如果编写的类希望比较改类创建的两个实例对象的内容是否相等,那么必须覆盖equals方法。

4.5.6 length

length针对数组,length()针对字符串

4.6异常处理

4.6.1 finally块的代码什么时候被执行?

        当程序进入try语句之前出现异常,会直接结束,不会执行finally块中的代码;当程序在try块中强制退出时也不会去执行finally 块中的代码

  1.   finally块里的代码在return之前执行;
  2. 当finally块中有return语句时,将会覆盖函数中其他return语句;
  3. 出现在java程序中的finally块不是一定会被执行:

 4.6.2 异常处理的原理是什么?

 异常:包括Error(不可恢复)和Exception(可恢复,编译器可捕获),它们的父类是Throwable

 - 运行时异常和普通异常区别:
 1.检查异常:发生在编译阶段,java编译器强制程序去捕获此类型的异常
   2.运行时异常:编译器没有强制对其进行捕获并处理

4.7 输入输出流

 1.java中有几种类型的流?

      常见的流有两种,字节流(8bit)和字符流(16bit)。  字节流继承于InputStream与OutputStream,字符流继承于Reader和Writer。流的主要作用是改善程序性能和使用方便。

4.7.3 Socket

套接字:网络上两个程序通过一个双向的通信连接实现数据交换,这个双向链路的一端称为一个Socket。

Socket可以分为两种类型:面向连接的Socket通信协议(TCP,传输控制协议)

                                           面向无连接的Socket协议(UDP,用户数据报协议)


4.8 Java平台与内存管理

  •  Java程序----编译----生成字节码文件(.class 是一种中间码)------通过类加载器(ClassLoader和它的子类)将类加载到JVM中------JVM负责将字节码翻译为硬件平台能执行的代码
  • 为什么说Java是平台独立性语言?      平台独立性是指可以在一个平台上编写和编译程序,而在其他平台上运行。保证java具有平台独立性的机制为中间码和java虚拟机。java程序被编译后不是生成在硬件平台上可执行的代码,而是生成中间码。不同的硬件平台会安装不同的JVM,由JVM来负责把中间码翻译成硬件平台能执行的代码。
  • 类加载过程:当运行程序时,JVM会将编译生成的.class文件按照一定需求和一定的规则加载到内存中,并组织成为一个完整的Java应用程序。由类加载器来完成,类加载器本身也是一个类,其实质是把类文件从硬盘读取到内存中。
  • 在Java语言中,类的加载是动态的,它并不会一次性将所有类全部加载后再运行,而是保证程序运行的基础类完全被加载到JVM中,至于其他类,则在需要时才加载。
  •  3种类加载器:负责加载系统类(Bootstrap Loader),负责加载扩展类(ExtClassLoader ),负责加载应用类(AppClassLoader).这三个类是如何让协调工作来完成类的加载的?它们通过委托的方式实现。当有类需要被加载时,类加载器会请求父类来完成这个载入工作,父类会使用其自己的搜索路径来搜索需要被载入的类,如果搜索不到,才会由子类按照其搜索路径来搜索待加载的类。
  • 类加载的主要步骤:

            1.装载。根据查找路径找到对应的.class文件,然后导入。
            2.链接。链接又分为3个小步骤:
                (1)检查。检查待加载的class文件的正确性。
                (2)准备。给类中的静态变量分配存储空间。
                (3)解析。将符号引用转换成直接引用(这一步是可选的)。
           3.初始化。对静态变量和静态代码块执行初始化工作。        

  •     对于垃圾回收器而言,使用有向图来记录和管理堆内存中的所有对象,通过这个有向图就可以识别哪些对象是可达的,哪些是不可达的,所有不可达对象都是课被回收的。
  • 垃圾回收算法:
  1. 引用计数法: 堆中每个对象都有一个引用计数器;当对象被引用时,计数器加1,当引用被置为空或离开作用域时,引用计数减1,由于这种方法无法解决相互引用的问题,因此JVM没有采用这种算法。
  2. 追踪回收算法:利用JVM维护的对象引用图,从根节点开始遍历对象的应用图,同时标记遍历到的对象。当遍历结束后,未被标记的对象就是目前不使用的,可以被回收。
  3. 标记整理算法:把堆中活动的对象移动到堆的一端,这样就会在堆中另外一端留出很大的空闲区域,相当于对堆中的碎片进行了集中处理。
  4. 复制回收算法:把堆分成两个大小相同的区域,在任何时刻,只有其中的一个区域被使用,直到这个区域被消耗完为止,此时垃圾回收器会中断程序的执行,通过遍历的方式把所有活动的对象复制到另一个区域中,在复制的过程中它们是紧挨着的,从而可以消除内存碎片。当复制过程结束后程序会接着执行,直到这块区域被使用完,然后再使用上面的方法继续进行垃圾回收。 
  5. 分代回收算法:把堆分成两个或者多个子堆,每一个子堆被视为一代。算法在运行的过程中优先收集那些年幼的对象,如果一个对象经过多次收集任然存活,那么就可以把这个对象转移到高一级的堆里,减少对其的扫描次数。     
  •     内存泄漏: 是指一个不再被程序使用的对象或者变量还在内存中占有存储空间。
  •    内存泄漏主要有两种情况:

             1.在堆中申请的空间没有被释放;
             2.对象已不再被使用,但还仍然在内存中保留着。
               垃圾回收机制的引入可以有效地解决第一种情况;而对于第二种情况,垃圾回收机制则无法保证不再使用的对象会被释放。

  •  堆和栈的区别:

         1.栈内存主要用来存放基本数据类型和引用变量。每当有函数调用时,都会通过压栈方式    创建新的栈帧,每当函数调用结束后都会通过弹栈的方式释放栈。
       2.堆内存是用来存放运行时创建的对象。一般来讲,通过new关键字创建出来的对象都存放在堆内存中。

  •   java中引用的用法:

   在堆中产生一个数组或对象后,可以在栈中定义一个特殊的变量,让栈中这个变量的取值等于数组或对象在堆内存中的首地址,栈中的这个变量就成了数组或对象的引用变量。引用变量就相当于为数组或对象起的一个名称,以后就可以在程序中使用栈中的引用变量来访问堆中的数组或对象。   P132


4.9  容器

  •  Collections: 是整个集合框架的基础,它里面存储一组对象,表示不同类型的Collections,它的作用只是提供维护一组对象的基本接口而已。
  •   ArrayList,Vector,LinkedList有什么区别:

        1.均为可伸缩数组,即可以动态改变长度的数组。
        2.ArrayList和Vector都是基于存储元素的Object[] array来实现的,它们会在内存中开辟一块连续的空间来存储,由于数据存储是连续的,因此,它们支持下标来访问元素,索引速度块,但在插入元素时须移动容器中元素,插入慢。扩容时,Vector默认扩充为原来2倍,ArrayList扩充为原来1.5倍。  这两者最大的区别是synchronization的使用,ArrayList不同步,而Vector的绝大多数方法是同步的,所以Vector线程安全ArrayList不是线程安全。正是由于Vector提供线程安全机制,其性能逊色于ArrayList。
       3 . LinkedList采用双向链表实现,对数据的索引需要从列表头开始遍历,因此随机访问效率低但是插入元素时不需要对数据进行移动,因此插入效率高

    在实际使用时,对数据主要操作为索引或者在集合末端进行增加,删除时,使用ArrayList或Vector效率高;当对数据的操作主要为为制定位置插入和删除时,使用LinkedList;当在多线程中使用容器时,选用Vector安全。

  • HashMap和Hashtable区别:

       1.HashMap是Hashtable的轻量级实现,都完成了Map接口。HashMap允许一条空(null)键值(key),而HashTable不允许。

       2.Hashtable继承自Dictionary类,而HashMap是java1.2引进的Map interface的一个实现。

       3.Hashtable线程安全,而HashMap不支持线程的同步,所以是非线程安全的。在多个线程访问Hashtable时,不需要开发人员对它进行同步,而对于HashMap,开发人员必须提供额外的同步机制。

  •  HashMap里面存入的键值对在取出时没有固定的顺序,一般而言,在Map中插入,删除和定位元素,HashMap是最好的选择。而TreeMap实现了SortMap接口,能够把它保存的记录根据键排序,因此取出来的是排序后的键值对。如果需要按自然顺序或自定义顺序遍历键,那么TreeMap更好。LinkedHashMap是HashMap的一个子类,如果需要输出的顺序和输入的相同,那么用LinkedHashMap。WeakHashMap和HashMap类似,不同在于WeakHashMap中key采用的是弱引用。
  • 使用自定义类作为HashMap的key时,注意:

      1.如果向根据对象的相关属性来自定义对象是否相等的逻辑,此时需要重写equals()方法,一旦重写了equals(),那么就必须重写hashCode()方法。

      2.当自定义类的多项作为HashMap(Hashtable)的key时,最好把这个类设计为不可变类。

      3.从HashMap的工作原理可以看出,如果两个对象相等,那么这两个对象有相同的hashCode,反之则不成立。

  • Collection和Collections区别:

     1.  Collection:集合接口。   提供了对集合对象进行基本操作的通用接口方法。   实现该接口的类有List和Set,  该接口的设计目标是为各种具体的集合提供最大化的统一的操作方式

     2.Colleactions: 针对集合类的包装类, 提供了一系列静态方法以实现对各种集合的搜索,排序,线性安全化等操作。  Coolections类不能实例化,如同一个工具类,服务于Collection框架。

4.10    多线程

  • 线程和进程:

      线程:指程序在执行过程中,能够执行程序代码的一个执行单元

      进程:指一段正在执行的程序

    线程又被称为轻量级进程,它是程序执行的最小单元,一个进程可以拥有多个线程,各个线程之间共享程序的内存空间(代码段,数据段,堆空间)及一些进程的资源,但是各个线程拥有自己的栈空间。

  • 实现多线程的方式:

    1. 继承Thread类,重写run()方法:   Thread本质上也是实现了Runnable接口的一个实例,它代表一个线程的实例,并且启动线程的唯一方法是通过Thread类的start()方法。

class MyThread extends Thread{   //继承Thread类
    public void run(){
      ...         //重写run()方法
    }
}
public class Test{
   public static void main(String[] args){
       Mythread thread = new Mythread();
       thread.start(); //开启线程
   }
}
    

    2. 实现Runnable接口,并实现该接口的run()方法:    自定义类并实现Runnable接口,实现run()方法   ----->      创建Thread对象,用实现Runnable接口的对象作为参数实例化该Thread对象   -------->     调用Thread的start()方法。

class MyThread implements Runnable{ //实现Runnable接口
   public void run(){
      ...    //实现run()方法
   }
}
public class Test{
   public static void main(String[] args){
      MyThread thread = new MyThread();
      Thread t = new Thread(thread);
      t.start(); //开启线程
   }
}
   

    3. 实现Callable接口,重写call()方法

public class CallableAndFuture{
   public static class CallableTest implement Callable<String>{ //实现Callable接口
       public String call() throws Exception{
          ...  //重写call()方法
       }
   }
   public static void main(String[] args){
       ExecutorService threadPool = Executors.newSingleThreadExecutor(); 
       Future<String> future = threadPool.submit(new CallableTest());//启动线程
       try{
           ...
           System.out.print(future.get()); //等待线程结束,并获取返回结果
       }catch(Exeception e){
           e.printStackTrace();
       }
   }
}
  •  run()和start()区别:

       start():启动一个线程,此时该线程属于就绪状态,而非运行状态,就意味着整个线程可以被JVM来调度。

       run():在调度过程中,JVM通过调用线程类的run()方法来完成实际的操作,当run()方法结束后,此线程就会终止。

  • 多线程同步的实现方法有哪些?

      1. synchronized()关键字:    该锁表明对象在任何时候只允许被一个线程拥有,当一个线程调用对象的一段synchronized代码时,需要先获得这个锁,然后去执行响应的代码,执行结束后,释放锁。

      2. wait()方法与notify()方法:   在synhronized代码被执行期间,线程可以调用对象的wait()方法,释放对象锁,进入等待状态,并且可以调用notify()或者notifyAll()通知正在等待的其他线程。

      3. Lock:    

  • sleep()方法和wait()方法区别:

       1. 原理不同:sleep()是Thread类的静态方法,它使此线程暂时执行一段时间,而把执行机会让给其他线程,等计时时间一到,此线程会自动苏醒。  wait()使Object类的方法,用于线程间的通信,这个方法使当前拥有该对象锁的进程等待,直到其他线程调用notify()方法。

      2. 对锁的处理机制不同:   sleep()方法不会释放锁;    wait()线程会释放掉它所占用的锁,从而使线程所在对象中的其他synchronized数据可被别的线程调用。

      3. 使用区域不同:   wait()必须放在同步控制方法或同步语句块中使用,sleep()可在任何地方使用。

  • 终止线程的方法有哪些?

      stop()和suspend()方法,用Thread.stop()来终止线程时,它会释放已经锁定的所有监视资源。suspend()方法容易造成死锁。所以一般采用的方法是让线程自行结束进入Dead状态。

  • synchronized和lock区别:

       1.用法:  synchronized既可以加在方法上,也可以加在特定代码块中;  而Lock需要显式地指定起始位置和终止位置。

       2. 性能:  在资源不是很激烈的情况下,synchronized性能优于ReetrantLock, 在资源竞争很激烈的情况下,synchronized性能下降很快,而ReetrantLock性能保持不变。

       3.机制:   synchronized,获得多个锁时,必须以相反顺序释放,自动解锁,不会引文出现异常引发死锁;   Lock,手动解锁,必须在finally块中释放,否则引起死锁问题。

  • join方法:

     join()方法的作用是让调用该方法的线程在执行完run()方法后,再执行join方法后面的代码。简单来说,就是将两个线程合并,实现同步功能。

第五章 JAVA WEB

5.1  Servlet与JSP

  • 客户端:  用户和浏览器
  • 服务端: 服务器
  • 浏览器作用: 

          1. 完成与服务器端的交互

          2. 完成HTML的解析,从而实现把用户需要查看的资源信息以直观的形式展示出来

  • 服务器端作用:   用来接收客户端发来的请求,并对该请求进行处理,找到客户端请求的资源,最后把请求的资源返回给客户端
  • 最基本的页面访问处理流程:

           1. 用户通过浏览器输入链接地址来请求所需的资源

           2. 浏览器接受用户的请求,并把该请求组装成指定的格式发送给服务器端,客户端与服务器端通过HTTP来完成具体的交互。请求的数据流中包括:HTTP请求方法,请求的网址URL, 参数

           3. 服务器收到客户端发来的请求,并查找用户所需要的资源

           4. 服务器找到用户请求的资源后,把该资源返回给客户端

           5.服务器通过把响应消息组装成特定的消息格式后返回给客户端,这个过程通过HTTP来完成。响应的数据流主要包括:状态编码,Comment-type,响应消息的内容

  • Servlet:    生成动态页面
  • cookie:    在HTTP下,服务器或脚本可以i维护客户工作站上信息的一种方法。它是由Web服务器保存在用户浏览器上的小文件,可以包含有关用户的信息
  • session:  用来在客户端与服务器端之间保持状态的解决方案以及存储结构
  • cookie和session的区别:

       1. cookie采用的是在客户端保持状态的方案, 数据存放在客户的浏览器上;  session机制采用的是在服务器端保持状态的方案,数据放在服务器上。

        2.cookie安全性不够     由于cookie信息存放在客户端,其他人可以很容易地得到存放在本地的cookie,并进行cookie欺骗;而session信息存放在服务器端,比较安全。

        3.cookie性能更高     由于session会在一定时间内保存在服务器上,因此当访问量增多时,会降低服务器的性能。

        4.单个cookie保存的数据不能超过4KB,很多浏览器都限制一个站点最多保存20个cookie;而session不存在此问题。

第六章 数据库原理

  • SQL操作   P217
  • delete和truncate区别:

          1.用delete操作后,被删除的数据占用的内存空间还在,可以恢复,delete执行的过程是每次从表中删除一行数据,同时将地喊出的操作以日志的形式保存,一遍将来进行回滚操作。        truncate操作删除数据后,被删除的数据会立即释放占用的存储空间,一旦执行不能被回滚,被删除的数据不能恢复

          2.truncate速度快于delete

  • 内链接和外连接区别:

       内链接只显示符合连接条件的记录,外连接除了显示符合连接条件的记录外,还显示表中的记录。外连接有左外连接,右外连接,全连接

  • 事务: 是数据库中一个单独的执行单元,它通常由高级数据库操作语言或编程语言编写的用户程序的执行所引起。当在数据库中更改数据成功时,在事务中更改的数据便会提交,不再改变。否则,事务就取消或者回滚,更改无效。
  • 事务的属性:
  1. 原子性: 事务是一个不可分割的整体,当数据修改时,要么全执行,要么不执行
  2. 一致性:一个事务执行之前或者执行之后,数据库数据必须保持一致性状态。 例如银行转账前后两个账户金额之和应保持不变
  3. 隔离性: 当两个或多个事务并发执行时,为了保证数据的安全性,将一个事务内部的操作与事务的操作隔离起来,不被其他正在进行的事务看到。实现隔离性时解决临时更新与消除级联回滚问题的一种方式。
  4. 持久性: 事务完成以后,增删查改保证它对数据库中的数据的修改是永久性的,当系统或介质发生故障时,该修改也永久保存。持久性一般通过数据库备份和恢复来保证。
  • 范式:
  1. 第一范式 1NF: 无重复的列
  2. 第二范式 2NF:   要求数据库表中的每个实例或行必须可以被唯一区分,即如果关系模式R为第一范式,并且R中的每一个非主属性完全函数依赖于R的某个候选键。
  3. 第三范式 3NF: 如果关系模式R是第二范式,且每个非主属性都不传递依赖于R的候选键。
  4. BCNF: 建立在第三范式上,关系模式是1NF,且每个属性都不传递依赖于R的候选键。
  5. 4NF: 只有一个1:多

第7章  设计模式

  • 单例模式: 保证在整个应用程序的生命周期中,任何一个时刻,单例类的实例都只存在一个。

  • 工厂模式:  工厂模式专门负责实例化大量公共接口的类。 工厂模式可以动态地决定将哪一个类实例化,而不必事先知道每次要实例化哪一个类。工厂类负责创建抽象产品的具体子类的实例。
  1.  简单工厂模式: 简单工厂模式的工厂类是根据提供给它的参数,返回的是几个可能产品中的一个类的实例。
  2. 工厂方法模式:  工厂方法模式是类的创建模式,其用意是定义一个用于创建产品对象的工厂的接口而将实际创建工作推迟到工厂接口的子类中。
  3. 抽象工厂模式: 抽象工厂模式可以向用户端提供一个接口,使用户端在不必指定产品的具体情况下,创建多个产品族中的产品对象。
  • 适配器模式: 把一个类的接口转换成客户端所期望的另一个接口,从而使原本因接口不匹配而无法一起工作的两个类能够一起工作。
  • 观察者模式:一个对象通过添加一个方法(该方法允许观察者注册自己)使本身变得可观察。当可观察的对象更改时,它会将信息发送到已注册的观察者。

数据库:

  •   数据库执行顺序

      

  • 不建议用查询缓存:因为在一个表上有更新的时候,跟这个表有关的查询缓存就会失效,所以这条语句就会把表上所有缓存结果都清空。
  • 日志模块:  redo log(重做日志)---InnoDB    binlog(归档日志)----Server
  1. redo log: 先写日志,再写磁盘;  有了redo log,InnoDB就可以保存即使数据库发生异常重启,之前提交的记录都不会丢失,这个能力称为crash-safe。 redo log中记录数据页的更新细节,支持崩溃恢复。循环写,这样历史日志没法保留。
  2. bin log:支持归档
  3. 不同点:   redo log是InnoDB引擎特有的;binlog是MySQL的Server层实现的,所有引擎都可以使用。   

                          redo log是物理日志,记录的是在某个数据页上做了什么修改;binlog是逻辑日志,记录的是这个语句的原始逻辑,比如给ID=2这一行的c字段加1

                          redo log循环写,空间固定会用完;binlog可追加写,不会覆盖以前的日志

  • 简单来说,事务就是要保证一组数据库操作,要么全部成功,要么全部失败; 事务可以保证中间结果不被别的事务读到,因此修改计数值和插入新纪录的顺序是不影响逻辑结果的
  • 经过索引优化,避免回表过程?由于覆盖索引可以减少树的搜索次数,显著提升查询性能,所以使用覆盖索引是一个常用的性能优化手段。
  •  根据加锁的范围,Mysql里面的锁大致可以分为 全局锁,表级锁,行锁
  1. 全局锁:加锁方法 Flush tables with read lock(整个库处于只读状态);典型使用场景--做全局逻辑备份;
  2. 行锁:在InnoDB事务中,行锁是在需要时候加上的,但并不是不需要了就立即释放,而是要等到事务结束后才释放。这就是两阶段锁协议。     如果你的事务中需要锁多个行,要把最可能造成锁冲突,最可能影响并发度的锁尽量往后放。

大多数情况下优化器都能找到正确的索引,但偶尔还是会碰到原本可以执行的很快的SQL语句,执行速度却比预期的慢很多

  • 索引选择异常和处理:
  1. 采用force index强行选择一个索引。 Mysql会根据词法解析的结果分析出可能使用的索引作为候选项,然后再候选列表中依次判断每个索引需要扫描多少行。如果force index指定的索引在候选索引列表中,就直接选择这个索引,不再评估其他索引的执行代价。
  2. 考虑修改语句,引导Mysql使用我们期望的索引。
  3. 在有些场景下,我们可以新建一个更适合的索引,来提供给优化器做选择,或删掉无用的索引。
  • 如果某次写入使用了change buffer机制,之后主机异常重启,是否会丢失change buffer和数据?    

不会丢失。虽然只更新内存,但是事务提交的时候,我们把change buffer的操作也记录到了redo log里了,所以崩溃恢复的时候,change buffer也能找回来。

  • 字符串创建索引的方式:
  1. 直接创建完整做因,这样可能比较占用空间
  2. 创建前缀索引,节省空间,但会增加查询扫描次数,并且不能使用覆盖索引
  3. 倒序存储,再创建前缀索引,可以绕过字符串本身前缀的区分度不够的问题
  4. 创建hash字段索引,查询性能稳定,有额外的存储和计算消耗,和第三种方式一样,都不支持范围扫描。
  5. 当内存数据页跟磁盘数据页内容不一致的时候,我们称这个内存页为“脏页”,内存数据写入到磁盘后,内存和磁盘上的数据页内容就一致了,称为干净页。无论是脏页还是干净页,都在内存中。
  6. InnoDB的刷盘速度考虑两个因素: 脏页比例,redo log写盘速度
  7. 收缩表空间的方法:

要收缩一个表,只是delete掉表里面不用的数据的话,表文件的大小是不会变的,你还要通过alter table命令来重建表,才能达到表文件变小的目的。

重建表的两种实现方式:Online DDL的方式是可以考虑再业务低峰期使用的,而Mysql5.5及以前的版本,这个命令是会阻塞DML的。

  • 如果一个高配的机器,redo log设置太小,会发生什么?

        每次事务都要提交redo log,如果设置太小,很快会被写满,这时候系统不得不停止所有更新,去推进checkpoint。磁盘压力很小,但是数据库出现间歇性的性能下跌。

  • 如果现在有一个页面经常要显示交易系统的操作记录总数,那只能自己计数。自己计数的方法和优缺点:
  1. 用缓存系统保存计数: 

    可以用一个redis服务来保存这个表的总行数。表每插入一行redis计数加1,每被删除一行redis计数减1;这种方式下,读和更新的操作很快,但是缓存系统可能会丢失更新,即使redis正常工作,这个值还是逻辑上不精确的,因为在并发系统里,我们无法精确控制不同线程的执行时刻。

    2.  在数据库保存计数:(把计数直接放到数据库里单独一张计数表)

     解决了崩溃丢失的问题,InnoDB是支持崩溃恢复不丢数据的;解决了一致性视图的问题。用事务来确保计数准确,由于事务可以保证中间结果不被别的事务读到,因此修改数值和插入新纪录的顺序是不影响逻辑结果的。

  • 什么时候使用alter table t engine = InnoDB会让一个表占用的空间反而变大?

     在DDL期间,如果刚好有外部的DML在执行,这期间可能会引起一些新的空洞。将表t重建依次,插入一部分数据,但插入的这些数据用掉了一部分预留空间(再重建表的时候,InnoDB不会把整张表占满,每个页留了1/16给后续的更新用),这种情况下,再重建一次表t,就可能出现问题中的现象。

  • 18. 为什么这些SQL语句逻辑相同,性能却差异巨大?

        对于索引字段做函数操作,可能会破坏索引值的有序性,因此优化器就决定放弃走树搜索功能。在这个例子中,优化器放弃了树搜索功能,优化器可以选择遍历主键索引和选择遍历索引t_modified,优化器对比索引大小后发现,索引t_modified更小,遍历这个索引比遍历主键索引来的快,因此最终选择索引t_modified。

       例子中,由于在t_modified字段加了month()函数操作,导致了全索引扫描。为了能够用上索引的快速定位能力,我们需要把sql语句改成基于字段本身的范围查询。

在mysql中,字符串和数字做比较的话,是将字符串转换成数字。

结果是:优化器会现在交易记录表tradelog上用主键索引查到id=2的行,但是没有用上交易详情表trade_detail上的tradeid索引,而是用主键id进行了全表扫描。

原因:两个表的字符集不同,一个是utf8,一个是utf8m64,所以做表连接查询的时候用不上关联字段的索引。

字符集utf8m64是utf8的超集,所以当这两个类型的字符串做比较的时候,mysql内部的操作是,先把utf8转换为utf8m64字符集,在做比较。-----> 字符集不同只是条件之一,连接过程中要求在被驱动表的索引字段上加函数操作,是直接导致对被驱动表做全表扫描的原因

小结:对索引字段做函数操作,可能会破坏索引值的有序性,因此优化器就决定放弃走树搜索功能

  • 为什么我只查一行的语句,页执行这么慢?

第一类---------查询长时间不返回     :这种情况大概率是表被锁住了,用show processlist命令分析原因。

1.等MDL锁----命令查看显示为Waiting for table metadata lock  :出现这个状态表示的是,现在有一个线程正在表上请求或者持有MDL写锁,把select语句堵住了。这类问题的处理方式,就是找到谁持有MDL写锁,然后把它kill掉。

2.等flush---waiting for table flush  :这个情况可能是有一个flush tables命令被别的语句堵住了,然后它又堵住了我们的select语句

3.等行锁---

第二类---查询慢:

1.由于字段上没有索引,因此查询语句只能走id主键顺序扫描

2.带lock in share mode的sql语句,是当前读,因此会直接读到最新的结果,所以速度很快;但是没有加事务的语句,是一致性读,需要依次执行undo log,所以慢。

  •  20.幻读:

幻读是指一个事务在前后两次查询同一个范围的时候,后一次查询看到了前一次查询没有看到的行。幻读专指新插入的行

  • 23    redo log写入机制:

    redo log的三种状态:

        1.存在redo log buffer中,物理上是在MySQL进程内存中;

         2.写到磁盘,但是没有持久化,物理上是在文件系统的page cache里面;

         3.持久化到磁盘,对应的是hard disk;

1.两个场景让一个没有提交事务的redo log写到磁盘中:

     1.redo log buffer占用的空间即将达到innodb_log_buffer_size一半的适合,后台线程会主动写盘;

     2.并行的事务提交的适合,顺带将这个事务的redo log budder持久化到磁盘

      

猜你喜欢

转载自blog.csdn.net/weixin_38361153/article/details/90347453