菜单 学习猿地 - LMONKEY

VIP

开通学习猿地VIP

尊享10项VIP特权 持续新增

知识通关挑战

打卡带练!告别无效练习

接私单赚外块

VIP优先接,累计金额超百万

学习猿地私房课免费学

大厂实战课仅对VIP开放

你的一对一导师

每月可免费咨询大牛30次

领取更多软件工程师实用特权

入驻
1643
30

【最近面试整理】PHP 面试详解之技术篇

原创
05/13 14:22
阅读数 2575

最近在面试,总结总结遇到的面试题.

基础问题

LRU算法

[内存管理]的一种页面置换算法,对于在内存中但又不用的[数据块](内存块)叫做LRU,操作系统会根据哪些数据属于LRU而将其移出内存而腾出空间来加载另外的数据,常用于页面置换算法,是为虚拟页式存储管理服务的。

设计原则
如果一个数据在最近一段时间没有被访问到,那么在将来它被访问的可能性也很小。也就是说,当限定的空间已存满数据时,应当把最久没有被访问到的数据淘汰。

实现LRU

  • 1.用一个数组来存储数据,给每一个数据项标记一个访问时间戳,每次插入新数据项的时候,先把数组中存在的数据项的时间戳自增,并将新数据项的时间戳置为0并插入到数组中。每次访问数组中的数据项的时候,将被访问的数据项的时间戳置为0。当数组空间已满时,将时间戳最大的数据项淘汰。
  • 2.利用一个链表来实现,每次新插入数据的时候将新数据插到链表的头部;每次缓存命中(即数据被访问),则将数据移到链表头部;那么当链表满的时候,就将链表尾部的数据丢弃。
  • 3.利用链表和hashmap。当需要插入新的数据项的时候,如果新数据项在链表中存在(一般称为命中),则把该节点移到链表头部,如果不存在,则新建一个节点,放到链表头部,若缓存满了,则把链表最后一个节点删除即可。在访问数据的时候,如果数据项在链表中存在,则把该节点移到链表头部,否则返回-1。这样一来在链表尾部的节点就是最近最久未访问的数据项。
  • 对于第一种方法,需要不停地维护数据项的访问时间戳,另外,在插入数据、删除数据以及访问数据时,时间复杂度都是O(n)。对于第二种方法,链表在定位数据的时候时间复杂度为O(n)。所以在一般使用第三种方式来是实现LRU算法。

http协议

一个HTTP请求报文由请求行(request line)、请求头部(header)、空行和请求数据4个部分组成

请求行

请求行由请求方法字段、URL字段和HTTP协议版本字段3个字段组成,它们用空格分隔。

GET

常见的一种请求方式,当客户端要从服务器中读取文档时,当点击网页上的链接或者通过在浏览器的地址栏输入网址来浏览网页 的,使用的都是GET方式。GET方法要求服务器将URL定位的资源放在响应报文的数据部分,回送给客户端。使用GET方法时,请 求参数和对应的值附加在URL后面,利用一个问号(“?”)代表URL的结尾与请求参数的开始,传递参数长度受限制。例 如,/index.jsp?id=100&op=bind,这样通过GET方式传递的数据直接表示在地址中,所以我们可以把请求结果以链接的形式发 送给好友

POST

使用POST方法可以允许客户端给服务器提供信息较 多。POST方法将请求参数封装在HTTP请求数据中,以名称/值的形式出现,可以传输大量数据,这样POST方式对传送的数据大 小没有限制,而且也不会显示在URL中,POST方式请求行中不包含数据字符串,这些数据保存在”请求内容”部分,各数据之间也是使用”&”符号隔开。POST方 式大多用于页面的表单中。因为POST也能完成GET的功能,因此多数人在设计表单的时候一律都使用POST方式,其实这是一个 误区。

HEAD

HEAD就像GET,只不过服务端接受到HEAD请求后只返回响应头,而不会发送响应内容。当我们只需要查看某个页面的状态的时 候,使用HEAD是非常高效的,因为在传输的过程中省去了页面内容。

请求头部

  • 请求头部由关键字/值对组成,每行一对,关键字和值用英文冒号“:”分隔。
  • 请求头部通知服务器有关于客户端请求的信息,典型的 请求头有:
    • User­Agent:产生请求的浏览器类型。
    • Accept:客户端可识别的内容类型列表。
    • Host:请求的主机名,允许多个域名同处一个IP地址,即虚拟主机。

空行

后一个请求头之后是一个空行,发送回车符和换行符,通知服务器以下不再有请求头。

请求数据

请求数据不在GET方法中使用,而是在POST方法中使用。POST方法适用于需要客户填写表单的场合。与请求数据相关的常使 用的请求头是Content­Type和Content­Length。

HTTP相应报文

HTTP响应也由三个部分组成,分别是:状态行、消息响应头、响应正文。

状态码

  • 1xx:指示信息表示请求已接收,继续处理。
  • 2xx:成功表示请求已被成功接收、理解、接受。
  • 3xx:重定向要完成请求必须进行更进一步的操作。
  • 4xx:客户端错误请求有语法错误或请求无法实现。
  • 5xx:服务器端错误服务器未能实现合法的请求。

常见状态代码、状态描述的说明如下

  • 200 OK:客户端请求成功。
  • 400 Bad Request:客户端请求有语法错误,不能被服务器所理解。
  • 401 Unauthorized:请求未经授权,这个状态代码必须和WWW­Authenticate报头域一起使用。
  • 403 Forbidden:服务器收到请求,但是拒绝提供服务。
  • 404 Not Found:请求资源不存在,举个例子:输入了错误的URL。 500 Internal Server Error:服务器发生不可预期的错误。
  • 503 Server Unavailable:服务器当前不能处理客户端的请求,一段时间后可能恢复正常,举个例子:HTTP/1.1  200 OK(CRLF)。

GET和POST

  • GET提交,请求的数据会附在URL之后(就是把数据放置在HTTP协议头<request­line>中),以?分割URL和传输数据,多个 参数用&连接;如果数 据是英文字母/数字,原样发送,如果是空格,转换为+,如果是中文/其他字符,则直接把字符串用BASE64加密,,GET提交的数据会在地址栏中显示出来,而POST提交,地址栏不会改变, POST提交:把提交的数据放置在是HTTP包的包体<request body>中。
  • HTTP协议没有对传输的数据大小进行限制,HTTP协议规范也没有对URL长度进行限制。 而在实际开发中存在的限制 主要有:
    •  GET:特定浏览器和服务器对URL长度有限制,例如IE对URL长度的限制是2083字节(2K+35)。对于其他浏览器,如 Netscape、FireFox等,理论上没有长度限制,其限制取决于操作系统的支持。   
    •   因此对于GET提交时,传输数据就会受到URL长度的限制。    
    •   POST:由于不是通过URL传值,理论上数据不受限。但实际各个WEB服务器会规定对post提交数据大小进行限制,Apache、 IIS6都有各自的配置。
  •   POST的安全性要比GET的安全性高。注意:这里所说的安全性和上面GET提到的“安全”不是同个概念。上面“安全”的含义仅仅 是不作数据修改,而这里安全的含义是真正的Security的含义,比如:通过GET提交数据,用户名和密码将明文出现在URL上,因 为(1)登录页面有可能被浏览器缓存, (2)其他人查看浏览器的历史纪录,那么别人就可以拿到你的账号和密码了,

