TCP

TCP握手协议TCP/IP协议中,TCP协议提供可靠的连接服务,采用三次握手建立一个连接.
第一次握手:建立连接时,客户端发送syn包(syn=j)到服务器,并进入SYN_SEND状态,等待服务器确认;
SYN:同步序列编号(Synchronize Sequence Numbers)
第二次握手:服务器收到syn包,必须确认客户的SYN(ack=j+1),同时自己也发送一个SYN包(syn=k),即SYN+ACK包,此时服务器进入SYN_RECV状态;
第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED状态,完成三次握手.
完成三次握手,客户端与服务器开始传送数据

为什么需要三次握手:

因为防止已经失效的请求报文段突然传到了B
A与B建立TCP连接时:首先A向B发SYN(同步请求),然后B回复SYN(搜索)+ACK(同步请求应答),最后A回复ACK确认,这样TCP的一次连接(三次握手)的过程就建立了!
image.png
四次挥手(Four-Way Wavehand)
即终止TCP连接
第一次挥手:Client发送一个FIN,用来关闭Client到Server的数据传送,Client进入FIN_WAIT_1状态。
(2)第二次挥手:Server收到FIN后,发送一个ACK给Client,确认序号为收到序号+1(与SYN相同,一个FIN占用一个序号),Server进入CLOSE_WAIT状态。
(3)第三次挥手:Server发送一个FIN,用来关闭Server到Client的数据传送,Server进入LAST_ACK状态。
(4)第四次挥手:Client收到FIN后,Client进入TIME_WAIT状态,接着发送一个ACK给Server,确认序号为收到序号+1,Server进入CLOSED状态,完成四次挥手。
为什么要有 time_wait 。忘了(其实不久前才看了一次,防止影响新连接,主动关闭方如果直接退出了,收到被动关闭方的重发fin 时,只能返回 RST 而不能返回 ACK
(2)为什么建立连接是三次握手,而关闭连接却是四次挥手呢? 这是因为服务端在LISTEN状态下,收到建立连接请求的SYN报文后,把ACK和SYN放在一个报文里发送给客户端。而关闭连接时,当收到对方的FIN报文时,仅仅表示对方不再发送数据了但是还能接收数据,己方也未必全部数据都发送给对方了,所以己方可以立即close,也可以发送一些数据给对方后,再发送FIN报文给对方来表示同意现在关闭连接,因此,己方ACK和FIN一般都会分开发送。
TCP(Transmission Control Protocol 传输控制协议)是一种面向连接的、可靠的、基于字节流的传输层通信协议
TCP层是位于IP层之上,应用层之下的中间层。不同主机的应用层之间经常需要可靠的、像管道一样的连接,但是IP层不提供这样的流机制,而是提供不可靠的包交换。
HTTP与HTTPS区别:
超文本传输协议HTTP协议被用于在Web浏览器和网站服务器之间传递信息,HTTP协议以明文方式发送内容,不提供任何方式的数据加密安全
套接字层超文本传输协议HTTPS,为了数据传输的安全,HTTPS在HTTP的基础上加入了SSL协议,SSL依靠证书来验证服务器的身份,并为浏览器和服务器之间的通信加密
image.png

浏览器从输入一个URL到响应的全过程
1. 域名解析
寻找DNS(字典)缓存(google.com),得到IP地址
2. 建立TCP连接(四次握手)
3. 发起HTTP请求
4. 服务器响应HTTP请求
5. 浏览器解析htm代码,并请求htm代码中的资源(如js、css图片等)
看是否是长连接。来决定是不是断开TCP连接
6. 断开TCP连接
7. 浏览器渲染页面
TCP可靠传输
1、确认和重传:接收方收到报文就会确认,发送方发送一段时间后没有收到确认就重传。
2、数据校验
3、数据合理分片和排序:
  UDP:IP数据报大于1500字节,大于MTU.这个时候发送方IP层就需要分片(fragmentation).把数据报分成若干片,使每 一片都小于MTU.而接收方IP层则需要进行数据报的重组.这样就会多做许多事情,而更严重的是,由于UDP的特性,当某一片数据传送中丢失时,接收方便 无法重组数据报.将导致丢弃整个UDP数据报.
  tcp会按MTU合理分片,接收方会缓存未按序到达的数据,重新排序后再交给应用层。
4、流量控制:当接收方来不及处理发送方的数据,能提示发送方降低发送的速率,防止包丢失。
5、拥塞控制:当网络拥塞时,减少数据的发送。
防火墙:可在自己的服务器上加防火措施,也可在节点上加防火措施。

ARP协议

ARP协议是“Address Resolution Protocol”(地址解析协议)的缩写。在局域网中,网络中实际传输的是“帧”,帧里面是有目标主机的MAC地址的。在以太网中,一个主机要和另一个主机进行直接通信,必须要知道目标主机的MAC地址。但这个目标MAC地址是如何获得的呢?它就是通过地址解析协议获得的。所谓“地址解析”就是主机在发送帧前将目标IP地址转换成目标MAC地址的过程。ARP协议的基本功能就是通过目标设备的IP地址,查询目标设备的MAC地址,以保证通信的顺利进行。
二、ARP协议的工作原理
在每台安装有TCP/IP协议的电脑里都有一个ARP缓存表,表里的IP地址与MAC地址是一一对应的,如附表所示。
附表
我们以主机A(192.168.1.5)向主机B(192.168.1.1)发送数据为例。当发送数据时,主机A会在自己的ARP缓存表中寻找是否有目标IP地址。如果找到了,也就知道了目标MAC地址,直接把目标MAC地址写入帧里面发送就可以了;如果在ARP缓存表中没有找到相对应的IP地址,主机A就会在网络上发送一个广播,目标MAC地址是“FF.FF.FF.FF.FF.FF”,这表示向同一网段内的所有主机发出这样的询问:“192.168.1.1的MAC地址是什么?”网络上其他主机并不响应ARP询问,只有主机B接收到这个帧时,才向主机A做出这样的回应:“192.168.1.1的MAC地址是00-aa-00-62-c6-09”。这样,主机A就知道了主机B的MAC地址,它就可以向主机B发送信息了。同时它还更新了自己的ARP缓存表,下次再向主机B发送信息时,直接从ARP缓存表里查找就可以了。ARP缓存表采用了老化机制,在一段时间内如果表中的某一行没有使用,就会被删除,这样可以大大减少ARP缓存表的长度,加快查询速度。




















进程与线程的区别:

进程与线程:进程就是一个程序在一个数据集上的一次动态执行过程。进程一般由程序、数据集、进程控制块三部分组成。
线程则是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。

1、进程是资源分配的最小单位线程是程序执行的最小单位(资源调度的最小单位)
2、进程有自己的独立地址空间,每启动一个进程,系统就会为它分配地址空间,建立数据表来维护代码段、堆栈段和数据段,这种操作非常昂贵。
而线程是共享进程中的数据的,使用相同的地址空间,因此CPU切换一个线程的花费远比进程要小很多,同时创建一个线程的开销也比进程要小很多。
3、线程之间的通信更方便,同一进程下的线程共享全局变量、静态变量等数据,而进程之间的通信需要以通信的方式(IPC)进行。不过如何处理好同步与互斥是编写多线程程序的难点。
4、但是多进程程序更健壮,多线程程序只要有一个线程死掉,整个进程也死掉了,而一个进程死掉并不会对另外一个进程造成影响,因为进程有自己独立的地址空间。

线程的几个状态

running dead sleep(释放资源,之后参与资源竞争) wait(释放资源,不再回来)

进程间通信

进程间通信(IPC,Interprocess communication)是一组编程接口,让程序员能够协调不同的进程,使之能在一个操作系统里同时运行,并相互传递、交换信息。这使得一个程序能够在同一时间里处理许多用户的要求。因为即使只有一个用户发出要求,也可能导致一个操作系统中多个进程的运行,进程之间必须互相通话。IPC接口就提供了这种可能性。每个IPC方法均有它自己的优点和局限性,一般,对于单个程序而言使用所有的IPC方法是不常见的。

消息传递(管道、FIFO、消息队列)
同步(互斥量、条件变量、读写锁、文件和写记录锁、信号量)
共享内存(匿名的和具名的) 远程过程调用(Solaris门和Sun RPC)
IPC方法包括管道(PIPE)、消息排队、旗语、共用内存以及套接字(Socket)socket是”打开—读/写—关闭”模式的实现

HTTP

Http定义了与服务器交互的不同方法,最基本的方法有4种,分别是GET,POST,PUT,DELETE。

根据HTTP规范,GET用于信息获取,而且应该是安全的和幂等的。
根据HTTP规范,POST表示可能修改变服务器上的资源的请求。继续引用上面的例子:还是新闻以网站为例,读者对新闻发表自己的评论应该通过POST实现,因为在评论提交后站点的资源已经不同了,或者说资源被修改了。
PUT: 把消息本体中的消息发送到一个URL,跟POST类似,但不常用。
1.GET请求的数据会附在URL之后(就是把数据放置在HTTP协议头中),POST把提交的数据则放置在是HTTP包的包体中。
2.”GET方式提交的数据最多只能是1024字节,理论上POST没有限制,可传较大量的数据
4.POST的安全性要比GET的安全性高
1、PUT和POST
PUT请求:如果两个请求相同,后一个请求会把第一个请求覆盖掉。(所以PUT用来改资源)
Post请求:后一个请求不会把第一个请求覆盖掉。(所以Post用来增资源)
502状态码是服务器(不一定是Web服务器)作为网关或代理,以满足客户的要求来访问所请求的URL 。此服务器收到无效响应从上游服务器访问履行它的要求。

子类拥有父类得特征,而父类没有,父类更通用,子类更具体,(特征包括属性和方法,自身的特性,拥有父类没有的)
多态性:允许将子类类型的指针赋值给父类类型的指针(virtual)
设计一个可以满足高效率获取第k大和前k个大的元素的数据结构
那如果选用最小堆的数据结构来解决,最顶端是最小值,再次遇到比它大的值,就可以入堆,入堆后重新调整堆,将小的值pass掉。这样我们就可以选出最大的前K个数据了。言外之意,假若我们要找出N个数据中最小的前k个数据,就要用最大堆了。
获取第k个可以用quick sort

面向对象

面向对象的编程
面向对象的程序设计包括了三个基本概念:封装性、继承性、多态性。面向对象的程序语言通过类、方法、对象和消息传递,来支持面向对象的程序设计范式。
1. 对象
世间万事万物都是对象。
面向对象的程序设计的抽象机制是将待解问题抽象为面向对象的程序中的对象。利用封装使每个对象都拥有个体的身份。程序便是成堆的对象,彼此通过消息的传递,请求其它对象 进行工作。
2. 类
每个对象都是其类中的一个实体。
物以类聚——就是说明:类是相似对象的集合。类中的对象可以接受相同的消息。换句话说:类包含和描述了“具有共同特性(数据元素)和共同行为(功能)”的一组对象。
比如:苹果、梨、橘子等等对象都属于水果类。
3. 封装
封装(有时也被称为信息隐藏)就是把数据和行为结合在一个包中,并对对象的使用者隐藏数据的实现过程。信息隐藏是面向对象编程的基本原则,而封装是实现这一原则的一种方式。
封装使对象呈现出“黑盒子”特性,这是对象再利用和实现可靠性的关键步骤。
4. 接口
每个对象都有接口。接口不是类,而是对符合接口需求的类所作的一套规范。接口说明类应该做什么但不指定如何作的方法。一个类可以有一个或多个接口。
5. 方法
方法决定了某个对象究竟能够接受什么样的消息。面向对象的设计有时也会简单地归纳为“将消息发送给对象”。
6. 继承
继承的思想就是允许在已存在类的基础上构建新的类。一个子类能够继承父类的所有成员,包括属性和方法。
继承的主要作用:通过实现继承完成代码重用;通过接口继承完成代码被重用。继承是一种规范的技巧,而不是一种实现的技巧。
7. 多态
多态提供了“接口与实现分离”。多态不但能改善程序的组织架构及可读性,更利于开发出“可扩充”的程序。

简单解释一下MVC:MVC本来是存在于Desktop程序中的,M是指数据模型,V是指用户界面,C则是控制器。使用MVC的目的是将M和V的实现代码分离,从而使同一个程序可以使用不同的表现形式。

c++的多态性用一句话概括就是:在基类的函数前加上virtual关键字,在派生类中重写该函数,运行时将会根据对象的实际类型来调用相应的函数,如果对象类型是派生类,就调用派生类的函数,如果对象类型是基类,就调用基类的函数。
虚函数是在基类中定义的,目的是不确定它的派生类的具体行为,例如:
定义一个基类:class Animal //动物,它的函数为breathe()
  再定义一个类class Fish //鱼。它的函数也为breathe()
  再定义一个类class Sheep //羊,它的函数也为breathe()
将Fish,Sheep定义成Animal的派生类,然而Fish与Sheep的breathe不一样,一个是在水中通过水来呼吸,一个是直接呼吸,所以基类不能确定该如何定义breathe,所以在基类中只定义了一个virtual breathe,它是一个空的虚函数,具体的函数在子类中分别定义,程序一般运行时,找到类,如果它有基类,再找到它的基类,最后运行的是基类中的函数,这时,它在基类中找到的是virtual标识的函数,它就会再回到子类中找同名函数,派生类也叫子类,基类也叫父类,这就是虚函数的产生,和类的多态性的体现。

协程
最大的优势就是协程极高的执行效率。因为子程序切换不是线程切换,而是由程序自身控制,因此,没有线程切换的开销,和多线程比,线程数量越多,协程的性能优势就越明显。
第二大优势就是不需要多线程的锁机制,因为只有一个线程,也不存在同时写变量冲突,在协程中控制共享资源不加锁,只需要判断状态就好了,所以执行效率比多线程高很多。
consumer执行至yield跳出

线程池:顾名思义就是事先创建若干个可执行的线程放入一个池(容器)中, 需要的时候从池中获取线程不用自行创建,使用完毕不需要销毁线程而是放回池中, 从而减少创建和销毁线程对象的开销。
系统启动一个新线程的成本是比较高的,因为它涉及与操作系统的交互。此时,使用线程池可以很好地提高性能,减少创建和销毁的时间,大量生存期很短暂的线程时,更应该考虑使用线程池。
设计一个动态大小的线程池,如何设计,应该有哪些方法?
一个线程池包括四个基本部分:
1 线程管理器(ThreadPool):用于创建并管理线程池,包括创建线程、销毁线程池、添加新任务。
2 工作线程(PoolWorker):线程池中线程,在没有任务时处于等待状态,可以循环的执行任务。
3 **任务接口(Task):每个任务必须实现的接口,以供工作线程调度任务的执行,它主要规定了任务的入口、任务执行 完后的收尾工作、任务的执行状态等。
4
任务队列(TaskQueue)**:用于存放没有处理的任务,提供一种缓冲机制。

重载与覆盖

重载是指不同函数使用相同函数名,但是函数参数个数和类型可以不同。

覆盖(重写)是指派生类中重新对基类的虚函数重新实现。函数名和参数一样,函数的实现体不一样。

虚函数与纯虚函数

1、纯虚函数声明如下: virtual void funtion1()=0; 纯虚函数一定没有定义,纯虚函数用来规范派生类的行为,即接口。包含纯虚函数的类是抽象类,抽象类不能定义实例,但可以声明指向实现该抽象类的具体类的指针或引用。
2、虚函数声明如下:virtual ReturnType FunctionName(Parameter);虚函数必须实现,如果不实现,编译器将报错,错误提示为:
error LNK**: unresolved external symbol “public: virtual void __thiscall ClassName::virtualFunctionName(void)”
3、对于虚函数来说,父类和子类都有各自的版本。由多态方式调用的时候动态绑定。
4、实现了纯虚函数的子类,该纯虚函数在子类中就编程了虚函数,子类的子类即孙子类可以覆盖该虚函数,由多态方式调用的时候动态绑定。
5、虚函数是C++中用于实现多态(polymorphism)的机制。核心理念就是通过基类访问派生类定义的函数。
6、在有动态分配堆上内存的时候,析构函数必须是虚函数,但没有必要是纯虚的。
7、友元不是成员函数,只有成员函数才可以是虚拟的,因此友元不能是虚拟函数。但可以通过让友元函数调用虚拟成员函数来解决友元的虚拟问题。
8、析构函数应当是虚函数,将调用相应对象类型的析构函数,因此,如果指针指向的是子类对象,将调用子类的析构函数,然后自动调用基类的析构函数。
有纯虚函数的类是抽象类,不能生成对象,只能派生。他派生的类的纯虚函数没有被改写,那么,它的派生类还是个抽象类。

同步与异步:

在计算机领域,同步就是指一个进程在执行某个请求的时候,若该请求需要一段时间才能返回信息,那么这个进程将会一直等待下去,直到收到返回信息才继续执行下去;异步是指进程不需要一直等下去,而是继续执行下面的操作,不管其他进程的状态。

索引

索引分为聚集索引和非聚集索引,数据库中的索引类似于一本书的目录,在一本书中通过目录可以快速找到你想要的信息,而不需要读完全书。
索引主要目的是提高了SQL Server系统的性能,加快数据的查询速度与减少系统的响应时间 。
但是索引对于提高查询性能也不是万能的,也不是建立越多的索引就越好。索引建少了,用 WHERE 子句找数据效率低,不利于查找数据。索引建多了,不利于新增、修改和删除等操作,因为做这些操作时,SQL SERVER 除了要更新数据表本身,还要连带立即更新所有的相关索引,而且过多的索引也会浪费硬盘空间。
分类:索引就类似于中文字典前面的目录,按照拼音或部首都可以很快的定位到所要查找的字。
唯一索引(UNIQUE):每一行的索引值都是唯一的(创建了唯一约束,系统将自动创建唯一索引)
主键索引:当创建表时指定的主键列,会自动创建主键索引,并且拥有唯一的特性。
聚集索引(CLUSTERED):聚集索引就相当于使用字典的拼音查找,因为聚集索引存储记录是物理上连续存在的,即拼音 a 过了后面肯定是 b 一样。
非聚集索引(NONCLUSTERED):非聚集索引就相当于使用字典的部首查找,非聚集索引是逻辑上的连续,物理存储并不连续。
PS:聚集索引一个表只能有一个,而非聚集索引一个表可以存在多个。

说一下建表时,建索引有哪些要注意的。选区分度比较大的,选数据类型比较小的比如整数而不要选长字符串,选where子句中出现的















全局解释器锁(GIL)

  1、什么是全局解释器锁
  在同一个进程中只要有一个线程获取了全局解释器(cpu)的使用权限,那么其他的线程就必须等待该线程的全局解释器(cpu)使用权消失后才能使用全局解释器(cpu),即时多个线程直接不会相互影响在同一个进程下也只有一个线程使用cpu,这样的机制称为全局解释器锁(GIL)。
  2、全局解释器锁的好处
    1、避免了大量的加锁解锁的好处
    2、使数据更加安全,解决多线程间的数据完整性和状态同步
  3、全局解释器的缺点
    多核处理器退化成单核处理器,只能并发不能并行

同步锁

  1、什么是同步锁?
  同一时刻的一个进程下的一个线程只能使用一个cpu,要确保这个线程下的程序在一段时间内被cpu执,那么就要用到同步锁。
  2、为什么用同步锁?
  因为有可能当一个线程在使用cpu时,该线程下的程序可能会遇到io操作,那么cpu就会切到别的线程上去,这样就有可能会影响到该程序结果的完整性。

死锁

死锁概念及产生原理
概念:多个并发进程因争夺系统资源而产生相互等待的现象。
原理:当一组进程中的每个进程都在等待某个事件发生,而只有这组进程中的其他进程才能触发该事件,这就称这组进程发生了死锁。
本质原因:
1)、系统资源有限。
2)、进程推进顺序不合理。
死锁产生的4个必要条件
1、互斥:某种资源一次只允许一个进程访问,即该资源一旦分配给某个进程,其他进程就不能再访问,直到该进程访问结束。
2、占有且等待:一个进程本身占有资源(一种或多种),同时还有资源未得到满足,正在等待其他进程释放该资源。
3、不可抢占:别人已经占有了某项资源,你不能因为自己也需要该资源,就去把别人的资源抢过来。
4、循环等待:存在一个进程链,使得每个进程都占有下一个进程所需的至少一种资源。
当以上四个条件均满足,必然会造成死锁,发生死锁的进程无法进行下去,它们所持有的资源也无法释放。这样会导致CPU的吞吐量下降。所以死锁情况是会浪费系统资源和影响计算机的使用性能的。那么,解决死锁问题就是相当有必要的了。
避免死锁 ——- 在使用前进行判断,只允许不会产生死锁的进程申请资源
的死锁避免是利用额外的检验信息,在分配资源时判断是否会出现死锁,只在不会出现死锁的情况下才分配资源。
两种避免办法:
1、如果一个进程的请求会导致死锁,则不启动该进程
2、如果一个进程的增加资源请求会导致死锁 ,则拒绝该申请。
使用两个互斥锁可以实现读写锁
import threading
class RWlock(object):
def init(self):
self._lock = threading.Lock()
self._extra = threading.Lock()
self.read_num = 0
def read_acquire(self):
with self._extra:
self.read_num += 1
if self.read_num == 1:
self._lock.acquire()
def read_release(self):
with self._extra:
self.read_num -= 1
if self.read_num == 0:
self._lock.release()
def write_acquire(self):
self._lock.acquire()
def write_release(self):
self._lock.release()
找是否有环:
def hasCycle(self, head):
mapping = set()
flag = False
p = head
while p:
if p not in mapping:
mapping.add(p)
else:
flag = True
break
p = p.next
return flag


