dejavu

dejavu 查看完整档案

苏州编辑  |  填写毕业院校  |  填写所在公司/组织填写个人主网站
编辑
_ | |__ _ _ __ _ | '_ \| | | |/ _` | | |_) | |_| | (_| | |_.__/ \__,_|\__, | |___/ 个人简介什么都没有

个人动态

dejavu 收藏了文章 · 2020-06-23

进程与线程的区别

人生就像一场马拉松,一时的快慢并不代表最终的结果,重要的是途中的你是否坚持。

两者的定义

进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位。

线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位.线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源。

进程与线程的区别

  1. 进程是资源分配最小单位,线程是程序执行的最小单位;
  2. 进程有自己独立的地址空间,每启动一个进程,系统都会为其分配地址空间,建立数据表来维护代码段、堆栈段和数据段,线程没有独立的地址空间,它使用相同的地址空间共享数据;
  3. CPU切换一个线程比切换进程花费小;
  4. 创建一个线程比进程开销小;
  5. 线程占用的资源要⽐进程少很多。
  6. 线程之间通信更方便,同一个进程下,线程共享全局变量,静态变量等数据,进程之间的通信需要以通信的方式(IPC)进行;(但多线程程序处理好同步与互斥是个难点)
  7. 多进程程序更安全,生命力更强,一个进程死掉不会对另一个进程造成影响(源于有独立的地址空间),多线程程序更不易维护,一个线程死掉,整个进程就死掉了(因为共享地址空间);
  8. 进程对资源保护要求高,开销大,效率相对较低,线程资源保护要求不高,但开销小,效率高,可频繁切换;

加强理解,做个简单的比喻:进程=火车,线程=车厢

  • 线程在进程下行进(单纯的车厢无法运行)
  • 一个进程可以包含多个线程(一辆火车可以有多个车厢)
  • 不同进程间数据很难共享(一辆火车上的乘客很难换到另外一辆火车,比如站点换乘)
  • 同一进程下不同线程间数据很易共享(A车厢换到B车厢很容易)
  • 进程要比线程消耗更多的计算机资源(采用多列火车相比多个车厢更耗资源)
  • 进程间不会相互影响,一个线程挂掉将导致整个进程挂掉(一列火车不会影响到另外一列火车,但是如果一列火车上中间的一节车厢着火了,将影响到所有车厢)
  • 进程可以拓展到多机,进程最多适合多核(不同火车可以开在多个轨道上,同一火车的车厢不能在行进的不同的轨道上)
  • 进程使用的内存地址可以上锁,即一个线程使用某些共享内存时,其他线程必须等它结束,才能使用这一块内存。(比如火车上的洗手间)-"互斥锁"
  • 进程使用的内存地址可以限定使用量(比如火车上的餐厅,最多只允许多少人进入,如果满了需要在门口等,等有人出来了才能进去)-“信号量”

参考:

https://www.zhihu.com/questio...
查看原文

dejavu 收藏了文章 · 2020-06-22

我去,Java面试的思维导图,全部给你准备好了,拿走不谢

点个赞,看一看,好习惯!本文 GitHubhttps://github.com/OUYANGSIHAI/JavaInterview 已收录,这是我花了 3 个月总结的一线大厂 Java 面试总结,本人已拿大厂 offer。
另外,原创文章首发在我的个人博客:blog.ouyangsihai.cn,欢迎访问。

大家好,又跟大家见面了,非常高兴能够跟大家分享一些Java相关的知识,也是很久没有分享视频,在上一期当中我就分享了一个非科班的学生,或者说从来没有接触到Java行业的从业人员的话,怎么能够更快的进入到Java这个行业,并且通过自己的一些学习的方法,更快的找到一份自己满意的工作。

这一期,就想更详细的来介绍一下找工作当中的一些问题。

我们都知道在找工作的过程中,需要去复习很多的知识,很多的技术,很多的知识点,那么这些知识点怎么去跟面试官讲的时候更加的清晰,我这里介绍一个方法。

我在面试的过程中,在复习的过程中,就把很多的知识点都整理成了思维导图,比如说,jvm的,多线程,数据库,数据网络,这些相关的一块一块的知识的话,我都会把它分类汇总成为一个思维导图,今天我就想给大家分享一些我整理过的思维导图,也希望在以后大家的面试过程中有一定的帮助。

从上面的图可以看得到,这个的话就是我把jvm相关的一些知识点整理成了一个思维导图,每一个点都列得非常的清晰,在面试中会遇到的知识点都已经整理出来了,非常的清晰。如果你有这个思维导图的话,那么你在面试当中碰到jvm的一些相关问题,根本就不用慌。首先的话,你对于jvm相关的一些知识都是有了解的,同时你对jvm有这样一整个思维导图的梳理的话,你的思路是比较清晰的,那么面试官肯定会觉得你对jvm这一块的相关的知识掌握的不错的,这样的话你在面试当中也会增加信心,同时也会更加有底气。

另外,这些知识的话,我都写了原创文章,比较系统的讲解了,大家可以看看,会有一定得收获的。

序号原创精品
1【原创】分布式架构系列文章
2【原创】实战 Activiti 工作流教程
3【原创】深入理解Java虚拟机教程
4【原创】Java8最新教程
5【原创】MySQL的艺术世界

这个的话,是我在面试当中觉得集合这方面确实在面试当中问得非常多,你在每一次面试的过程当中,你基本上都会遇到集合相关的面试问题,比如说list,set这两个最基本集合就不用说了,基本上会问到。另外一个问的最多的可以说就是HashMap了。HashMap在面试当中问的特别多,不管哪个大厂,他基本都会问你HashMap,有一些变态的问题,还甚至会让你去实现一个HashMap,就在面试过程中让你去自己实现一个HashMap,或者你讲一下你怎么去实现一个HashMap,其实这个问题还是比较难的,如果你对于整个HashMap不了解的话还是比较难回答的。

另外一个的话,除了HashMap,当然就是ConcurrentHashMap,这个也是面试必备的一个知识点,如果你这个不会,那么你肯定会在一些面试当中被面试官怼烂的。

最后一个就是线程安全的集合了,这一块也不用多说,以上这些都是在面试中问得非常多的,一定要好好掌握,这里都给大家总结了,其他的话可能还会更加详细的进行总结。

最后一块的话,这个也是我在面试当中问的,也是遇到的被问的特别多的一个问题,就是多线程的相关的一些知识,synchronized关键字,从哪些方面去回答,volitale关键字,从哪些方面去回答,怎么展开,思路是怎样的,怎样回答才会让面试官满意呢?

还有就是JUC相关的一些知识了,比如说

  • ReentrantLock和synchronized的区别,ReentrantLock在面试中-怎样去回答面试官的问题,
  • AQS有哪些知识点是可以被问到的,CAS是怎样的一个概念?在面试中,面试官会怎么样去问CAS的问题呢?

最后的话就是大名鼎鼎的线程池,这一个知识点在面试当中也是问的特别多,我在每一次面试大厂的过程中基本上都会问到线程池相关的一些知识,比如说:

  • Java线程池是怎么样的,有什么参数

你一一的都要去解释清楚,另外的话,他不会去问Java线程池有哪些参数,有什么意义,在遇到实际问题当中怎么去运用这些参数,而是它会让你去设计一个线程池,其实这种思路,跟你去解释这些基本的参数,是一样的道理的。

这一期就先给大家介绍这么多相关的知识点,下期再见。

最后,如果大家需要这些思维导图的话,可以扫描下面的二维码,在我的公众号回复:思维导图,就可以获取思维导图的源文件了,原创不易,点个赞吧。

最后,再分享我历时三个月总结的 Java 面试 + Java 后端技术学习指南,这是本人这几年及春招的总结,已经拿到了大厂 offer,整理成了一本电子书,拿去不谢,目录如下:

现在免费分享大家,在下面我的公众号 程序员的技术圈子 回复 面试 即可获取。

查看原文

dejavu 收藏了文章 · 2020-06-22

硬不硬你说了算!35 张图解被问千百遍的 TCP 三次握手和四次挥手面试题

每日一句英语学习,每天进步一点点:


前言

不管面试 Java 、C/C++、Python 等开发岗位, TCP 的知识点可以说是的必问的了。

任 TCP 虐我千百遍,我仍待 TCP 如初恋。

遥想小林当年校招时常因 TCP 面试题被刷,真是又爱又狠....

过去不会没关系,今天就让我们来消除这份恐惧,微笑着勇敢的面对它吧!

所以小林整理了关于 TCP 三次握手和四次挥手的面试题型,跟大家一起探讨探讨。

  1. TCP 基本认识

  1. TCP 连接建立

  1. TCP 连接断开

  1. Socket 编程

PS:本次文章不涉及 TCP 流量控制、拥塞控制、可靠性传输等方面知识,这些留在下篇哈!

正文

01 TCP 基本认识

瞧瞧 TCP 头格式

我们先来看看 TCP 头的格式,标注颜色的表示与本文关联比较大的字段,其他字段不做详细阐述。

TCP 头格式

序列号:在建立连接时由计算机生成的随机数作为其初始值,通过 SYN 包传给接收端主机,每发送一次数据,就「累加」一次该「数据字节数」的大小。用来解决网络包乱序问题。

确认应答号:指下一次「期望」收到的数据的序列号,发送端收到这个确认应答以后可以认为在这个序号以前的数据都已经被正常接收。用来解决不丢包的问题。

控制位:

  • ACK:该位为 1 时,「确认应答」的字段变为有效,TCP 规定除了最初建立连接时的 SYN 包之外该位必须设置为 1
  • RST:该位为 1 时,表示 TCP 连接中出现异常必须强制断开连接。
  • SYC:该位为 1 时,表示希望建立连,并在其「序列号」的字段进行序列号初始值的设定。
  • FIN:该位为 1 时,表示今后不会再有数据发送,希望断开连接。当通信结束希望断开连接时,通信双方的主机之间就可以相互交换 FIN 位置为 1 的 TCP 段。
为什么需要 TCP 协议? TCP 工作在哪一层?

IP 层是「不可靠」的,它不保证网络包的交付、不保证网络包的按序交付、也不保证网络包中的数据的完整性。

OSI 参考模型与 TCP/IP 的关系

如果需要保障网络数据包的可靠性,那么就需要由上层(传输层)的 TCP 协议来负责。

因为 TCP 是一个工作在传输层可靠数据传输的服务,它能确保接收端接收的网络包是无损坏、无间隔、非冗余和按序的。

什么是 TCP ?

TCP 是面向连接的、可靠的、基于字节流的传输层通信协议。

  • 面向连接:一定是「一对一」才能连接,不能像 UDP 协议 可以一个主机同时向多个主机发送消息,也就是一对多是无法做到的;
  • 可靠的:无论的网络链路中出现了怎样的链路变化,TCP 都可以保证一个报文一定能够到达接收端;
  • 字节流:消息是「没有边界」的,所以无论我们消息有多大都可以进行传输。并且消息是「有序的」,当「前一个」消息没有收到的时候,即使它先收到了后面的字节已经收到,那么也不能扔给应用层去处理,同时对「重复」的报文会自动丢弃。
什么是 TCP 连接?

我们来看看 RFC 793 是如何定义「连接」的:

*Connections:
The reliability and flow control mechanisms described above require
that TCPs initialize and maintain certain status information for
each data stream. The combination of this information, including
sockets, sequence numbers, and window sizes, is called a connection.*

简单来说就是,用于保证可靠性和流量控制维护的某些状态信息,这些信息的组合,包括Socket、序列号和窗口大小称为连接。

所以我们可以知道,建立一个 TCP 连接是需要客户端与服务器端达成上述三个信息的共识。

  • Socket:由 IP 地址和端口号组成
  • 序列号:用来解决乱序问题等
  • 窗口大小:用来做流量控制
如何唯一确定一个 TCP 连接呢?

TCP 四元组可以唯一的确定一个连接,四元组包括如下:

  • 源地址
  • 源端口
  • 目的地址
  • 目的端口

TCP 四元组

源地址和目的地址的字段(32位)是在 IP 头部中,作用是通过 IP 协议发送报文给对方主机。

源端口和目的端口的字段(16位)是在 TCP 头部中,作用是告诉 TCP 协议应该把报文发给哪个进程。

有一个 IP 的服务器监听了一个端口,它的 TCP 的最大连接数是多少?

服务器通常固定在某个本地端口上监听,等待客户端的连接请求。

因此,客户端 IP 和 端口是可变的,其理论值计算公式如下:

对 IPv4,客户端的 IP 数最多为 232 次方,客户端的端口数最多为 216 次方,也就是服务端单机最大 TCP 连接数,约为 248 次方。

当然,服务端最大并发 TCP 连接数远不能达到理论上限。

  • 首先主要是文件描述符限制,Socket 都是文件,所以首先要通过 ulimit 配置文件描述符的数目;
  • 另一个是内存限制,每个 TCP 连接都要占用一定内存,操作系统是有限的。
UDP 和 TCP 有什么区别呢?分别的应用场景是?

UDP 不提供复杂的控制机制,利用 IP 提供面向「无连接」的通信服务。

UDP 协议真的非常简,头部只有 8 个字节( 64 位),UDP 的头部格式如下:

UDP 头部格式

  • 目标和源端口:主要是告诉 UDP 协议应该把报文发给哪个进程。
  • 包长度:该字段保存了 UDP 首部的长度跟数据的长度之和。
  • 校验和:校验和是为了提供可靠的 UDP 首部和数据而设计。

TCP 和 UDP 区别:

1. 连接

  • TCP 是面向连接的传输层协议,传输数据前先要建立连接。
  • UDP 是不需要连接,即刻传输数据。

2. 服务对象

  • TCP 是一对一的两点服务,即一条连接只有两个端点。
  • UDP 支持一对一、一对多、多对多的交互通信

3. 可靠性

  • TCP 是可靠交付数据的,数据可以无差错、不丢失、不重复、按需到达。
  • UDP 是尽最大努力交付,不保证可靠交付数据。

4. 拥塞控制、流量控制

  • TCP 有拥塞控制和流量控制机制,保证数据传输的安全性。
  • UDP 则没有,即使网络非常拥堵了,也不会影响 UDP 的发送速率。

5. 首部开销

  • TCP 首部长度较长,会有一定的开销,首部在没有使用「选项」字段时是 20 个字节,如果使用了「选项」字段则会变长的。
  • UDP 首部只有 8 个字节,并且是固定不变的,开销较小。

TCP 和 UDP 应用场景:

由于 TCP 是面向连接,能保证数据的可靠性交付,因此经常用于:

  • FTP 文件传输
  • HTTP / HTTPS

由于 UDP 面向无连接,它可以随时发送数据,再加上UDP本身的处理既简单又高效,因此经常用于:

  • 包总量较少的通信,如 DNSSNMP
  • 视频、音频等多媒体通信
  • 广播通信
为什么 UDP 头部没有「首部长度」字段,而 TCP 头部有「首部长度」字段呢?

原因是 TCP 有可变长的「选项」字段,而 UDP 头部长度则是不会变化的,无需多一个字段去记录 UDP 的首部长度。

为什么 UDP 头部有「包长度」字段,而 TCP 头部则没有「包长度」字段呢?

先说说 TCP 是如何计算负载数据长度:

其中 IP 总长度 和 IP 首部长度,在 IP 首部格式是已知的。TCP 首部长度,则是在 TCP 首部格式已知的,所以就可以求得 TCP 数据的长度。

大家这时就奇怪了问:“ UDP 也是基于 IP 层的呀,那 UDP 的数据长度也可以通过这个公式计算呀? 为何还要有「包长度」呢?”

这么一问,确实感觉 UDP 「包长度」是冗余的。

因为为了网络设备硬件设计和处理方便,首部长度需要是 4 字节的整数倍。

如果去掉 UDP 「包长度」字段,那 UDP 首部长度就不是 4 字节的整数倍了,所以小林觉得这可能是为了补全 UDP 首部长度是 4 字节的整数倍,才补充了「包长度」字段。

02 TCP 连接建立

TCP 三次握手过程和状态变迁

TCP 是面向连接的协议,所以使用 TCP 前必须先建立连接,而建立连接是通过三次握手而进行的。

TCP 三次握手

  • 一开始,客户端和服务端都处于 CLOSED 状态。先是服务端主动监听某个端口,处于 LISTEN 状态

第一个报文—— SYN 报文

  • 客户端会随机初始化序号(client_isn),将此序号置于 TCP 首部的「序号」字段中,同时把 SYN 标志位置为 1 ,表示 SYN 报文。接着把第一个 SYN 报文发送给服务端,表示向服务端发起连接,该报文不包含应用层数据,之后客户端处于 SYN-SENT 状态。

第二个报文 —— SYN + ACK 报文

  • 服务端收到客户端的 SYN 报文后,首先服务端也随机初始化自己的序号(server_isn),将此序号填入 TCP 首部的「序号」字段中,其次把 TCP 首部的「确认应答号」字段填入 client_isn + 1, 接着把 SYNACK 标志位置为 1。最后把该报文发给客户端,该报文也不包含应用层数据,之后服务端处于 SYN-RCVD 状态。

第三个报文 —— ACK 报文

  • 客户端收到服务端报文后,还要向服务端回应最后一个应答报文,首先该应答报文 TCP 首部 ACK 标志位置为 1 ,其次「确认应答号」字段填入 server_isn + 1 ,最后把报文发送给服务端,这次报文可以携带客户到服务器的数据,之后客户端处于 ESTABLISHED 状态。
  • 服务器收到客户端的应答报文后,也进入 ESTABLISHED 状态。

从上面的过程可以发现第三次握手是可以携带数据的,前两次握手是不可以携带数据的,这也是面试常问的题。

一旦完成三次握手,双方都处于 ESTABLISHED 状态,此致连接就已建立完成,客户端和服务端就可以相互发送数据了。

如何在 Linux 系统中查看 TCP 状态?

TCP 的连接状态查看,在 Linux 可以通过 netstat -napt 命令查看。

TCP 连接状态查看

为什么是三次握手?不是两次、四次?

相信大家比较常回答的是:“因为三次握手才能保证双方具有接收和发送的能力。”

这回答是没问题,但这回答是片面的,并没有说出主要的原因。

在前面我们知道了什么是 TCP 连接

  • 用于保证可靠性和流量控制维护的某些状态信息,这些信息的组合,包括Socket、序列号和窗口大小称为连接。

所以,重要的是为什么三次握手才可以初始化Socket、序列号和窗口大小并建立 TCP 连接。

接下来以三个方面分析三次握手的原因:

  • 三次握手才可以阻止重复历史连接的初始化(主要原因)
  • 三次握手才可以同步双方的初始序列号
  • 三次握手才可以避免资源浪费

原因一:避免历史连接

我们来看看 RFC 793 指出的 TCP 连接使用三次握手的首要原因

The principle reason for the three-way handshake is to prevent old duplicate connection initiations from causing confusion.

简单来说,三次握手的首要原因是为了防止旧的重复连接初始化造成混乱。