数据类型

  • 标量 :boolean (布尔型)integer (整型)float (浮点型, 也称作 double) string (字符串)
  • 复合 :数组 对象
  • 特殊 :NULL resource(资源)

常见的header头

  • // 信息型状态码,提示目前为止一切正常,客户端应该继续请求, 如果已完成请求则忽略.
    • header('HTTP/1.1 100 OK');
  • //通知浏览器 页面不存在
    • header('HTTP/1.1 404 Not Found');
  • //资源被永久的重定向 301 ;302:临时重定向(该资源临时被改变位置)
    • header('HTTP/1.1 301 Moved Permanently');
  • //跳转到一个新的地址
  • //延迟转向也就是隔几秒跳转
  • 内容类型

  • //网页编码
    • header('Content-Type: text/html;charset=utf-8');
  • //纯文本格式
    • header('Content-Type:text/plain');
  • //JPG、JPEG
    • header('Content-Type:image/jpeg');
  • //ZIP文件
    • header('Content-Type:application/zip');
  • //PDF文件
    • header('Content-Type:application/pdf');
  • //音频文件
    • header('Content-Type: ');
  • //css文件
    • header('Content-type:text/css');
  • 声明一个下载的文件

    • header('Content-Type:application/octet-stream');
    • header('Content-Disposition:attachment;filename="ITblog.zip"');
  • 显示一个需要验证的登陆对话框

    • header('HTTP/1.1 401Unauthorized');
    • header('WWW-Authenticate:Basic realm="TopSecret"');

魔术方法

  • __autoload() 类文件自动加载函数
  • __construct() 构造函数,PHP将在对象创建时调用这个方法
  • __destruct()  析构函数,PHP将在对象被销毁前(即从内存中清除前)调用这个方法
  • __call() 当所调用的成员方法不存在(或者没有权限)该类时调用,用于对错误后做一些操作或者提示信息
  • __clone() 该函数在对象克隆时自动调用,其作用是对克隆的副本做一些初始化操作
  • __get() 当所对象所调用的成员属性未声明或者级别为private或者protected等时,我们可以在这个函数里进行自己的一些操作
  • __set() 当所对未声明或者级别为private或者protected等进行赋值时调用此函数,我们可以在这个函数里进行自己的一些操作
  • __isset() 当对一个未声明或者访问级别受限的成员属性调用isset函数时调用此函数,共用户做一些操作
  • __unset() 当对一个未声明或者访问级别受限的成员属性调用unset函数时调用此函数,共用户做一些操作
  • __toString()函数 该函数在将对象引用作为字符串操作时自动调用,返回一个字符串
  • __sleep()函数 该函数是在序列化时自动调用的,序列化这里可以理解成将信息写如文件中更长久保存
  • __wakeup()函数 该魔术方法在反序列化的时候自动调用,为反序列化生成的对象做一些初始化操作
  • invoke()函数,当尝试以调用函数的方式调用一个对象时,invoke 方法会被自动调用。
  • _callStatic()函数,它的工作方式类似于 call() 魔术方法,callStatic() 是为了处理静态方法调用,

超全局变量

  • $GLOBALS是PHP的一个超级全局变量组,在一个PHP脚本的全部作用域中都可以访问。是一个包含了全部变量的全局组合数组。变量的名字就是数组的键。
  • $_SERVER是一个包含了诸如头信息(header)、路径(path)、以及脚本位置(script locations)等等信息的数组。这个数组中的项目由 Web 服务器创建。不能保证每个服务器都提供全部项目;服务器可能会忽略一些,或者提供一些没有在这里列举出来的项目。
  • $_REQUEST 用于收集HTML表单提交的数据。
  • $_POST 被广泛应用于收集表单数据,在HTML form标签的指定该属性:"method="post"。
  • $_GET 同样被广泛应用于收集表单数据,在HTML form标签的指定该属性:"method="get"。
  • $_COOKIE 经由 HTTP Cookies 方法提交至脚本的变量
  • $_SESSION 当前注册给脚本会话的变量。类似于旧数组 $HTTP_SESSION_VARS 数组。
  • $_FILES 经由 HTTP POST 文件上传而提交至脚本的变量。类似于旧数组 $HTTP_POST_FILES 数组。
  • $_ENV 执行环境提交至脚本的变量。类似于旧数组 $HTTP_ENV_VARS 数组。

http 和 https 区别

1、https协议需要到ca申请证书,一般免费证书较少,因而需要一定费用。
2、http是超文本传输协议,信息是明文传输,https则是具有安全性的ssl加密传输协议。
3、http和https使用的是完全不同的连接方式,用的端口也不一样,前者是80,后者是443。
4、http的连接很简单,是无状态的;HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,比http协议安全。

hash取模算法

hash算法
分布式cahce系统中的一致性hash算法应该满足以下几个方面

平衡性(Balance)

哈希的结果能够尽可能分布到所有的缓冲中去,这样可以使得所有的缓冲空间都得到利用。

单调性(Monotonicity)

如果已经有一些内容通过哈希分派到了相应的缓冲中,又有新的缓冲区加入到系统中,那么哈希的结果应能够保证原有已分配的内容可以被映射到新的缓冲区中去,而不会被映射到旧的缓冲集合中的其他缓冲区。简单的哈希算法往往不能满足单调性的要求,如最简单的线性哈希:x = (ax + b) mod (P),在上式中,P表示全部缓冲的大小。不难看出,当缓冲大小发生变化时(从P1到P2),原来所有的哈希结果均会发生变化,从而不满足单调性的要求。哈希结果的变化意味着当缓冲空间发生变化时,所有的映射关系需要在系统内全部更新。而在P2P系统内,缓冲的变化等价于Peer加入或退出系统,这一情况在P2P系统中会频繁发生,因此会带来极大计算和传输负荷。单调性就是要求哈希算法能够应对这种情况。

分散性(Spread)

在分布式环境中,终端有可能看不到所有的缓冲,而是只能看到其中的一部分。当终端希望通过哈希过程将内容映射到缓冲上时,由于不同终端所见的缓冲范围有可能不同,从而导致哈希的结果不一致,最终的结果是相同的内容被不同的终端映射到不同的缓冲区中。这种情况显然是应该避免的,因为它导致相同内容被存储到不同缓冲中去,降低了系统存储的效率。分散性的定义就是上述情况发生的严重程度。好的哈希算法应能够尽量避免不一致的情况发生,也就是尽量降低分散性。

负载(Load)