内存

说到内存,很多人应该都多多少少有点了解了,我们在这再稍微多说几句:
  一般我们可以把内存理解为三个部分:静态区,栈,堆。有些朋友搞不清到底什么是栈什么是堆,堆栈有多人会认为是堆和栈,两个放在一块。其实不然,其中我们口中讲的堆栈就是栈,而不是堆。堆的英文是heap ;栈的英文是stack(也翻译为堆栈)。
存储内容:
  静态区:保存自动全局变量和static变量(包括static全局和局部变量)。静态区的内容在整个程序的生命周期内都存在,有编译器在编译的时候分配(数据段(存储全局数据和静态数据)和代码段(可执行的代码/只读常量))。
  栈:保存局部变量。栈上的内容只在函数范围内存在,当函数运行结束的时候,这些内容也会自动销毁。其特点是效率高但是空间大小有限。
堆:由malloc系列函数或者new操作符分配的内存。其生命周期由free和delete决定。在没有释放之前一直存在,直到函数结束。其特点是使用灵活,空间比较大,但容易出错。
函数的内存释放完后,一定要把p指针置为NULL。为什么?
  从上面可以看出,free掉之后p只是切断了和内存空间的关系,但是p指针本身内部依旧存在一个地址,如果不把它置成空,那这个指针就会变成一个野指针(悬垂指针),迟早会出事。

