记录些Spring+题集(29)

JAVA中常用的集合的区别

在java中集合主要分为:List,Set,Map三种,其中List与Set继承自Collection,而Map不是。List与Set的区别:List中的元素有存放顺序,并且可以存放重复元素,检索效率高,插入删除效率低,Set没有存放顺序,而且不可以存放重复元素,后来的元素会把前面重复的元素替换掉,检索效率低,插入删除效率高。Set存储位置是由它的HashCode码决定的,所以它存储的对象必须有equals()方法,而且Set遍历只能用迭代,因为它没有下标。

String,Stringbuff,StringBuilder的区别

  • String :不可变的字符序列;底层使用 byte[] 存储。

  • StringBuffer :可变的字符序列;线程安全的,效率低;底层使用 byte[] 存储。

  • StringBuilder :可变的字符序列;jdk5.0新增的,线程不安全的,效率高;底层使用 byte[] 存储。

效率从高到底依次是:StringBuilder>StringBuffer>String

List遍历中删除元素会有什么问题

遍历删除List中符合条件的元素主要有以下几种方法:

  1. 普通for循环

  2. 增强for循环 foreach

  3. 迭代器iterator

  4. removeIf  和 方法引用 (一行代码搞定)

使用增强for循环foreach会报java.util.ConcurrentModificationException并发修改异常。其中使用普通for循环容易造成遗漏元素的问题。所以推荐使用迭代器iterator,或者JDK1.8以上使用lambda表达式进行List的遍历删除元素操作。

以下是上述几种方法的具体分析:

普通for循环
/** 
 * 普通for循环遍历删除元素
 */  
List<Student> students = this.getStudents();  
for (int i=0; i<students.size(); i++) {  
    if (students.get(i).getId()%3 == 0) {  
        Student student = students.get(i);  
        students.remove(student);  
    }  
}

由于在循环中删除元素后,list的索引会自动变化,list.size()获取到的list长度也会实时更新,所以会造成漏掉被删除元素后一个索引的元素。比如循环到第2个元素时你把它删了,接下来去访问第3个元素,实际上访问到的是原来list的第4个元素,因为原来的第3个元素变成了现在的第2个元素。这样就造成了元素的遗漏。

增强for循环 foreach
/**
 * 增强for循环遍历删除元素
 */
List<Student> students = this.getStudents();  
for (Student stu : students) {  
    if (stu.getId() == 2)   
        students.remove(stu);  
}

使用foreach遍历循环删除符合条件的元素,不会出现普通for循环的遗漏元素问题,但是会产生ConcurrentModificationException并发修改异常的错误。报ConcurrentModificationException错误的原因:

先来看一下JDK源码中ArrayList的remove源码是怎么实现的:

public boolean remove(Object o) {
    if (o == null) {
        for (int index = 0; index < size; index++)
            if (elementData[index] == null) {
                fastRemove(index);
                return true;
            }
    } else {
        for (int index = 0; index < size; index++)
            if (o.equals(elementData[index])) {
                fastRemove(index);
                return true;
            }
    }
    return false;
}

一般情况下程序的执行路径会走到else路径下最终调用fastRemove方法:

private void fastRemove(int index) {
    modCount++;
    int numMoved = size - index - 1;
    if (numMoved > 0)
        System.arraycopy(elementData, index+1, elementData, index,
                         numMoved);
    elementData[--size] = null; // clear to let GC do its work
}

在fastRemove方法中,可以看到第2行把modCount变量的值加一,但在ArrayList返回的迭代器会做迭代器内部的修改次数检查:

final void checkForComodification() {
    if (modCount != expectedModCount)
        throw new ConcurrentModificationException();
}

而foreach写法是对实际的Iterable、hasNext、next方法的简写,因为上面的remove(Object)方法修改了modCount的值,所以才会报出并发修改异常。

要避免这种情况的出现则在使用迭代器迭代时(显式或for-each的隐式)不要使用List的remove,改为用Iterator的remove即可。

迭代器iterator
/**
 *  迭代器iterator
 */
List<Student> students = this.getStudents();  
System.out.println(students);  
Iterator<Student> iterator = students.iterator();  
while (iterator .hasNext()) {  
    Student student = iterator .next();  
    if (iterator.getId() % 2 == 0)  
        iterator.remove();
    //这里要使用Iterator的remove方法移除当前对象,
    // 如果使用List的remove方法,则同样会出现ConcurrentModificationException  
}

由上述foreach报错的原因,注意要使用迭代器的remove方法,而不是List的remove方法。

removeIf 和 方法引用

在JDK1.8中,Collection以及其子类新加入了removeIf方法,作用是按照一定规则过滤集合中的元素。方法引用是也是JDK1.8的新特性之一。方法引用通过方法的名字来指向一个方法,使用一对冒号 :: 来完成对方法的调用,可以使语言的构造更紧凑简洁,减少冗余代码。

使用removeIf和方法引用删除List中符合条件的元素:

List<String> urls = this.getUrls();  
 
// 使用方法引用删除urls中值为"null"的元素
urls.removeIf("null"::equals);

作为removeIf的条件,为true时就删除元素。使用removeIf 和 方法引用,可以将原本需要七八行的代码,缩减到一行即可完成,使代码的构造更紧凑简洁,减少冗余代码。

Java中如何直接访问内存

Java最初被设计为一种安全的受控环境。尽管如此,Java HotSpot还是包含了一个“后门”,提供了一些可以直接操控内存和线程的低层次操作。这个后门类—sun.misc.Unsafe—被JDK广泛用于自己的包中,如java.nio和java.util.concurrent。

这个类在JDK中有广泛的应用,例如,java.nio和java.util.concurrent、Netty、Caffeine。但是不建议随意使用这个后门。因为这个API十分不安全、不轻便、而且不稳定。然而,Unsafe提供一种简单的方法来观察HotSpot JVM内部的一些技巧。

获取Unsafe

sun.misc.Unsafe这个类的访问是受限的,它的构造方法是私有的,相应的工厂方法要求必须被Bootloader载入才能使用,也就是说,只有JDK内部分才能使用这个工厂方法来构造Unsafe对象。

public final class Unsafe {
    …
    private Unsafe() {}
    private static final Unsafe theUnsafe = new Unsafe();
    …
        public static Unsafe getUnsafe() {
        Class cc = sun.reflect.Reflection.getCallerClass(2);
        if (cc.getClassLoader() != null)
            throw new SecurityException(“Unsafe”);
        return theUnsafe;

    }
    …
}

幸运地是,有一个theUnsafe属性可以被利用来检索Unsafe实例,我们可以见到的写一个反射方法,来获取Unsafe实例:

public static Unsafe getUnsafe() {
    try {
        Field f = Unsafe.class.getDeclaredField(“theUnsafe”);
        f.setAccessible(true);
        return (Unsafe)f.get(null);
    } catch (Exception e) { /* … */ }
}

下面将看看一些Unsafe的方法。

1.对直接内存进行读写。

long getAddress(long address) 和void putAddress(long address, long x)

2.另一个类似的方法对直接内存进行读写,将C语言的结构体和Java对象进行转换。

int getInt(Object o, long offset) , void putInt(Object o, long offset, int x)

3.分配内存,可以看做是C语言的malloc()函数的一种包装

long allocateMemory(long bytes)

sun.misc.Unsafe提供了几乎是不受限制的监控和修改虚拟机运行时数据结构的能力。

尽管这些能力几乎是和Java开发本身不相干的,但是对于想要学习HotSpot虚拟机但是没有C++代码调试,或者需要去创建特别的分析工具的人来说,Unsafe是一个伟大的工具。

sleep和wait的区别

sleep 方法和 wait 方法都是用来将线程进入休眠状态的,并且 sleep 和 wait 方法都可以响应 interrupt 中断,也就是线程在休眠的过程中,如果收到中断信号,都可以进行响应并中断,且都可以抛出 InterruptedException 异常,那 sleep 和 wait 有什么区别呢?

区别一:语法使用不同

wait 方法必须配合 synchronized 一起使用,不然在运行时就会抛出 IllegalMonitorStateException 的异常,而 sleep 可以单独使用,无需配合 synchronized 一起使用。

区别二:所属类不同

wait 方法属于 Object 类的方法,而 sleep 属于 Thread 类的方法

区别三:唤醒方式不同

sleep 方法必须要传递一个超时时间的参数,且过了超时时间之后,线程会自动唤醒。

而 wait 方法可以不传递任何参数,不传递任何参数时表示永久休眠,直到另一个线程调用了 notify 或 notifyAll 之后,休眠的线程才能被唤醒。

也就是说 sleep 方法具有主动唤醒功能,而不传递任何参数的 wait 方法只能被动的被唤醒。

区别四:释放锁资源不同

wait 方法会主动的释放锁,而 sleep 方法则不会。

数据库中删除表数据的方法和区别

truncate、delete、drop区别
区别点droptruncatedelete
执行速度较快
命令分类DDL(数据定义语言)DDL(数据定义语言)DML(数据操作语言)
删除对象删除整张表和表结构,以及表的索引、约束和触发器。只删除表数据,表的结构、索引、约束等会被保留。只删除表的全部或部分数据,表结构、索引、约束等会被保留。
删除条件(where)不能用不能用可使用
回滚不可回滚不可回滚可回滚
自增初始值-重置不重置

truncate、drop 和 delete 的区别主要有以下 6 点:

  • 执行速度:drop > truncate > detele。

  • delete 和 truncate 只删除表数据,而 drop 会删除表数据和表结构以及表的索引、约束和触发器。

  • delete 可以加 where 条件实现部分数据删除,而 truncate 和 drop 不能加 where 条件是整体删除。

  • truncate 和 drop 是立即执行,且不能恢复;而 delete 会走事务,可以撤回和恢复。

  • truncate 会重置自增列为 1,而 delete 不会重置自增列。

  • truncate 和 drop 是 DDL 语句,而 delete 是 DML 语句。