负载问题实际上是从另一个角度看待分散性问题。既然不同的终端可能将相同的内容映射到不同的缓冲区中,那么对于一个特定的缓冲区而言,也可能被不同的用户映射为不同的内容。与分散性一样,这种情况也是应当避免的,因此好的哈希算法应能够尽量降低缓冲的负荷。

平滑性(Smoothness)

平滑性是指缓存服务器的数目平滑改变和缓存对象的平滑改变是一致的。

hash取模算法

常用的算法是对hash结果取余数 (hash() mod N):对机器编号从0到N-1,按照自定义的hash()算法,对每个请求的hash()值按N取模,得到余数i,然后将请求分发到编号为i的机器。但这样的算法方法存在致命问题,如果某一台机器宕机,那么应该落在该机器的请求就无法得到正确的处理,这时需要将当掉的服务器从算法从去除,此时候会有(N-1)/N的服务器的缓存数据需要重新进行计算;

为何是 (N-1)/N 呢

  • 比如有 3 台机器,hash值 1-6 在这3台上的分布就是:
    • host 1: 1 4
    • host 2: 2 5
    • host 3: 3 6
  • 如果挂掉一台,只剩两台,模数取 2 ,那么分布情况就变成:
    • host 1: 1 3 5
    • host 2: 2 4 6
  • 可以看到,还在数据位置不变的只有2个: 1,2,位置发生改变的有4个,占共6个数据的比率是 4/6 = 2/3。

虚拟节点

  • 对于hash取模算法,当某个节点的服务器坏了,算有压力将转到下个节点的服务器。那我们考虑是否能够将压力转到其他节点。
  • 虚拟节点即:N个真实节点,把每个真实节点映射成M个虚拟节点, 再把M* N个虚拟节点,
  • 散列在圆环上. 各真实节点对应的虚拟节点相互交错分布,这样,某真实节点down后,则把其影响平均分担到其他所有节点上。

访问方法:

如果有一个写入缓存的请求,其中Key值为K,计算器hash值Hash(K), Hash(K) 对应于环中的某一个点,如果该点对应没有映射到具体的某一个机器节点,那么顺时针查找,直到第一次找到有映射机器的节点,该节点就是确定的目标节点,如果超过了2^32仍然找不到节点,则命中第一个机器节点。

一致性哈希算法
一种特殊的哈希算法,目前主要应用于分布式缓存当中,可以有效地解决分布式存储结构下动态增加和删除节点所带来的问题。

  1. 一致性Hash算法是对2^32取模,2^32个点组成的圆环称为Hash环。根据服务节点的IP或者机器名称进行哈希,就能确定每台机器就能确定其在哈希环上的位置;
  2. 将数据key使用相同的函数Hash计算出哈希值,并确定此数据在环上的位置,从此位置沿环顺时针“行走”,第一台遇到的服务器就是其应该定位到的服务器。
  3. 添加、删除节点的时候,只影响相邻一个节点的数据,其他节点的数据不影响。具有较好的扩展性和容错性。
  4. 为了防止数据分布不均匀,可以应用虚拟节点来映射物理节点。

hash槽

Redis准备了16384个hash槽,类似于Memcached Hash环上的一个个位置。 这16384个hash槽被分配给不同节点,存放数据时,根据数据的key计算出所在的槽,再根据槽找到对应的机器。hash函数为:CRC16(key) % 16384。

为什么是16384?

我们需要维护节点和槽之间的映射关系,每个节点需要知道自己有哪些槽,并且需要在结点之间传递这个消息。为了节省存储空间,每个节点用一个Bitmap来存放其对应的槽: 2k = 210248 = 16384,也就是说,每个结点用2k的内存空间,总共16384个比特位,就可以存储该结点对应了哪些槽。然后这2k的信息,通过Gossip协议,在结点之间传递。

php远程获取文件

第一种:file_get_contents

$url = '![](file:///C:\Users\ASUS\AppData\Roaming\Tencent\QQ\Temp\%W@GJ$ACOF(TYDYECOKVDYB.png)http://www.xxx.com/';
$contents = file_get_contents($url)

curl
第二种使用 curl  

$url = “![](file:///C:\Users\ASUS\AppData\Roaming\Tencent\QQ\Temp\%W@GJ$ACOF(TYDYECOKVDYB.png)http://www.xxx.com/”;
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $timeout);\
$contents = curl_exec($ch);

第三种
3.fopen->fread->fclose