image.png
四、new/delete
  前头讲了很多但好像还没有讲到c++的动态内存这方面。下面我们来进行一些讨论。
  我们知道c++是兼容c的,那我们明明已经有了malloc和free来进行动态内容的管理,为什么c++还要定义new和delete运算符来动态管理内存。
  来看一下它们之间的区别和联系:
  1.它们都是动态管理内存的入口。
  2.malloc/free是c/c++标准库的函数,new/delete是c++操作符。
  3.malloc/free只是动态分配/释放内存空间。而new/delete出来分配空间还会调用构造函数和析构函数进行初始化与清理。
  4.malloc/free需要手动计算类型大小且会返回void*, new/delete可以自己计算类型的大小,返回对应类型的指针
五、new和delete在内存中所做的事
new做的事:
1.调用operator new分配空间
2.调用构造函数初始化空间

delete做的事:
1.调用析构函数清理对象
2.调用operator delete释放空间

MySQL 连接的使用

在前几章节中,我们已经学会了如何在一张表中读取数据,这是相对简单的,但是在真正的应用中经常需要从多个数据表中读取数据。
本章节我们将向大家介绍如何使用 MySQL 的 JOIN 在两个或多个表中查询数据。
你可以在 SELECT, UPDATE 和 DELETE 语句中使用 Mysql 的 JOIN 来联合多表查询。
JOIN 按照功能大致分为如下三类:
·
INNER JOIN(内连接,或等值连接):获取两个表中字段匹配关系的记录。
·
LEFT JOIN(左连接):获取左表所有记录,即使右表没有对应匹配的记录。
·
RIGHT JOIN(右连接): 与 LEFT JOIN 相反,用于获取右表所有记录,即使左表没有对应匹配的记录。