为什么delete相对比较慢

delete 是逐行执行的,并且在执行时会把操作日志记录下来,以备日后回滚使用,所以 delete 的执行速度是比较慢的;而 truncate 的操作是先复制一个新的表结构,再把原先的表整体删除,所以它的执行速度居中,而 drop 的执行速度最快。

往深入说,为什么delete相对比较慢,原因复杂:

查询的表,没有加索引

写了一个查询sql,结果查询的条件字段没有索引,导致需要全表扫描,查找数据,这是大家遇到最多,也是最容易理解的。这种,一般在表数据量比较少时,如低于十万级,不会觉得慢,但是当表中数据量达到或超过十万级时,就会体现出查询时间特别长了。

查询的索引,无效

知道索引很重要,所以,一般建表的时候,都会加上一些索引,但是,有了索引,并不代表查询速度就一定会快,因为,还要看能否正确使用索引。

  • 查询条件,没有索引字段

  • 查询条件使用 or, 选择式过滤条件,导致索引无效

  • 查询条件使用like,且从头部开始模糊匹配,导致索引无效

  • 查询条件不满足复合索引的最左匹配原则,导致索引无效

  • 查询条件,索引列使用了隐式类型转换,导致索引无效

  • 查询条件,索引列使用了聚合函数,导致索引无效

  • 查询条件,索引列使用了算术运算(+、-、...),导致索引无效

  • 查询条件,索引列使用了逻辑运算(!=、<>、is null、 is not null ...),导致索引无效

  • 左右关联时,字段类型不一致,导致索引无效

查询使用了临时表

临时表可能大家不知道,但是回表查询,大家可能听说过,就是说一次查询不满足,还需要再查一次,查两次才能出结果,这当然就会慢啦。

那临时表一般都是怎么产生的呢?

通过一次查询返回的数据,要进行下一步的过滤、显示时,发现返回的数据中不满足过滤条件,或者没有显示的字段,又要回头查一次原表,从原表中获取满足条件的数据,这些数据,就放在临时表中。

本来,回头查一次,就已经消耗了时间了,奈何,临时表还有空间大小限制,占用内存空间,还可能空间不够用,存放不下所有数据。所以,一般,只要出现使用了临时表,这个sql的性能都很差。

join或子查询太多

关联查询,在实际工作中,非常场景,关联的表越多,那么,数据过滤筛选就越来复杂,时间自然就会越长了。所以,一般而言,关联表不建议超过3个,而且数据量小的表放左边,大的表放在右边。

查询结果数据量太大

查询结果数据量太大,常见的有两种,第1种,就是直查的表数据量太大,如千万级。一张表千万级,即使建了索引,索引文件也会很大,深度也会很深,查询速度,自然就会很慢了。第2种,就是联表笛卡尔积量太大。对于第一种,优化建议,一般是对表采用分表分区了。而第二种,就简单粗暴的sql拆分优化。

锁竞争

现在MySQL的表一般都是InnoDB存储引擎,这种引擎的表是行锁,每次锁定一行。即,如果有一个事务在操作某一行数据,就会锁定这一行的操作行为,其他事务不能操作,直到前一个事务操作完成,commit数据变更之后,后面的事务才能获取操作。这就会出现,一个事务,做变更,没有结束,后面的所有事务操作就得等待,如果此时又有多个事务在排队等待,当前一事务操作结束,等待的事务就会竞争抢锁,这种‘你不仁,我不义’,一旦发生,SQL的性能就会很慢了。

limit分页太深

有些时候,我们需要偏移一定量数据之后,获取某些数据,就很容易想到用limit,但是,如果偏移量很大时,就会发现SQL执行起来非常非常慢了,因为,偏移量会分页读取到buffpool中,数据量大,占用的buffpool空间就会大,而这个空间大小是配置的,一般不会很大,所以,导致了慢sql。对于这个问题的优化,建议写一个过滤条件,再与limit结合实现。

配置参数不合理

我们很多时候使用数据库,都是安装了之后,就直接用,不会对数据库配置参数进行过多了解和设置。比如buff相关的参数这就是数据库中一类非常重要的配置参数,在mysql中,有很多带有 buff、cache、size、length、max、min、limit等字样的配置参数,都是非常重要的配置参数。这些配置参数,是直接关系数据库的性能的。如果,你数据库安装在一个配置很高的机器上,但是,这些配置参数却不知道修改,都用默认值。

频繁刷脏页

脏页,是内存数据页和磁盘数据页不一致。这个一般发生在数据更新操作中。更新数据,需要先把数据读取出来,然后再内存中更新,然后再生成日志文件,再回放日志文件,实现表数据更新。而当更新数据量大,buffpool写满,或者是后续生成的回放日志文件写满,都会导致这个操作过程变慢。对于这种问题优化,一般建议是少批量修改,多次提交。

硬件资源不够用

系统资源,不够用数据库,使用来存储数据的,要频繁进行磁盘操作,所以,一般,我们都会选择磁盘IO性能比较好的机器作为数据库服务器。同时,数据库还要经常进行数据交换,所以,也需要有足够的内存,所以,内存也会相应要求高些。而这些硬件,仅仅只是作为数据库服务器硬件选择的基本要求;数据库也是一个软件,软件也是安装在操作系统中的,所以,也会受操作系统的参数的一些限制,所以,当硬件资源不够用,或者达到了系统参数限制值时,也是会导致操作变慢的。

cpu在什么情况下会发生指令重排序

指令重排序是编译器处于性能考虑,在不影响程序(单线程程序)正确性的条件下进行重新排序指令重排序不是必然发生的,指令重排序会导致线程安全问题。

cms中为什么需要重新标记这一步?

实际上CMS 垃圾回收器于 JDK1.5 时期推出,在 JDK9 中被废弃,在 JDK14 中被移除。而用来替换 CMS 垃圾回收器的便是我们常说的 G1 垃圾回收器。但 G1 垃圾回收器也是在 CMS 的基础上进行改进的,因此简单了解下 CMS 垃圾回收器也是有必要的。

CMS(Concurrent Mark Sweep)垃圾回收器是第一个关注 GC 停顿时间的垃圾收集器。 在这之前的垃圾回收器,要么就是串行垃圾回收方式,要么就是关注系统吞吐量。

这样的垃圾回收器对于强交互的程序很不友好,而 CMS 垃圾回收器的出现,则打破了这个尴尬的局面。因此,CMS 垃圾回收器诞生之后就受到了大家的欢迎,导致现在还有非常多的应用还在继续使用它。

对于 CMS 垃圾回收器来说,其实通过「标记 - 清除」算法实现的,它的运行过程分为 4 个步骤,包括:

  • 初始标记

  • 并发标记

  • 重新标记

  • 并发清除

初始标记,指的是寻找所有被 GCRoots 引用的对象,该阶段需要「Stop the World」。 这个步骤仅仅只是标记一下 GC Roots 能直接关联到的对象,并不需要做整个引用的扫描,因此速度很快。

并发标记,指的是对「初始标记阶段」标记的对象进行整个引用链的扫描,该阶段不需要「Stop the World」。 对整个引用链做扫描需要花费非常多的时间,因此通过垃圾回收线程与用户线程并发执行,可以降低垃圾回收的时间,从而降低系统响应时间。这也是 CMS 垃圾回收器能极大降低 GC 停顿时间的核心原因,但这也带来了一些问题,即:并发标记的时候,引用可能发生变化,因此可能发生漏标(本应该回收的垃圾没有被回收)和多标(本不应该回收的垃圾被回收)了。

重新标记,指的是对「并发标记」阶段出现的问题进行校正,该阶段需要「Stop the World」。

为什么需要重新标记?正如并发标记阶段说到的,由于垃圾回收算法和用户线程并发执行,虽然能降低响应时间,但是会发生漏标和多标的问题。所以对于 CMS 回收器来说,它需要这个阶段来做一些校验,解决并发标记阶段发生的问题。

并发清除,指的是将标记为垃圾的对象进行清除,该阶段不需要「Stop the World」。 在这个阶段,垃圾回收线程与用户线程可以并发执行,因此并不影响用户的响应时间。

从上面的描述步骤中我们可以看出:CMS 之所以能极大地降低 GC 停顿时间,本质上是将原本冗长的引用链扫描进行切分。通过 GC 线程与用户线程并发执行,加上重新标记校正的方式,减少了垃圾回收的时间。

CMS的并发清除阶段,如果之前被标记为垃圾的对象又被重新引用了怎么办?

可达性分析算法,标记死亡的已不可达。不会被引用。

62条SQL优化策略

MySQL的执行过程

(1)客户端发送一条查询语句到服务器;

(2)服务器先查询缓存,如果命中缓存,则立即返回存储在缓存中的数据;

(3)未命中缓存后,MySQL通过关键字将SQL语句进行解析,并生成一颗对应的解析树,MySQL解析器将使用MySQL语法进行验证和解析。例如,验证是否使用了错误的关键字,或者关键字的使用是否正确;

(4)预处理是根据一些MySQL规则检查解析树是否合理,比如检查表和列是否存在,还会解析名字和别名,然后预处理器会验证权限;根据执行计划查询执行引擎,调用API接口调用存储引擎来查询数据;

(5)将结果返回客户端,并进行缓存;

图片

SQL语句性能优化策略

1、 为 WHERE 及 ORDER BY 涉及的列上建立索引

对查询进行优化,应尽量避免全表扫描,首先应考虑在 WHERE 及 ORDER BY 涉及的列上建立索引。

2、where中使用默认值代替null

应尽量避免在 WHERE 子句中对字段进行 NULL 值判断,创建表时 NULL 是默认值,但大多数时候应该使用 NOT NULL,或者使用一个特殊的值,如 0,-1 作为默认值。为啥建议where中使用默认值代替null,四个原因:

(1)并不是说使用了is null或者 is not null就会不走索引了,这个跟mysql版本以及查询成本都有关;

(2)如果mysql优化器发现,走索引比不走索引成本还要高,就会放弃索引,这些条件 !=,<>,is null,is not null经常被认为让索引失效;

(3)其实是因为一般情况下,查询的成本高,优化器自动放弃索引的;

(4)如果把null值,换成默认值,很多时候让走索引成为可能,同时,表达意思也相对清晰一点;

3、慎用  != 或 <> 操作符

MySQL 只有对以下操作符才使用索引:<,<=,=,>,>=,BETWEEN,IN,以及某些时候的 LIKE。所以:应尽量避免在 WHERE 子句中使用 != 或 <> 操作符, 会导致全表扫描。

4、慎用  OR 来连接条件

使用or可能会使索引失效,从而全表扫描;应尽量避免在 WHERE 子句中使用 OR 来连接条件,否则将导致引擎放弃使用索引而进行全表扫描,可以使用 UNION 合并查询:

select id from t where num=10 

union all 

select id from t where num=20

一个关键的问题是否用到索引。他们的速度只同是否使用索引有关,如果查询需要用到联合索引,用 UNION all 执行的效率更高。多个 OR 的字句没有用到索引,改写成 UNION 的形式再试图与索引匹配。

5、慎用 IN 和 NOT IN

IN 和 NOT IN 也要慎用,否则会导致全表扫描。对于连续的数值,能用 BETWEEN 就不要用 IN:select id from t where num between 1 and 3。

6、慎用 左模糊like ‘%...’

模糊查询,程序员最喜欢的就是使用like,like很可能让索引失效。比如:

select id from t where name like‘%abc%’ 
select id from t where name like‘%abc’

而select id from t where name like‘abc%’才用到索引。

所以:

  • 首先尽量避免模糊查询,如果必须使用,不采用全模糊查询,也应尽量采用右模糊查询, 即like ‘…%’,是会使用索引的;

  • 左模糊like ‘%...’无法直接使用索引,但可以利用reverse + function index的形式,变化成 like ‘…%’;

  • 全模糊查询是无法优化的,一定要使用的话建议使用搜索引擎,比如 ElasticSearch。

如果一定要用左模糊like ‘%...’检索, 一般建议 ElasticSearch+Hbase架构,

7、WHERE条件使用参数会导致全表扫描

如下面语句将进行全表扫描:

select id from t where num=@num

因为SQL只有在运行时才会解析局部变量,但优化程序不能将访问计划的选择推迟到 运行时;它必须在编译时进行选择。然而,如果在编译时建立访问计划,变量的值还是未知的,因而无法作为索引选择的输入项。所以, 可以改为强制查询使用索引:

select id from t with(index(索引名)) where num=@num

8、应避免WHERE 表达式操作/对字段进行函数操作

任何对列的操作都将导致表扫描,它包括数据库函数、计算表达式等等,应尽量避免在 WHERE 子句中对字段进行表达式操作,应尽量避免在 WHERE 子句中对字段进行函数操作。

如:

select id from t where num/2=100
应改为:
select id from t where num=100*2

应尽量避免在where子句中对字段进行函数操作,这将导致引擎放弃使用索引而进行全表扫描。如:

select id from t where substring(name,1,3)=‘abc’

select id from t where datediff(day,createdate,‘2005-11-30’)=0

应改为:

select id from t where name like ‘abc%’

select id from t where createdate>=‘2005-11-30’ and createdate<‘2005-12-1’

9、用 EXISTS 代替 IN 是一个好的选择

很多时候用exists 代替in 是一个好的选择:

select num from a where num in(select num from b)
用下面的语句替换:
select num from a where exists(select 1 from b where num=a.num)

10、索引并不是越多越好

索引固然可以提高相应的 SELECT 的效率,但同时也降低了 INSERT 及 UPDATE 的效。因为 INSERT 或 UPDATE 时有可能会重建索引,所以怎样建索引需要慎重考虑,视具体情况而定。一个表的索引数最好不要超过 6 个,若太多则应考虑一些不常使用到的列上建的索引是否有必要。

11、应尽可能的避免更新 clustered 索引数据列

应尽可能的避免更新 clustered 索引数据列, 因为 clustered 索引数据列的顺序就是表记录的物理存储顺序,一旦该列值改变将导致整个表记录的顺序的调整,会耗费相当大的资源。若应用系统需要频繁更新 clustered 索引数据列,那么需要考虑是否应将该索引建为 clustered 索引。

12、尽量使用数字型字段

(1)因为引擎在处理查询和连接时会逐个比较字符串中每一个字符;

(2)而对于数字型而言只需要比较一次就够了;

(3)字符会降低查询和连接的性能,并会增加存储开销;

所以:尽量使用数字型字段,若只含数值信息的字段尽量不要设计为字符型,这会降低查询和连接的性能,并会增加存储开销。

13、尽可能的使用 varchar, nvarchar 代替 char, nchar

(1)varchar变长字段按数据内容实际长度存储,存储空间小,可以节省存储空间;

(2)char按声明大小存储,不足补空格;

(3)其次对于查询来说,在一个相对较小的字段内搜索,效率更高;

因为首先变长字段存储空间小,可以节省存储空间,其次对于查询来说,在一个相对较小的字段内搜索效率显然要高些。

14、查询SQL尽量不要使用select *,而是具体字段

最好不要使用返回所有:select * from t ,用具体的字段列表代替 “*”,不要返回用不到的任何字段。

select *的弊端:

(1)增加很多不必要的消耗,比如CPU、IO、内存、网络带宽;

(2)增加了使用覆盖索引的可能性;

(3)增加了回表的可能性;

(4)当表结构发生变化时,前端也需要更改;

(5)查询效率低;

15、尽量避免向客户端返回大数据量

大数据量增加很多不必要的消耗,比如CPU、IO、内存、网络带宽。尽量避免向客户端返回大数据量,若数据量过大,应该考虑相应需求是否合理。

16、使用表的别名(Alias)

当在 SQL 语句中连接多个表时,请使用表的别名并把别名前缀于每个 Column 上。这样一来,就可以减少解析的时间并减少那些由 Column 歧义引起的语法错误。

17、使用“临时表”暂存中间结果 

简化 SQL 语句的重要方法就是采用临时表暂存中间结果。但是临时表的好处远远不止这些,将临时结果暂存在临时表,后面的查询就在 tempdb 中了,这可以避免程序中多次扫描主表,也大大减少了程序执行中“共享锁”阻塞“更新锁”,减少了阻塞,提高了并发性能。

18、一些 SQL 查询语句应加上 nolock

一些 SQL 查询语句应加上 nolock,读、写是会相互阻塞的,为了提高并发性能。对于一些查询,可以加上 nolock,这样读的时候可以允许写,但缺点是可能读到未提交的脏数据。使用 nolock 有3条原则:

  • 查询的结果用于“插、删、改”的不能加 nolock;

  • 查询的表属于频繁发生页分裂的,慎用 nolock ;

  • 使用临时表一样可以保存“数据前影”,起到类似 Oracle 的 undo 表空间的功能,能采用临时表提高并发性能的,不要用 nolock。

19、常见的简化规则如下

不要有超过 5 个以上的表连接(JOIN),考虑使用临时表或表变量存放中间结果。少用子查询,视图嵌套不要过深,一般视图嵌套不要超过 2 个为宜。

20、将需要查询的结果预先计算好

将需要查询的结果预先计算好放在表中,查询的时候再Select,而不是查询的时候进行计算。

这在SQL7.0以前是最重要的手段,例如医院的住院费计算。

21、IN后出现最频繁的值放在最前面

如果一定用IN,那么:在IN后面值的列表中,将出现最频繁的值放在最前面,出现得最少的放在最后面,减少判断的次数。

22、使用存储过程进行数据处理

尽量将数据的处理工作放在服务器上,减少网络的开销,如使用存储过程。存储过程是编译好、优化过、并且被组织到一个执行规划里、且存储在数据库中的 SQL 语句,是控制流语言的集合,速度当然快。反复执行的动态 SQL,可以使用临时存储过程,该过程(临时表)被放在 Tempdb 中。

23、尽量使用 EXISTS 代替 select count(1) 来判断是否存在记录。

count 函数只有在统计表中所有行数时使用,而且 count(1) 比 count(*) 更有效率。

24、索引的使用规范

  • 索引的创建要与应用结合考虑,建议大的 OLTP 表不要超过 6 个索引;

  • 尽可能的使用索引字段作为查询条件,尤其是聚簇索引,必要时可以通过 index index_name 来强制指定索引;

  • 避免对大表查询时进行 table scan,必要时考虑新建索引;

  • 在使用索引字段作为条件时,如果该索引是联合索引,那么必须使用到该索引中的第一个字段作为条件时才能保证系统使用该索引,否则该索引将不会被使用;

  • 要注意索引的维护,周期性重建索引,重新编译存储过程。

25、下列 SQL 条件语句中的列都建有恰当的索引,但执行速度却非常慢

SELECT * FROM record WHERE substrINg(card_no, 1, 4) = '5378' --13秒 

SELECT * FROM record WHERE amount/30 < 1000 --11秒 

SELECT * FROM record WHERE convert(char(10), date, 112) = '19991201' --10秒

分析:

WHERE 子句中对列的任何操作结果都是在 SQL 运行时逐列计算得到的,因此它不得不进行表搜索,而没有使用该列上面的索引。如果这些结果在查询编译时就能得到,那么就可以被 SQL 优化器优化,使用索引,避免表搜索,因此将 SQL 重写成下面这样:

SELECT * FROM record WHERE card_no like '5378%' -- < 1秒 

SELECT * FROM record WHERE amount < 1000*30 -- < 1秒 

SELECT * FROM record WHERE date = '1999/12/01' -- < 1秒