$handle = fopen (“![](file:///C:\Users\ASUS\AppData\Roaming\Tencent\QQ\Temp\%W@GJ$ACOF(TYDYECOKVDYB.png)http://www.xxx.com/”, “rb”);
$data = fread($handle, 8192)

一般我们使用  curl 来进行 文章的抓取   他可以设置 超时时间  比较方便

怎么解决请求被劫持的问题

 推荐用https,充分测试无问题以后在服务器端配置HSTS头,但即使这样也还不能解决首次访问时的劫持问题,不过已经能解决绝大部分的问题了。如果是个人网站,建议直接用sha2的证书,sha1的证书已经不安全了,双证书费用和维护成本都不低,何况第三方浏览器现在是流量的大头

用户输入url到页面显示经历了哪些

通过DNS找对应的IP 

  • 找浏览器缓存,浏览器会保存一段时间你之前访问过的一些网址的DNS信息
  • 通过dns 找  域 名对应ip  本地的  host   通过dns 找  域名对应ip 
  • 接着会发送一个请求到路由器上,然后路由器在自己的路由器缓存上查找记录,路由器一 般也存有DNS信息。
  • 通过dns 找  域名对应ip   你的ISP的DNS服务器会将请求发向根域名服务器进行搜通过dns 找  域名对应ip 

通过IP 向对应的web服务器发送请求

浏览器终于得到了IP以后,浏览器接着给这个IP的服务器发送了一个http请求,方式为get,例如访问nbut.cn

服务器接受到请求后,如果是nginx 通过nginx的location匹配 后缀是.php的文件, 然后如果是,则将这个请求转发到127.0.0.1:9000的个服务,而9000 这个服务是PHP, 把请求交给php来进行处理,php处理完毕,把处理的数据发送给nginx ,nginx把数据再 相应,并发送给浏览器

服务器收到浏览器的请求以后(其实是WEB服务器接收到了这个请求,WEB服务器有iis、 apache等 会解析这个请求(读请求头),然后生成一个响应头和具体响应内容 如果是个静态页面,那么基本上到这一步就没了

页面还有其他资源(css img js ) 继续重复执行
主页(index)页面框架传送过来以后,浏览器还要继续向服务器发送请求,请求的内容是 主页里面包含的一些资源,如图片,视频,css样式等等

mysql

建表,修改字段,字段类型

链接:博客:MySQL 细致总结之基础篇

事务

事务(transaction)是作为一个单元的一组有序的数据库操作。如果组中的所有操作都成功,则认为事务成功,即使只有一个操作失败,事务也不成功。如果所有操作完成,事务则提交,其修改将作用于所有其他数据库进程。如果一个操作失败,则事务将回滚,该事务所有操作的影响都将取消。

事务控制语句

  • BEGIN 或 START TRANSACTION 显式地开启一个事务;
  • COMMIT 也可以使用 COMMIT WORK,不过二者是等价的。COMMIT 会提交事务,并使已对数据库进行的所有修改成为永久性的;
  • ROLLBACK 也可以使用 ROLLBACK WORK,不过二者是等价的。回滚会结束用户的事务,并撤销正在进行的所有未提交的修改;
  • SAVEPOINT identifier,SAVEPOINT 允许在事务中创建一个保存点,一个事务中可以有多个 SAVEPOINT;
  • RELEASE SAVEPOINT identifier 删除一个事务的保存点,当没有指定的保存点时,执行该语句会抛出一个异常;
  • ROLLBACK TO identifier 把事务回滚到标记点;
  • SET TRANSACTION 用来设置事务的隔离级别。
  • InnoDB 存储引擎提供事务的隔离级别有READ UNCOMMITTED、READ COMMITTED、REPEATABLE READ 和 SERIALIZABLE。

事务的处理方法

  • 用 BEGIN, ROLLBACK, COMMIT来实现
    • BEGIN 开始一个事务
    • ROLLBACK 事务回滚
    • COMMIT 事务确认
  • 直接用 SET 来改变 MySQL 的自动提交模式:
    • SET AUTOCOMMIT=0 禁止自动提交
    • SET AUTOCOMMIT=1 开启自动提交


php实现事务

mysql_query("COMMIT");//提交事务
mysql_query("ROLLBACK");//至少有一条sql语句执行错误,事务回滚
mysql_query("END");//事务结束

mysqli

$conn = mysqli_connect('127.0.0.1', 'root', 'root') or die(mysqli_error());  //连接数据库 
mysqli_query($conn, 'BEGIN');    //开启事务
mysqli_query($conn, 'COMMIT');    //提交事务
mysqli_query($conn, 'rollBack');    //回滚事务

php的PDO的实现方式 $pdo为实例化PDO对象

//1.开启事务 $pdo->beginTransaction();
//提交事务  $pdo->commit();
//回滚事务  $pdo->rollBack();
//结束事务  $pdo->end();

TP实现事务
无model

// 启动事务  Db::startTrans();
// 提交事务  Db::commit();  
// 回滚事务  Db::rollback();

有model

$model=M('myself_audio');
//事务开启  $model->startTrans();
//事务提交  $model->commit();
 // 事务回滚   $model->rollback();

laravel框架实现事务
想要在一个数据库事务中运行一连串操作,可以使用DB门面的transaction方法,如果事务闭包中抛出异常,事务将会自动回滚。如果闭包执行成功,事务将会自动提交。

DB::transaction(function () {
    DB::table('users')->update(['votes' => 1]);
    DB::table('posts')->delete();
});

手动使用事务

DB::beginTransaction();  //开启事务
DB::commit();  //事务提交
DB::rollBack();  // 事务回滚 

四大特性

  • 原子性(Autmic):事务在执行性,要做到“要么不做,要么全做!”,就是说不允许事务部分得执行。即使因为故障而使事务不能完成,在rollback时也要消除对数据库得影响!
  • 一致性(Consistency):事务得操作应该使使数据库从一个一致状态转变倒另一个一致得状态!就拿网上购物来说吧,你只有即让商品出库,又让商品进入顾客得购物篮才能构成事务!
  • 隔离性(Isolation):如果多个事务并发执行,应象各个事务独立执行一样!
  • 持久性(Durability):一个成功执行得事务对数据库得作用是持久得,即使数据库应故障出错,也应该能够恢复!

四种隔离级别

  • 读未提交(read-uncommitted)所有事务都可以看到其他未提交事务的执行结果。很少用于实际应用,因为它的性能也不比其他级别好多少。会产生脏读,不可重复读以及幻读
  • 不可重复读(read-committed)这是大多数数据库系统的默认隔离级别(但不是MySQL默认的)。它满足了隔离的简单定义:一个事务只能看见已经提交事务所做的改变。会产生不可重复读,幻读
  • 可重复读(repeatable-read)这是MySQL的默认事务隔离级别,它确保同一事务的多个实例在并发读取数据时,会看到同样的数据行。不过理论上,会导致另一个棘手的问题:幻读 (Phantom Read)。简单的说,幻读指当用户读取某一范围的数据行时,另一个事务又在该范围内插入了新行,当用户再读取该范围的数据行时,会发现有新的“幻影” 行。InnoDB和Falcon存储引擎通过多版本并发控制(MVCC,Multiversion Concurrency Control)机制解决了该问题。
  • 串行化(serializable)这是最高的隔离级别,它通过强制事务排序,使之不可能相互冲突,从而解决幻读问题。简言之,它是在每个读的数据行上加上共享锁。在这个级别,可能导致大量的超时现象和锁竞争。

操作

  • 查看当前会话隔离级别
    select @@tx_isolation; 
  • 查看系统当前隔离级别
    select @@global.tx_isolation; 
  • 设置当前会话隔离级别
    set session transaction isolatin level repeatable read; 
  • 设置系统当前隔离级别
    set global transaction isolation level repeatable read; 
  • 命令行,开始事务时
    set autocommit=off 或者 start transaction 

锁机制

锁是计算机协调多个进程或纯线程并发访问某一资源的机制。在数据库中,除传统的计算资源(CPU、RAM、I/O)的争用以外,数据也是一种供许多用户共享的资源。如何保证数据并发访问的一致性、有效性是所在有数据库必须解决的一个问题,锁冲突也是影响数据库并发访问性能的一个重要因素。从这个角度来说,锁对数据库而言显得尤其重要,也更加复杂。

mysql的三种锁

  • 表级锁:开销小,加锁快;不会出现死锁;锁定粒度大,发生锁冲突的概率最高,并发度最低。
  • 行级锁:开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高。
  • 页面锁:开销和加锁时间界于表锁和行锁之间;会出现死锁;锁定粒度界于表锁和行锁之间,并发度一般

表级锁的锁模式(MyISAM)

  • MySQL表级锁有两种模式:表共享锁(Table Read Lock)和表独占写锁(Table Write Lock)。
    • 对MyISAM的读操作,不会阻塞其他用户对同一表请求,但会阻塞对同一表的写请求;
    • 对MyISAM的写操作,则会阻塞其他用户对同一表的读和写操作;
    • MyISAM表的读操作和写操作之间,以及写操作之间是串行的。
  • 当一个线程获得对一个表的写锁后,只有持有锁线程可以对表进行更新操作。其他线程的读、写操作都会等待,直到锁被释放为止。

对MyISAM表的读操作,不会阻塞其他用户对同一表的读请求,但会阻塞对同一表的写请求;对MyISAM表的写操作,则会阻塞其他用户对同一表的读和写请求;MyISAM表的读和写操作之间,以及写和写操作之间是串行的!(当一线程获得对一个表的写锁后,只有持有锁的线程可以对表进行更新操作。其他线程的读、写操作都会等待,直到锁被释放为止。)

如何加表锁

MyISAM在执行查询语句(SELECT)前,会自动给涉及的所有表加读锁,在执行更新操作(UPDATE、DELETE、INSERT等)前,会自动给涉及的表加写锁,这个过程并不需要用户干预,因此用户一般不需要直接用LOCK TABLE命令给MyISAM表显式加锁。

一些命令

查询表级锁争用情况

show status like 'table%';

如何加表锁
加读锁/表共享锁(Table Read Lock):

lock table tbl_name read;

加写锁/表独占写锁(Table Write Lock):

lock table tbl_name write;

释放锁:

unlock tables;

并发锁

  • 在一定条件下,MyISAM也支持查询和操作的并发进行。
  • MyISAM存储引擎有一个系统变量concurrent_insert,专门用以控制其并发插入的行为,其值分别可以为0、1或2。
    • 当concurrent_insert设置为0时,不允许并发插入。
    • 当concurrent_insert设置为1时,如果MyISAM允许在一个读表的同时,另一个进程从表尾插入记录。这也是MySQL的默认设置。
    • 当concurrent_insert设置为2时,无论MyISAM表中有没有空洞,都允许在表尾插入记录,都允许在表尾并发插入记录。
  • 可以利用MyISAM存储引擎的并发插入特性,来解决应用中对同一表查询和插入锁争用。例如,将concurrent_insert系统变量为2,总是允许并发插入;同时,通过定期在系统空闲时段执行OPTIONMIZE TABLE语句来整理空间碎片,收到因删除记录而产生的中间空洞。

MyISAM的锁调度

  • 大量的更新操作会造成查询操作很难获得读锁,从而可能永远阻塞。我们可以通过一些设置来调节MyISAM的调度行为。
    • 通过指定启动参数low-priority-updates,使MyISAM引擎默认给予读请求以优先的权利。
    • 通过执行命令SET LOW_PRIORITY_UPDATES=1,使该连接发出的更新请求优先级降低。
    • 通过指定INSERT、UPDATE、DELETE语句的LOW_PRIORITY属性,降低该语句的优先级。
  • 虽然上面3种方法都是要么更新优先,要么查询优先的方法,但还是可以用其来解决查询相对重要的应用(如用户登录系统)中,读锁等待严重的问题。
  • 另外,MySQL也提供了一种折中的办法来调节读写冲突,即给系统参数max_write_lock_count设置一个合适的值,当一个表的读锁达到这个值后,MySQL变暂时将写请求的优先级降低,给读进程一定获得锁的机会。
  • 注意:一些需要长时间运行的查询操作,也会使写进程“饿死”!因此,应用中应尽量避免出现长时间运行的查询操作,不要总想用一条SELECT语句来解决问题。因为这种看似巧妙的SQL语句,往往比较复杂,执行时间较长,在可能的情况下可以通过使用中间表等措施对SQL语句做一定的“分解”,使每一步查询都能在较短时间完成,从而减少锁冲突。如果复杂查询不可避免,应尽量安排在数据库空闲时段执行,比如一些定期统计可以安排在夜间执行。

INNODB锁

并发问题

1、脏读:事务A读取了事务B更新的数据,然后B回滚操作,那么A读取到的数据是脏数据
2、不可重复读:事务 A 多次读取同一数据,事务 B 在事务A多次读取的过程中,对数据作了更新并提交,导致事务A多次读取同一数据时,结果 不一致。
3、幻读:系统管理员A将数据库中所有学生的成绩从具体分数改为ABCDE等级,但是系统管理员B就在这个时候插入了一条具体分数的记录,当系统管理员A改结束后发现还有一条记录没有改过来,就好像发生了幻觉一样,这就叫幻读。
4、更新丢失(Lost Update):当两个或多个事务选择同一行,然后基于最初选定的值更新该行时,由于每个事务都不知道其他事务的存在,就会发生丢失更新问题--最后的更 新覆盖了由其他事务所做的更新。例如,两个编辑人员制作了同一文档的电子副本。每个编辑人员独立地更改其副本,然后保存更改后的副本,这样就覆盖了原始文 档。最后保存其更改副本的编辑人员覆盖另一个编辑人员所做的更改。如果在一个编辑人员完成并提交事务之前,另一个编辑人员不能访问同一文件,则可避免此问题。
不可重复读的和幻读很容易混淆,不可重复读侧重于修改,幻读侧重于新增或删除。解决不可重复读的问题只需锁住满足条件的行,解决幻读需要锁表

查看InonoD行锁争用情况

show status like 'innodb_row_lock%';

打开监视器

CREATE TABLE innodb_monitor(a INT) ENGINE=INNODB;
Show innodb status\G;

停止监视器

DROP TABLE innodb_monitor;

打开监视器以后,默认情况下每15 秒会向日志中记录监控的内容,如果长时间打开会导致.err 文件变得非常的巨大,所以用户在确认问题原因之后,要记得删除监控表以关闭监视器,或者通过使用“--console”选项来启动服务器以关闭写日志文件。

锁机制

共享锁和排它锁

  • InnoDB实现了标准的行级锁,有两种类型:共享锁(S锁)允许事务持有锁并读取一行记录和排它锁(X锁)允许事务持有并更新或者删除一行记录。
  • 如果事务T1持有行记录r的上一个共享(S)锁,接着另一个事务T2请求行记录r上的锁
  • 如果事务T1持有r上的排它(S)锁,事务T2请求任意一个锁都不立刻获得。此时,T2必须等待T1释放r上的锁。

上共享锁的写法:lock in share mode

例如:

select  math from zje where math>60 lock in share mode;

上排它锁的写法:for update
例如:

select math from zje where math >60 for update;

注意

  • 行锁必须有索引才能实现,否则会自动锁全表,那么就不是行锁了。
  • 两个事务不能锁同一个索引,例如:
    • 事务A先执行:
      select math from zje where math>60 for update;
    • 事务B再执行:
      select math from zje where math<60 for update;
    • 这样的话,事务B是会阻塞的。如果事务B把 math索引换成其他索引就不会阻塞,
    • 但注意,换成其他索引锁住的行不能和math索引锁住的行有重复。
  • insert ,delete , update在事务中都会自动默认加上排它锁。

意向锁

  • InnoDB支持多粒度的锁,多粒度的锁允许行锁和表锁共存。为了在实际中实现多粒度的锁,使用了另外一种类型的锁:意向锁。在InnoDB中,意向锁是表级锁,它标明了一个事务在将在表中行记录上使用锁的类型(共享锁和排它锁)。在InnoDB中使用了两种意向锁(假设事务T请求了表t上的锁)
    • 意向共享锁(IS):事务T打算在表t的某些行设置S共享锁。
    • 意向排它锁(IX)::事务T打算在表t的某些行设置T排他锁。
  • 意向锁的协议如下:
    • 一个事务获得表t中某行的共享锁之前,必须先获得t表上的IS意向共享锁或者更强类型的锁。
    • 一个事务获得表t中某行的排他锁之前, 必须先获得t表上的IX意向排它锁。
  • IX意向共享锁和IS意向排它锁的主要目的是显示有人锁定了一行记录或者准备锁定一行记录。

记录锁

  • 记录锁是索引记录上的锁。SELECT c1 FOR UPDATE FROM t WHERE c1 = 10;可以防止任何其它事务插入、 更新或删除t.c1等于10的行。
  • 记录锁锁定的是索引,即使表没有定义索引。这种情况下,InnoDB会创建一个隐式的聚簇索引并将此索引用作记录锁。

间隙锁

  • 间隙锁会锁定一个范围内的索引,或者是某索引记录之前或者之后的索引。例如,SELECT c1 FOR UPDATE FROM t WHERE c1 BETWEEN 10 and 20;可以防止其他事务将一个t.c1等于15的值插入到表中,无论列中是否有该记录,因为该范围内所有可能存在的值都被锁定。
  • 间隙有可能跨越单索引值,多个索引值,甚至空值。
  • 在InnoDB中,间隙锁是“完全被抑制”的,意思是它只阻止其它事务给往间隙中插入。它们不阻止其他事务在同一个间隙上获得间隙锁。因此,一个间隙X锁和一个间隙S锁效果一样

Next-Key锁

  • Next-Key锁是索引记录上的记录锁与此索引记录之前间隙上的间隙锁两者的结合。InnoDB在查找或扫描索引时使用行级锁,给遇到的索引记录上设置共享锁或者排它锁。因此,行级锁就是索引记录锁。一个索引记录上的next-key锁也影响在该索引之间的“间隙”。也就是说,next-key是一个索引记录锁加上在该索引记录之前间隙上的间隙锁。如果一个会话在记录R的索引上持有一个共享锁或者排它锁,另一个会话无法在在R的索引之前立刻插入一个新的索引记录。
  • InnoDB使用next-key锁查找和扫描索引,阻止了幻行。

插入意向锁

插入意向锁时在插入行前设置的一种间隙锁。这个锁示意如果多个事务感兴趣的不是索引区间中的同一个位置,则事务在同一个索引区间插入不需要相互等待。假设有索引记录值为4和7的行。两个事务分别尝试插入5和6,分别用插入意向锁锁住4和7之间的间隙,然后再取得插入行的排它锁,但是锁相互不会冲突,因为插入行没有冲突。

自动增长锁

  • 自动增长锁是一个特殊的表级锁,事务持有它用于给带有auto_increment列的表插入数据。在最简单的情况下,如果一个事务正在给表中插入数据,其他的事务想要插入数据,必须等待,这样第一个事务才能插入具有连续主键的值。
  • innodb_autoinc_lock_mode配置选项控制用于自动增长锁的算法。它允许你选择如何权衡可预测的自动增长值序列和最大的插入操作并发性

空间索引的预测锁

  • 在MySQL 5.7.5,InnoDB支持包含空间数据列的空间索引
  • 为了使得空间索引支持隔离级别,InoDB使用预测锁。一个空间索引包含最小边界矩形 (MBR)值,所以InnoDB通过在MBR上设置预测锁来查询数据,强制一致性读。其它事务不能插入或修改与查询条件匹配的行。

InnoDB的行锁模式及加锁方法

InnoDB的行锁有两种:共享锁(S)和排他锁(X)。为了允许行锁和表锁共存,实现多粒度锁机制,InnoDB还有两种内部使用的意向锁:意向共享锁和意向排他锁,这两种意向锁都是表锁。一个事务在给数据行加锁之前必须先取得对应表对应的意向锁。

  • 两种类型的行锁。

    • 共享锁(s):允许一个事务去读一行,阻止其他事务获得相同数据集的排他锁。
    • 排他锁(X):允许获取排他锁的事务更新数据,阻止其他事务取得相同的数据集共享读锁和排他写锁。
  • 另外,为了允许行锁和表锁共存,实现多粒度锁机制,InnoDB还有两种内部使用的意向锁(Intention Locks),这两种意向锁都是表锁。

    • 意向共享锁(IS):事务打算给数据行共享锁,事务在给一个数据行加共享锁前必须先取得该表的IS锁。
    • 意向排他锁(IX):事务打算给数据行加排他锁,事务在给一个数据行加排他锁前必须先取得该表的IX锁。
  • 如果一个事务请求的锁模式与当前的锁兼容,InnoDB就请求的锁授予该事务;反之,如果两者两者不兼容,该事务就要等待锁释放。

    • 意向锁是InnoDB自动加的,不需用户干预。对于UPDATE、DELETE和INSERT语句,InnoDB会自动给涉及及数据集加排他锁(X);对于普通SELECT语句,InnoDB会自动给涉及数据集加排他锁(X);对于普通SELECT语句,InnoDB不会任何锁;事务可以通过以下语句显示给记录集加共享锁或排锁。
    • 共享锁(S):SELECT * FROM table_name WHERE ... LOCK IN SHARE MODE
    • 排他锁(X):SELECT * FROM table_name WHERE ... FOR UPDATE
    • 释放锁:unlock tables;(会隐含提交事务)
  • 用SELECT .. IN SHARE MODE获得共享锁,主要用在需要数据依存关系时确认某行记录是否存在,并确保没有人对这个记录进行UPDATE或者DELETE操作。但是如果当前事务也需要对该记录进行更新操作,则很有可能造成死锁,对于锁定行记录后需要进行更新操作的应用,应该使用SELECT ... FOR UPDATE方式获取排他锁。

InnoDB行锁实现方式

  • InnoDB行锁是通过给索引上的索引项加锁来实现的,InnoDB 这种行锁实现特点意味着:
    • 只有通过索引条件检索数据,InnoDB才使用行级锁,否则,InnoDB 将使用表锁。
    • 由于MySQL的行锁是针对索引加的锁,不是针对记录加的锁,所以虽然是访问不同行的记录,但是如果是使用相同的索引键,是会出现锁冲突的。
    • 当表有多个索引的时候,不同的事务可以使用不同的索引锁定不同的行,另外,不论是使用主键索引、唯一索引或普通索引,InnoDB 都会使用行锁来对数据加锁。(虽然使用的是不同的索引,但是如果记录已经被其他session锁定的话也是需要等待的。)
    • 即便在条件中使用了索引字段,但是否使用索引来检索数据是由MySQL 通过判断不同执行计划的代价来决定的,如果MySQL 认为全表扫描效率更高,比如对一些很小的表,它就不会使用索引,这种情况下InnoDB将使用表锁,而不是行锁。

什么时候使用表锁

  • 第一种情况是:事务需要更新大部分或全部数据,表又比较大,如果使用默认的行锁,不仅这个事务执行效率低,而且可能造成其他事务长时间锁等待和锁冲突,这种情况下可以考虑使用表锁来提高该事务的执行速度。

  • 第二种情况是:事务涉及多个表,比较复杂,很可能引起死锁,造成大量事务回滚。这种情况也可以考虑一次性锁定事务涉及的表,从而避免死锁、减少数据库因事务回滚带来的开销。

  • 当然,应用中这两种事务不能太多,否则,就应该考虑使用MyISAM表。

  • 在InnoDB下 ,使用表锁要注意以下两点。

    • 使用LOCK TALBES虽然可以给InnoDB加表级锁,但必须说明的是,表锁不是由InnoDB存储引擎层管理的,而是由其上一层MySQL Server负责的,仅当autocommit=0、innodb_table_lock=1(默认设置)时,InnoDB层才能知道MySQL加的表锁,MySQL Server才能感知InnoDB加的行锁,这种情况下,InnoDB才能自动识别涉及表级锁的死锁;否则,InnoDB将无法自动检测并处理这种死锁。
    • 在用LOCAK TABLES对InnoDB锁时要注意,要将AUTOCOMMIT设为0,否则MySQL不会给表加锁;事务结束前,不要用UNLOCAK TABLES释放表锁,因为UNLOCK TABLES会隐含地提交事务;COMMIT或ROLLBACK产不能释放用LOCAK TABLES加的表级锁,必须用UNLOCK TABLES释放表锁,正确的方式见如下语句。

总结

MyISAM表锁

(1)共享读锁(S)之间是兼容的,但共享读锁(S)和排他写锁(X)之间,以及排他写锁之间(X)是互斥的,也就是说读和写是串行的。
(2)在一定条件下,MyISAM允许查询和插入并发执行,我们可以利用这一点来解决应用中对同一表和插入的锁争用问题。
(3)MyISAM默认的锁调度机制是写优先,这并不一定适合所有应用,用户可以通过设置LOW_PRIPORITY_UPDATES参数,或在INSERT、UPDATE、DELETE语句中指定LOW_PRIORITY选项来调节读写锁的争用。
(4)由于表锁的锁定粒度大,读写之间又是串行的,因此,如果更新操作较多,MyISAM表可能会出现严重的锁等待,可以考虑采用InnoDB表来减少锁冲突。

InnoDB表

(1)InnoDB的行销是基于索引实现的,如果不通过索引访问数据,InnoDB会使用表锁。
(2)InnoDB间隙锁机制,以及InnoDB使用间隙锁的原因。
(3)在不同的隔离级别下,InnoDB的锁机制和一致性读策略不同。
(4)MySQL的恢复和复制对InnoDB锁机制和一致性读策略也有较大影响。
(5)锁冲突甚至死锁很难完全避免。

减少锁冲突和死锁

  • 尽量使用较低的隔离级别
  • 精心设计索引,并尽量使用索引访问数据,使加锁更精确,从而减少锁冲突的机会。
  • 选择合理的事务大小,小事务发生锁冲突的几率也更小。
  • 给记录集显示加锁时,最好一次性请求足够级别的锁。比如要修改数据的话,最好直接申请排他锁,而不是先申请共享锁,修改时再请求排他锁,这样容易产生死锁。
  • 不同的程序访问一组表时,应尽量约定以相同的顺序访问各表,对一个表而言,尽可能以固定的顺序存取表中的行。这样可以大减少死锁的机会。
  • 尽量用相等条件访问数据,这样可以避免间隙锁对并发插入的影响。
  • 不要申请超过实际需要的锁级别;除非必须,查询时不要显示加锁。
  • 对于一些特定的事务,可以使用表锁来提高处理速度或减少死锁的可能。

INNODB MVCC

  • MVCC (Multiversion Concurrency Control),即多版本并发控制技术,它使得大部分支持行锁的事务引擎,不再单纯的使用行锁来进行数据库的并发控制,取而代之的是把数据库的行锁与行的多个版本结合起来,只需要很小的开销,就可以实现非锁定读,从而大大提高数据库系统的并发性能
  • innodb MVCC主要是为Repeatable-Read(可重复读)事务隔离级别做的。在此隔离级别下,A、B客户端所示的数据相互隔离,互相更新不可见
  • 具体的执行拿select,insert,update和delete来说,
  • Select Innodb检查每行数据,确保他们符合两个标准:
    • 1、InnoDB只查找版本早于当前事务版本的数据行(也就是数据行的版本必须小于等于事务的版本),这确保当前事务读取的行都是事务之前已经存在的,或者是由当前事务创建或修改的行
    • 2、行的删除操作的版本一定是未定义的或者大于当前事务的版本号,确定了当前事务开始之前,行没有被删除
  • 符合了以上两点则返回查询结果。
    • INSERT InnoDB为每个新增行记录当前系统版本号作为创建ID。
    • DELETE InnoDB为每个删除行的记录当前系统版本号作为行的删除ID。
    • UPDATE InnoDB复制了一行。这个新行的版本号使用了系统版本号。它也把系统版本号作为了删除行的版本。

MYSQL死锁

产生的原因

竞争资源

  • 产生死锁中的竞争资源之一指的是竞争不可剥夺资源 [当系统把这类资源分配给某进程后,再不能强行收回,只能在进程用完后自行释放,如磁带机、打印机等](例如:系统中只有一台打印机,可供进程P1使用,假定P1已占用了打印机,若P2继续要求打印机打印将阻塞)
  • 产生死锁中的竞争资源另外一种资源指的是竞争临时资源 [指某进程在获得这类资源后,该资源可以再被其他进程或系统剥夺,CPU和主存均属于可剥夺性资源] (临时资源包括硬件中断、信号、消息、缓冲区内的消息等),通常消息通信顺序进行不当,则会产生死锁

进程间推进顺序非法

若P1保持了资源R1,P2保持了资源R2,系统处于不安全状态,因为这两个进程再向前推进,便可能发生死锁

产生的必要条件

  • 互斥条件:进程要求对所分配的资源进行排它性控制,即在一段时间内某资源仅为一进程所占用。
  • 请求和保持条件:当进程因请求资源而阻塞时,对已获得的资源保持不放。
  • 不剥夺条件:进程已获得的资源在未使用完之前,不能剥夺,只能在使用完时由自己释放。
  • 环路等待条件:在发生死锁时,必然存在一个进程--资源的环形链。

预防死锁

  • 资源一次性分配:一次性分配所有资源,这样就不会再有请求了:(破坏请求条件)
  • 只要有一个资源得不到分配,也不给这个进程分配其他的资源:(破坏请保持条件)
  • 可剥夺资源:即当某进程获得了部分资源,但得不到其它资源,则释放已占有的资源(破坏不可剥夺条件)
  • 资源有序分配法:系统给每类资源赋予一个编号,每一个进程按编号递增的顺序请求资源,释放则相反(破坏环路等待条件)

避免死锁

  • 预防死锁的几种策略,会严重地损害系统性能。因此在避免死锁时,要施加较弱的限制,从而获得 较满意的系统性能。由于在避免死锁的策略中,允许进程动态地申请资源。因而,系统在进行资源分配之前预先计算资源分配的安全性。若此次分配不会导致系统进入不安全的状态,则将资源分配给进程;否则,进程等待。其中最具有代表性的避免死锁算法是银行家算法。
  • 银行家算法:首先需要定义状态和安全状态的概念。系统的状态是当前给进程分配的资源情况。因此,状态包含两个向量Resource(系统中每种资源的总量)和Available(未分配给进程的每种资源的总量)及两个矩阵Claim(表示进程对资源的需求)和Allocation(表示当前分配给进程的资源)。安全状态是指至少有一个资源分配序列不会导致死锁。当进程请求一组资源时,假设同意该请求,从而改变了系统的状态,然后确定其结果是否还处于安全状态。如果是,同意这个请求;如果不是,阻塞该进程知道同意该请求后系统状态仍然是安全的。

检测死锁

  • 首先为每个进程和每个资源指定一个唯一的号码;
  • 然后建立资源分配表和进程等待表。

解除死锁

  • 剥夺资源:从其它进程剥夺足够数量的资源给死锁进程,以解除死锁状态;
  • 撤消进程:可以直接撤消死锁进程或撤消代价最小的进程,直至有足够的资源可用,死锁状态.消除为止;所谓代价是指优先级、运行代价、进程的重要性和价值等。

索引

是什么

官方对索引的定义

索引(Index)是帮助MySQL高效获取数据的数据结构。我们可以简单理解为:快速查找排好序的一种数据结构。Mysql索引主要有两种结构:B+Tree索引和Hash索引。我们平常所说的索引,如果没有特别指明,一般都是指B树结构组织的索引(B+Tree索引)。

存储引擎
MyISAM存储引擎

  • .frm后缀的文件存储的是表结构。
  • .myd后缀的文件存储的是表数据。
  • .myi后缀的文件存储的就是索引文件。

InnoDB存储引擎

  • .frm后缀的文件存储的是表结构。
  • .ibd后缀的文件存放索引文件和数据(需要开启innodb_file_per_table参数)

总结

  1. 索引是按照特定的数据结构把数据表中的数据放在索引文件中,以便于快速查找;
  2. 索引存在于磁盘中,会占据物理空间。

索引的类型

B-Tree 索引

以 B-Tree 为结构的索引是最常见的索引类型,比如 InnoDB 和 MyISAM 都是以 B-Tree 为索引结构的索引,事实上是以 B+ Tree 为索引结构,B-Tree 和 B+Tree 区别在于,B+ Tree 在叶子节点上增加了顺序访问指针,方便叶子节点的范围遍历.

InnoDB

  • InnoDB 支持聚簇索引,聚簇索引和非聚簇索引严格来说不是一种索引,而是一种数据存储方式,这个名字跟它本身的存储方式有关系,“聚簇“表示数据行和相邻的键值存储在一起,简单的说,就是叶子节点中存储的实际是真实的数据。InnoDB 通过主键聚集数据,所以一个表只能有一个聚簇索引,且必须有主键,如果没有定义主键,且不存在非空索引可以代替,InnoDB 会隐式定义一个主键作为聚簇索引。
  • 聚簇索引的二级索引存储的不是指向行的物理位置的指针,而是行的主键值,所以如果通过二级索引查找行,需要找到二级索引的叶子结点获得对应的主键值,然后再去查找对应的行。对于 InnoDB,自适应哈希索引可以减少这样的重复工作。

  • InnoDB 使用的是行锁,所以支持事务,而 MyISAM 使用的是表锁,不支持事务。
  • 适用范围
    • B-Tree 索引适用于区间查询,因为 B-Tree 存储后的叶子节点本身就是有序的,并且 B+ Tree 结构还增加了叶子节点的连续顺序指针,对于区间查询来说就更加方便了。

哈希索引

哈希索引是基于哈希表实现的,只有精确匹配索引所有列的查询才有效。方法是,对所有的索引列计算一个 hash code,hash code 作为索引,在哈希表中保存指向每个数据行的指针。

优点

索引本身只存储 hash code,所以结构很紧凑,并且查找速度很快

限制

  • 索引中的 hash code 是顺序存储的,但是 hash code 对应的数据并不是顺序的,所以无法用于排序
  • 不支持部分索引列匹配查找,因为哈希索引是使用索引列的全部内容来计算 hash code
  • 只支持等值比较,不支持范围查询
  • 如果哈希冲突严重时,必须遍历链表中所有行指针
  • 哈希冲突严重的话,索引维护操作的代价也很高

InnoDB 的自适应哈希索引

  • 自适应哈希索引对于用户来说是无感知的,这是一个完全自动、内部的行为,用户无法控制或者配置,但是可以关闭。
  • 当 InnoDB 注意到某个索引值被使用的非常频繁时,它会在内存中基于 B-Tree 索引之上再创建一个哈希索引,这样 B-Tree 也可以具有哈希索引的一些优点,比如快速的哈希查找。
  • 当然如果存储引擎不支持哈希索引,用户也可以自定义哈希索引,这样性能会比较高,缺陷是需要自己维护哈希值,如果采用这种方法,不要使用 SHA1() 和 MD5() 作为哈希函数,因为这两个是强加密函数,设计目标是最大限度消除冲突,生成的 hash code 是一个非常长的字符串,浪费大量的空间,哈希索引中对于索引的冲突要求没有那么高。

索引的存储结构

  • MySQL支持在所有关系数据库表中创建主键、唯一键、不唯一的非主码索引等多种类型的索引。此外MySQL还支持纯文本和空间索引类型。
  • MySQL内置的存储引擎对各种索引技术有不同的实现方式,包括:B-树,B+树,R-树以及散列类型。

B-树

  • B-树中有两种节点类型:索引节点和叶子节点。叶子节点是用来存储数据的,而索引节点则用来告诉用户存储在叶子节点中的数据顺序,并帮助用户找到相应的数据。
  • B-树的搜索,从根节点开始,对节点内的关键字有序进行二分查找,如果命中则结束,否则进入查询关键字所属范围的儿子节点,重复。直到所对应的儿子指针为空,或已经是叶子节点。
  • B-树是一种多路搜索树:
    • 定义任意非叶子节点最多有M个儿子,且M>2;
    • 根节点的儿子数为[2,M];
    • 除根节点以外的非叶子节点的儿子数为[M/2,M];
    • 每个节点存放至少M/2-1(取上整)和至多M-1个关键字;
    • 非叶子节点的关键字个数=指向儿子节点的指针的个数-1;
    • 非叶子节点的关键字:k[i]<k[i+1];
    • 非叶子节点的指针:p[1],p[2],·····,p[M];其中p[1]指向的关键字小于k[1]的子树,p[M]指向的关键字大于K[m-1]的子树;
    • 所有的叶子节点位于同一层;

B+树

  • B+树数据结构是B-树实现的增强版本。尽管B+树支持B-树索引的所有特性,它们之间最显著的不同点在于B+树中底层数据是根据被提及的索引列进行排序的。B+树还通过叶子节点之间的附加引用来优化扫描性能。
  • B+搜索和B-搜索不同,区别是B+树只有达到叶子节点才命中(B-

发表评论

0/200
1643 点赞
30 评论
收藏
为你推荐 换一批