Mysql中ACID的原理

原子性 (Atomicity)

原子性是指一个事务是一个不可分割的工作单位,其中的操作要么都做,要么都不做。

隔离性 (Isolation)

隔离性是指多个事务并发执行的时候,事务内部的操作与其他事务是隔离的,并发执行的各个事务之间不能互相干扰。

持久性 (Durability)

持久性是指事务一旦提交,它对数据库的改变就应该是永久性的。接下来的其他操作或故障不应该对其有任何影响。

一致性 (Consistency)

  1. 一致性是指事务执行前后,数据处于一种合法的状态,这种状态是语义上的而不是语法上的。 这个状态是满足预定的约束就叫做合法的状态,再通俗一点,这状态是由你自己来定义的。满足这个状态,数据就是一致的,不满足这个状态,数据就是不一致的!

mysql实例

select 查询结果 [学号,平均成绩:汇总函数avg(成绩)]
count() avg(成绩) count(课程号) as 选修课程数目 等等
from 从哪张表中查找数据 [成绩在成绩表中,所以查找的是成绩表score]
where 查询条件 (例如课程号是0002, where 课程号 = ‘0002’)
where 姓名 like ‘侯’
group by 分组 [平均成绩:先按学号分组,再计算平均成绩](例如,课程号)
having 对分组结果指定条件 [平均成绩大于60分]
having 对分组结果指定条件 [姓名相同:count(
)>=2]
order by 对查询结果排序[没有];
order by 平均成绩 asc,课程号 desc;
/
select 学号, avg(成绩)
from score
group by 学号
having avg(成绩)>60;
查询各科成绩最高和最低的分, 以如下的形式显示:课程号,最高分,最低分
/