26、用批量插入或批量更新

当有一批处理的插入或更新时,用批量插入或批量更新,绝不会一条条记录的去更新。

(1)多条提交

INSERT INTO user (id,username) VALUES(1,'技术自由圈');
INSERT INTO user (id,username) VALUES(2,'疯狂创客圈');

(2)批量提交

INSERT INTO user (id,username) VALUES(1,'技术自由圈'),(2,'疯狂创客圈');

默认新增SQL有事务控制,导致每条都需要事务开启和事务提交,而批量处理是一次事务开启和提交,效率提升明显,达到一定量级,效果显著,平时看不出来。

27、存储过程中慎用循环

在所有的存储过程中,能够用 SQL 语句的,我绝不会用循环去实现。例如:列出上个月的每一天,我会用 connect by 去递归查询一下,绝不会去用循环从上个月第一天到最后一天。

28、选择最有效率的表名顺序

选择最有效率的表名顺序(只在基于规则的优化器中有效):Oracle 的解析器按照从右到左的顺序处理 FROM 子句中的表名,FROM 子句中写在最后的表(基础表 driving table)将被最先处理,在 FROM 子句中包含多个表的情况下,你必须选择记录条数最少的表作为基础表。

如果有 3 个以上的表连接查询,那就需要选择交叉表(interp table)作为基础表,交叉表是指那个被其他表所引用的表。

29、将不需要的记录在 GROUP BY 之前过滤掉

提高 GROUP BY 语句的效率,可以通过将不需要的记录在 GROUP BY 之前过滤掉。下面两个查询返回相同结果,但第二个明显就快了许多。

低效:

SELECT JOB, AVG(SAL) 
FROM EMP 
GROUP BY JOB 
HAVING JOB = 'PRESIDENT' 
OR JOB = 'MANAGER' 

高效:

SELECT JOB, AVG(SAL) 
FROM EMP
WHERE JOB = 'PRESIDENT' 
OR JOB = 'MANAGER' 
GROUP BY JOB

30、别名的使用

别名是大型数据库的应用技巧,就是表名、列名在查询中以一个字母为别名,查询速度要比建连接表快 1.5 倍。

31、避免死锁

在你的存储过程和触发器中访问同一个表时总是以相同的顺序;事务应经可能地缩短,在一个事务中应尽可能减少涉及到的数据量;永远不要在事务中等待用户输入。

32、避免使用临时表,可以使用表变量代替

避免使用临时表,除非却有需要,否则应尽量避免使用临时表,相反,可以使用表变量代替。大多数时候(99%),表变量驻扎在内存中,因此速度比临时表更快,临时表驻扎在 TempDb 数据库中,因此临时表上的操作需要跨数据库通信,速度自然慢。

33、最好不要使用触发器

  • 触发一个触发器,执行一个触发器事件本身就是一个耗费资源的过程;

  • 如果能够使用约束实现的,尽量不要使用触发器;

  • 不要为不同的触发事件(Insert、Update 和 Delete)使用相同的触发器;

  • 不要在触发器中使用事务型代码。

34、索引创建规则

  • 表的主键、外键必须有索引;

  • 数据量超过 300 的表应该有索引;

  • 经常与其他表进行连接的表,在连接字段上应该建立索引;

  • 经常出现在 WHERE 子句中的字段,特别是大表的字段,应该建立索引;

  • 索引应该建在选择性高的字段上;

  • 索引应该建在小字段上,对于大的文本字段甚至超长字段,不要建索引;

  • 复合索引的建立需要进行仔细分析,尽量考虑用单字段索引代替;

  • 正确选择复合索引中的主列字段,一般是选择性较好的字段;

  • 复合索引的几个字段是否经常同时以 AND 方式出现在 WHERE 子句中?单字段查询是否极少甚至没有?如果是,则可以建立复合索引;否则考虑单字段索引;

  • 如果复合索引中包含的字段经常单独出现在 WHERE 子句中,则分解为多个单字段索引;

  • 如果复合索引所包含的字段超过 3 个,那么仔细考虑其必要性,考虑减少复合的字段;

  • 如果既有单字段索引,又有这几个字段上的复合索引,一般可以删除复合索引;

  • 频繁进行数据操作的表,不要建立太多的索引;

  • 删除无用的索引,避免对执行计划造成负面影响;

  • 表上建立的每个索引都会增加存储开销,索引对于插入、删除、更新操作也会增加处理上的开销。另外,过多的复合索引,在有单字段索引的情况下,一般都是没有存在价值的;相反,还会降低数据增加删除时的性能,特别是对频繁更新的表来说,负面影响更大。

  • 尽量不要对数据库中某个含有大量重复的值的字段建立索引。

35、在写 SQL 语句时,应尽量减少空格的使用

查询缓冲并不自动处理空格,因此,在写 SQL 语句时,应尽量减少空格的使用,尤其是在 SQL 首和尾的空格(因为查询缓冲并不自动截取首尾空格)。

36、member 用 mid 做标准进行分表方便查询么?

member 用 mid 做标准进行分表方便查询么?

一般的业务需求中基本上都是以 username 为查询依据,正常应当是 username 做 hash 取模来分表。而分表的话 MySQL 的 partition 功能就是干这个的,对代码是透明的;在代码层面去实现貌似是不合理的。

37、每张表都设置一个 ID 做为其主键

我们应该为数据库里的每张表都设置一个 ID 做为其主键,而且最好的是一个 INT 型的(推荐使用 UNSIGNED),并设置上自动增加的 AUTO_INCREMENT 标志。

38、在所有的存储过程和触发器的开始处设置 SET NOCOUNT ON

在所有的存储过程和触发器的开始处设置 SET NOCOUNT ON,在结束时设置 SET NOCOUNT OFF。无需在执行存储过程和触发器的每个语句后向客户端发送 DONE_IN_PROC 消息。

39、MySQL 查询可以启用高速查询缓存

这是提高数据库性能的有效MySQL优化方法之一。当同一个查询被执行多次时,从缓存中提取数据和直接从数据库中返回数据快很多。

40、EXPLAIN SELECT 查询用来跟踪查看效果

使用 EXPLAIN 关键字可以让你知道 MySQL 是如何处理你的 SQL 语句的。这可以帮你分析你的查询语句或是表结构的性能瓶颈。EXPLAIN 的查询结果还会告诉你你的索引主键被如何利用的,你的数据表是如何被搜索和排序的。

41、当只要一行数据时使用 LIMIT 1

当你查询表的有些时候,你已经知道结果只会有一条结果,但因为你可能需要去fetch游标,或是你也许会去检查返回的记录数。在这种情况下,加上 LIMIT 1 可以增加性能。这样一来,MySQL 数据库引擎会在找到一条数据后停止搜索,而不是继续往后查少下一条符合记录的数据。

42、选择表合适存储引擎

  • myisam:应用时以读和插入操作为主,只有少量的更新和删除,并且对事务的完整性,并发性要求不是很高的。

  • InnoDB:事务处理,以及并发条件下要求数据的一致性。除了插入和查询外,包括很多的更新和删除。(InnoDB 有效地降低删除和更新导致的锁定)。

    对于支持事务的 InnoDB类 型的表来说,影响速度的主要原因是 AUTOCOMMIT 默认设置是打开的,而且程序没有显式调用 BEGIN 开始事务,导致每插入一条都自动提交,严重影响了速度。可以在执行 SQL 前调用 begin,多条 SQL 形成一个事物(即使 autocommit 打开也可以),将大大提高性能。

43、优化表的数据类型,选择合适的数据类型

原则:更小通常更好,简单就好,所有字段都得有默认值,尽量避免 NULL。

例如:数据库表设计时候更小的占磁盘空间尽可能使用更小的整数类型。(mediumint 就比 int 更合适)

比如时间字段:datetime 和 timestamp。datetime 占用8个字节,timestamp 占用4个字节,只用了一半。而 timestamp 表示的范围是 1970—2037 适合做更新时间。

MySQL可以很好的支持大数据量的存取,但是一般说来,数据库中的表越小,在它上面执行的查询也就会越快。因此,在创建表的时候,为了获得更好的性能,我们可以将表中字段的宽度设得尽可能小。

例如:在定义邮政编码这个字段时,如果将其设置为 CHAR(255),显然给数据库增加了不必要的空间。甚至使用VARCHAR 这种类型也是多余的,因为 CHAR(6) 就可以很好的完成任务了。

同样的,如果可以的话,我们应该使用 MEDIUMINT 而不是 BIGINT 来定义整型字段,应该尽量把字段设置为 NOT NULL,这样在将来执行查询的时候,数据库不用去比较 NULL 值。

对于某些文本字段,例如“省份”或者“性别”,我们可以将它们定义为 ENUM 类型。因为在 MySQL 中,ENUM 类型被当作数值型数据来处理,而数值型数据被处理起来的速度要比文本类型快得多。这样,我们又可以提高数据库的性能。

44、将大的DELETE,UPDATE、INSERT 查询变成多个小查询

能写一个几十行、几百行的SQL语句是不是显得逼格很高?然而,为了达到更好的性能以及更好的数据控制,你可以将他们变成多个小查询。

45、关于临时表

(1)避免频繁创建和删除临时表,以减少系统表资源的消耗;

(2)在新建临时表时,如果一次性插入数据量很大,那么可以使用 select into 代替 create table,避免造成大量 log;

(3)如果数据量不大,为了缓和系统表的资源,应先create table,然后insert;

(4)如果使用到了临时表,在存储过程的最后务必将所有的临时表显式删除。先 truncate table ,然后 drop table ,这样可以避免系统表的较长时间锁定。

46、使用explain分析你SQL执行计划