网络环境是错综复杂的,往往并不是如我们期望的一样,先发送的数据包,就先到达目标主机,反而它很骚,可能会由于网络拥堵等乱七八糟的原因,会使得旧的数据包,先到达目标主机,那么这种情况下 TCP 三次握手是如何避免的呢?

三次握手避免历史连接

客户端连续发送多次 SYN 建立连接的报文,在网络拥堵等情况下:

  • 一个「旧 SVN 报文」比「最新的 SYN 」 报文早到达了服务端;
  • 那么此时服务端就会回一个 SYN + ACK 报文给客户端;
  • 客户端收到后可以根据自身的上下文,判断这是一个历史连接(序列号过期或超时),那么客户端就会发送 RST 报文给服务端,表示中止这一次连接。

如果是两次握手连接,就不能判断当前连接是否是历史连接,三次握手则可以在客户端(发送方)准备发送第三次报文时,客户端因有足够的上下文来判断当前连接是否是历史连接:

  • 如果是历史连接(序列号过期或超时),则第三次握手发送的报文是 RST 报文,以此中止历史连接;
  • 如果不是历史连接,则第三次发送的报文是 ACK 报文,通信双方就会成功建立连接;

所以, TCP 使用三次握手建立连接的最主要原因是防止历史连接初始化了连接。

原因二:同步双方初始序列号

TCP 协议的通信双方, 都必须维护一个「序列号」, 序列号是可靠传输的一个关键因素,它的作用:

  • 接收方可以去除重复的数据;
  • 接收方可以根据数据包的序列号按序接收;
  • 可以标识发送出去的数据包中, 哪些是已经被对方收到的;

可见,序列号在 TCP 连接中占据着非常重要的作用,所以当客户端发送携带「初始序列号」的 SYN 报文的时候,需要服务端回一个 ACK 应答报文,表示客户端的 SVN 报文已被服务端成功接收,那当服务端发送「初始序列号」给客户端的时候,依然也要得到客户端的应答回应,这样一来一回,才能确保双方的初始序列号能被可靠的同步。

四次握手与三次握手

四次握手其实也能够可靠的同步双方的初始化序号,但由于第二步和第三步可以优化成一步,所以就成了「三次握手」。

而两次握手只保证了一方的初始序列号能被对方成功接收,没办法保证双方的初始序列号都能被确认接收。

原因三:避免资源浪费

如果只有「两次握手」,当客户端的 SYN 请求连接在网络中阻塞,客户端没有接收到 ACK 报文,就会重新发送 SYN ,由于没有第三次握手,服务器不清楚客户端是否收到了自己发送的建立连接的 ACK 确认信号,所以每收到一个 SYN 就只能先主动建立一个连接,这会造成什么情况呢?

如果客户端的 SYN 阻塞了,重复发送多次 SYN 报文,那么服务器在收到请求后就会建立多个冗余的无效链接,造成不必要的资源浪费。

两次握手会造成资源浪费

即两次握手会造成消息滞留情况下,服务器重复接受无用的连接请求 SYN 报文,而造成重复分配资源。

小结

TCP 建立连接时,通过三次握手能防止历史连接的建立,能减少双方不必要的资源开销,能帮助双方同步初始化序列号。序列号能够保证数据包不重复、不丢弃和按序传输。

不使用「两次握手」和「四次握手」的原因:

  • 「两次握手」:无法防止历史连接的建立,会造成双方资源的浪费,也无法可靠的同步双方序列号;
  • 「四次握手」:三次握手就已经理论上最少可靠连接建立,所以不需要使用更多的通信次数。
为什么客户端和服务端的初始序列号 ISN 是不相同的?

因为网络中的报文会延迟、会复制重发、也有可能丢失,这样会造成的不同连接之间产生互相影响,所以为了避免互相影响,客户端和服务端的初始序列号是随机且不同的。

初始序列号 ISN 是如何随机产生的?

起始 ISN 是基于时钟的,每 4 毫秒 + 1,转一圈要 4.55 个小时。

RFC1948 中提出了一个较好的初始化序列号 ISN 随机生成算法。

ISN = M + F (localhost, localport, remotehost, remoteport)

  • M 是一个计时器,这个计时器每隔 4 毫秒加 1。
  • F 是一个 Hash 算法,根据源 IP、目的 IP、源端口、目的端口生成一个随机数值。要保证 Hash 算法不能被外部轻易推算得出,用 MD5 算法是一个比较好的选择。
既然 IP 层会分片,为什么 TCP 层还需要 MSS 呢?

我们先来认识下 MTU 和 MSS

MTU 与 MSS

  • MTU:一个网络包的最大长度,以太网中一般为 1500 字节;
  • MSS:除去 IP 和 TCP 头部之后,一个网络包所能容纳的 TCP 数据的最大长度;

如果在 TCP 的整个报文(头部 + 数据)交给 IP 层进行分片,会有什么异常呢?

当 IP 层有一个超过 MTU 大小的数据(TCP 头部 + TCP 数据)要发送,那么 IP 层就要进行分片,把数据分片成若干片,保证每一个分片都小于 MTU。把一份 IP 数据报进行分片以后,由目标主机的 IP 层来进行重新组装后,在交给上一层 TCP 传输层。

这看起来井然有序,但这存在隐患的,那么当如果一个 IP 分片丢失,整个 IP 报文的所有分片都得重传

因为 IP 层本身没有超时重传机制,它由传输层的 TCP 来负责超时和重传。

当接收方发现 TCP 报文(头部 + 数据)的某一片丢失后,则不会响应 ACK 给对方,那么发送方的 TCP 在超时后,就会重发「整个 TCP 报文(头部 + 数据)」。

因此,可以得知由 IP 层进行分片传输,是非常没有效率的。

所以,为了达到最佳的传输效能 TCP 协议在建立连接的时候通常要协商双方的 MSS 值,当 TCP 层发现数据超过 MSS 时,则就先会进行分片,当然由它形成的 IP 包的长度也就不会大于 MTU ,自然也就不用 IP 分片了。

握手阶段协商 MSS

经过 TCP 层分片后,如果一个 TCP 分片丢失后,进行重发时也是以 MSS 为单位,而不用重传所有的分片,大大增加了重传的效率。

什么是 SYN 攻击?如何避免 SVN 攻击?

SYN 攻击

我们都知道 TCP 连接建立是需要三次握手,假设攻击者短时间伪造不同 IP 地址的 SYN 报文,服务端每接收到一个 SVN 报文,就进入SYN_RCVD 状态,但服务端发送出去的 ACK + SYN 报文,无法得到未知 IP 主机的 ACK 应答,久而久之就会占满服务端的 SYN 接收队列(未连接队列),使得服务器不能为正常用户服务。

SYN 攻击

避免 SVN 攻击方式一

其中一种解决方式是通过修改 Linux 内核参数,控制队列大小和当队列满时应做什么处理。

  • 当网卡接收数据包的速度大于内核处理的速度时,会有一个队列保存这些数据包。控制该队列的最大值如下参数:
net.core.netdev_max_backlog
  • SYN_RCVD 状态连接的最大个数:
net.ipv4.tcp_max_syn_backlog
  • 超出处理能时,对新的 SYN 直接回报 RST,丢弃连接:
net.ipv4.tcp_abort_on_overflow

避免 SVN 攻击方式二

我们先来看下Linux 内核的 SYN (未完成连接建立)队列与 Accpet (已完成连接建立)队列是如何工作的?

正常流程

正常流程:

  • 当服务端接收到客户端的 SYN 报文时,会将其加入到内核的「 SYN 队列」;
  • 接着发送 SYN + ACK 给客户端,等待客户端回应 ACK 报文;
  • 服务端接收到 ACK 报文后,从「 SVN 队列」移除放入到「 Accept 队列」;
  • 应用通过调用 accpet() socket 接口,从「 Accept 队列」取出的连接。

应用程序过慢

应用程序过慢:

  • 如果应用程序过慢时,就会导致「 Accept 队列」被占满。

受到 SVN 攻击

受到 SVN 攻击:

  • 如果不断受到 SVN 攻击,就会导致「 SYN 队列」被占满。

tcp_syncookies 的方式可以应对 SYN 攻击的方法:

net.ipv4.tcp_syncookies = 1

tcp_syncookies 应对 SYN 攻击

  • 当 「 SYN 队列」满之后,后续服务器收到 SYN 包,不进入「 SYN 队列」;
  • 计算出一个 cookie 值,再以 SYN + ACK 中的「序列号」返回客户端,
  • 服务端接收到客户端的应答报文时,服务器会检查这个 ACK 包的合法性。如果合法,直接放入到「 Accept 队列」。
  • 最后应用通过调用 accpet() socket 接口,从「 Accept 队列」取出的连接。

03 TCP 连接断开

TCP 四次挥手过程和状态变迁

天下没有不散的宴席,对于 TCP 连接也是这样, TCP 断开连接是通过四次挥手方式。

双方都可以主动断开连接,断开连接后主机中的「资源」将被释放。

客户端主动关闭连接 —— TCP 四次挥手

  • 客户端打算关闭连接,此时会发送一个 TCP 首部 FIN 标志位被置为 1 的报文,也即 FIN 报文,之后客户端进入 FIN_WAIT_1 状态。
  • 服务端收到该报文后,就向客户端发送 ACK 应答报文,接着服务端进入 CLOSED_WAIT 状态。
  • 客户端收到服务端的 ACK 应答报文后,之后进入 FIN_WAIT_2 状态。
  • 等待服务端处理完数据后,也向客户端发送 FIN 报文,之后服务端进入 LAST_ACK 状态。
  • 客户端收到服务端的 FIN 报文后,回一个 ACK 应答报文,之后进入 TIME_WAIT 状态
  • 服务器收到了 ACK 应答报文后,就进入了 CLOSE 状态,至此服务端已经完成连接的关闭。
  • 客户端在经过 2MSL 一段时间后,自动进入 CLOSE 状态,至此客户端也完成连接的关闭。

你可以看到,每个方向都需要一个 FIN 和一个 ACK,因此通常被称为四次挥手

这里一点需要注意是:主动关闭连接的,才有 TIME_WAIT 状态。

为什么挥手需要四次?

再来回顾下四次挥手双方发 FIN 包的过程,就能理解为什么需要四次了。

  • 关闭连接时,客户端向服务端发送 FIN 时,仅仅表示客户端不再发送数据了但是还能接收数据。
  • 服务器收到客户端的 FIN 报文时,先回一个 ACK 应答报文,而服务端可能还有数据需要处理和发送,等服务端不再发送数据时,才发送 FIN 报文给客户端来表示同意现在关闭连接。

从上面过程可知,服务端通常需要等待完成数据的发送和处理,所以服务端的 ACKFIN 一般都会分开发送,从而比三次握手导致多了一次。

为什么 TIME_WAIT 等待的时间是 2MSL?

MSL 是 Maximum Segment Lifetime,报文最大生存时间,它是任何报文在网络上存在的最长时间,超过这个时间报文将被丢弃。因为 TCP 报文基于是 IP 协议的,而 IP 头中有一个 TTL 字段,是 IP 数据报可以经过的最大路由数,每经过一个处理他的路由器此值就减 1,当此值为 0 则数据报将被丢弃,同时发送 ICMP 报文通知源主机。

MSL 与 TTL 的区别: MSL 的单位是时间,而 TTL 是经过路由跳数。所以 MSL 应该要大于等于 TTL 消耗为 0 的时间,以确保报文已被自然消亡。

TIME_WAIT 等待 2 倍的 MSL,比较合理的解释是: 网络中可能存在来自发送方的数据包,当这些发送方的数据包被接收方处理后又会向对方发送响应,所以一来一回需要等待 2 倍的时间

比如如果被动关闭方没有收到断开连接的最后的 ACK 报文,就会触发超时重发 Fin 报文,另一方接收到 FIN 后,会重发 ACK 给被动关闭方, 一来一去正好 2 个 MSL。

2MSL 的时间是从客户端接收到 FIN 后发送 ACK 开始计时的。如果在 TIME-WAIT 时间内,因为客户端的 ACK 没有传输到服务端,客户端又接收到了服务端重发的 FIN 报文,那么 2MSL 时间将重新计时

在 Linux 系统里 2MSL 默认是 60 秒,那么一个 MSL 也就是 30 秒。Linux 系统停留在 TIME_WAIT 的时间为固定的 60 秒

其定义在 Linux 内核代码里的名称为 TCP_TIMEWAIT_LEN:

#define TCP_TIMEWAIT_LEN (60*HZ) /* how long to wait to destroy TIME-WAIT 
                                    state, about 60 seconds  */

如果要修改 TIME_WAIT 的时间长度,只能修改 Linux 内核代码里 TCP_TIMEWAIT_LEN 的值,并重新编译 Linux 内核。

为什么需要 TIME_WAIT 状态?

主动发起关闭连接的一方,才会有 TIME-WAIT 状态。

需要 TIME-WAIT 状态,主要是两个原因:

  • 防止具有相同「四元组」的「旧」数据包被收到;
  • 保证「被动关闭连接」的一方能被正确的关闭,即保证最后的 ACK 能让被动关闭方接收,从而帮助其正常关闭;

原因一:防止旧连接的数据包

假设 TIME-WAIT 没有等待时间或时间过短,被延迟的数据包抵达后会发生什么呢?

接收到历史数据的异常

  • 如上图黄色框框服务端在关闭连接之前发送的 SEQ = 301 报文,被网络延迟了。
  • 这时有相同端口的 TCP 连接被复用后,被延迟的 SEQ = 301 抵达了客户端,那么客户端是有可能正常接收这个过期的报文,这就会产生数据错乱等严重的问题。

所以,TCP 就设计出了这么一个机制,经过 2MSL 这个时间,足以让两个方向上的数据包都被丢弃,使得原来连接的数据包在网络中都自然消失,再出现的数据包一定都是新建立连接所产生的。

原因二:保证连接正确关闭

在 RFC 793 指出 TIME-WAIT 另一个重要的作用是:

TIME-WAIT - represents waiting for enough time to pass to be sure the remote TCP received the acknowledgment of its connection termination request.

也就是说,TIME-WAIT 作用是等待足够的时间以确保最后的 ACK 能让被动关闭方接收,从而帮助其正常关闭。

假设 TIME-WAIT 没有等待时间或时间过短,断开连接会造成什么问题呢?

没有确保正常断开的异常

  • 如上图红色框框客户端四次挥手的最后一个 ACK 报文如果在网络中被丢失了,此时如果客户端 TIME-WAIT 过短或没有,则就直接进入了 CLOSE 状态了,那么服务端则会一直处在 LASE-ACK 状态。
  • 当客户端发起建立连接的 SYN 请求报文后,服务端会发送 RST 报文给客户端,连接建立的过程就会被终止。

如果 TIME-WAIT 等待足够长的情况就会遇到两种情况:

  • 服务端正常收到四次挥手的最后一个 ACK 报文,则服务端正常关闭连接。
  • 服务端没有收到四次挥手的最后一个 ACK 报文时,则会重发 FIN 关闭连接报文并等待新的 ACK 报文。

所以客户端在 TIME-WAIT 状态等待 2MSL 时间后,就可以保证双方的连接都可以正常的关闭。

TIME_WAIT 过多有什么危害?

如果服务器有处于 TIME-WAIT 状态的 TCP,则说明是由服务器方主动发起的断开请求。

过多的 TIME-WAIT 状态主要的危害有两种:

  • 第一是内存资源占用;
  • 第二是对端口资源的占用,一个 TCP 连接至少消耗一个本地端口;

第二个危害是会造成严重的后果的,要知道,端口资源也是有限的,一般可以开启的端口为 32768~61000,也可以通过如下参数设置指定

net.ipv4.ip_local_port_range

如果服务端 TIME_WAIT 状态过多,占满了所有端口资源,则会导致无法创建新连接。

如何优化 TIME_WAIT?

这里给出优化 TIME-WAIT 的几个方式,都是有利有弊:

  • 打开 net.ipv4.tcp_tw_reuse 和 net.ipv4.tcp_timestamps 选项;
  • net.ipv4.tcp_max_tw_buckets
  • 程序中使用 SO_LINGER ,应用强制使用 RST 关闭。

方式一:net.ipv4.tcp_tw_reuse 和 tcp_timestamps

如下的 Linux 内核参数开启后,则可以复用处于 TIME_WAIT 的 socket 为新的连接所用

net.ipv4.tcp_tw_reuse = 1

使用这个选项,还有一个前提,需要打开对 TCP 时间戳的支持,即

net.ipv4.tcp_timestamps=1(默认即为 1)

这个时间戳的字段是在 TCP 头部的「选项」里,用于记录 TCP 发送方的当前时间戳和从对端接收到的最新时间戳。

由于引入了时间戳,我们在前面提到的 2MSL 问题就不复存在了,因为重复的数据包会因为时间戳过期被自然丢弃。

温馨提醒:net.ipv4.tcp_tw_reuse要慎用,因为使用了它就必然要打开时间戳的支持 net.ipv4.tcp_timestamps当客户端与服务端主机时间不同步时,客户端的发送的消息会被直接拒绝掉。小林在工作中就遇到过。。。排查了非常的久

方式二:net.ipv4.tcp_max_tw_buckets

这个值默认为 18000,当系统中处于 TIME_WAIT 的连接一旦超过这个值时,系统就会将所有的 TIME_WAIT 连接状态重置。

这个方法过于暴力,而且治标不治本,带来的问题远比解决的问题多,不推荐使用。

方式三:程序中使用 SO_LINGER

我们可以通过设置 socket 选项,来设置调用 close 关闭连接行为。

struct linger so_linger;
so_linger.l_onoff = 1;
so_linger.l_linger = 0;
setsockopt(s, SOL_SOCKET, SO_LINGER, &so_linger,sizeof(so_linger));

如果l_onoff为非 0, 且l_linger值为 0,那么调用close后,会立该发送一个RST标志给对端,该 TCP 连接将跳过四次挥手,也就跳过了TIME_WAIT状态,直接关闭。

但这为跨越TIME_WAIT状态提供了一个可能,不过是一个非常危险的行为,不值得提倡。

如果已经建立了连接,但是客户端突然出现故障了怎么办?

TCP 有一个机制是保活机制。这个机制的原理是这样的:

定义一个时间段,在这个时间段内,如果没有任何连接相关的活动,TCP 保活机制会开始作用,每隔一个时间间隔,发送一个探测报文,该探测报文包含的数据非常少,如果连续几个探测报文都没有得到响应,则认为当前的 TCP 连接已经死亡,系统内核将错误信息通知给上层应用程序。

在 Linux 内核可以有对应的参数可以设置保活时间、保活探测的次数、保活探测的时间间隔,以下都为默认值:

net.ipv4.tcp_keepalive_time=7200
net.ipv4.tcp_keepalive_intvl=75  
net.ipv4.tcp_keepalive_probes=9
  • tcp_keepalive_time=7200:表示保活时间是 7200 秒(2小时),也就 2 小时内如果没有任何连接相关的活动,则会启动保活机制
  • tcp_keepalive_intvl=75:表示每次检测间隔 75 秒;
  • tcp_keepalive_probes=9:表示检测 9 次无响应,认为对方是不可达的,从而中断本次的连接。

也就是说在 Linux 系统中,最少需要经过 2 小时 11 分 15 秒才可以发现一个「死亡」连接。

这个时间是有点长的,我们也可以根据实际的需求,对以上的保活相关的参数进行设置。

如果开启了 TCP 保活,需要考虑以下几种情况:

第一种,对端程序是正常工作的。当 TCP 保活的探测报文发送给对端, 对端会正常响应,这样 TCP 保活时间会被重置,等待下一个 TCP 保活时间的到来。

第二种,对端程序崩溃并重启。当 TCP 保活的探测报文发送给对端后,对端是可以响应的,但由于没有该连接的有效信息,会产生一个 RST 报文,这样很快就会发现 TCP 连接已经被重置。

第三种,是对端程序崩溃,或对端由于其他原因导致报文不可达。当 TCP 保活的探测报文发送给对端后,石沉大海,没有响应,连续几次,达到保活探测次数后,TCP 会报告该 TCP 连接已经死亡


03 Socket 编程

针对 TCP 应该如何 Socket 编程?

基于 TCP 协议的客户端和服务器工作

  • 服务端和客户端初始化 socket,得到文件描述符;
  • 服务端调用 bind,将绑定在 IP 地址和端口;
  • 服务端调用 listen,进行监听;
  • 服务端调用 accept,等待客户端连接;
  • 客户端调用 connect,向服务器端的地址和端口发起连接请求;
  • 服务端 accept 返回用于传输的 socket 的文件描述符;
  • 客户端调用 write 写入数据;服务端调用 read 读取数据;
  • 客户端断开连接时,会调用 close,那么服务端 read 读取数据的时候,就会读取到了 EOF,待处理完数据后,服务端调用 close,表示连接关闭。

这里需要注意的是,服务端调用 accept 时,连接成功了会返回一个已完成连接的 socket,后续用来传输数据。

所以,监听的 socket 和真正用来传送数据的 socket,是「两个」 socket,一个叫作监听 socket,一个叫作已完成连接 socket

成功连接建立之后,双方开始通过 read 和 write 函数来读写数据,就像往一个文件流里面写东西一样。

listen 时候参数 backlog 的意义?

Linux内核中会维护两个队列:

  • 未完成连接队列(SYN 队列):接收到一个 SYN 建立连接请求,处于 SYN_RCVD 状态;
  • 已完成连接队列(Accpet 队列):已完成 TCP 三次握手过程,处于 ESTABLISHED 状态;

 SYN 队列 与 Accpet 队列

int listen (int socketfd, int backlog)
  • 参数一 socketfd 为 socketfd 文件描述符
  • 参数二 backlog,这参数在历史有一定的变化

在 早期 Linux 内核 backlog 是 SYN 队列大小,也就是未完成的队列大小。

在 Linux 内核 2.2 之后,backlog 变成 accept 队列,也就是已完成连接建立的队列长度,所以现在通常认为 backlog 是 accept 队列。

accept 发送在三次握手的哪一步?

我们先看看客户端连接服务端时,发送了什么?

客户端连接服务端

  • 客户端的协议栈向服务器端发送了 SYN 包,并告诉服务器端当前发送序列号 client_isn,客户端进入 SYNC_SENT 状态;
  • 服务器端的协议栈收到这个包之后,和客户端进行 ACK 应答,应答的值为 client_isn+1,表示对 SYN 包 client_isn 的确认,同时服务器也发送一个 SYN 包,告诉客户端当前我的发送序列号为 server_isn,服务器端进入 SYNC_RCVD 状态;
  • 客户端协议栈收到 ACK 之后,使得应用程序从 connect 调用返回,表示客户端到服务器端的单向连接建立成功,客户端的状态为 ESTABLISHED,同时客户端协议栈也会对服务器端的 SYN 包进行应答,应答数据为 server_isn+1;
  • 应答包到达服务器端后,服务器端协议栈使得 accept 阻塞调用返回,这个时候服务器端到客户端的单向连接也建立成功,服务器端也进入 ESTABLISHED 状态。

从上面的描述过程,我们可以得知客户端 connect 成功返回是在第二次握手,服务端 accept 成功返回是在三次握手成功之后。

客户端调用 close 了,连接是断开的流程是什么?

我们看看客户端主动调用了 close,会发生什么?

客户端调用 close 过程

  • 客户端调用 close,表明客户端没有数据需要发送了,则此时会向服务端发送 FIN 报文,进入 FIN_WAIT_1 状态;
  • 服务端接收到了 FIN 报文,TCP 协议栈会为 FIN 包插入一个文件结束符 EOF 到接收缓冲区中,应用程序可以通过 read 调用来感知这个 FIN 包。这个 EOF 会被放在已排队等候的其他已接收的数据之后,这就意味着服务端需要处理这种异常情况,因为 EOF 表示在该连接上再无额外数据到达。此时,服务端进入 CLOSE_WAIT 状态;
  • 接着,当处理完数据后,自然就会读到 EOF,于是也调用 close 关闭它的套接字,这会使得会发出一个 FIN 包,之后处于 LAST_ACK 状态;
  • 客户端接收到服务端的 FIN 包,并发送 ACK 确认包给服务端,此时客户端将进入 TIME_WAIT 状态;
  • 服务端收到 ACK 确认包后,就进入了最后的 CLOSE 状态;
  • 客户端进过 2MSL 时间之后,也进入 CLOSED 状态;

巨人的肩膀

[1] 趣谈网络协议专栏.刘超.极客时间.

[2] 网络编程实战专栏.盛延敏.极客时间.

[3] 计算机网络-自顶向下方法.陈鸣 译.机械工业出版社

[4] TCP/IP详解 卷1:协议.范建华 译.机械工业出版社

[5] 图解TCP/IP.竹下隆史.人民邮电出版社

[6] https://www.rfc-editor.org/rf...

[7] https://draveness.me/whys-the...

[9] https://draveness.me/whys-the...


往期好文

听说你 ping 用的很 6 ?给我图解一下 ping 的工作原理!

探究!一个数据包在网络中的心路历程

硬核!30 张图解 HTTP 常见的面试题


唠叨唠叨

小林为写此文重学了一篇 TCP,深感 TCP 真的是一个非常复杂的协议,要想轻易拿下,也不是一天两天的事,所以小林花费了一个星期多才写完此文章。

正所谓知道的越多,不知道的也越多。

下篇给大家带来 TCP 滑动窗口、流量控制、拥塞控制的图解文章!

本文只是抛砖引玉,若你有更好的想法或文章有误的地方,欢迎留言讨论!

小林是专为大家图解的工具人,Goodbye,我们下次见!

查看原文

dejavu 收藏了文章 · 2020-06-12

系统的讲解 - PHP WEB 安全防御

常见漏洞

clipboard.png

看到上图的漏洞是不是特别熟悉,如果不进行及时防御,就会产生蝴蝶效应。

往下看,可能会找到你要的答案。

SQL注入攻击

定义

SQL注入攻击是通过WEB表单提交,在URL参数提交或Cookie参数提交,将怀有恶意的“字符串”,提交给后台数据库,欺骗服务器执行恶意的SQL语句。

案例

//以用户登录为例,当验证用户名和密码是否正确时
$sql = "SELECT * FROM user WHERE 
        username = '".$_GET['username']."' AND 
        password = '".$_GET['password']."'";

用户恶意输入:

$_GET['username'] = "' or 1=1 -- '";
$_GET['password'] = "123456";

注入后的Sql语句:

$sql = "SELECT * FROM user WHERE username = '' 
        or 1=1 -- ''AND password = '123456'";

执行注入后的Sql语句,可以返回 user 表的全部数据。

平时我们可以进行自测,比如使用单引号、双引号,如果是数字进行+1或-1。

SQL注入的危害很大,利用SQL注入可以进行,拖库删库删表UDF提权读取文件...

推荐一个开源的自动化的SQL注入工具。

SQLmaphttp://sqlmap.org/

  • 支持各种数据库管理系统(MySql、Oracle、SQL Server、SQLite ... )。
  • 支持自动识别密码哈希格式并通过字典破解密码哈希。
  • 支持枚举用户、密码、哈希、权限、角色、数据库、数据表和列。
  • 支持完全地下载某个数据库中的某个表、某个列。
  • 支持在数据库管理系统中搜索指定的数据库名、表名或列名。
  • 支持下载或上传文件。
  • 支持执行任意命令并回现标准输出。
  • 支持布尔型盲注、时间型盲注、基于错误信息的注入、联合查询注入和堆查询注入。

尝试着利用工具,注入自己的项目,发现问题,然后解决问题。

SQL注入的危害,远比我们想象的要大!

防御

推荐解决方案是使用 PDOMySQLi 的数据库扩展。

PHP官方文档中介绍,MySQL扩展自PHP 5.5.0起已废弃,并在自PHP7.0.0开始被移除。

如果已经在用MySQL扩展了,可以对传入的每个参数做验证,并使用框架的ORM进行查询。

另外:addslashes 和 mysql_real_escape_string 这种转义是不安全的!

XSS攻击

定义

XSS攻击是一种经常出现在WEB应用中的计算机安全漏洞,通过WEB表单提交或在URL参数提交将代码植入在用户的使用页面上。

分类

存储型

注入的恶意代码存储在服务器上(常用于留言板、论坛帖子、CRM),受害者请求服务器获取信息的时候,这些恶意代码就被浏览器成功执行。

反射型

注入的恶意代码没有存储在服务器上,通过引诱用户点击一个链接到目标网站进行实施攻击。

DOM型

注入的恶意代码并未显式的包含在web服务器的响应页面中,但会被页面中的js脚本以变量的形式来访问到的方式来进行实施攻击。

案例

存储型:论坛帖子界面input输入框中,输入 /><script>alert("xss")</script> 进行提交。

反射型:在浏览器输入框中,输入 /xxx.php?name=<script>alert(/xss/)</script>

//DOM型,代码举例
<script>
var temp = document.URL;
var temp_new = temp+'test'
document.write(decodeURI(temp_new));
</script>

XSS的危害有很多,包括盗号,挂马,控制受害者机器想其他网站发起攻击 ...

自测的方法,看见输入框就输入:/><script>alert("xss")</script> 进行提交。

推荐一个专门针对浏览器攻击的框架。

BeEFhttps://beefproject.com/

防御

简单的防御可以对style、script、image、src、a等等不安全的因素进行过滤或转义。

可以自己封装一个方法,也可以使用框架的自带方法,比如 xss_clean 。

可以利用一些模板引擎避免XSS攻击,比如Laravel框架使用的Blade,还有twig,Smarty等。

可以利用HTTP-only,将cookie设置成HTTP-only防止XSS攻击。

//设置Cookie
setcookie($name, $value, $expire, $path, $domain, $secure, $httponly); 

//然后服务端通过使用 $_COOKIE 进行验证。

可以使用Content Security Policy,它的实质就是白名单制度。

入门教程请参考:http://www.ruanyifeng.com/blo...

SSRF攻击

定义

SSRF(Server-Side Request Forgery:服务器端请求伪造) 是攻击者伪造服务器端发起的请求,虽然攻击者无法从外网访问内网的系统,但是它通过注入恶意代码从服务端发起,通过服务端就再访问内网的系统,然后获取不该获取的数据。

clipboard.png

案例

漏洞主要产生在包含这些方法的代码中,比如 curl、file_get_contents、fsockopen。

//代码片段
<?php
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $_GET['url']);
curl_setopt($ch, CURLOPT_HEADER, false);
curl_exec($ch);
curl_close($ch);

请求地址:http://www.xxx.com/demo.php?u...

将url参数修改成,file:///etc/passwd,可以获取敏感文件的信息。

将url参数修改成,dict://127.0.0.1:3306/info,可以获取目标主机3306端口运行的应用。

除了 file协议,dict协议,还可以利用gopher协议 和 http/https 协议进行攻击。

可以攻击redis,memcache,内网应用,还可以查看一下敏感文件的信息 等等。

防御

对 curl、file_get_contents、fsockopen 这些方法中的参数进行严格验证!

限制协议只能为HTTP或HTTPS,禁止进行跳转。

如果有白名单,解析参数中的URL,判断是否在白名单内。

如果没有白名单,解析参数中的URL,判断是否为内网IP。

CSRF攻击

定义

CSRF(Cross-site request forgery:跨站请求伪造)是攻击者通过伪装成受信任的用户,盗用受信任用户的身份,用受信任用户的身份发送恶意请求。

clipboard.png

从上图看出,完成一次CSRF攻击,需要完成两个步骤:

1.登录受信任网站A,本地生成网站A的Cookie。

2.未退出网站A的情况下,访问危险网站B。

危害

具体危害要看受信任网站是做什么的,如果是社交网站可以操控发广告,如果是电商网站可以操控购物,如果是银行网站甚至还可以操控转账,......

这样危害会造成个人信息的泄露和财产的损失。

防御

Cookie Hashing,表单提交或Ajax提交,必须使用csrf token。

对于不确定是否有csrf风险的请求,可以使用验证码(尽管体验会变差)。

对于一些重要的操作(修改密码、修改邮箱),必须使用二次验证。

文件上传漏洞

定义

文件上传漏洞是攻击者上传了一个可执行的文件到服务器上执行。

可执行文件包括有病毒、木马、恶意脚本等。

危害

文件上传漏洞与SQL注入或XSS相比,其风险更大,如果存在上传漏洞攻击者甚至可以直接上传一个webshell脚本到服务器上。

防御

  • 文件扩展名检测
  • 文件 MIME 验证
  • 文件重命名
  • 文件目录设置不可执行权限
  • 设置单独域名的文件服务器

信息泄露

定义

信息泄露主要指用户的手机号、邮箱、密码、身份证、地址等敏感数据泄露,还有服务器上的文件和环境变量等敏感数据泄露,还包括将直接将企业源码上传到开发平台。

案例

比如开发接口时,接口返回明文的手机号。

比如调试代码时,代码中提交了一些调试信息,未进行删除。

......

防御

  • 敏感数据脱敏(比如手机号、身份证、邮箱、地址)。
  • 服务器上不允许提交包含打印 phpinfo 、$_SERVER 和 调试信息等代码。
  • 定期从开发平台扫描关于企业相关的源码项目。

越权

定义

“超出了你自己所拥有的权限,干了你本来不可能干的事情。”

水平越权:用户A未授权可以访问用户B的数据。

垂直越权:未登录用户可以访问需要授权的应用。

举例,本来用户A只能查看自己的订单数据,但是他通过修改URL参数就能查看到用户B的订单数据。

未登录的用户,可以访问到后台模块并进行操作。

防御

对于所有涉及到用户数据的操作,必须严格判断当前用户的身份。

对于所有需要权限控制的位置,必须严格检验用户权限级别。

设计缺陷

返回信息过多

举例,登录时进行验证,当用户不存在时,返回“用户不存在”,当用户被禁用时,返回“用户已被禁用”。

避免攻击者进行恶意尝试,不应该返回过多的信息,可以统一返回“用户名或密码错误”。

短信接口被恶意攻击

举例,注册或登录时用户输入手机号码就可直接触发短信接口,这块最容易被攻击者进行短信轰炸。

应该进行增加接口调用限制:

  • 设置同一手机号短信发送间隔
  • 设置每个IP地址每日最大发送量
  • 设置每个手机号每日最大发送量
  • 升级验证码,采用滑动拼图、文字点选、图表点选...
  • 升级短信接口的验证方法

小结

文章主要讲解了 SQL注入攻击、XSS攻击、SSRF攻击、CSRF攻击、文件上传漏洞、信息泄露、越权、设计缺陷等八大方面,通过这次的梳理,也使我自己对PHP WEB安全防御有了一个全面了解。

同时还要时刻保持警惕,所有用户输入的数据都是不值得信任的,接收到的数据必须经过校验和过滤。

也不要轻易相信从网络上发现的代码,在使用第三方类库或代码时抱有怀疑的态度,要多进行调试和验证,看起来没问题的代码可能隐藏了很多的安全隐患。

最后,推荐一款开源的漏洞演示平台,包含了100多个漏洞。

这个是安装在本地的,大家尝试注入恶意代码,同时也警醒自己不要犯这样的错误。

bWAPPhttp://www.itsecgames.com/

推荐阅读

本文欢迎转发,转发请注明作者和出处,谢谢!

clipboard.png

查看原文

dejavu 收藏了文章 · 2020-06-12

系统的讲解 - SSO单点登录

概念

SSO 英文全称 Single Sign On,单点登录。

在多个应用系统中,只需要登录一次,就可以访问其他相互信任的应用系统。

比如:淘宝网(www.taobao.com),天猫网(www.tmall.com),聚划算(ju.taobao.com),飞猪网(www.fliggy.com)等,这些都是阿里巴巴集团的网站。在这些网站中,我们在其中一个网站登录了,再访问其他的网站时,就无需再进行登录,这就是 SSO 的主要用途。

好处

用户角度

用户能够做到一次登录多次使用,无需记录多套用户名和密码,省心。

系统管理员角度

管理员只需维护好一个统一的账号中心就可以了,方便。

新系统开发角度

新系统开发时只需直接对接统一的账号中心即可,简化开发流程,省时。

技术实现

流程图

clipboard.png

流程介绍

如果没这个介绍,看上图肯定是懵懵的。

系统A和系统B都是前后端分离的,比如前端框架用的 React / Vue / Angular,都是通过 NPM 编译后独立部署的,前后端完全通过HTTP接口的方式进行交互,也有可能前后端项目的域名都不一样。

SSO认证中心不是前后端分离的,就是前端代码和后端代码部署在一个项目中。

为什么用这两种情况呢?

其实就是为了,在流程图上出现这两种情况,这样的清楚了,后期改成任何一种就都清楚了。

试想一下:

三个系统都是前后端分离的情况,流程图应该怎么调整?

三个系统都不是前后端分离的情况,流程图应该怎么调整?

对外接口

系统A和系统B:用户退出接口。

SSO 认证中心:用户退出接口和token验证接口。

登录

如上述流程图一致。

系统A和系统B:使用token认证登录。

SSO 认证中心:使用会话认证登录。

前后端分离项目,登录使用token进行解决,前端每次请求接口时都必须传递token参数。

退出

clipboard.png

上图,表示的是从某一个系统退出的流程图。

退出,还可以从SSO认证中心退出,然后调取各个系统的用户退出接口。

当用户再进行操作的时候,就会跳转到SSO的登录界面。

Token 生成方式

创建全局会话可以使用session,将session存储到redis中。

令牌的生成可以使用JWT。

PHP JWT参考地址:https://github.com/lcobucci/jwt

当然还可以自定义token的生成方式。

小结

讲解了什么是SSO,以及SSO的用途与好处,同时根据流程图一步步进行梳理,基本上就可以实现了。