分析思路
select 查询结果 [课程ID:是课程号的别名,最高分:max(成绩) ,最低分:min(成绩)]
from 从哪张表中查找数据 [成绩表score]
where 查询条件 [没有]
group by 分组 [各科成绩:也就是每门课程的成绩,需要按课程号分组];
/
select 课程号,max(成绩) as 最高分,min(成绩) as 最低分
from score
group by 课程号;
查询两门以上不及格课程的同学的学号及其平均成绩
/

分析思路
先分解题目:
1)[两门以上][不及格课程]限制条件
2)[同学的学号及其平均成绩],也就是每个学生的平均成绩,显示学号,平均成绩
分析过程:
第1步:得到每个学生的平均成绩,显示学号,平均成绩
第2步:再加上限制条件:
1)不及格课程
2)两门以上[不及格课程]:课程数目>2
/
第1步:得到每个学生的平均成绩,显示学号,平均成绩
select 查询结果 [学号,平均成绩:汇总函数avg(成绩)]
from 从哪张表中查找数据 [涉及到成绩:成绩表score]
where 查询条件 [没有]
group by 分组 [每个学生的平均:按学号分组]
having 对分组结果指定条件 [没有]
order by 对查询结果排序[没有];
/
select 学号, avg(成绩) as 平均成绩
from score
group by 学号;
/
第2步:再加上限制条件:
1)不及格课程
2)两门以上[不及格课程]
select 查询结果 [学号,平均成绩:汇总函数avg(成绩)]
from 从哪张表中查找数据 [涉及到成绩:成绩表score]
where 查询条件 [限制条件:不及格课程,平均成绩<60]
group by 分组 [每个学生的平均:按学号分组]
having 对分组结果指定条件 [限制条件:课程数目>2,汇总函数count(课程号)>2]
order by 对查询结果排序[没有];
/
select 学号, avg(成绩) as 平均成绩
from score
where 成绩 <60
group by 学号
having count(课程号)>=2;