(1)type

  • system:表仅有一行,基本用不到;

  • const:表最多一行数据配合,主键查询时触发较多;

  • eq_ref:对于每个来自于前面的表的行组合,从该表中读取一行。这可能是最好的联接类型,除了const类型;

  • ref:对于每个来自于前面的表的行组合,所有有匹配索引值的行将从这张表中读取;

  • range:只检索给定范围的行,使用一个索引来选择行。当使用=、<>、>、>=、<、<=、IS NULL、<=>、BETWEEN或者IN操作符,用常量比较关键字列时,可以使用range;

  • index:该联接类型与ALL相同,除了只有索引树被扫描。这通常比ALL快,因为索引文件通常比数据文件小;

  • all:全表扫描;

  • 性能排名:system > const > eq_ref > ref > range > index > all。

  • 实际sql优化中,最后达到ref或range级别。

(2)Extra常用关键字

  • Using index:只从索引树中获取信息,而不需要回表查询;

  • Using where:WHERE子句用于限制哪一个行匹配下一个表或发送到客户。除非你专门从表中索取或检查所有行,如果Extra值不为Using where并且表联接类型为ALL或index,查询可能会有一些错误。需要回表查询。

  • Using temporary:mysql常建一个临时表来容纳结果,典型情况如查询包含可以按不同情况列出列的GROUP BY和ORDER BY子句时;

47、使用合理的分页方式以提高分页的效率

select id,name from user limit 100000, 20

使用上述SQL语句做分页的时候,随着表数据量的增加,直接使用limit语句会越来越慢。

此时,可以通过取前一页的最大ID,以此为起点,再进行limit操作,效率提升显著。

select id,name from user where id> 100000 limit 20

48、尽量控制单表数据量的大小,建议控制在500万以内

500万并不是MySQL数据库的限制,过大会造成修改表结构,备份,恢复都会有很大的问题。可以用历史数据归档(应用于日志数据),分库分表(应用于业务数据)等手段来控制数据量大小。

49、谨慎使用MySQL分区表

(1)分区表在物理上表现为多个文件,在逻辑上表现为一个表;

(2)谨慎选择分区键,跨分区查询效率可能更低;

(3)建议采用物理分表的方式管理大数据。

50、尽量做到冷热数据分离,减小表的宽度

MySQL限制每个表最多存储4096列,并且每一行数据的大小不能超过65535字节。减少磁盘IO,保证热数据的内存缓存命中率(表越宽,把表装载进内存缓冲池时所占用的内存也就越大,也会消耗更多的IO);更有效的利用缓存,避免读入无用的冷数据;经常一起使用的列放到一个表中(避免更多的关联操作)。

51、禁止在表中建立预留字段

(1)预留字段的命名很难做到见名识义;

(2)预留字段无法确认存储的数据类型,所以无法选择合适的类型;

(3)对预留字段类型的修改,会对表进行锁定;

52、禁止在数据库中存储图片,文件等大的二进制数据

通常文件很大,会短时间内造成数据量快速增长,数据库进行数据库读取时,通常会进行大量的随机IO操作,文件很大时,IO操作很耗时。通常存储于文件服务器,数据库只存储文件地址信息。

53、建议把BLOB或是TEXT列分离到单独的扩展表中

MySQL内存临时表不支持TEXT、BLOB这样的大数据类型,如果查询中包含这样的数据,在排序等操作时,就不能使用内存临时表,必须使用磁盘临时表进行。而且对于这种数据,MySQL还是要进行二次查询,会使sql性能变得很差,但是不是说一定不能使用这样的数据类型。如果一定要使用,建议把BLOB或是TEXT列分离到单独的扩展表中,查询时一定不要使用select * 而只需要取出必要的列,不需要TEXT列的数据时不要对该列进行查询。

54、TEXT或BLOB类型只能使用前缀索引

因为MySQL对索引字段长度是有限制的,所以TEXT类型只能使用前缀索引,并且TEXT列上是不能有默认值的。

55、建议使用预编译语句进行数据库操作

预编译语句可以重复使用这些计划,减少SQL编译所需要的时间,还可以解决动态SQL所带来的SQL注入的问题。只传参数,比传递SQL语句更高效。相同语句可以一次解析,多次使用,提高处理效率。

56、表连接不宜太多,索引不宜太多,一般5个以内

(1)表连接不宜太多,一般5个以内

  • 关联的表个数越多,编译的时间和开销也就越大

  • 每次关联内存中都生成一个临时表

  • 应该把连接表拆开成较小的几个执行,可读性更高

  • 如果一定需要连接很多表才能得到数据,那么意味着这是个糟糕的设计了

  • 阿里规范中,建议多表联查三张表以下

(2)索引不宜太多,一般5个以内

  • 索引并不是越多越好,虽其提高了查询的效率,但却会降低插入和更新的效率;

  • 索引可以理解为一个就是一张表,其可以存储数据,其数据就要占空间;

  • 索引表的数据是排序的,排序也是要花时间的;

  • insert或update时有可能会重建索引,如果数据量巨大,重建将进行记录的重新排序,所以建索引需要慎重考虑,视具体情况来定;

  • 一个表的索引数最好不要超过5个,若太多需要考虑一些索引是否有存在的必要;

57、数据库和表的字符集统一使用UTF8

兼容性更好,统一字符集可以避免由于字符集转换产生的乱码,不同的字符集进行比较前需要进行转换会造成索引失效,如果数据库中有存储emoji表情的需要,字符集需要采用utf8mb4字符集。

58、合理选择索引列的顺序

建立索引的目的是:希望通过索引进行数据查找,减少随机IO,增加查询性能 ,索引能过滤出越少的数据,则从磁盘中读入的数据也就越少。

区分度最高的放在联合索引的最左侧(区分度=列中不同值的数量/列的总行数)。尽量把字段长度小的列放在联合索引的最左侧(因为字段长度越小,一页能存储的数据量越大,IO性能也就越好)。使用最频繁的列放到联合索引的左侧(这样可以比较少的建立一些索引)。

59、对于频繁的查询优先考虑使用覆盖索引

覆盖索引:就是包含了所有查询字段(where,select,ordery by,group by包含的字段)的索引。

覆盖索引的好处:

(1)避免Innodb表进行索引的二次查询

Innodb是以聚集索引的顺序来存储的,对于Innodb来说,二级索引在叶子节点中所保存的是行的主键信息,如果是用二级索引查询数据的话,在查找到相应的键值后,还要通过主键进行二次查询才能获取我们真实所需要的数据。

而在覆盖索引中,二级索引的键值中可以获取所有的数据,避免了对主键的二次查询 ,减少了IO操作,提升了查询效率。

(2)可以把随机IO变成顺序IO加快查询效率

由于覆盖索引是按键值的顺序存储的,对于IO密集型的范围查找来说,对比随机从磁盘读取每一行的数据IO要少的多,因此利用覆盖索引在访问时也可以把磁盘的随机读取的IO转变成索引查找的顺序IO。

60、MySQL 查询优化的一般策略

使用慢查询日志去发现慢查询,使用执行计划去判断查询是否正常运行,总是去测试你的查询看看是否他们运行在最佳状态下。

避免在整个表上使用 count(*),它可能锁住整张表,使查询保持一致以便后续相似的查询可以使用查询缓存,在适当的情形下使用 GROUP BY 而不是 DISTINCT,在 WHERE、GROUP BY 和 ORDER BY 子句中使用有索引的列,保持索引简单,不在多个索引中包含同一个列。有时候 MySQL 会使用错误的索引,对于这种情况使用 USE INDEX,检查使用 SQL_MODE=STRICT 的问题,对于记录数小于5的索引字段,在 UNION 的时候使用LIMIT不是是用OR。为了避免在更新之前进行一次 SELECT,使用 INSERT ON DUPLICATE KEY 或者 INSERT IGNORE;不要用 UPDATE 去实现,不要使用 MAX;使用索引字段和 ORDER BY子句 LIMIT M,N 实际上可以减缓查询在某些情况下,有节制地使用,在 WHERE 子句中使用 UNION 代替子查询,在重新启动的 MySQL,记得来温暖你的数据库,以确保数据在内存和查询速度快,考虑持久连接,而不是多个连接,以减少开销。

基准查询,包括使用服务器上的负载,有时一个简单的查询可以影响其他查询,当负载增加在服务器上,使用 SHOW PROCESSLIST 查看慢的和有问题的查询,在开发环境中产生的镜像数据中测试的所有可疑的查询。

61、定期的进行 MySQL 数据库的备份

MySQL 数据库的备份流程:

  • 从二级复制服务器上进行备份;

  • 在进行备份期间停止复制,以避免在数据依赖和外键约束上出现不一致;

  • 彻底停止 MySQL,从数据库文件进行备份;

  • 如果使用 MySQL dump 进行备份,请同时备份二进制日志文件 – 确保复制没有中断;

  • 不要信任 LVM 快照,这很可能产生数据不一致,将来会给你带来麻烦;

  • 为了更容易进行单表恢复,以表为单位导出数据—如果数据是与其他表隔离的。

  • 当使用 mysqldump 时请使用 –opt;

  • 在备份之前检查和优化表;

  • 为了更快的进行导入,在导入时临时禁用外键约束;

  • 为了更快的进行导入,在导入时临时禁用唯一性检测;

  • 在每一次备份后计算数据库,表以及索引的尺寸,以便更够监控数据尺寸的增长;

  • 通过自动调度脚本监控复制实例的错误和延迟;

  • 定期执行备份。

62、分库分表与NoSQL结合使用

当数据量达到一定的数量之后,限制数据库存储性能的就不再是数据库层面的优化就能够解决的;

这个时候往往采用的是读写分离与分库分表同时也会结合缓存一起使用,而这个时候数据库层面的优化只是基础。一般的演进规则是:

  • 读写分离适用于较小一些的数据量;

  • 分表适用于中等数据量;

  • 而分库与分表一般是结合着用,这就适用于大数据量的存储了,