期间遇到任何问题,都可以关注公众号和我进行交流。

扩展

SSO与OAuth的区别

谈到SSO很多人就想到OAuth,也有谈到OAuth想到SSO的,在这里我简单的说一下区别。

通俗的解释,SSO是处理一个公司内的不同应用系统之间的登录问题,比如阿里巴巴旗下有很多应用系统,我们只需要登录一个系统就可以实现不同系统之间的跳转。

OAuth是不同公司遵循的一种授权方案,也是一种授权协议,通常都是由大公司提供,比如腾讯,微博。我们常用的QQ登录,微博登录等,使用OAuth的好处是可以使用其他第三方账号进行登录系统,减少了因用户懒,不愿注册而导致用户流失的风险。

现在一些支付业务也用OAuth,比如微信支付,支付宝支付。

还有一些开放平台也用OAuth,比如百度开放平台,腾讯开放平台。

SSO与RBAC的关系

如果企业有多个管理系统,现由原来的每个系统都有一个登录,调整为统一登录认证。

那么每个管理系统都有权限控制,吸取统一登录认证的经验,我们也可以做一套统一的RBAC权限认证。

推荐阅读

一起学习

查看原文

dejavu 收藏了文章 · 2020-06-12

系统的讲解 - PHP 接口签名验证

概览

工作中,我们时刻都会和接口打交道,有的是调取他人的接口,有的是为他人提供接口,在这过程中肯定都离不开签名验证。

在设计签名验证的时候,一定要满足以下几点:

  • 可变性:每次的签名必须是不一样的。
  • 时效性:每次请求的时效性,过期作废。
  • 唯一性:每次的签名是唯一的。
  • 完整性:能够对传入数据进行验证,防止篡改。

下面主要分享一些工作中常用的加解密的方法。

常用验证

举例:/api/login?username=xxx&password=xxx&sign=xxx

发送方和接收方约定一个加密的盐值,进行生成签名。

示例代码:

//创建签名
private function _createSign()
{
    $strSalt = '1scv6zfzSR1wLaWN';
    $strVal  = '';
    if ($this->params) {
        $params = $this->params;
        ksort($params);
        $strVal = http_build_query($params, '', '&', PHP_QUERY_RFC3986);
    }
    return md5(md5($strSalt).md5($strVal));
}

//验证签名
if ($_GET['sign'] != $this->_createSign()) {
    echo 'Invalid Sign.';
}

上面使用到了 MD5 方法,MD5 属于单向散列加密。

单向散列加密

定义

把任意长的输入串变化成固定长的输出串,并且由输出串难以得到输入串,这种方法称为单项散列加密。

常用算法

  • MD5
  • SHA
  • MAC
  • CRC

优点

以 MD5 为例。

  • 方便存储:加密后都是固定大小(32位)的字符串,能够分配固定大小的空间存储。
  • 损耗低:加密/加密对于性能的损耗微乎其微。
  • 文件加密:只需要32位字符串就能对一个巨大的文件验证其完整性。
  • 不可逆:大多数的情况下不可逆,具有良好的安全性。

缺点

  • 存在暴力破解的可能性,最好通过加盐值的方式提高安全性。

应用场景

  • 用于敏感数据,比如用户密码,请求参数,文件加密等。

推荐密码的存储方式

password_hash() 使用足够强度的单向散列算法创建密码的哈希(hash)。

示例代码:

//密码加密
$password = '123456';
$strPwdHash = password_hash($password, PASSWORD_DEFAULT);

//密码验证
if (password_verify($password, $strPwdHash)) {
  //Success
} else {
  //Fail
}

PHP 手册地址:

http://php.net/manual/zh/func...

对称加密

定义

同一个密钥可以同时用作数据的加密和解密,这种方法称为对称加密。

常用算法

  • DES
  • AES

AES 是 DES 的升级版,密钥长度更长,选择更多,也更灵活,安全性更高,速度更快。

优点

算法公开、计算量小、加密速度快、加密效率高。

缺点

发送方和接收方必须商定好密钥,然后使双方都能保存好密钥,密钥管理成为双方的负担。

应用场景

相对大一点的数据量或关键数据的加密。

AES

AES 加密类库在网上很容易找得到,请注意类库中的 mcrypt_encryptmcrypt_decrypt 方法!

clipboard.png

clipboard.png

在 PHP7.2 版本中已经被弃用了,在新版本中使用 openssl_encryptopenssl_decrypt 两个方法。

示例代码(类库):

class Aes
{
    /**
     * var string $method 加解密方法
     */
    protected $method;

    /**
     * var string $secret_key 加解密的密钥
     */
    protected $secret_key;

    /**
     * var string $iv 加解密的向量
     */
    protected $iv;

    /**
     * var int $options
     */
    protected $options;

    /**
     * 构造函数
     * @param string $key     密钥
     * @param string $method  加密方式
     * @param string $iv      向量
     * @param int    $options
     */
    public function __construct($key = '', $method = 'AES-128-CBC', $iv = '', $options = OPENSSL_RAW_DATA)
    {
        $this->secret_key = isset($key) ? $key : 'CWq3g0hgl7Ao2OKI';
        $this->method = in_array($method, openssl_get_cipher_methods()) ? $method : 'AES-128-CBC';
        $this->iv = $iv;
        $this->options = in_array($options, [OPENSSL_RAW_DATA, OPENSSL_ZERO_PADDING]) ? $options : OPENSSL_RAW_DATA;
    }

    /**
     * 加密
     * @param string $data 加密的数据
     * @return string
     */
    public function encrypt($data = '')
    {
        return base64_encode(openssl_encrypt($data, $this->method, $this->secret_key, $this->options, $this->iv));
    }

    /**
     * 解密
     * @param string $data 解密的数据
     * @return string
     */
    public function decrypt($data = '')
    {
        return openssl_decrypt(base64_decode($data), $this->method, $this->secret_key, $this->options, $this->iv);
    }
}

示例代码:

$aes = new Aes('HFu8Z5SjAT7CudQc');
$encrypted = $aes->encrypt('锄禾日当午');
echo '加密前:锄禾日当午<br>加密后:', $encrypted, '<hr>';

$decrypted = $aes->decrypt($encrypted);
echo '加密后:', $encrypted, '<br>解密后:', $decrypted;

运行结果:

clipboard.png

非对称加密

定义

需要两个密钥来进行加密和解密,这两个秘钥分别是公钥(public key)和私钥(private key),这种方法称为非对称加密。

常用算法

  • RSA

优点

与对称加密相比,安全性更好,加解密需要不同的密钥,公钥和私钥都可进行相互的加解密。

缺点

加密和解密花费时间长、速度慢,只适合对少量数据进行加密。

应用场景

适合于对安全性要求很高的场景,适合加密少量数据,比如支付数据、登录数据等。

RSA 与 RSA2

算法名称标准名称备注
RSA2SHA256WithRSA强制要求RSA密钥的长度至少为2048
RSASHA1WithRSA对RSA密钥的长度不限制,推荐使用2048位以上

RSA2 比 RSA 有更强的安全能力。

蚂蚁金服,新浪微博 都在使用 RSA2 算法。

创建公钥和私钥:

openssl genrsa -out private_key.pem 2048
openssl rsa -in private_key.pem -pubout -out public_key.pem

执行上面命令,会生成 private_key.pempublic_key.pem 两个文件。

示例代码(类库):

class Rsa2 
{
    private static $PRIVATE_KEY = 'private_key.pem 内容';
    private static $PUBLIC_KEY  = 'public_key.pem 内容';

    /**
     * 获取私钥
     * @return bool|resource
     */
    private static function getPrivateKey()
    {
        $privateKey = self::$PRIVATE_KEY;
        return openssl_pkey_get_private($privateKey);
    }

    /**
     * 获取公钥
     * @return bool|resource
     */
    private static function getPublicKey()
    {
        $publicKey = self::$PUBLIC_KEY;
        return openssl_pkey_get_public($publicKey);
    }

    /**
     * 私钥加密
     * @param string $data
     * @return null|string
     */
    public static function privateEncrypt($data = '')
    {
        if (!is_string($data)) {
            return null;
        }
        return openssl_private_encrypt($data,$encrypted,self::getPrivateKey()) ? base64_encode($encrypted) : null;
    }

    /**
     * 公钥加密
     * @param string $data
     * @return null|string
     */
    public static function publicEncrypt($data = '')
    {
        if (!is_string($data)) {
            return null;
        }
        return openssl_public_encrypt($data,$encrypted,self::getPublicKey()) ? base64_encode($encrypted) : null;
    }

    /**
     * 私钥解密
     * @param string $encrypted
     * @return null
     */
    public static function privateDecrypt($encrypted = '')
    {
        if (!is_string($encrypted)) {
            return null;
        }
        return (openssl_private_decrypt(base64_decode($encrypted), $decrypted, self::getPrivateKey())) ? $decrypted : null;
    }

    /**
     * 公钥解密
     * @param string $encrypted
     * @return null
     */
    public static function publicDecrypt($encrypted = '')
    {
        if (!is_string($encrypted)) {
            return null;
        }
        return (openssl_public_decrypt(base64_decode($encrypted), $decrypted, self::getPublicKey())) ? $decrypted : null;
    }

    /**
     * 创建签名
     * @param string $data 数据
     * @return null|string
     */
    public function createSign($data = '')
    {
        if (!is_string($data)) {
            return null;
        }
        return openssl_sign($data, $sign, self::getPrivateKey(), OPENSSL_ALGO_SHA256) ? base64_encode($sign) : null;
    }

    /**
     * 验证签名
     * @param string $data 数据
     * @param string $sign 签名
     * @return bool
     */
    public function verifySign($data = '', $sign = '')
    {
        if (!is_string($sign) || !is_string($sign)) {
            return false;
        }
        return (bool)openssl_verify($data, base64_decode($sign), self::getPublicKey(), OPENSSL_ALGO_SHA256);
    }
}

示例代码:

$rsa2 = new Rsa2();
        
$privateEncrypt = $rsa2->privateEncrypt('锄禾日当午');
echo '私钥加密后:'.$privateEncrypt.'<br>';

$publicDecrypt = $rsa2->publicDecrypt($privateEncrypt);
echo '公钥解密后:'.$publicDecrypt.'<br>';

$publicEncrypt = $rsa2->publicEncrypt('锄禾日当午');
echo '公钥加密后:'.$publicEncrypt.'<br>';

$privateDecrypt = $rsa2->privateDecrypt($publicEncrypt);
echo '私钥解密后:'.$privateDecrypt.'<br>';

$sign = $rsa2->createSign('锄禾日当午');
echo '生成签名:'.$privateEncrypt.'<br>';

$status = $rsa2->verifySign('锄禾日当午', $sign);
echo '验证签名:'.($status ? '成功' : '失败') ;

运行结果:

部分数据截图如下:

clipboard.png

JS-RSA

JSEncrypt :用于执行OpenSSL RSA加密、解密和密钥生成的Javascript库。

Git源:https://github.com/travist/js...

应用场景:

我们在做 WEB 的登录功能时一般是通过 Form 提交或 Ajax 方式提交到服务器进行验证的。

为了防止抓包,登录密码肯定要先进行一次加密(RSA),再提交到服务器进行验证。

一些大公司都在使用,比如淘宝、京东、新浪 等。

示例代码就不提供了,Git上提供的代码是非常完善的。

密钥安全管理

这些加密技术,能够达到安全加密效果的前提是 密钥的保密性

实际工作中,不同环境的密钥都应该不同(开发环境、预发布环境、正式环境)。

那么,应该如何安全保存密钥呢?

环境变量

将密钥设置到环境变量中,每次从环境变量中加载。

配置中心

将密钥存放到配置中心,统一进行管理。

密钥过期策略

设置密钥有效期,比如一个月进行重置一次。

在这里希望大佬提供新的思路 ~

接口调试工具

Postman

一款功能强大的网页调试与发送网页 HTTP 请求的 Chrome插件。

这个不用多介绍,大家肯定都使用过。

SocketLog

Git源:https://github.com/luofei614/...

解决的痛点:

  • 正在运行的API有Bug,不能在文件中使用var_dump进行调试,因为会影响到client的调用。将日志写到文件中,查看也不是很方便。
  • 我们在二次开发一个新系统的时候,想查看执行了哪些Sql语句及程序的warning,notice等错误信息。

SocketLog,可以解决以上问题,它通过WebSocket将调试日志输出到浏览器的console中。

使用方法

  1. 安装、配置Chrome插件
  2. SocketLog服务端安装
  3. PHP中用SocketLog调试
  4. 配置日志类型和相关参数

在线接口文档

接口开发完毕,需要给请求方提供接口文档,文档的编写现在大部分都使用Markdown格式。

也有一些开源的系统,可以下载并安装到自己的服务器上。

也有一些在线的系统,可以在线使用同时也支持离线导出。

根据自己的情况,选择适合自己的文档平台吧。

常用的接口文档平台:

  • eolinker
  • Apizza
  • Yapi
  • RAP2
  • DOClever

扩展

一、在 HTTP 和 RPC 的选择上,可能会有一些疑问,RPC框架配置比较复杂,明明用HTTP能实现为什么要选择RPC?

下面简单的介绍下 HTTP 与 RPC 的区别。

传输协议:

  • HTTP 基于 HTTP 协议。
  • RPC 即可以 HTTP 协议,也可以 TCP 协议。

HTTP 也是 RPC 实现的一种方式。

性能消耗:

  • HTTP 大部分基于 JSON 实现的,序列化需要时间和性能。
  • RPC 可以基于二进制进行传输,消耗性能少一点。

推荐一个像 JSON ,但比 JSON 传输更快占用更少的新型序列化类库 MessagePack

官网地址:https://msgpack.org/

还有一些服务治理、负载均衡配置的区别。

使用场景:

比如浏览器接口、APP接口、第三方接口,推荐使用 HTTP。

比如集团内部的服务调用,推荐使用 RPC。

RPC 比 HTTP 性能消耗低,传输效率高,服务治理也方便。

推荐使用的 RPC 框架:Thrift

二、动态令牌

简单介绍下几种动态令牌,感兴趣的可以深入了解下。

OTP:One-Time Password 一次性密码。

HOTP:HMAC-based One-Time Password 基于HMAC算法加密的一次性密码。

TOTP:Time-based One-Time Password 基于时间戳算法的一次性密码。

使用场景:

  • 公司VPN登录双因素验证
  • 服务器登录动态密码验证
  • 网银、网络游戏的实体动态口令牌
  • 银行转账动态密码
  • ...

小结

本文讲了设计签名验证需要满足的一些条件:可变性、时效性、唯一性、完整性。

还讲了一些加密方法:单向散列加密、对称加密、非对称加密,同时分析了各种加密方法的优缺点,大家可以根据自己的业务特点进行自由选择

提供了 Aes、Rsa 相关代码示例。

分享了可以编写接口文档的在线系统。

分享了开发过程中使用的接口调试工具。

扩展中分析了 HTTP 和 RPC 的区别,动态令牌的介绍等。

还提出了一个问题,关于如何安全的进行密钥管理? , 欢迎各位 前辈/大佬,提供新的思路 ~

推荐阅读

一起学习

查看原文

dejavu 收藏了文章 · 2020-06-12

系统的讲解 - PHP 缓存技术

概述

缓存已经成了项目中是必不可少的一部分,它是提高性能最好的方式,例如减少网络I/O、减少磁盘I/O 等,使项目加载速度变的更快。

缓存可以是CPU缓存、内存缓存、硬盘缓存,不同的缓存查询速度也不一样(CPU缓存 > 内存缓存 > 硬盘缓存)。

接下来,给大家逐一进行介绍。

浏览器缓存

浏览器将请求过的页面存储在客户端缓存中,当访问者再次访问这个页面时,浏览器就可以直接从客户端缓存中读取数据,减少了对服务器的访问,加快了网页的加载速度。

强缓存

用户发送的请求,直接从客户端缓存中获取,不请求服务器。

根据 Expires 和 Cache-Control 判断是否命中强缓存。

代码如下:

header('Expires: '. gmdate('D, d M Y H:i:s', time() + 3600). ' GMT');
header("Cache-Control: max-age=3600"); //有效期3600秒

Cache-Control 还可以设置以下参数:

  • public:可以被所有的用户缓存(终端用户的浏览器/CDN服务器)
  • private:只能被终端用户的浏览器缓存
  • no-cache:不使用本地缓存
  • no-store:禁止缓存数据

协商缓存

用户发送的请求,发送给服务器,由服务器判定是否使用客户端缓存。

代码如下:

$last_modify = strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']);
if (time() - $last_modify < 3600) {
    header('Last-Modified: '. gmdate('D, d M Y H:i:s', $last_modify).' GMT');
    header('HTTP/1.1 304'); //Not Modified
    exit;
}
header('Last-Modified: '. gmdate('D, d M Y H:i:s').' GMT');

用户操作行为对缓存的影响

操作行为ExpiresLast-Modified
地址栏回车有效有效
页面跳转有效有效
新开窗口有效有效
前进/后退有效有效
F5刷新无效有效
Ctrl+F5刷新无效无效

文件缓存

数据文件缓存

将更新频率低,读取频率高的数据,缓存成文件。

比如,项目中多个地方用到城市数据做三级联动,我们就可以将城市数据缓存成一个文件(city_data.json),JS 可以直接读取这个文件,无需请求后端服务器。

全站静态化

CMS(内容管理系统),也许大家都比较熟悉,比如早期的 DEDE、PHPCMS,后台都可以设置静态化HTML,用户在访问网站的时候读取的都是静态HTML,不用请求后端的数据库,也不用Ajax请求数据接口,加快了网站的加载速度。

静态化HTML有以下优点:

  • 有利于搜索引擎的收录(SEO)
  • 页面打开速度快
  • 减少服务器负担

CDN缓存

CDN(Content Delivery Network)内容分发网络。

用户访问网站时,自动选择就近的CDN节点内容,不需要请求源服务器,加快了网站的打开速度。

缓存主要包括 HTML、图片、CSS、JS、XML 等静态资源。

NoSQL缓存

Memcached 缓存

Memcached 是高性能的分布式内存缓存服务器。

一般的使用目的是,通过缓存数据库查询结果,减少数据库访问次数,以提高动态Web应用的速度、提高可扩展性。

它也能够用来存储各种格式的数据,包括图像、视频、文件等。

Memcached 仅支持K/V类型的数据,不支持持久化存储。

Memcache 与 Memcached 的区别

  • Memcached 从0.2.0开始,要求PHP版本>=5.2.0,Memcache 要求PHP版本>=4.3。
  • Memcached 最后发布时间为2018-12-24,Memcache 最后发布时间2013-04-07。
  • Memcached 基于libmemcached,Memcache 基于PECL扩展。

可以将 Memcached 看作是 Memcache 的升级版。

PHP Memcached 使用手册:

http://www.php.net/manual/zh/...

Memcached 经常拿来与 Redis 做对比,接下来介绍下 Redis 缓存。