3.复杂查询(with join)

查询所有课程成绩小于60分学生的学号、姓名
select 学号,姓名
from student
where 学号 in (
select 学号
from student
where 成绩 < 60
);
查询平均成绩大于85的所有学生的学号、姓名和平均成绩
select a.学号,a.姓名, avg(b.成绩) as 平均成绩
from student as a left join score as b
on a.学号 = b.学号
group by a.学号
having avg(b.成绩)>85;

查询出每门课程的及格人数和不及格人数

机器学习

梯度下降

机器学习中常见的过拟合解决方法

  在机器学习中,我们将模型在训练集上的误差称之为训练误差,又称之为经验误差,在新的数据集(比如测试集)上的误差称之为泛化误差,泛化误差也可以说是模型在总体样本上的误差。对于一个好的模型应该是经验误差约等于泛化误差,也就是经验误差要收敛于泛化误差,根据霍夫丁不等式可知经验误差在一定条件下是可以收敛于泛化误差的。
  当机器学习模型对训练集学习的太好的时候(再学习数据集的通性的时候,也学习了数据集上的特性,这些特性是会影响模型在新的数据集上的表达能力的,也就是泛化能力),此时表现为经验误差很小,但往往此时的泛化误差会很大,这种情况我们称之为过拟合,而当模型在数据集上学习的不够好的时候,此时经验误差较大,这种情况我们称之为欠拟合。具体表现如下图所示,第一幅图就是欠拟合,第三幅图就是过拟合。
  基础知识 - 图4
  再如下面这幅图所示,只要我们愿意,在训练集上的误差是可以无限小的,但是此时的泛化误差会增大。
  基础知识 - 图5
  对于欠拟合的问题比较好处理,只要增大模型的复杂度就行,而且欠拟合会在训练过程中就表现出来(通过看训练集的损失来判断),更容易去控制。对于过拟合不会体现在训练集上,因此常见的方式是采用交叉验证来检测过拟合。解决过拟合的两条主线:一是增大数据集,二是降低模型的复杂度(根据VC维理论可知)。一般来说扩展数据集是比较难的,而且数据集大,模型复杂度高的时候即使能获得好的泛化结果,也会增大计算量。所以常见的方式都是以降低模型的复杂度为主,接下来看看有哪些常见的方法可以自适应地降低模型的复杂度。