这也是现在大型互联网公司解决数据存储的方法之一。

  • 分库分表与NoSQL结合使用,一般建议 ElasticSearch+Hbase架构。

  • 左手大数据,右手云原生, 要掌握高并发架构达到技术自由, 大数据、云原生的技术必不可少。

MySQL数据库设计规范

一:库表设计规范

以下所有规范会按照【高危】、【强制】、【建议】三个级别进行标注,遵守优先级从高到低。对于不满足【高危】和【强制】两个级别的设计,DBA会强制打回要求修改。

1、库名规范
  1. 【强制】库的名称必须控制在32个字符以内,相关模块的表名与表名之间尽量体现join的关系,如user表和user_login表。

  2. 【强制】库的名称格式:业务系统名称_子系统名,同一模块使用的表名尽量使用统一前缀。

  3. 【强制】一般分库名称命名格式是库通配名_编号,编号从0开始递增,比如wenda_001以时间进行分库的名称格式是“库通配名_时间”

  4. 【强制】创建数据库时必须显式指定字符集,并且字符集只能是utf8或者utf8mb4。创建数据库SQL举例:create database db1 default character set utf8;。

2、表结构规范
  1. 【强制】表和列的名称必须控制在32个字符以内,表名只能使用字母、数字和下划线,一律小写。

  2. 【强制】表名要求模块名强相关,如师资系统采用”sz”作为前缀,渠道系统采用”qd”作为前缀等。

  3. 【强制】创建表时必须显式指定字符集为utf8或utf8mb4。

  4. 【强制】创建表时必须显式指定表存储引擎类型,如无特殊需求,一律为InnoDB。当需要使用除InnoDB/MyISAM/Memory以外的存储引擎时,必须通过DBA审核才能在生产环境中使用。因为Innodb表支持事务、行锁、宕机恢复、MVCC等关系型数据库重要特性,为业界使用最多的MySQL存储引擎。而这是其他大多数存储引擎不具备的,因此首推InnoDB。

  5. 【强制】建表必须有comment

  6. 【建议】建表时关于主键:(1)强制要求主键为id,类型为int或bigint,且为auto_increment(2)标识表里每一行主体的字段不要设为主键,建议设为其他字段如user_id,order_id等,并建立unique key索引(可参考cdb.teacher表设计)。因为如果设为主键且主键值为随机插入,则会导致innodb内部page分裂和大量随机I/O,性能下降。

  7. 【建议】核心表(如用户表,金钱相关的表)必须有行数据的创建时间字段create_time和最后更新时间字段update_time,便于查问题。

  8. 【建议】表中所有字段必须都是NOT NULL属性,业务可以根据需要定义DEFAULT值。因为使用NULL值会存在每一行都会占用额外存储空间、数据迁移容易出错、聚合函数计算结果偏差等问题。

  9. 【建议】建议对表里的blob、text等大字段,垂直拆分到其他表里,仅在需要读这些对象的时候才去select。

  10. 【建议】反范式设计:把经常需要join查询的字段,在其他表里冗余一份。如user_name属性在user_account,user_login_log等表里冗余一份,减少join查询。

  11. 【强制】中间表用于保留中间结果集,名称必须以tmp_开头。

    备份表用于备份或抓取源表快照,名称必须以bak_开头。

    中间表和备份表定期清理。

  12. 【强制】对于超过100W行的大表进行alter table,必须经过DBA审核,并在业务低峰期执行。

    因为alter table会产生表锁,期间阻塞对于该表的所有写入,对于业务可能会产生极大影响。

3、列数据类型优化
  1. 【建议】表中的自增列(auto_increment属性),推荐使用bigint类型。因为无符号int存储范围为-2147483648~2147483647(大约21亿左右),溢出后会导致报错。

  2. 【建议】业务中选择性很少的状态status、类型type等字段推荐使用tinytint或者smallint类型节省存储空间。

  3. 【建议】业务中IP地址字段推荐使用int类型,不推荐用char(15)。因为int只占4字节,可以用如下函数相互转换,而char(15)占用至少15字节。一旦表数据行数到了1亿,那么要多用1.1G存储空间。SQL:select inet_aton('192.168.2.12'); select inet_ntoa(3232236044); PHP: ip2long(‘192.168.2.12’); long2ip(3530427185);

  4. 【建议】不推荐使用enum,set。因为它们浪费空间,且枚举值写死了,变更不方便。推荐使用tinyint或smallint。

  5. 【建议】不推荐使用blob,text等类型。它们都比较浪费硬盘和内存空间。在加载表数据时,会读取大字段到内存里从而浪费内存空间,影响系统性能。建议和PM、RD沟通,是否真的需要这么大字段。Innodb中当一行记录超过8098字节时,会将该记录中选取最长的一个字段将其768字节放在原始page里,该字段余下内容放在overflow-page里。不幸的是在compact行格式下,原始page和overflow-page都会加载。

  6. 【建议】存储金钱的字段,建议用int,程序端乘以100和除以100进行存取。因为int占用4字节,而double占用8字节,空间浪费。

  7. 【建议】文本数据尽量用varchar存储。因为varchar是变长存储,比char更省空间。MySQL server层规定一行所有文本最多存65535字节,因此在utf8字符集下最多存21844个字符,超过会自动转换为mediumtext字段。而text在utf8字符集下最多存21844个字符,mediumtext最多存2^24/3个字符,longtext最多存2^32个字符。一般建议用varchar类型,字符数不要超过2700。

  8. 【建议】时间类型尽量选取timestamp。

    因为datetime占用8字节,timestamp仅占用4字节,但是范围为1970-01-01 00:00:01到2038-01-01 00:00:00。更为高阶的方法,选用int来存储时间,使用SQL函数unix_timestamp()和from_unixtime()来进行转换。

详细存储大小参加下图:

图片

4、索引设计
  1. 【强制】InnoDB表必须主键为id int/bigint auto_increment,且主键值禁止被更新。

  2. 【建议】主键的名称以“pk_”开头,唯一键以“uk_”或“uq_”开头,普通索引以“idx_”开头,一律使用小写格式,以表名/字段的名称或缩写作为后缀。

  3. 【强制】InnoDB和MyISAM存储引擎表,索引类型必须为BTREE;MEMORY表可以根据需要选择HASH或者BTREE类型索引。

  4. 【强制】单个索引中每个索引记录的长度不能超过64KB。

  5. 【建议】单个表上的索引个数不能超过7个。

  6. 【建议】在建立索引时,多考虑建立联合索引,并把区分度最高的字段放在最前面。如列userid的区分度可由select count(distinct userid)计算出来。

  7. 【建议】在多表join的SQL里,保证被驱动表的连接列上有索引,这样join执行效率最高。

  8. 【建议】建表或加索引时,保证表里互相不存在冗余索引。对于MySQL来说,如果表里已经存在key(a,b),则key(a)为冗余索引,需要删除。

5、分库分表、分区表
  1. 【强制】分区表的分区字段(partition-key)必须有索引,或者是组合索引的首列。

  2. 【强制】单个分区表中的分区(包括子分区)个数不能超过1024。

  3. 【强制】上线前RD或者DBA必须指定分区表的创建、清理策略。

  4. 【强制】访问分区表的SQL必须包含分区键。

  5. 【建议】单个分区文件不超过2G,总大小不超过50G。建议总分区数不超过20个。

  6. 【强制】对于分区表执行alter table操作,必须在业务低峰期执行。

  7. 【强制】采用分库策略的,库的数量不能超过1024

  8. 【强制】采用分表策略的,表的数量不能超过4096

  9. 【建议】单个分表不超过500W行,ibd文件大小不超过2G,这样才能让数据分布式变得性能更佳。

  10. 【建议】水平分表尽量用取模方式,日志、报表类数据建议采用日期进行分表。

6、字符集
  1. 【强制】数据库本身库、表、列所有字符集必须保持一致,为utf8或utf8mb4。

  2. 【强制】前端程序字符集或者环境变量中的字符集,与数据库、表的字符集必须一致,统一为utf8。

二:SQL编写规范

图片

1、DML语句
  1. 【强制】SELECT语句必须指定具体字段名称,禁止写成*。因为select *会将不该读的数据也从MySQL里读出来,造成网卡压力。且表字段一旦更新,但model层没有来得及更新的话,系统会报错。

  2. 【强制】insert语句指定具体字段名称,不要写成insert into t1 values(…),道理同上。

  3. 【建议】insert into…values(XX),(XX),(XX)…。这里XX的值不要超过5000个。值过多虽然上线很很快,但会引起主从同步延迟。

  4. 【建议】SELECT语句不要使用UNION,推荐使用UNION ALL,并且UNION子句个数限制在5个以内。因为union all不需要去重,节省数据库资源,提高性能。

  5. 【建议】in值列表限制在500以内。例如select… where userid in(….500个以内…),这么做是为了减少底层扫描,减轻数据库压力从而加速查询。

  6. 【建议】事务里批量更新数据需要控制数量,进行必要的sleep,做到少量多次。

  7. 【强制】事务涉及的表必须全部是innodb表。否则一旦失败不会全部回滚,且易造成主从库同步终端。

  8. 【强制】写入和事务发往主库,只读SQL发往从库。

  9. 【强制】除静态表或小表(100行以内),DML语句必须有where条件,且使用索引查找。

  10. 【强制】生产环境禁止使用hint,如sql_no_cache,force index,ignore key,straight join等。

    因为hint是用来强制SQL按照某个执行计划来执行,但随着数据量变化我们无法保证自己当初的预判是正确的,因此我们要相信MySQL优化器!

  11. 【强制】where条件里等号左右字段类型必须一致,否则无法利用索引。

  12. 【建议】SELECT|UPDATE|DELETE|REPLACE要有WHERE子句,且WHERE子句的条件必需使用索引查找。

  13. 【强制】生产数据库中强烈不推荐大表上发生全表扫描,但对于100行以下的静态表可以全表扫描。查询数据量不要超过表行数的25%,否则不会利用索引。

  14. 【强制】WHERE 子句中禁止只使用全模糊的LIKE条件进行查找,必须有其他等值或范围查询条件,否则无法利用索引。

  15. 【建议】索引列不要使用函数或表达式,否则无法利用索引。如where length(name)='Admin'或where user_id+2=10023。

  16. 【建议】减少使用or语句,可将or语句优化为union,然后在各个where条件上建立索引。如where a=1 or b=2优化为where a=1… union …where b=2, key(a),key(b)。

  17. 【建议】分页查询,当limit起点较高时,可先用过滤条件进行过滤。如select a,b,c from t1 limit 10000,20;优化为:select a,b,c from t1 where id>10000 limit 20;。