Redis缓存

Redis 是一个高性能的 K/V 数据库。

Redis 很大程度补偿了 Memcached K/V存储的不足,比如 List(链表)、Set(集合)、Zset(有序集合)、Hash(散列),既可以将数据存储在内存中,也可以将数据持久化到磁盘上,支持主从同步。

总的来说,可以将 Redis 看作是 Memcached 的扩展版,更加重量级,功能更强大。

Redis 在日常工作中使用的居多。

Redis 学习网址:http://www.redis.cn/

MongoDB缓存

MongoDB 是一个基于分布式文件存储的数据库。由 C++ 语言编写。

旨在为 WEB 应用提供可扩展的高性能数据存储解决方案。

MongoDB 是一个介于关系数据库和非关系数据库之间的产品,是非关系数据库当中功能最丰富,最像关系数据库的。

MongoDB 学习网址:http://www.mongodb.org.cn

WEB服务器缓存

Apache缓存

利用 mod_expires ,指定缓存的过期时间,可以缓存HTML、图片、JS、CSS 等。

打开 http.conf,开启模块:

LoadModule expires_module modules/mod_expires.so

指定缓存的过期时间:

<IfModule expires_module>
     #打开缓存
     ExpiresActive on 

     #css缓存(8640000秒=10天)
     ExpiresByType text/css A8640000

     #js缓存
     ExpiresByType application/x-javascript A8640000
     ExpiresByType application/javascript A8640000

     #html缓存
     ExpiresByType text/html A8640000

     #图片缓存
     ExpiresByType image/jpeg A8640000
     ExpiresByType image/gif A8640000
     ExpiresByType image/png A8640000
     ExpiresByType image/x-icon A8640000

 </IfModule>

Nginx缓存

利用 expire 参数,指定缓存的过期时间,可以缓存HTML、图片、JS、CSS 等。

打开 nginx.conf

//以图片为例:
location ~\.(gif|jpg|jepg|png|bmp|ico)$ { #加入新的location
    root html;
    expires 1d; #指定缓存时间
}

大家也可以了解下:proxy_cache_path 和 proxy_cache,进行缓存的设置。

Opcode缓存

Opcode(Operate Code)操作码。

PHP程序运行完后,马上释放所有内存,所有程序中的变量都销毁,每次请求都要重新翻译、执行,导致速度可能会偏慢。

当解释器完成对脚本代码的分析后,便将它们生成可以直接运行的中间代码,也称为操作码。

操作码 的目地是避免重复编译,减少CPU和内存开销。

APC缓存

APC(Alternative PHP Cache)可选 PHP 缓存。

APC 的目标是提供一个自由、 开放,和健全的框架,用于缓存、优化 PHP 中间代码。

APC 可以去掉 php 动态解析以及编译的时间,使php脚本可以执行的更快。

APC 扩展最后的发布时间为 2012-09-03。

感兴趣可以了解下,官方介绍:http://php.net/manual/zh/book...

eAccelerator

eAccelerator:A PHP opcode cache。

感兴趣可以了解下,官方介绍:http://eaccelerator.net/

XCache

XCache 是一个又快又稳定的 PHP opcode 缓存器。

感兴趣可以了解下,官方介绍:http://xcache.lighttpd.net/

小结

文章主要简单的介绍了 浏览器缓存、文件缓存、NoSQL缓存、WEB服务器缓存、Opcode缓存。

每一种缓存都可以深入研究,从介绍 -> 安装 -> 使用 -> 总结应用场景。

大家可以思考下,通过上面的介绍,工作中我们使用了哪些缓存?

还可以再使用哪些缓存,可以对我们的项目有帮助?

关于缓存的常见问题

用过缓存,大家肯定遇到过比较头痛的问题,比如数据一致性,雪崩,热点数据缓存,缓存监控等等。

给大家列出几个问题,纯属抛转引玉。

当项目中使用到缓存,我们是选择 Redis 还是 Memcached ,为什么?

举一些场景:

一、比如实现一个简单的日志收集功能或发送大量短信、邮件的功能,实现方式是先将数据收集到队列中,然后有一个定时任务去消耗队列,处理该做的事情。

直接使用 Redis 的 lpush,rpop 或 rpush,lpop。

//进队列
$redis->lpush(key, value);

//出队列
$redis->rpop(key);

Memcached 没有这种数据结构。

二、比如我们要存储用户信息,ID、姓名、电话、年龄、身高 ,怎么存储?

方案一:key => value

key = user_data_用户ID

value = json_encode(用户数据)

查询时,先取出key,然后进行json_decode解析。

方案二:hash

key = user_data_用户ID

hashKey = 姓名,value = xx

hashKey = 电话,value = xx

hashKey = 年龄,value = xx

hashKey = 身高,value = xx

查询时,取出key即可。

//新增
$redis->hSet(key, hashKey, value);
$redis->hSet(key, hashKey, value);
$redis->hSet(key, hashKey, value);

//编辑
$redis->hSet(key, hashKey, value);

//查询
$redis->hGetAll(key); //查询所有属性
$redis->hGet(key, hashKey); //查询某个属性

方案二 优于 方案一。

三、比如社交项目类似于新浪微博,个人中心的关注列表和粉丝列表,双向关注列表,还有热门微博,还有消息订阅 等等。

以上都用 Redis 提供的相关数据结构即可。

四、Memcached 只存储在内存中,而 Redis 既可以存储在内存中,也可以持久化到磁盘上。

如果需求中的数据需要持久化,请选择 Redis 。

个人在工作中没有用到 Memcached ,通过查询资料得到 Memcached 内存分配时优于 Redis。

Memcached 默认使用 Slab Allocation 机制管理内存,按照预先规定的大小,将分配的内存分割成特定长度的块以存储相应长度的key-value数据记录,以完全解决内存碎片问题。

如何保证,缓存与数据库的数据一致性?

新增数据:先新增到数据库,再新增到缓存。

编辑数据:先删除缓存数据,再修改数据库中数据,再新增到缓存。

删除数据:先删除缓存数据,再删除数据库中数据。

查询数据:先查询缓存数据,没有,再查询数据库,再新增到缓存。

强一致性是很难保证的,比如事务一致性,时间点一致性,最终一致性等。

具体问题具体分析吧。

缓存穿透怎么办?

用户请求缓存中不存在的数据,导致请求直接落在数据库上。

一、设置有规则的Key值,先验证Key是否符合规范。

二、接口限流、降级、熔断,请研究 istio:https://istio.io/

三、布隆过滤器。

四、为不存在的key值,设置空缓存和过期时间,如果存储层创建了数据,及时更新缓存。

雪崩怎么办?

一、互斥锁,只允许一个请求去重建索引,其他请求等待缓存重建执行完,重新从缓存获取数据。

二、双缓存策略,原始缓存和拷贝缓存,当原始缓存失效请求拷贝缓存,原始缓存失效时间设置为短期,拷贝缓存设置为长期。

已上,纯属抛转引玉,结合自己的情况,具体问题,具体分析吧。

推荐阅读

一起学习

查看原文

dejavu 收藏了文章 · 2020-05-26

PHP 将 mcrypt_encrypt 迁移至 openssl_encrypt 的方法

注:phpmcrypt_簇7.1.0 版本中开始 deprecated,并在 7.2.0 版本中彻底废弃。其实在 2015 就已经开始建议大家使用 openssl_encrypt/openssl_decrypt 来代替mcrypt_encrypt/mcrypt_decrypt,缓冲了 N 久,这一天终于在 7.2.0 版本上到来了。

为什么要提及迁移,比如 A & B 两套系统使用 AES 加密做数据传输,A 作为客户端,B 则是第三方的服务,且 A 已经在使用 7.2.0+ 版本,而 B 作为长期运行的服务仍在使用 7.1.0-,那我们能做的就是在 A 上使用 openssl_簇 原样的实现 mcrypt_簇 的加解密功能,以便兼容 B 服务,且 mcrypt_簇 是有一些需要多加注意的地方,否则迁移之路略微坎坷。

mcrypt_簇 虽说被遗弃了,但 文档页 上依然有很多值得注意的文档贡献,有助于我们将 mcrypt_簇 迁移至 openssl_簇,大家应该仔细看一下。

1.If you're writing code to encrypt/encrypt data in 2015, you should use openssl_encrypt() and openssl_decrypt(). The underlying library (libmcrypt) has been abandoned since 2007, and performs far worse than OpenSSL (which leverages AES-NI on modern processors and is cache-timing safe).

2.Also, MCRYPT_RIJNDAEL_256 is not AES-256, it's a different variant of the Rijndael block cipher. If you want AES-256 in mcrypt, you have to use MCRYPT_RIJNDAEL_128 with a 32-byte key. OpenSSL makes it more obvious which mode you are using (i.e. 'aes-128-cbc' vs 'aes-256-ctr').

3.OpenSSL also uses PKCS7 padding with CBC mode rather than mcrypt's NULL byte padding. Thus, mcrypt is more likely to make your code vulnerable to padding oracle attacks than OpenSSL.

1、即刻起,应尽可能的使用 openssl_簇 代替 mcrypt_簇 来实现数据的加密功能。

2、MCRYPT_RIJNDAEL_256 并不是 AES-256,如果想使用mcrypt_簇 实现 AES-256,则你应该使用 MCRYPT_RIJNDAEL_128 算法 + 32 位的 key,openssl_簇 则更为清晰的明确了各种模式。这里我整理了一下对应关系供大家参考:

MCRYPT_RIJNDAEL_128 & MCRYPT_MODE_CBC + 16位Key = openssl_encrypt(AES-128-CBC, 16位Key) = AES-128
MCRYPT_RIJNDAEL_128 & MCRYPT_MODE_CBC + 24位Key = openssl_encrypt(AES-192-CBC, 24位Key) = AES-192
MCRYPT_RIJNDAEL_128 & MCRYPT_MODE_CBC + 32位Key = openssl_encrypt(AES-256-CBC, 32位Key) = AES-256

(注:AES-128, 192 and 256 的加密 key 的长度分别为 16, 24 and 32 位)

openssl_簇 的确更为准确,而且 mcrypt_get_key_size 得到的 key 长度都是 32 位,所以不太靠谱。

iv 到是会根据 cipher 变更 16 、24、32,但 openssl_簇AES cipheriv 长度 固定16 位。

所以,我们为了最大的适配,即便现在不会再用,也要知道 mcrypt_簇 实现 AES-128/192/256 的 标准方式 为:

  1. cipher 固定选用 MCRYPT_RIJNDAEL_128
  2. 根据 cipher 和 mode 生成 iv(mcrypt_create_iv(mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128 , MCRYPT_MODE_CBC))),因 cipher 我们固定为 MCRYPT_RIJNDAEL_128 所以 iv 固定为 16 位,同 openssl 兼容。
  3. 根据实际业务来确定 key 长度: AES-128 16位 / AES-192 24位 / AES-256 32位,而不是使用 mcrypt_get_key_size(MCRYPT_RIJNDAEL_128 下使用此方法获取的 key 长度固定为 16 位,不符合要求)

3、这一点其实蛮重要的,涉及加密算法数据块&填充算法PKCS7的概念。我在支付宝alipay SDK中有看到此算法的实现,虽然 sdk 中仍然使用的mcrypt_簇,但已结合了PKCS7填充算法,为什么要这样做呢?其一是为了安全&兼容,php mcrypt会默认使用null('\000')对数据块进行填充,java/.net则默认使用PKCS7。其二则是为后期迁移至openssl_簇的准备,openssl的默认填充算法也是PKCS7(当然也可以指定使用null('\000')填充模式,但极力不推荐的)。

mcrypt_encrypt / mcrypt_decrypt

相关的支持函数

// 支持的算法 rijndael-128|rijndael-192|rijndael-256(此算法并非AES-256,需使用rijndael-128 + key32byte实现)
mcrypt_list_algorithms()
// 支持的模式 cbc ecb 等
mcrypt_list_modes()
// 算法所对应的 key 长度:AES-128, 192 and 256 的加密 key 的长度分别为 16, 24 and 32 位
mcrypt_get_key_size(string $cipher , string $mode)
// 算法所对应的加密向量 iv 的长度
mcrypt_get_iv_size(string $cipher , string $mode)
// 生成 iv
mcrypt_create_iv(mcrypt_get_iv_size(string $cipher , string $mode))
// 加密算法数据块的大小 主要用于填充算法
mcrypt_get_block_size(string $cipher , string $mode)

PKCS7 填充算法的实现

/**
 * 填充算法
 * @param string $source
 * @return string
 */
function addPKCS7Padding($source, $cipher = MCRYPT_RIJNDAEL_128, $mode = MCRYPT_MODE_CBC)
{
    $source     = trim($source);
    // 获取加密算法数据块大小 用于计算需要填充多少位
    $block_size = mcrypt_get_block_size($cipher, $mode);
    $pad        = $block_size - (strlen($source) % $block_size);
    if ($pad <= $block_size) {
        $char = chr($pad);
        $source .= str_repeat($char, $pad);
    }
    return $source;
}

/**
 * 移去填充算法
 * @param string $source
 * @return string
 */
function stripPKCS7Padding($source)
{
    $source = trim($source);
    $char   = substr($source, -1);
    $num    = ord($char);
    if ($num == 62) {
        return $source;
    }

    $source = substr($source, 0, -$num);
    return $source;
}

openssl_encrypt/openssl_decrypt

简单讲解一下日常开发中用到的参数

/**
 * $data 待加密内容
 * $method 加密算法
 * $key 加密key
 * $options 数据块填充模式
 * $iv 加密向量
**/
openssl_encrypt(string $data, string $method, string $key[, int $options = 0[, string $iv = ""
[, string &$tag = NULL[, string $aad = ""[, int $tag_length = 16 ]]]]]): string

openssl_decrypt(string $data, string $method, string $key[, int $options = 0[, string $iv = ""
[, string $tag = "" [, string $aad = "" ]]]] ) : string

这里需要特别注意的就是 options 选项,很多人 mcrypt_簇 迁移至 openssl_簇 时二者加密结果内容不一致,大都是此处没有搞清楚的原因。options 共 3 个值可选

0 默认值 使用 PKCS7 填充算法,对加密结果进行 base64encode
1 OPENSSL_RAW_DATA 使用 PKCS7 填充算法,不对加密结果进行 base64encode
2 OPENSSL_ZERO_PADDING 使用 null('\0') 进行填充,且对加密结果进行 base64encode

所以要注意填充算法及对结果是否进行了 base64encode 编码。

mcrypt_簇 迁移至 openssl_簇

mcrypt_簇

/**
 * 加密算法
 * @param  string $content    待加密数据
 * @param  string $key    加密key
 * @param  string $iv     加密向量
 * @param  string $cipher 加密算法
 * @param  string $mode   加密模式
 * @return string         加密后的内容且base64encode
 */
function encrypt($content, $key, $iv, $cipher = MCRYPT_RIJNDAEL_128, $mode = MCRYPT_MODE_CBC)
{
    //AES, 128 模式加密数据 CBC
    $content           = addPKCS7Padding($content);
    $content_encrypted = mcrypt_encrypt($cipher, $key, $content, $mode, $iv);
    return base64_encode($content_encrypted);
}

/**
 * 解密算法
 * @param  [type] $content [description]
 * @param  [type] $key     [description]
 * @param  [type] $iv      [description]
 * @param  [type] $cipher  [description]
 * @param  [type] $mode    [description]
 * @return [type]          [description]
 */
function decrypt($content_encrypted, $key, $iv, $cipher = MCRYPT_RIJNDAEL_128, $mode = MCRYPT_MODE_CBC)
{
    //AES, 128 模式加密数据 CBC
    $content_encrypted = base64_decode($content_encrypted);
    $content           = mcrypt_decrypt($cipher, $key, $content_encrypted, $mode, $iv);
    $content           = stripPKSC7Padding($content);
    return $content;
}

/**
 * PKCS7填充算法
 * @param string $source
 * @return string
 */
function addPKCS7Padding($source, $cipher = MCRYPT_RIJNDAEL_128, $mode = MCRYPT_MODE_CBC)
{
    $source = trim($source);
    $block  = mcrypt_get_block_size($cipher, $mode);
    $pad    = $block - (strlen($source) % $block);
    if ($pad <= $block) {
        $char = chr($pad);
        $source .= str_repeat($char, $pad);
    }
    return $source;
}
/**
 * 移去PKCS7填充算法
 * @param string $source
 * @return string
 */
function stripPKSC7Padding($source)
{
    $source = trim($source);
    $char   = substr($source, -1);
    $num    = ord($char);
    if ($num == 62) {
        return $source;
    }

    $source = substr($source, 0, -$num);
    return $source;
}

openssl_簇

转换实例

以 AES-128 为例


// 固定使用此算法 然后通过 key 的长度来决定具体使用的是何种 AES
$cipher = MCRYPT_RIJNDAEL_128;
$mode   = MCRYPT_MODE_CBC;

// openssl_簇 iv 固定为 16 位,mcrypt_簇 MCRYPT_RIJNDAEL_128 是 16位
// 但改为 MCRYPT_RIJNDAEL_192/256 就是 24/32 位了,会不兼容 openssl_簇
// 所以务必注意向量长度统一固定 16 位方便两套算法对齐
// $iv = mcrypt_create_iv(mcrypt_get_iv_size($cipher, $mode), MCRYPT_RAND);

// 根据需要自行定义相应的 key 长度 aes-128=16 aes-192=24 aes-256=32
$key = '0123456789012345';
// 固定为 16 位
$iv  = '0123456789012345';

$content = "hello world";

// mcrypt 加解密
$mcrypt_data = encrypt($content, $key, $iv, $cipher, $mode);
var_dump($mcrypt_data);
$content = decrypt($mcrypt_data, $key, $iv, $cipher, $mode);
var_dump($content);

// mcrypt 时使用了 PKCS7 填充 并对结果 base64encode
// 如果 +PKCS7 +base64encode 则 option = 0
// 如果 +PKCS7 -base64encode 则 option = 1
// 如果 -PKCS7 +base64encode 则 option = 2
$openssl_data = openssl_encrypt($content, "AES-128-CBC", $key, 0, $iv)
var_dump($openssl_data);
$content = openssl_decrypt($openssl_data, "AES-128-CBC", $key, 0, $iv)
var_dump($content);

// 相互转换
$content = openssl_decrypt($mcrypt_data, "AES-128-CBC", $key, 0, $iv)
var_dump($content);
$content = decrypt($openssl_data, $key, $iv, $cipher, $mode);
var_dump($content);

总结