1、正则化
  正则化是机器学习中最常见的过拟合解决方法,在损失函数中加入正则项来惩罚模型的参数,以此来降低模型的复杂度,常见的添加正则项的正则化技术有L1,L2正则化。
  L1正则化
  基础知识 - 图6
  L1正则化是基于L1范数的,J是我们的损失函数,在损失函数优化时,我们要使得损失函数无限小,要满足这个结果,表达式中的第二项也必须无限小。关于L1正则化项的函数可以在二维平面图中表示出来,令
   基础知识 - 图7

  基础知识 - 图8
  图中的等值线(就是那些曲线)是J的等值线,黑色方形是正则项函数L的图形。在图中。当J等值线和L图形首次相交的地方就是最优解(根据拉格朗日约束问题的最小值可知,L函数可以看作J函数的约束,没有该约束,J函数的最小值应该是最里面的等值线,加上约束之后,就是首次相交的点处),从图中可以看出在相交点这里有w为0,这也是L1正则的特点。加入L1正则项之后,数据集中那些对模型贡献不大的特征所对应的参数w可以为0,因此L1正则项得出的参数是稀疏的。
  L2正则化
  基础知识 - 图9
  同样可以画出在二维平面中的图形来描述
  基础知识 - 图10
  原理和L1正则中差不多,但是L2正则化不会获得稀疏解,只会将对模型贡献不大的特征所对应的参数置于无限小的值,以此来忽略该特征对模型的影响。
  因此正则化都是在通过控制模型参数的大小来降低模型的复杂度。