2、多表连接
  1. 【强制】禁止跨db的join语句。因为这样可以减少模块间耦合,为数据库拆分奠定坚实基础。

  2. 【强制】禁止在业务的更新类SQL语句中使用join,比如update t1 join t2…。

  3. 【建议】不建议使用子查询,建议将子查询SQL拆开结合程序多次查询,或使用join来代替子查询。

  4. 【建议】线上环境,多表join不要超过3个表。

  5. 【建议】多表连接查询推荐使用别名,且SELECT列表中要用别名引用字段,数据库.表格式,如select a from db1.table1 alias1 where …。

  6. 【建议】在多表join中,尽量选取结果集较小的表作为驱动表,来join其他表。

3、事务
  1. 【建议】事务中INSERT|UPDATE|DELETE|REPLACE语句操作的行数控制在2000以内,以及WHERE子句中IN列表的传参个数控制在500以内。

  2. 【建议】批量操作数据时,需要控制事务处理间隔时间,进行必要的sleep,一般建议值5-10秒。

  3. 【建议】对于有auto_increment属性字段的表的插入操作,并发需要控制在200以内。

  4. 【强制】程序设计必须考虑“数据库事务隔离级别”带来的影响,包括脏读、不可重复读和幻读。线上建议事务隔离级别为repeatable-read。

  5. 【建议】事务里包含SQL不超过5个(支付业务除外)。因为过长的事务会导致锁数据较久,MySQL内部缓存、连接消耗过多等雪崩问题。

  6. 【建议】事务里更新语句尽量基于主键或unique key,如update … where id=XX; 否则会产生间隙锁,内部扩大锁定范围,导致系统性能下降,产生死锁。

  7. 【建议】尽量把一些典型外部调用移出事务,如调用webservice,访问文件存储等,从而避免事务过长。

  8. 【建议】对于MySQL主从延迟严格敏感的select语句,请开启事务强制访问主库。

4、排序和分组
  1. 【建议】减少使用order by,和业务沟通能不排序就不排序,或将排序放到程序端去做。order by、group by、distinct这些语句较为耗费CPU,数据库的CPU资源是极其宝贵的。

  2. 【建议】order by、group by、distinct这些SQL尽量利用索引直接检索出排序好的数据。如where a=1 order by可以利用key(a,b)。

  3. 【建议】包含了order by、group by、distinct这些查询的语句,where条件过滤出来的结果集请保持在1000行以内,否则SQL会很慢。

5、线上禁止使用的SQL语句
  1. 【高危】禁用update|delete t1 … where a=XX limit XX; 这种带limit的更新语句。因为会导致主从不一致,导致数据错乱。建议加上order by PK。

  2. 【高危】禁止使用关联子查询,如update t1 set … where name in(select name from user where…);效率极其低下。

  3. 【强制】禁用procedure、function、trigger、views、event、外键约束。因为他们消耗数据库资源,降低数据库实例可扩展性。推荐都在程序端实现。

  4. 【强制】禁用insert into …on duplicate key update…在高并发环境下,会造成主从不一致。

  5. 【强制】禁止联表更新语句,如update t1,t2 where t1.id=t2.id。

网络三张表:ARP表, MAC表, 路由表

网络中的数据包

网络上传输的东西是什么?在使用TCP/IP协议传输时,网络上的数据,是以二进制数据包的形式传输的,核心的三个部分,如下:

图片

这个数据包,其中有这么几个字段很重要:

  • 源 IP 地址

  • 源 MAC 地址

  • 目标 IP 地址

  • 目标 MAC 地址

这些都是和地址相关的,在网络中,每台计算机,每个通讯设备,都有自己的地址,包含 IP 地址和 MAC 地址。MAC地址 又称为 硬件地址、物理地址(因为这种地址用在MAC帧中)。

为什么需要那么多地址呢?

一个ip地址,还要一个mac地址。原因是,网络协议是分层的。不同的地址,给不同的层使用。

  • ip地址,网络层使用,

  • mac地址, 数据链路层使用。

图片

什么是网络层,什么是链路层?

图片

图:TCP/IP协议与七层ISO模型的对应关系

网络三张表1:ARP表

提起ARP表必然先想起ARP(address resolution protocol)协议,地址解析协议。为什么需要 ARP 协议?

链路层中,同一局域网中的一台主机要和另一台主机进行通信,需要通过 MAC 地址进行定位,然后才能进行数据包的发送。而在网络层中,计算机之间是通过 IP 地址定位目标主机,对应的数据报文只包含目标主机的 IP 地址,而没有 MAC 地址。因此,在发送之前,需要做个翻译的工作:根据 IP 地址获取 MAC 地址。

图片

只有翻译成功,才能将数据包发送到正确的目标主机,而这个获取过程是通过 ARP 协议完成的。

网关设备上的ARP表项

在日常维护工作中,在华为的网关设备上,可以执行display arp相关命令,查看设备上的ARP表项信息。通过在网关设备上查看ARP表项,网络管理员可以查看下挂用户的IP地址、MAC地址和接口等信息。

例如,当网络管理员知道某个用户的IP地址,想查询该用户的MAC地址时,可以通过查看ARP表项信息获取。用户可以执行display arp all.看到IP-MAC的表项,我们可以看到已知的IP地址的MAC地址是什么,这样很方便我们排查故障

图片

上述回显中,每行ARP表项的具体含义如下:

IP地址为172.16.10.3,MAC地址为0025-9efb-be55,TYPE字段为S(代表该ARP表项为静态ARP表项)。这条静态ARP表项出接口为GE1/0/6,VLAN编号为100。

IP地址为172.16.20.3,MAC地址为0200-0000-00e8,TYPE字段为S(代表该ARP表项为静态ARP表项)。这条静态ARP表项出接口为GE1/0/19。

IP地址为172.16.10.1,MAC地址为0025-9ef4-abcd,TYPE字段为I(代表该ARP表项为接口本身的ARP表项)。这条ARP表项代表IP地址172.16.10.1是接口Vlanif100的IP地址。

IP地址为172.16.10.2,MAC地址为0025-9efb-be55,TYPE字段为D(代表该ARP表项为动态ARP表项)。这条动态ARP表项是从接口GE1/0/6动态学习到的,VLAN编号为100,剩余存活时间为20分钟。

IP地址为172.16.20.1,MAC地址为0025-9ef4-abcd,TYPE字段为I(代表该ARP表项为接口本身的ARP表项)。这条ARP表项代表IP地址172.16.20.1是接口GE1/0/19的IP地址。

IP地址为172.16.20.2,MAC地址为0200-0000-00e8,TYPE字段为D(代表该ARP表项为动态ARP表项)。这条动态ARP表项是从接口GE1/0/19动态学习到的,剩余存活时间为18分钟。代码转自,华为企业技术文档。

ARP表的使用过程

由于IP协议使用了ARP协议,因此通常把ARP协议划归到网络层。但ARP协议的用途是为了从网络层使用IP地址,解析出在链路层使用的硬件地址。每一台主机都设有一个ARP高速缓存,里面有本局域网上的各种IP地址到MAC硬件地址的映射表(ARP表),表里面的内容由ARP协议进行动态更新。ARP表内的数据会老化,达到老化时间会自动删除,在此通信时,由ARP协议重新添加。

有PC0,PC1两台主机

图片

PC0向PC1发送一个ping报文,向PC0输入1.1.1.3 然后开始发送,PC0先查询本地APR表查询1.1.1.3对应MAC地址,但并没查到,这种情况PC0在本局域网上广播发送一个ARP请求分组。ARP请求分组的主要内容是:

我的IP地址是1.1.1.2,硬件地址是xx-xx-xx-xx-xx-xx。我想知道IP地址为1.1.1.3的主机的硬件地址。

在本局域网上的所有主机上运行的ARP进程都会收到此ARP请求分组。PC1的IP地址与ARP请求分组中要查询的IP地址一致,就收下ARP请求分组,并向PC0发送ARP响应分组,同时在这个ARP响应分组中写入自己的硬件地址。其余主机ip地址都与ARP请求要查询的ip地址不一致,不做任何回应。响应内容为:

我的ip地址是1.1.1.3,我的硬件地址是xx-xx-xx-xx-xx-xx

虽然ARP请求分组是广播发送的,但ARP响应分组是普通的单播,即从一个原地址发送到一个目的地址。接下来PC0就可以使用刚获取的PC1MAC地址,进行icmp数据发送。

图片

网络三张表之2:MAC表

首先,来看交换机是怎么进行数据转发的。

交换机是怎么进行数据转发的

MAC表工作在链路层,主要给 交换机用的。一个局域网的主机设备,多了以后,需要通过交换机, 组织和管理起来。

图片

主机设备接到 交换机的端口上。交换机内部维护一张 【MAC 地址表】,记录着每一个 MAC 地址的设备,连接在哪一个端口上。

  • 如果发来的包首部中包含的目标 MAC 地址在 MAC 地址表中没有映射关系,交换机就将此包广播给所有端口,也即发给了所有机器;

  • 如果地址表有映射,那就只发给那一个端口