1、PKCS7 填充算法。
2、openssl_encrypt / openssl_decrypt 三种模式所表示的 PKCS7/base64encode。
3、mcrypt_簇 的 cipher/mode 同 openssl_簇 的转换。
<?php
/**
 * MCRYPT_RIJNDAEL_128 & CBC + 16位Key + 16位iv = openssl_encrypt(AES-128-CBC, 16位Key, 16位iv) = AES-128
 * MCRYPT_RIJNDAEL_128 & CBC + 24位Key + 16位iv = openssl_encrypt(AES-192-CBC, 24位Key, 16位iv) = AES-192
 * MCRYPT_RIJNDAEL_128 & CBC + 32位Key + 16位iv = openssl_encrypt(AES-256-CBC, 32位Key, 16位iv) = AES-256
 * ------------------------------------------------------------------------------------------------------
 * openssl_簇 options
 * 0 : 自动对明文进行 pkcs7 padding, 返回的数据经过 base64 编码.
 * 1 : OPENSSL_RAW_DATA, 自动对明文进行 pkcs7 padding, 但返回的结果未经过 base64 编码
 * 2 : OPENSSL_ZERO_PADDING, 自动对明文进行 null('0') 填充, 同 mcrpty 一致,且返回的结果经过 base64 编码, openssl 不推荐 0 填充的方式, 即使选择此项也不会自动进行 padding, 仍需手动 padding
 * --------------------------------------------------------------------------------------------------------
 * mcrypt 默认是用 0 填充,为保持良好的兼容性建议使用 pkcs7 填充数据 openssl 0|1 都使用的 pkcs7
 * pkcs7 填充
 * 加密工具类
 */

// 随机字符串
function get_random_str($length = 16)
{
    $char_set = array_merge(range('a', 'z'), range('A', 'Z'), range('0', '9'));
    shuffle($char_set);
    return implode('', array_slice($char_set, 0, $length));
}

// 固定使用此算法 然后通过 key 的长度来决定具体使用的是何种 AES
$mcrypt_cipher = MCRYPT_RIJNDAEL_128;
$mcrypt_mode   = MCRYPT_MODE_CBC;
// openssl_簇 AES iv 固定为 16 位,mcrypt_簇只有在 MCRYPT_RIJNDAEL_128 为 16 位 需注意保持一致
$iv = mcrypt_create_iv(mcrypt_get_iv_size($mcrypt_cipher, $mcrypt_mode), MCRYPT_RAND);
// aes-128=16 aes-192=24 aes-256=32
$key_size = 16;
$key      = get_random_str($key_size);
// openssl_ AES 向量长度固定 16 位 这里为
$iv = get_random_str(16);

/**
 * 加密算法
 * @param  string $content    待加密数据
 * @param  string $key    加密key
 * @param  string $iv     加密向量
 * @param  string $cipher 加密算法
 * @param  string $mode   加密模式
 * @return string         加密后的内容且base64encode
 */
function encrypt($content, $key, $iv, $cipher = MCRYPT_RIJNDAEL_128, $mode = MCRYPT_MODE_CBC)
{
    //AES, 128 模式加密数据 CBC
    $content           = addPKCS7Padding($content);
    $content_encrypted = mcrypt_encrypt($cipher, $key, $content, $mode, $iv);
    return base64_encode($content_encrypted);
}

/**
 * 解密算法
 * @param  [type] $content [description]
 * @param  [type] $key     [description]
 * @param  [type] $iv      [description]
 * @param  [type] $cipher  [description]
 * @param  [type] $mode    [description]
 * @return [type]          [description]
 */
function decrypt($content_encrypted, $key, $iv, $cipher = MCRYPT_RIJNDAEL_128, $mode = MCRYPT_MODE_CBC)
{
    //AES, 128 模式加密数据 CBC
    $content_encrypted = base64_decode($content_encrypted);
    $content           = mcrypt_decrypt($cipher, $key, $content_encrypted, $mode, $iv);
    $content           = stripPKSC7Padding($content);
    return $content;
}

/**
 * PKCS7填充算法
 * @param string $source
 * @return string
 */
function addPKCS7Padding($source, $cipher = MCRYPT_RIJNDAEL_128, $mode = MCRYPT_MODE_CBC)
{
    $source = trim($source);
    $block  = mcrypt_get_block_size($cipher, $mode);
    $pad    = $block - (strlen($source) % $block);
    if ($pad <= $block) {
        $char = chr($pad);
        $source .= str_repeat($char, $pad);
    }
    return $source;
}
/**
 * 移去PKCS7填充算法
 * @param string $source
 * @return string
 */
function stripPKSC7Padding($source)
{
    $source = trim($source);
    $char   = substr($source, -1);
    $num    = ord($char);
    if ($num == 62) {
        return $source;
    }

    $source = substr($source, 0, -$num);
    return $source;
}

$content = "hello world";

var_dump($data = encrypt($content, $key, $iv, $mcrypt_cipher, $mcrypt_mode));
var_dump(decrypt($data, $key, $iv, $mcrypt_cipher, $mcrypt_mode));
var_dump($openssl_data = openssl_encrypt($content, "AES-128-CBC", $key, 0, $iv));
var_dump(openssl_decrypt($openssl_data, "AES-128-CBC", $key, 0, $iv));

// var_dump(openssl_cipher_iv_length('AES-256-CBC'));
// var_dump(mcrypt_get_key_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC));
查看原文

dejavu 收藏了文章 · 2020-04-28

19 个强大、有趣、又好玩的 Linux 命令!

1. sl 命令

你会看到一辆火车从屏幕右边开往左边……

安装

$ sudo apt-get install sl

运行

$ sl

命令有 -a l F e 几个选项,

-a An accident seems to happen. You'll feel pity for people who cry for help.
-l shows little one.
-F It flies.
-e Allow interrupt by Ctrl+C.

可以给别人来个恶作剧,他一敲ls,不知道的肯定很有效果

$alias ls=sl

2. fortune 命令

输出一句话,有笑话,名言什么的 (还有唐诗宋词sudo apt-get install fortune-zh)

安装

$sudo apt-get install fortune

运行

$fortune

3. cowsay 命令

用ASCII字符打印牛,羊等动物,还有个cowthink,这个是奶牛想,那个是奶牛说,哈哈,差不多

安装

$sudo apt-get install cowsay

运行

$cowsay "I am not a cow, hahaha"

$cowsay -l 查看其它动物的名字,然后 -f 跟上动物名,如

$cowsay -f tux "坑爹啊"

还可以让cowsay说出fortune的内容,就像这样:

$fortune | cowsay

4. cmatrix 命令

这个很酷!《黑客帝国》那种矩阵风格的动画效果

安装

$ sudo apt-get install cmatrix

运行

$cmatrix

5. figlet 、toilet命令

艺术字生成器,由ASCII字符组成,把文本显示成标题栏。此外还有banner这个命令

安装

$sudo apt-get install figlet

$sudo apt-get install toilet

运行

$figlet fuck you !

$toilet i love you

toilet 还可以添加颜色,里面的选项请自己 man 一下

$ toilet -f mono12 -F gay a girl

6. oneko 命令

桌面上出现一直喵星人,跟着你的鼠标跑,你不动了它就睡觉。哈哈,这个挺不错

安装

$sudo apt-get install oneko

运行

$oneko

要关掉这家伙,按ctrl+c 结束

7. xeyes 命令

在屏幕上出现一双眼睛,盯着你的鼠标指针

安装运行同上

8. yes 命令

输出无穷无尽的字符,按ctrl+c结束,如

$yes 我很NB

9. cal 9 1752

cal是打印日历,不过这个是很奇葩的一个月,

10. shred

覆盖搞乱文件,就是文档粉碎,哈哈,要把你私藏的大片种子和电影销毁,不被恢复出来,就靠它了

11. factor

分解因数,这个……小学生可以用一下

12. 挨个敲下面这一堆,aptitude 没有的可以安装上

aptitude moo

aptitude -v moo

aptitude -vv moo

aptitude -vvv moo

aptitude -vvvv moo

aptitude -vvvvv moo

aptitude -vvvvvv moo

aptitude -vvvvvvv moo

13、高大上仪表盘blessed-contrib——假装自己指点江山,纵横捭阖  

sudo apt-get install npm

sudo apt install nodejs-legacy

git clone https://github.com/yaronn/blessed-contrib.git

cd blessed-contrib

npm install

node ./examples/dashboard.js

高大上黑客仪表盘

14、高大上仪表盘hollywood——假装自己日理万机,宵衣旰食

 Dustin Kirkland 利用一个长途飞行的时间,编写了这个炫酷、有趣但也没什么实际作用的软件。  

在其它Linux发行版中,可以通过以下命令安装并运行。  

sudo apt-add-repository ppa:hollywood/ppa

sudo apt-get install hollywood

sudo apt-get install byobu
hollywood

15、追逐鼠标的小猫oneko

 在桌面的命令行界面输入  

sudo apt-get install onekooneko

然后输入oneko,即可看到效果。  

微信图片_20200425131611.gif

16、ASCII艺术框:box命令 

sudo apt-get install boxes

echo "Tongji Univerisity" | boxes

echo "Tongji Univerisity" | boxes -d dog

fortune | boxes -d cat | lolcat

17、燃起字符串大火aafire在命令行界面输入 

sudo apt-get install libaa-bin
aafire

然后输入 aafire,即可看到效果

aafire字符串大火

18、图片转字符串

这条命令在树莓派上运行会出问题,建议在云主机或虚拟机上运行。

sudo apt-get install aview imagemagick

wget http://labfile.oss.aliyuncs.com/courses/1/Linus.png
asciiview Linus.png