2、剪枝处理
  剪枝是决策树中一种控制过拟合的方法,我们知道决策树是一种非常容易陷入过拟合的算法,剪枝处理主要有预剪枝和后剪枝这两种,常见的是两种方法一起使用。预剪枝通过在训练过程中控制树深、叶子节点数、叶子节点中样本的个数等来控制树的复杂度。后剪枝则是在训练好树模型之后,采用交叉验证的方式进行剪枝以找到最优的树模型。

3、提前终止迭代(Early stopping)
  该方法主要是用在神经网络中的,在神经网络的训练过程中我们会初始化一组较小的权值参数,此时模型的拟合能力较弱,通过迭代训练来提高模型的拟合能力,随着迭代次数的增大,部分的权值也会不断的增大。如果我们提前终止迭代可以有效的控制权值参数的大小,从而降低模型的复杂度。

4、权值共享
  权值共享最常见的就是在卷积神经网络中,权值共享的目的旨在减小模型中的参数,同时还能较少计算量。在循环神经网络中也用到了权值共享。

5、增加噪声
  这也是深度学习中的一种避免过拟合的方法(没办法,深度学习模型太复杂,容易过拟合),添加噪声的途径有很多,可以在输入数据上添加,增大数据的多样性,可以在权值上添加噪声,这种方法类似于L2正则化。

6、Batch Normalization
  BM算法是一种非常有用的正则化方法,而且可以让大型的卷积神经网络快速收敛,同时还能提高分类的准确率,而且可以不需要使用局部响应归一化处理,也可以不需要加入Dropout。BM算法会将每一层的输入值做归一化处理,并且会重构归一化处理之后的数据,确保数据的分布不会发生变化。
  而且有很多变种,比如Layer Normalization,在RNN或者Transformer中用的比较多。
  上面的几种方法都是操作在一个模型上 ,通过改变模型的复杂度来控制过拟合。另一种可行的方法是结合多种模型来控制过拟合。

7、Bagging和Boosting
  Bagging和Boosting是机器学习中的集成方法,多个模型的组合可以弱化每个模型中的异常点的影响,保留模型之间的通性,弱化单个模型的特性。

8、Dropout
  Dropout是深度学习中最常用的控制过拟合的方法,主要用在全连接层处。Dropout方法是在一定的概率上(通常设置为0.5,原因是此时随机生成的网络结构最多)隐式的去除网络中的神经元,具体如下图
  基础知识 - 图11
  Dropout控制过拟合的思想和机器学习中的集成方法中的bagging类似,在每个batch更新的网络结构都有所不同,也就相当于在训练时有很多个不同的子网络,在每次迭代时dropout的神经元都不一样,因此对于整个模型参数而言,每次都会有一些参数不被训练到。Dropout会导致网络的训练速度慢2、3倍,而且数据小的时候,Dropout的效果并不会太好。因此只会在大型网络上使用。

select a.课程号,b.课程名称,
sum(case when 成绩 between 85 and 100 
     then 1 else 0 end) as '[100-85]',
sum(case when 成绩 >=70 and 成绩<85 
     then 1 else 0 end) as '[85-70]',
sum(case when 成绩>=60 and 成绩<70  
     then 1 else 0 end) as '[70-60]',
sum(case when 成绩<60 then 1 else 0 end) as '[<60]'
from score as a right join course as b 
on a.课程号=b.课程号
group by a.课程号,b.课程名称;