比如主机 A 想要给 主机C 发送消息,就需要用到mac表。但是,注意一下,一开始交换机的 MAC 地址表是空的,交换机并不知道 C 的端口号,因此 A 发送的消息将会被广播, 同时,A 的 MAC 地址和它对应的端口号会被记录到 MAC 地址表中。

具体如下图:

图片

C收到广播之后,发现是发给自己的,就对A进行回复。C 对 A 的消息进行响应的时候,交换机就不需要进行广播消息了,因此它已经知道 A 计算机在哪个端口了,并且同样的,C 的 MAC 地址和它对应的端口号会被记录到交换机的 MAC 地址表中。

图片

C 通过交换机给 A 发送消息,此时 MAC 地址表中含有 A 的信息

MAC表的数据内容

MAC表放在了另一个层级,数据链路层

图片

如上图所示,mac 表这样设计的。交换机内部维护一张 MAC 地址表,记录着每一个 MAC 地址的设备,连接在其哪一个端口上。

MAC 地址端口
bb-bb-bb-bb-bb-bb1
cc-cc-cc-cc-cc-cc3
aa-aa-aa-aa-aa-aa4
dd-dd-dd-dd-dd-dd5

假如你仍然要发给 B 一个数据包,构造了如下的数据结构从网口出去。

MAC表的使用过程

交换机是根据MAC地址转发数据帧的。

图片

当PC0发送ARP数据包,交换机会把数据包发往PC0之外的所有主机,并在相应包中记录下相应Mac地址与接口数据。

当PC0向PC1发送一帧数据,从1口进到交换机。交换机收到帧后,根据帧中的目的MAC先查本地MAC表,没有查到应从哪个接口转发这个帧。

接着,交换机把这个帧的源MAC和接口1写入交换表中,并向除1以外的所有接口广播这个帧,PC2将此广播帧丢弃,因为目的地址不对。

PC1收下这个目的地址是自己MAC的数据,并回应数据包,此时交换机会把PC1的MAC和对应接口2写入表中。然后当PC0与PC1再次发送数据交换机可以根据目的MAC查表找出对应的接口,将数据包直接送达对应的主机。

考虑到有可能在交换机的接口更换主机,或者主机要跟换主机更换网卡,这时交换机中MAC表也是动态的、有自己的老化时间,会自动删除相关数据。

当交换机中一台主机突然从2口切换到3口,交换机收到的此主机的数据仍然会发送到原端口2口就会出现超时情况,直到Mac中数据更新,或者手动把交换机断电重启重更新Mac表数据。

图片

网络三张表之3:路由表

说起路由表,就不得不提起路由器

路由器是一种具有多个输入端口和多个输出端口的专用设备也可称为计算机。其任务是转发分组。

路由器结构由两部分组成:路由选择部分和分组转发部分。

说明:此处的路由器与家用路由器略有不同,家用路由器集成了路由器和交换机的功能为了,更方便用户使用

路由表的起源

前面的mac表,是因为局域网内的主机多了,被逼出来对主机进行分组管理的。相当于按照端口,对主机分组管理。

路由表怎么来的呢? 也是被逼的。但是这一次,是被IP地址逼的。

当IP地址多了之后,需要划分子网,路由器的根本目标,就是解决跨子网IP之间的路由。而跨子网IP之间的路由,是通过路由表完成的。

网关的由来

有了子网之后,每个子网,有一个网关,相当于子网的总代理。所有的报文,需要进行这个子网网关的转发。有了网关之后, A 在自己电脑里配置的一个网关 IP 地址,以便在发给不同子网的机器时,发给这个 网关IP 地址, 由这个网关转发。

图片

有了这个代理之后, 数据包的转发规则如下:

  • 如果源 IP 与目的 IP 处于一个子网,直接将包通过交换机发出去。

  • 如果源 IP 与目的 IP 不处于一个子网,就交给路由器去处理。

怎么知道,两个IP在同一个子网呢?

比如,我们希望达到下面的目标:

  • 192.168.0.1 和 192.168.0.2   处于同一个子网

  • 192.168.0.1 和 192.168.1.1   处于不同子网

那么,我们可以认为的规定一个规则:

  • 以 192.168.0.xxx 开头的,就算是在一个子网

  • 否则就是在不同的子网。

192.168.0.xxx 开头的,就算是在一个子网,否则就是在不同的子网。

那对于计算机来说,怎么表达这个意思呢?于是人们发明了子网掩码的概念。在上面的规则中,咱们的子网掩码定为 255.255.255.0。计算的时候,将源 IP 与目的 IP 分别同这个子网掩码进行与运算

  • 相等则是在一个子网,

  • 不相等就是在不同子网,就这么简单。

比如

  • A电脑:192.168.0.1 & 255.255.255.0 = 192.168.0.0

  • B电脑:192.168.0.2 & 255.255.255.0 = 192.168.0.0

  • C电脑:192.168.1.1 & 255.255.255.0 = 192.168.1.0

  • D电脑:192.168.1.2 & 255.255.255.0 = 192.168.1.0

那么 A 与 B 在同一个子网,C 与 D 在同一个子网,但是 A 与 C 就不在同一个子网,与 D 也不在同一个子网,以此类推。

图片

所以如果 A 给 C 发消息,A 和 C 的 IP 地址分别 & A 机器配置的子网掩码,

  • 发现相等,网关就把 数据发给 C

  • 发现不相等,网关则 A 认为 C 和自己不在同一个子网,于是把包发给路由器,就不管了,之后怎么转发,A 不关心

路由器怎么进行跨子网的路由

现在 A 要给 C 发数据包,已经可以成功发到路由器这里了,问题就是,路由器怎么知道,收到的这个数据包,该从自己的哪个端口出去,才能直接(或间接)地最终到达目的地 C 呢。路由器收到的数据包有目的 IP 也就是 C 的 IP 地址,需要转化成从自己的哪个端口出去,很容易想到,应该有个表,就像 MAC 地址表一样。这个表就叫路由表

和 MAC 地址表的不同的是,路由表并不是一对一这种明确关系,我们下面看一个路由表的结构。

目的地址子网掩码下一跳端口
192.168.0.0255.255.255.00
192.168.0.254255.255.255.2550
192.168.1.0255.255.255.01
192.168.1.254255.255.255.2551

由于子网掩码其实就表示前多少位表示子网的网段,所以如 192.168.0.0(255.255.255.0) 也可以简写为 192.168.0.0/24。

目的地址下一跳端口
192.168.0.0/240
192.168.0.254/320
192.168.1.0/241
192.168.1.254/321

路由表就表示,192.168.0.xxx 这个子网下的,都转发到 0 号端口,192.168.1.xxx 这个子网下的,都转发到 1 号端口。配合着结构图来看(这里把子网掩码和默认网关都补齐了)

图片

表中的下一跳列  还没有值,在这里不重要,重要的是,咱们理解了路由表的核心。

路由表的使用过程

路由器工作在网络层,主要功能就是实现跨网段传输数据。

如上图:一网段的主机要与其他网段的主机通信,则数据会先发送给指定的网关,也就是路由器,由路由器选择网段继续通信,所以发送的数据包起始中网络层的ip地址不变,源ip地址就是当前主机的ip地址,目的ip地址就是不同网段的主机的ip地址,但是链路层由于数据要先发给路由器,那么目的mac就是路由器的mac地址,然后路由器选择其他网段,则源mac变成路由器另一个网口的mac地址,目的mac就是目标主机的mac地址,所以整个过程网络层的一直不变,但是链路层一直再变。

图片

如上图:由于现在是两台路由器和两个网段的主机,所以需要构成三个局域网,当源主机跨网段与目标主机通信时,由于网段不同,数据会先发送给路由器,然后路由器再选择对应的目的网段,但是此时路由器另一端所在的网段与之目标主机的网段不同,所以无法继续向下发送,此时需要设置下一跳,下一跳的目的就是指定当前自己网段的主机要访问其他网段的主机时从这个路由器应该跳到哪个路由器,只要设置了,就可以直接发送数据到指定的路由器,整个过程就完成

1、 当网络中有多个路由器是,两个主机需要通信路由器需要记录下一跳信息,下一跳的目的就是指定当前自己网段的主机要访问其他网段的主机时从这个路由器应该跳到哪个路由器

2、 路由表记录着下一跳。

路由表中记录着不同网段的信息。路由表中记录的信息有的需要手动添加(称为静态路由表),通过路由协议自动获取的(称为动态路由表),我们的主机直接连到路由器上(中间无三层网络设备)这种情况是直连路由,属于静态路由。

路由选择处理机的任务是根据所选定的路由选择协议(路由协议后续在做总结)构造出路由表,同时经常或定期和相邻路由器交换路由信息而不断地跟新和维护路由表。

图片

聊聊ARP地址解析协议

1:首先,每个主机都会在自己的ARP缓冲区中建立一个ARP列表,以表示IP地址和MAC地址之间的对应关系。

2:当源主机要发送数据时,首先检查ARP列表中是否有对应IP地址的目的主机的MAC地址,如果有,则直接发送数据,如果没有,就向本网段的所有主机发送ARP数据包,该数据包包括的内容有:源主机IP地址,源主机MAC地址,目的主机的IP地址。

3:当本网络的所有主机收到该ARP数据包时,首先检查数据包中的IP地址是否是自己的IP地址,如果不是,则忽略该数据包,如果是,则首先从数据包中取出源主机的IP和MAC地址写入到ARP列表中,如果已经存在,则覆盖,然后将自己的MAC地址写入ARP响应包中,告诉源主机自己是它想要找的MAC地址。

4:源主机收到ARP响应包后。将目的主机的IP和MAC地址写入ARP列表,并利用此信息发送数据。如果源主机一直没有收到ARP响应数据包,表示ARP查询失败。

注意:广播发送ARP请求,单播发送ARP响应。

  • 14
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值