19、从删库到跑路 sudo rm -rf /* 

sudo rm -rf /*
sudo:获取root管理员权限
rm:remove,即删除
-rf:r表示递归删除,即删除所有的子目录,f表示不需要再进行确认
/:根目录
*:所有文件

微信图片_20200425131833.gif

友情提示:千万不要轻易尝试这个命令,特别是在运行有网站服务器、数据库的Linux主机上 。

这些好玩的命令可以通过查 man 手册,然后在加上自己想出来的创意,改编成更多有趣的东西,哈哈,给别人捣乱也是挺好玩的……

查看原文

dejavu 收藏了文章 · 2020-04-28

1047 行 MySQL 详细学习笔记(值得学习与收藏)

timg.jpg
作者:格物    
https://www.cnblogs.com/shock...

Windows服务

-- 启动MySQL
net start mysql

-- 创建Windows服务
sc create mysql binPath= mysqld_bin_path(注意:等号与值之间有空格)

连接与断开服务器

mysql -h 地址 -P 端口 -u 用户名 -p 密码

SHOW PROCESSLIST -- 显示哪些线程正在运行
SHOW VARIABLES -- 显示系统变量信息

数据库操作

-- 查看当前数据库
SELECT DATABASE();

-- 显示当前时间、用户名、数据库版本
SELECT now(), user(), version();

-- 创建库
CREATE DATABASE[ IF NOT EXISTS] 数据库名 数据库选项    
数据库选项:        
CHARACTER SET charset_name        
COLLATE collation_name

-- 查看已有库    
SHOW DATABASES[ LIKE 'PATTERN']

-- 查看当前库信息    
SHOW CREATE DATABASE 数据库名

-- 修改库的选项信息    
ALTER DATABASE 库名 选项信息

-- 删除库    DROP DATABASE[ IF EXISTS] 数据库名        
    同时删除该数据库相关的目录及其目录内容

表的操作

-- 创建表
CREATE [TEMPORARY] TABLE[ IF NOT EXISTS] [库名.]表名 ( 表的结构定义 )[ 表选项]
每个字段必须有数据类型
最后一个字段后不能有逗号
TEMPORARY 临时表,会话结束时表自动消失

对于字段的定义:   
字段名 数据类型 [NOT NULL | NULL] [DEFAULT default_value] [AUTO_INCREMENT] [UNIQUE [KEY] | [PRIMARY] KEY] [COMMENT 'string']
-- 表选项  
-- 字符集  
CHARSET = charset_name  
如果表没有设定,则使用数据库字符集  
-- 存储引擎  ENGINE = engine_name  
表在管理数据时采用的不同的数据结构,结构不同会导致处理方式、提供的特性操作等不同  
常见的引擎:InnoDB MyISAM Memory/Heap BDB Merge Example CSV MaxDB Archive  
不同的引擎在保存表的结构和数据时采用不同的方式  
MyISAM表文件含义:.frm表定义,.MYD表数据,.MYI表索引  
InnoDB表文件含义:.frm表定义,表空间数据和日志文件  
SHOW ENGINES -- 显示存储引擎的状态信息  
SHOW ENGINE 引擎名 {LOGS|STATUS} -- 显示存储引擎的日志或状态信息    
-- 自增起始数        
AUTO_INCREMENT = 行数    
-- 数据文件目录        
DATA DIRECTORY = '目录'    
-- 索引文件目录        
INDEX DIRECTORY = '目录'    
-- 表注释        
COMMENT = 'string'    
-- 分区选项        
PARTITION BY ... (详细见手册)
-- 查看所有表
SHOW TABLES[ LIKE 'pattern']
SHOW TABLES FROM 表名

-- 查看表机构
SHOW CREATE TABLE 表名 (信息更详细)
DESC 表名 / DESCRIBE 表名 / EXPLAIN 表名 / 
SHOW COLUMNS FROM 表名 [LIKE 'PATTERN']SHOW TABLE STATUS [FROM db_name] [LIKE 'pattern']

-- 修改表   
-- 修改表本身的选项    
ALTER TABLE 表名 表的选项    
eg: ALTER TABLE 表名 ENGINE=MYISAM;    

-- 对表进行重命名    
RENAME TABLE 原表名 TO 新表名    
RENAME TABLE 原表名 TO 库名.表名 (可将表移动到另一个数据库)    
-- RENAME可以交换两个表名    
-- 修改表的字段机构(13.1.2. ALTER TABLE语法)       
ALTER TABLE 表名 操作名       

-- 操作名          
ADD[ COLUMN] 字段定义       -- 增加字段            
AFTER 字段名          -- 表示增加在该字段名后面            
FIRST               -- 表示增加在第一个            
ADD PRIMARY KEY(字段名)   -- 创建主键           
ADD UNIQUE [索引名] (字段名)-- 创建唯一索引            
ADD INDEX [索引名] (字段名) -- 创建普通索引            
DROP[ COLUMN] 字段名      -- 删除字段            
MODIFY[ COLUMN] 字段名 字段属性     -- 支持对字段属性进行修改,不能修改字段名(所有原有属性也需写上)            
CHANGE[ COLUMN] 原字段名 新字段名 字段属性      -- 支持对字段名修改            
DROP PRIMARY KEY    -- 删除主键(删除主键前需删除其AUTO_INCREMENT属性)            
DROP INDEX 索引名 -- 删除索引            
DROP FOREIGN KEY 外键    -- 删除外键

-- 删除表    
DROP TABLE[ IF EXISTS] 表名 ...

-- 清空表数据    
TRUNCATE [TABLE] 表名

-- 复制表结构    
CREATE TABLE 表名 LIKE 要复制的表名

-- 复制表结构和数据    
CREATE TABLE 表名 [AS] SELECT * FROM 要复制的表名

-- 检查表是否有错误    
CHECK TABLE tbl_name [, tbl_name] ... [option] 

...-- 优化表   
OPTIMIZE [LOCAL | NO_WRITE_TO_BINLOG] TABLE tbl_name [, tbl_name] ...

-- 修复表   
REPAIR [LOCAL | NO_WRITE_TO_BINLOG] TABLE tbl_name [, tbl_name] ... [QUICK] [EXTENDED] [USE_FRM]

-- 分析表   
ANALYZE [LOCAL | NO_WRITE_TO_BINLOG] TABLE tbl_name [, tbl_name] ...

数据操作

-- 增  
INSERT [INTO] 表名 [(字段列表)] VALUES (值列表)[, (值列表), ...]    
-- 如果要插入的值列表包含所有字段并且顺序一致,则可以省略字段列表。    
-- 可同时插入多条数据记录!    
REPLACE 与 INSERT 完全一样,可互换。    
INSERT [INTO] 表名 SET 字段名=值[, 字段名=值, ...]

-- 查  
SELECT 字段列表 FROM 表名[ 其他子句]        
-- 可来自多个表的多个字段        
-- 其他子句可以不使用        
-- 字段列表可以用*代替,表示所有字段

-- 删    
DELETE FROM 表名[ 删除条件子句]        
没有条件子句,则会删除全部

-- 改    
UPDATE 表名 SET 字段名=新值[, 字段名=新值] [更新条件]

字符集编码

-- MySQL、数据库、表、字段均可设置编码
-- 数据编码与客户端编码不需一致
SHOW VARIABLES LIKE 'character_set_%'   -- 查看所有字符集编码项    
character_set_client        客户端向服务器发送数据时使用的编码
character_set_results       服务器端将结果返回给客户端所使用的编码    
character_set_connection    连接层编码

SET 变量名 = 变量值    
SET character_set_client = gbk;    
SET character_set_results = gbk;    
SET character_set_connection = gbk;SET NAMES GBK;  -- 相当于完成以上三个设置

-- 校对集    
校对集用以排序    
SHOW CHARACTER SET [LIKE 'pattern']/SHOW CHARSET [LIKE 'pattern']   查看所有字符集    
SHOW COLLATION [LIKE 'pattern']     查看所有校对集
CHARSET 字符集编码     设置字符集编码    
COLLATE 校对集编码     设置校对集编码

数据类型(列类型)

1. 数值类型  
-- a. 整型 ----------  
    类型         字节     范围(有符号位)  
    tinyint     1字节    -128 ~ 127      无符号位:0 ~ 255  
    smallint    2字节    -32768 ~ 32767  
    mediumint   3字节    -8388608 ~ 8388607  
    int         4字节  
    bigint      8字节  
    int(M)  M表示总位数  
    - 默认存在符号位,unsigned 属性修改  
    - 显示宽度,如果某个数不够定义字段时设置的位数,则前面以0补填,zerofill 属性修改  
        例:int(5)   插入一个数'123',补填后为'00123'  
    - 在满足要求的情况下,越小越好。  
    - 1表示bool值真,0表示bool值假。MySQL没有布尔类型,通过整型0和1表示。常用tinyint(1)表示布尔型。  
  
-- b. 浮点型 ----------  
    类型             字节     范围  
   float(单精度)     4字节  
   double(双精度)    8字节  
    浮点型既支持符号位 unsigned 属性,也支持显示宽度 zerofill 属性。  
        不同于整型,前后均会补填0.  
    定义浮点型时,需指定总位数和小数位数。  
float(M, D)     double(M, D)  
        M表示总位数,D表示小数位数。  
        M和D的大小会决定浮点数的范围。不同于整型的固定范围。  
        M既表示总位数(不包括小数点和正负号),也表示显示宽度(所有显示符号均包括)。  
        支持科学计数法表示。  
        浮点数表示近似值。  
  
-- c. 定点数 ----------  
decimal -- 可变长度  
decimal(M, D)   M也表示总位数,D表示小数位数。  
    保存一个精确的数值,不会发生数据的改变,不同于浮点数的四舍五入。  
    将浮点数转换为字符串来保存,每9位数字保存为4个字节。  
  
2. 字符串类型  
-- a. char, varchar ----------  
char    定长字符串,速度快,但浪费空间  
    varchar 变长字符串,速度慢,但节省空间  
    M表示能存储的最大长度,此长度是字符数,非字节数。  
    不同的编码,所占用的空间不同。  
char,最多255个字符,与编码无关。  
    varchar,最多65535字符,与编码有关。  
    一条有效记录最大不能超过65535个字节。  
        utf8 最大为21844个字符,gbk 最大为32766个字符,latin1 最大为65532个字符  
    varchar 是变长的,需要利用存储空间保存 varchar 的长度,如果数据小于255个字节,则采用一个字节来保存长度,反之需要两个字节来保存。  
    varchar 的最大有效长度由最大行大小和使用的字符集确定。  
    最大有效长度是65532字节,因为在varchar存字符串时,第一个字节是空的,不存在任何数据,然后还需两个字节来存放字符串的长度,所以有效长度是64432-1-2=65532字节。  
    例:若一个表定义为 CREATE TABLE tb(c1 int, c2 char(30), c3 varchar(N)) charset=utf8; 问N的最大值是多少? 答:(65535-1-2-4-30*3)/3  
  
-- b. blob, text ----------  
    blob 二进制字符串(字节字符串)  
        tinyblob, blob, mediumblob, longblob  
    text 非二进制字符串(字符字符串)  
        tinytext, text, mediumtext, longtext  
    text 在定义时,不需要定义长度,也不会计算总长度。  
    text 类型在定义时,不可给default值  
  
-- c. binary, varbinary ----------  
    类似于char和varchar,用于保存二进制字符串,也就是保存字节字符串而非字符字符串。  
char, varchar, text 对应 binary, varbinary, blob.  
  
3. 日期时间类型  
    一般用整型保存时间戳,因为PHP可以很方便的将时间戳进行格式化。  
    datetime    8字节    日期及时间     1000-01-0100:00:00 到 9999-12-3123:59:59  
    date        3字节    日期         1000-01-01 到 9999-12-31  
    timestamp   4字节    时间戳        19700101000000 到 2038-01-1903:14:07  
    time        3字节    时间         -838:59:59 到 838:59:59  
    year        1字节    年份         1901 - 2155  
datetime    YYYY-MM-DD hh:mm:ss  
timestamp   YY-MM-DD hh:mm:ss  
            YYYYMMDDhhmmss  
            YYMMDDhhmmss  
            YYYYMMDDhhmmss  
            YYMMDDhhmmss  
date        YYYY-MM-DD  
            YY-MM-DD  
            YYYYMMDD  
            YYMMDD  
            YYYYMMDD  
            YYMMDD  
time        hh:mm:ss  
            hhmmss  
            hhmmss  
year        YYYY  
            YY  
            YYYY  
            YY  
  
4. 枚举和集合  
-- 枚举(enum) ----------  
enum(val1, val2, val3...)  
    在已知的值中进行单选。最大数量为65535.  
    枚举值在保存时,以2个字节的整型(smallint)保存。每个枚举值,按保存的位置顺序,从1开始逐一递增。  
    表现为字符串类型,存储却是整型。  
    NULL值的索引是NULL。  
    空字符串错误值的索引值是0。  
-- 集合(set) ----------  
set(val1, val2, val3...)  
create table tab ( gender set('男', '女', '无') );  
insert into tab values ('男, 女');  
    最多可以有64个不同的成员。以bigint存储,共8个字节。采取位运算的形式。  
    当创建表时,SET成员值的尾部空格将自动被删除。

选择类型

-- PHP角度  
1. 功能满足  
2. 存储空间尽量小,处理效率更高  
3. 考虑兼容问题  
  
-- IP存储 ----------  
1. 只需存储,可用字符串  
2. 如果需计算,查找等,可存储为4个字节的无符号int,即unsigned  
   1) PHP函数转换  
       ip2long可转换为整型,但会出现携带符号问题。需格式化为无符号的整型。  
       利用sprintf函数格式化字符串  
       sprintf("%u", ip2long('192.168.3.134'));  
       然后用long2ip将整型转回IP字符串  
   2) MySQL函数转换(无符号整型,UNSIGNED)  
       INET_ATON('127.0.0.1') 将IP转为整型  
       INET_NTOA(2130706433) 将整型转为IP

列属性(列约束)

1. PRIMARY 主键  
    - 能唯一标识记录的字段,可以作为主键。  
    - 一个表只能有一个主键。  
    - 主键具有唯一性。  
    - 声明字段时,用 primary key 标识。  
        也可以在字段列表之后声明  
            例:createtable tab ( idint, stu varchar(10), primary key (id));  
    - 主键字段的值不能为null。  
    - 主键可以由多个字段共同组成。此时需要在字段列表后声明的方法。  
        例:createtable tab ( idint, stu varchar(10), age int, primary key (stu, age));  
  
2. UNIQUE 唯一索引(唯一约束)  
    使得某字段的值也不能重复。  
  
3. NULL 约束  
    null不是数据类型,是列的一个属性。  
    表示当前列是否可以为null,表示什么都没有。  
    null, 允许为空。默认。  
    not null, 不允许为空。  
insertinto tab values (null, 'val');  
-- 此时表示将第一个字段的值设为null, 取决于该字段是否允许为null  
  
  
4. DEFAULT 默认值属性  
    当前字段的默认值。  
insertinto tab values (default, 'val');    -- 此时表示强制使用默认值。  
createtable tab ( add_time timestampdefaultcurrent_timestamp );  
-- 表示将当前时间的时间戳设为默认值。  
        current_date, current_time  
  
5. AUTO_INCREMENT 自动增长约束  
    自动增长必须为索引(主键或unique)  
    只能存在一个字段为自动增长。  
    默认为1开始自动增长。可以通过表属性 auto_increment = x进行设置,或 altertable tbl auto_increment = x;  
  
6. COMMENT 注释  
    例:createtable tab ( idint ) comment'注释内容';  
  
7. FOREIGN KEY 外键约束  
    用于限制主表与从表数据完整性。  
altertable t1 addconstraint`t1_t2_fk` foreign key (t1_id) references t2(id);  
-- 将表t1的t1_id外键关联到表t2的id字段。  
-- 每个外键都有一个名字,可以通过 constraint 指定  
    存在外键的表,称之为从表(子表),外键指向的表,称之为主表(父表)。  
    作用:保持数据一致性,完整性,主要目的是控制存储在外键表(从表)中的数据。  
    MySQL中,可以对InnoDB引擎使用外键约束:  
    ----语法:  
    foreign key (外键字段) references 主表名 (关联字段) [主表记录删除时的动作] [主表记录更新时的动作]  
    此时需要检测一个从表的外键需要约束为主表的已存在的值。外键在没有关联的情况下,可以设置为null.前提是该外键列,没有not null。  
    可以不指定主表记录更改或更新时的动作,那么此时主表的操作被拒绝。  
    如果指定了 on update 或 ondelete:在删除或更新时,有如下几个操作可以选择:  
1.cascade,级联操作。主表数据被更新(主键值更新),从表也被更新(外键值更新)。主表记录被删除,从表相关记录也被删除。  
2.setnull,设置为null。主表数据被更新(主键值更新),从表的外键被设置为null。主表记录被删除,从表相关记录外键被设置成null。但注意,要求该外键列,没有notnull属性约束。  
3. restrict,拒绝父表删除和更新。  
    注意,外键只被InnoDB存储引擎所支持。其他引擎是不支持的。

建表规范

-- Normal Format, NF   
  - 每个表保存一个实体信息   
  - 每个具有一个ID字段作为主键   
  - ID主键 + 原子表   

-- 1NF, 第一范式   
   字段不能再分,就满足第一范式。        

-- 2NF, 第二范式   
  满足第一范式的前提下,不能出现部分依赖。   
  消除符合主键就可以避免部分依赖。增加单列关键字。        

-- 3NF, 第三范式   
  满足第二范式的前提下,不能出现传递依赖。   
  某个字段依赖于主键,而有其他字段依赖于该字段。这就是传递依赖。
  将一个实体信息的数据放在一个表内实现。

SELECT查询

SELECT [ALL|DISTINCT] select_expr FROM -> WHERE -> GROUP BY [合计函数] -> HAVING -> ORDER BY -> LIMITa. select_expr    
-- 可以用 * 表示所有字段。        
select * from tb;    
-- 可以使用表达式(计算公式、函数调用、字段也是个表达式)        
select stu, 29+25, now() from tb;    
-- 可以为每个列使用别名。适用于简化列标识,避免多个列标识符重复。        
- 使用 as 关键字,也可省略 as.        
select stu+10 as add10 from tb;      

b. FROM 子句    
用于标识查询来源。    
-- 可以为表起别名。使用as关键字。        
SELECT * FROM tb1 AS tt, tb2 AS bb;    
-- from子句后,可以同时出现多个表。        
-- 多个表会横向叠加到一起,而数据会形成一个笛卡尔积。        
SELECT * FROM tb1, tb2;    
-- 向优化符提示如何选择索引        
USE INDEX、IGNORE INDEX、FORCE INDEX        
SELECT * FROM table1 USE INDEX (key1,key2) WHERE key1=1 AND key2=2 AND key3=3;        
SELECT * FROM table1 IGNORE INDEX (key3) WHERE key1=1 AND key2=2 AND key3=3;c. WHERE 子句    
-- 从from获得的数据源中进行筛选。    
-- 整型1表示真,0表示假。    
-- 表达式由运算符和运算数组成。        
-- 运算数:变量(字段)、值、函数返回值        
-- 运算符:           
=, <=>, <>, !=, <=, <, >=, >, !, &&, ||,            
in (not) null, (not) like, (not) in, (not) between and, is (not), and, or, not, xor           
is/is not 加上ture/false/unknown,检验某个值的真假            
<=>与<>功能相同,<=>可用于null比较            

d. GROUP BY 子句, 分组子句    
GROUP BY 字段/别名 [排序方式]    
分组后会进行排序。升序:ASC,降序:DESC   
以下[合计函数]需配合 GROUP BY 使用:    
count 返回不同的非NULL值数目  count(*)、count(字段)    
sum 求和    
max 求最大值    
min 求最小值    
avg 求平均值    
group_concat 返回带有来自一个组的连接的非NULL值的字符串结果。组内字符串连接。    

e. HAVING 子句,条件子句    
与 where 功能、用法相同,执行时机不同。    
where 在开始时执行检测数据,对原数据进行过滤。    
having 对筛选出的结果再次进行过滤。    
having 字段必须是查询出来的,where 字段必须是数据表存在的。   
where 不可以使用字段的别名,having 可以。因为执行WHERE代码时,可能尚未确定列值。   
where 不可以使用合计函数。一般需用合计函数才会用 having    
SQL标准要求HAVING必须引用GROUP BY子句中的列或用于合计函数中的列。    

f. ORDER BY 子句,排序子句    
order by 排序字段/别名 排序方式 [,排序字段/别名 排序方式]...    
升序:ASC,降序:DESC    
支持多个字段的排序。    

g. LIMIT 子句,限制结果数量子句    
仅对处理好的结果进行数量限制。将处理好的结果的看作是一个集合,按照记录出现的顺序,索引从0开始。    
limit 起始位置, 获取条数    
省略第一个参数,表示从索引0开始。limit 获取条数    

h. DISTINCT, ALL 选项    
distinct 去除重复记录    
默认为 all, 全部记录

UNION

将多个select查询的结果组合成一个结果集合。
SELECT ... UNION [ALL|DISTINCT] SELECT ...
默认 DISTINCT 方式,即所有返回的行都是唯一的建议,对每个SELECT查询加上小括号包裹。
ORDER BY 排序时,需加上 LIMIT 进行结合。
需要各select查询的字段数量一样。
每个select查询的字段列表(数量、类型)应一致,因为结果中的字段名以第一条select语句为准。

子查询

- 子查询需用括号包裹。
-- from型    
 from后要求是一个表,必须给子查询结果取个别名。    
 - 简化每个查询内的条件。    
 - from型需将结果生成一个临时表格,可用以原表的锁定的释放。    
 - 子查询返回一个表,表型子查询。    
 select * from (select * from tb where id>0) as subfrom  where id>1;    

-- where型    
- 子查询返回一个值,标量子查询。    
- 不需要给子查询取别名。    
- where子查询内的表,不能直接用以更新。    
select * from tb where money = (select max(money) from tb);

-- 列子查询        
如果子查询结果返回的是一列。        
使用 in 或 not in 完成查询        
exists 和 not exists 条件            
如果子查询返回数据,则返回1或0。常用于判断条件。
select column1 from t1 where exists (select * from t2);

-- 行子查询        
查询条件是一个行。        
select * from t1 where (id, gender) in (select id, gender from t2);        
行构造符:(col1, col2, ...) 或 ROW(col1, col2, ...) 
行构造符通常用于与对能返回两个或两个以上列的子查询进行比较。

-- 特殊运算符    
!= all()    
相当于 not in    
= some()    
相当于 in。any 是 some 的别名    
!= some()   
不等同于 not in,不等于其中某一个。    
all, some 可以配合其他运算符一起使用。

连接查询(join)

将多个表的字段进行连接,可以指定连接条件。
-- 内连接(inner join)    
- 默认就是内连接,可省略inner。    
- 只有数据存在时才能发送连接。即连接结果不能出现空行。    
on 表示连接条件。其条件表达式与where类似。也可以省略条件(表示条件永远为真)    
也可用where表示连接条件。    
还有 using, 但需字段名相同。 using(字段名)    

-- 交叉连接 cross join        
即,没有条件的内连接。        
select * from tb1 cross join tb2;    

-- 外连接(outer join)    
- 如果数据不存在,也会出现在连接结果中。    
   -- 左外连接 left join        
   如果数据不存在,左表记录会出现,而右表为null填充    
   -- 右外连接 right join        
   如果数据不存在,右表记录会出现,而左表为null填充 
   
-- 自然连接(natural join)    
   自动判断连接条件完成连接。    
   相当于省略了using,会自动查找相同字段名。    
   natural join    
   natural left join    
   natural right join    
   
   select info.id, info.name, info.stu_num, extra_info.hobby, extra_info.sex from info, extra_info where info.stu_num = extra_info.stu_id;

导出

select * into outfile 文件地址 [控制格式] from 表名;   -- 导出表数据

load data [local] infile 文件地址 [replace|ignore] into table 表名 [控制格式]; -- 导入数据    
生成的数据默认的分隔符是制表符    
local未指定,则数据文件必须在服务器上    
replace 和 ignore 关键词控制对现有的唯一键记录的重复的处理

-- 控制格式
fields  控制字段格式
默认:fields terminated by '\t' enclosed by '' escaped by '\\'    
  terminated by 'string'  -- 终止    
  enclosed by 'char'      -- 包裹    
  escaped by 'char'       -- 转义        
   -- 示例:        
   SELECT a,b,a+b INTO OUTFILE '/tmp/result.text' 
   FIELDS TERMINATED BY ',' OPTIONALLY ENCLOSED BY '"' 
   LINES TERMINATED BY '\n'        
   FROM test_table;
   
   lines   控制行格式
   默认:lines terminated by '\n'    
   terminated by 'string'  -- 终止

INSERT

select语句获得的数据可以用insert插入。
可以省略对列的指定,要求 values () 括号内,提供给了按照列顺序出现的所有字段的值。    
 或者使用set语法。    
 INSERT INTO tbl_name SET field=value,...;    

可以一次性使用多个值,采用(), (), ();的形式。    
 INSERT INTO tbl_name VALUES (), (), ();    

可以在列值指定时,使用表达式。    
 INSERT INTO tbl_name VALUES (field_value, 10+10, now()); 

可以使用一个特殊值 DEFAULT,表示该列使用默认值。    
 INSERT INTO tbl_name VALUES (field_value, DEFAULT);    

可以通过一个查询的结果,作为需要插入的值。    
 INSERT INTO tbl_name SELECT ...;    

可以指定在插入的值出现主键(或唯一索引)冲突时,更新其他非主键列的信息。    
 INSERT INTO tbl_name VALUES/SET/SELECT ON DUPLICATE KEY UPDATE 字段=值, …;

DELETE

DELETE FROM tbl_name [WHERE where_definition] [ORDER BY ...] [LIMIT row_count]
按照条件删除。where
指定删除的最多记录数。limit
可以通过排序条件删除。order by + limit
支持多表删除,使用类似连接语法。
delete from 需要删除数据多表1,表2 using 表连接操作 条件。

TRUNCATE

TRUNCATE [TABLE] tbl_name
清空数据删
除重建表

区别:
1,truncate 是删除表再创建,delete 是逐条删除
2,truncate 重置auto_increment的值。而delete不会
3,truncate 不知道删除了几条,而delete知道。
4,当被用于带分区的表时,truncate 会保留分区

备份与还原

备份,将数据的结构与表内数据保存起来。
利用 mysqldump 指令完成。

-- 导出
mysqldump [options] db_name [tables]mysqldump [options] 
---database DB1 [DB2 DB3...]mysqldump [options] 
--all--database
1. 导出一张表  
mysqldump -u用户名 -p密码 库名 表名 > 文件名(D:/a.sql)
2. 导出多张表  
mysqldump -u用户名 -p密码 库名 表1 表2 表3 > 文件名(D:/a.sql)
3. 导出所有表  
mysqldump -u用户名 -p密码 库名 > 文件名(D:/a.sql)
4. 导出一个库  
mysqldump -u用户名 -p密码 --lock-all-tables --database 库名 > 文件名(D:/a.sql)
可以-w携带WHERE条件

-- 导入
1. 在登录mysql的情况下:  
source  备份文件
2. 在不登录的情况下  
mysql -u用户名 -p密码 库名 < 备份文件

视图

什么是视图:    
视图是一个虚拟表,其内容由查询定义。同真实的表一样,视图包含一系列带有名称的列和行数据。但是,视图并不在数据库中以存储的数据值集形式存在。行和列数据来自由定义视图的查询所引用的表,并且在引用视图时动态生成。    
视图具有表结构文件,但不存在数据文件。    
对其中所引用的基础表来说,视图的作用类似于筛选。定义视图的筛选可以来自当前或其它数据库的一个或多个表,或者其它视图。通过视图进行查询没有任何限制,通过它们进行数据修改时的限制也很少。    
视图是存储在数据库中的查询的sql语句,它主要出于两种原因:安全原因,视图可以隐藏一些数据,如:社会保险基金表,可以用视图只显示姓名,地址,而不显示社会保险号和工资数等,另一原因是可使复杂的查询易于理解和使用。

-- 创建视图
CREATE [OR REPLACE] [ALGORITHM = {UNDEFINED | MERGE | TEMPTABLE}] VIEW view_name [(column_list)] AS select_statement    
- 视图名必须唯一,同时不能与表重名。    
- 视图可以使用select语句查询到的列名,也可以自己指定相应的列名。
- 可以指定视图执行的算法,通过ALGORITHM指定。    
- column_list如果存在,则数目必须等于SELECT语句检索的列数

-- 查看结构    
SHOW CREATE VIEW view_name

-- 删除视图    
- 删除视图后,数据依然存在。    
- 可同时删除多个视图。    
DROP VIEW [IF EXISTS] view_name ...

-- 修改视图结构    
- 一般不修改视图,因为不是所有的更新视图都会映射到表上。
ALTER VIEW view_name [(column_list)] AS select_statement

-- 视图作用    
1. 简化业务逻辑    
2. 对客户端隐藏真实的表结构

-- 视图算法(ALGORITHM)    
MERGE       合并        
 将视图的查询语句,与外部查询需要先合并再执行!    
TEMPTABLE   临时表        
 将视图执行完毕后,形成临时表,再做外层查询!    
UNDEFINED   
 未定义(默认),指的是MySQL自主去选择相应的算法。

事务(transaction)

事务是指逻辑上的一组操作,组成这组操作的各个单元,要不全成功要不全失败。    
- 支持连续SQL的集体成功或集体撤销。    
- 事务是数据库在数据晚自习方面的一个功能。    
- 需要利用 InnoDB 或 BDB 存储引擎,对自动提交的特性支持完成。
- InnoDB被称为事务安全型引擎。

-- 事务开启    
START TRANSACTION; 或者 BEGIN;    
开启事务后,所有被执行的SQL语句均被认作当前事务内的SQL语句。

-- 事务提交    
COMMIT;

-- 事务回滚    
ROLLBACK;    
如果部分操作发生问题,映射到事务开启前。

-- 事务的特性    
1. 原子性(Atomicity)        
 事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。    
2. 一致性(Consistency)        
 事务前后数据的完整性必须保持一致。        
  - 事务开始和结束时,外部数据一致        
  - 在整个事务过程中,操作是连续的    
3. 隔离性(Isolation)        
 多个用户并发访问数据库时,一个用户的事务不能被其它用户的事物所干扰,多个并发事务之间的数据要相互隔离。    
4. 持久性(Durability)        
 一个事务一旦被提交,它对数据库中的数据改变就是永久性的。

-- 事务的实现    
1. 要求是事务支持的表类型    
2. 执行一组相关的操作前开启事务    
3. 整组操作完成后,都成功,则提交;如果存在失败,选择回滚,则会回到事务开始的备份点。

-- 事务的原理    
利用InnoDB的自动提交(autocommit)特性完成。    
普通的MySQL执行语句后,当前的数据提交操作均可被其他客户端可见。
而事务是暂时关闭“自动提交”机制,需要commit提交持久化数据操作。

-- 注意    
1. 数据定义语言(DDL)语句不能被回滚,比如创建或取消数据库的语句,和创建、取消或更改表或存储的子程序的语句。    
2. 事务不能被嵌套

-- 保存点    
SAVEPOINT 保存点名称 -- 设置一个事务保存点    
ROLLBACK TO SAVEPOINT 保存点名称 -- 回滚到保存点    
RELEASE SAVEPOINT 保存点名称 -- 删除保存点

-- InnoDB自动提交特性设置    
SET autocommit = 0|1;   0表示关闭自动提交,1表示开启自动提交。
- 如果关闭了,那普通操作的结果对其他客户端也不可见,需要commit提交后才能持久化数据操作。    
- 也可以关闭自动提交来开启事务。但与START TRANSACTION不同的是,
SET autocommit是永久改变服务器的设置,直到下次再次修改该设置。(针对当前连接)        
而START TRANSACTION记录开启前的状态,而一旦事务提交或回滚后就需要再次开启事务。(针对当前事务)

锁表

表锁定只用于防止其它客户端进行不正当地读取和写入
  MyISAM 支持表锁,
  InnoDB 支持行锁
-- 锁定    
  LOCK TABLES tbl_name [AS alias]
-- 解锁    
  UNLOCK TABLES

触发器

触发程序是与表有关的命名数据库对象,当该表出现特定事件时,将激活该对象。
监听:记录的增加、修改、删除。

-- 创建触发器CREATE TRIGGER trigger_name trigger_time trigger_event ON tbl_name FOR EACH ROW trigger_stmt    
参数:    
trigger_time是触发程序的动作时间。它可以是 before 或 after,以指明触发程序是在激活它的语句之前或之后触发。    
trigger_event指明了激活触发程序的语句的类型        
INSERT:将新行插入表时激活触发程序        
UPDATE:更改某一行时激活触发程序        
DELETE:从表中删除某一行时激活触发程序    
tbl_name:监听的表,必须是永久性的表,不能将触发程序与TEMPORARY表或视图关联起来。    
trigger_stmt:当触发程序激活时执行的语句。执行多个语句,可使用BEGIN...END复合语句结构

-- 删除
DROP TRIGGER [schema_name.]trigger_name可以使用old和new代替旧的和新的数据    
更新操作,更新前是old,更新后是new.    
删除操作,只有old.    
增加操作,只有new.

-- 注意    
1. 对于具有相同触发程序动作时间和事件的给定表,不能有两个触发程序。
-- 字符连接函数
concat(str1,str2,...])concat_ws(separator,str1,str2,...)

-- 分支语句if 条件 then    
执行语句
elseif 条件 then    
执行语句
else    
执行语句
end if;

-- 修改最外层语句结束符delimiter 
自定义结束符号    
SQL语句自定义结束符号delimiter ;     -- 修改回原来的分号

-- 语句块包裹
begin    
 语句块
end

-- 特殊的执行
1. 只要添加记录,就会触发程序。
2. Insert into on duplicate key update 语法会触发:    
  如果没有重复记录,会触发 before insert, after insert;    
  如果有重复记录并更新,会触发 before insert, before update, after update;    
  如果有重复记录但是没有发生更新,则触发 before insert, before update
3. Replace 语法 如果有记录,则执行 before insert, before delete, after delete, after insert

SQL编程

--// 局部变量 ----------

-- 变量声明    
declare var_name[,...] type [default value]    
这个语句被用来声明局部变量。要给变量提供一个默认值,请包含一个default子句。值可以被指定为一个表达式,不需要为一个常数。如果没有default子句,初始值为null。

-- 赋值    
使用 set 和 select into 语句为变量赋值。    
- 注意:在函数内是可以使用全局变量(用户自定义的变量)

--// 全局变量 ----------
-- 定义、赋值
set 语句可以定义并为变量赋值。
set @var = value;
也可以使用select into语句为变量初始化并赋值。这样要求select语句只能返回一行,但是可以是多个字段,就意味着同时为多个变量进行赋值,变量的数量需要与查询的列数一致。
还可以把赋值语句看作一个表达式,通过select执行完成。此时为了避免=被当作关系运算符看待,使用:=代替。(set语句可以使用= 和 :=)。
select @var:=20;select @v1:=id, @v2=name from t1 limit 1;
select * from tbl_name where @var:=30;
select into 可以将表中查询获得的数据赋给变量。    
-| select max(height) into @max_height from tb;    

-- 自定义变量名
为了避免select语句中,用户自定义的变量与系统标识符(通常是字段名)冲突,用户自定义变量在变量名前使用@作为开始符号。@var=10;
  - 变量被定义后,在整个会话周期都有效(登录到退出)

--// 控制结构 ----------
-- if语句
if search_condition then    
statement_list   
[elseif search_condition then    
statement_list]
...
[else    
statement_list]
end if;

-- case语句
CASE value WHEN [compare-value] THEN result
[WHEN [compare-value] THEN result ...]
[ELSE result]
END

-- while循环
[begin_label:] while search_condition do
statement_listend while 
[end_label];
- 如果需要在循环内提前终止 while循环,则需要使用标签;标签需要成对出现。    
 -- 退出循环        
  退出整个循环 leave        
  退出当前循环 iterate        
  通过退出的标签决定退出哪个循环

--// 内置函数 ----------
-- 数值函数
abs(x)          -- 绝对值 abs(-10.9) = 10
format(x, d)    -- 格式化千分位数值 format(1234567.456, 2) = 1,234,567.46
ceil(x)         -- 向上取整 ceil(10.1) = 11
floor(x)        -- 向下取整 floor (10.1) = 10
round(x)        -- 四舍五入去整
mod(m, n)       -- m%n m mod n 求余 10%3=1
pi()            -- 获得圆周率
pow(m, n)       -- m^n
sqrt(x)         -- 算术平方根
rand()          -- 随机数
truncate(x, d)  -- 截取d位小数

-- 时间日期函数
now(), current_timestamp();     -- 当前日期时间
current_date();                 -- 当前日期
current_time();                 -- 当前时间
date('yyyy-mm-dd hh:ii:ss');    -- 获取日期部分
time('yyyy-mm-dd hh:ii:ss');    -- 获取时间部分
date_format('yyyy-mm-dd hh:ii:ss', '%d %y %a %d %m %b %j'); -- 格式化时间
unix_timestamp();               - 获得unix时间戳
from_unixtime();                -- 从时间戳获得时间

-- 字符串函数
length(string)          -- string长度,字节
char_length(string)     -- string的字符个数
substring(str, position [,length])      -- 从str的position开始,取length个字符
replace(str ,search_str ,replace_str)   -- 在str中用replace_str替换search_str
instr(string ,substring)    -- 返回substring首次在string中出现的位置
concat(string [,...])   -- 连接字串
charset(str)            -- 返回字串字符集lcase(string)           -- 转换成小写
left(string, length)    -- 从string2中的左边起取length个字符
load_file(file_name)    -- 从文件读取内容
locate(substring, string [,start_position]) -- 同instr,但可指定开始位置
lpad(string, length, pad)   -- 重复用pad加在string开头,直到字串长度为length
ltrim(string)           -- 去除前端空格
repeat(string, count)   -- 重复count次
rpad(string, length, pad)   --在str后用pad补充,直到长度为length
rtrim(string)           -- 去除后端空格
strcmp(string1 ,string2)    -- 逐字符比较两字串大小

-- 流程函数
case when [condition] then result [when [condition] then result ...] [else result] end   多分支if(expr1,expr2,expr3)  双分支。

-- 聚合函数
count()sum();max();min();avg();group_concat()

-- 其他常用函数
md5();default();

--// 存储函数,自定义函数 ----------
-- 新建    
CREATE FUNCTION function_name (参数列表) RETURNS 返回值类型 
函数体    
- 函数名,应该合法的标识符,并且不应该与已有的关键字冲突。    
- 一个函数应该属于某个数据库,可以使用db_name.funciton_name的形式执行当前函数所属数据库,否则为当前数据库。 
- 参数部分,由"参数名"和"参数类型"组成。多个参数用逗号隔开。
- 函数体由多条可用的mysql语句,流程控制,变量声明等语句构成。
- 多条语句应该使用 begin...end 语句块包含。    
- 一定要有 return 返回值语句。

-- 删除    
DROP FUNCTION [IF EXISTS] function_name;

-- 查看    
SHOW FUNCTION STATUS LIKE 'partten'    
SHOW CREATE FUNCTION function_name;

-- 修改    
ALTER FUNCTION function_name 函数选项

--// 存储过程,自定义功能 ----------

-- 定义存储存储过程 
是一段代码(过程),存储在数据库中的sql组成。
一个存储过程通常用于完成一段业务逻辑,例如报名,交班费,订单入库等。
而一个函数通常专注与某个功能,视为其他程序服务的,需要在其他语句中调用函数才可以,而存储过程不能被其他调用,是自己执行 通过call执行。

-- 创建
CREATE PROCEDURE sp_name (参数列表)    
过程体
参数列表:
不同于函数的参数列表,
需要指明参数类型IN,
表示输入型OUT,
表示输出型INOUT,
表示混合型
注意,没有返回值。

/* 存储过程 */ ------------------
存储过程是一段可执行性代码的集合。相比函数,更偏向于业务逻辑。
调用:
CALL 过程名
-- 注意- 没有返回值。
- 只能单独调用,不可夹杂在其他语句中
-- 参数IN|OUT|INOUT 参数名 数据类型
IN 输入:
在调用过程中,将数据输入到过程体内部的参数
OUT     输出:在调用过程中,将过程体处理完的结果返回到客户端
INOUT   输入输出:既可输入,也可输出

-- 语法
CREATE PROCEDURE 过程名 (参数列表)
BEGIN    
过程体
END

用户和权限管理

-- root密码重置
1. 停止MySQL服务
2.  [Linux] 
/usr/local/mysql/bin/safe_mysqld --skip-grant-tables &  
[Windows] 
mysqld --skip-grant-tables
3. use mysql;
4. UPDATE `user` SET PASSWORD=PASSWORD("密码") WHERE `user` = "root";
5. FLUSH PRIVILEGES;

用户信息表:mysql.user
 -- 刷新权限FLUSH PRIVILEGES;

-- 增加用户
CREATE USER 用户名 IDENTIFIED BY [PASSWORD] 密码(字符串)      - 必须拥有mysql数据库的全局CREATE USER权限,或拥有INSERT权限。
 - 只能创建用户,不能赋予权限。    
 - 用户名,注意引号:如 'user_name'@'192.168.1.1'    
 - 密码也需引号,纯数字密码也要加引号    
 - 要在纯文本中指定密码,需忽略PASSWORD关键词。要把密码指定为由PASSWORD()函数返回的混编值,需包含关键字PASSWORD

-- 重命名用户
RENAME USER old_user TO new_user

-- 设置密码
SET PASSWORD = PASSWORD('密码')  -- 为当前用户设置密码
SET PASSWORD FOR 用户名 = PASSWORD('密码') -- 为指定用户设置密码

-- 删除用户
DROP USER 用户名

-- 分配权限/添加用户
GRANT 权限列表 ON 表名 TO 用户名 [IDENTIFIED BY [PASSWORD] 'password']    
- all privileges 表示所有权限    
- *.* 表示所有库的所有表    
- 库名.表名 表示某库下面的某表    
GRANT ALL PRIVILEGES ON `pms`.* TO 'pms'@'%' IDENTIFIED BY 'pms0817';

-- 查看权限
SHOW GRANTS FOR 用户名    
  -- 查看当前用户权限    
   SHOW GRANTS; 或 SHOW GRANTS FOR CURRENT_USER; 或 
   SHOW   GRANTS FOR CURRENT_USER();

-- 撤消权限
REVOKE 权限列表 ON 表名 FROM 用户名
REVOKE ALL PRIVILEGES, GRANT OPTION FROM 用户名   -- 撤销所有权限

-- 权限层级
-- 要使用GRANT或REVOKE,您必须拥有GRANT OPTION权限,并且您必须用于您正在授予或撤销的权限。
全局层级:全局权限适用于一个给定服务器中的所有数据库,mysql.user
  GRANT ALL ON *.*和 REVOKE ALL ON *.*只授予和撤销全局权限。
数据库层级:数据库权限适用于一个给定数据库中的所有目标,mysql.db, mysql.host    
   GRANT ALL ON db_name.*和REVOKE ALL ON db_name.*只授予和撤销数据库权限。
表层级:表权限适用于一个给定表中的所有列,mysql.talbes_priv 
   GRANT ALL ON db_name.tbl_name和REVOKE ALL ON db_name.tbl_name只授予和撤销表权限。
列层级:列权限适用于一个给定表中的单一列,mysql.columns_priv 
   当使用REVOKE时,您必须指定与被授权列相同的列。
   
-- 权限列表
ALL [PRIVILEGES]    -- 设置除GRANT OPTION之外的所有简单权限
ALTER   -- 允许使用ALTER TABLE
ALTER ROUTINE   -- 更改或取消已存储的子程序
CREATE  -- 允许使用CREATE TABLE
CREATE ROUTINE  -- 创建已存储的子程序
CREATE TEMPORARY TABLES     -- 允许使用CREATE TEMPORARY TABLE
CREATE USER     -- 允许使用CREATE USER, DROP USER, RENAME USER和REVOKE ALL PRIVILEGES。
CREATE VIEW     -- 允许使用CREATE VIEW
DELETE  -- 允许使用DELETE
DROP    -- 允许使用DROP TABLE
EXECUTE     -- 允许用户运行已存储的子程序
FILE    -- 允许使用SELECT...INTO OUTFILE和LOAD DATA INFILE
INDEX   -- 允许使用CREATE INDEX和DROP INDEX
INSERT  -- 允许使用INSERT
LOCK TABLES     -- 允许对您拥有SELECT权限的表使用LOCK TABLES
PROCESS     -- 允许使用SHOW FULL PROCESSLIST
REFERENCES  -- 未被实施
RELOAD  -- 允许使用FLUSH
REPLICATION CLIENT  -- 允许用户询问从属服务器或主服务器的地址
REPLICATION SLAVE   -- 用于复制型从属服务器(从主服务器中读取二进制日志事件)
SELECT  -- 允许使用SELECT
SHOW DATABASES  -- 显示所有数据库
SHOW VIEW   -- 允许使用SHOW CREATE VIEW
SHUTDOWN    -- 允许使用mysqladmin shutdown
SUPER   -- 允许使用CHANGE MASTER, KILL, PURGE MASTER LOGS和SET GLOBAL语句,mysqladmin debug命令;允许您连接(一次),即使已达到max_connections。
UPDATE  -- 允许使用UPDATE
USAGE   -- “无权限”的同义词
RANT OPTION    -- 允许授予权限

表维护

-- 分析和存储表的关键字分布
ANALYZE [LOCAL | NO_WRITE_TO_BINLOG] TABLE 表名 ...

-- 检查一个或多个表是否有错误
CHECK TABLE tbl_name [, tbl_name] ... [option] ...
option = {QUICK | FAST | MEDIUM | EXTENDED | CHANGED}

-- 整理数据文件的碎片
OPTIMIZE [LOCAL | NO_WRITE_TO_BINLOG] TABLE tbl_name [, tbl_name] ...

杂项

1. 可用反引号(`)为标识符(库名、表名、字段名、索引、别名)包裹,以避免与关键字重名!中文也可以作为标识符!
2. 每个库目录存在一个保存当前数据库的选项文件db.opt。
3. 注释:    
  单行注释 # 注释内容    
  多行注释 /* 注释内容 */    
  单行注释 -- 注释内容     (标准SQL注释风格,要求双破折号后加一空格符(空格、TAB、换行等))
4. 模式通配符:    
  _   任意单个字符    
  %   任意多个字符,甚至包括零字符    
  单引号需要进行转义 \'
5. CMD命令行内的语句结束符可以为 ";", "\G", "\g",仅影响显示结果。其他地方还是用分号结束。delimiter 可修改当前对话的语句结束符。
6. SQL对大小写不敏感
7. 清除已有语句:\c

如有错误或其它问题,欢迎小伙伴留言评论、指正。如有帮助,欢迎点赞+转发分享。

欢迎大家关注民工哥的公众号:民工哥技术之路
image.png

查看原文

认证与成就

  • 获得 22 次点赞
  • 获得 1 枚徽章 获得 0 枚金徽章, 获得 0 枚银徽章, 获得 1 枚铜徽章

擅长技能
编辑

开源项目 & 著作
编辑

(゚∀゚ )
暂时没有

注册于 2018-03-24
个人主页被 788 人浏览