<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>数据库</title>
</head>
<body>
<div>
<!-- 数据库创建索引 start -->
<div>
<h1>数据库索引</h1>
<h3>1、索引是什么?</h3>
<p>索引是一种数据结构,即结构型的数据,大白话说: 索引就是一种组织数据的方式</p>
<h3>那么索引到底如何组织数据的呢?</h3>
<p>为表中的一条条记录创建索引就跟为书的一页页内容创建目录很类似,但是也有区别</p>
<div>
<h3>创建索引</h3>
<p>不一样的是,创建索引分为俩步:</p>
<ol>
<li>
<p>以索引字段为<mark>key</mark>与数据对应,例如</p>
<p>
create index idx_id on t1(id); -- <strong>key为id</strong>
</p>
<p>
创建索引数据结构初始版
</p>
<ul>
<li> key</li>
<li>记录1 <---- 1</li>
<li>记录2 <---- 2</li>
<li>记录3 <---- 3</li>
<li>记录4 <---- 4</li>
<li>记录5 <---- 5</li>
<li style="padding-left: 10px">. . . .</li>
</ul>
</li>
<li>
<p>以 <mark>key</mark>为基础创建 <mark>B+树</mark>, 那么什么是B+树???</p>
<div>
<p>
介绍下列树的原理结构以及特点->> 将明白下列树是如何提速查询的,以及每种树的问题和演变升级过程
</p>
<ul>
<li>
二叉树
<p>
二叉树是一种以key和一行行的记录对应关系为节点建立的一种树形结构
<br>
该树形结构有一个根节点和多个树枝节点和多个叶子节点构成
<br>
特点是:左边节点的索引值永远比当前节点的索引值小,右边节点的索引值永远比当前节点大.
</p>
</li>
<li>
平衡二叉树
<p>
平衡二叉树是二叉树的一种特殊结构的树,普通的二叉树可能会形成一根棍状的树,那样
<br>
其实对查询优化不大
<br>
而平衡二叉树在于平衡二字,即左边节点和右边节点之间的差值不大于1
<br>
这样在同等数据量下,树的高度更低,查询优化更好.
<br>
存也是key对应记录
</p>
</li>
<li>
B树
<p>
B树在平衡二叉树上又进行了优化,它将多个节点放到一个页面中,即一个磁盘块,又进一步的压缩了树的高度.
<br>
每个页面中的每个节点存放的还是key和一行行的记录
</p>
</li>
<li>
B+树
<p>
B+树是innodb存储引擎默认使用的索引查询,它在B树的基础上又进一步的做了优化.
<br>
注意这个B是平衡(balance)的意思,不是二叉的意思.
<br>
它存放的格式为只在叶子节点上放对应的key和一行行的记录.其他节点只放索引值.又进一步压缩了树的高度.
</p>
</li>
</ul>
</div>
</li>
</ol>
</div>
</div>
<!-- 数据库创建索引 end -->
<!-- 数据库索引分类 start-->
<div>
<h3>索引分类及区别</h3>
<h4>hash索引</h4>
<ul>
<li>
<p>
hash索引是一种以key=value的形式存的一种数据结构
</p>
<p>
它是以hash(索引字段)=索引对应记录
</p>
<p>
它一般是做等值查询的,单挑记录查询速度很快.但是对于范围查询比起B+树就慢一点.
</p>
</li>
</ul>
<h4>B+树索引</h4>
<ul>
<li>
<p>
上面详细的介绍了B+树的演变过程
</p>
<p>
特点:
</p>
<p>
<mark>
它的优势是对于范围查询时,在叶子节点中每个页面中的节点,索引是排好序的.所以对于查询同一个页面
或者说可以不用从根页面开始查询,直接相对于当前页面的节点开始查询.大大加快了查询速度
</mark>
</p>
</li>
</ul>
</div>
<!-- 数据库索引分类 end-->
<!-- B+树索引分类start -->
<div>
<h3>B+树索引分类</h3>
<h4>聚簇索引</h4>
<ul>
<li>
<p>
聚集索引即是以表中的主键字段为索引,构建的B+树.
</p>
<p>
<mark>它只能有一个</mark>
</p>
<p>
典型特点: 叶子节点放key: 一整条完整的记录
</p>
</li>
</ul>
<h4>辅助索引</h4>
<ul>
<li>
<p>
以表中除了主键的字段为索引构成的B+树
</p>
<p>
<mark>一张表中可以存在多个辅助索引</mark>
</p>
<p>
典型的特点: 叶子节点放的是key: 该记录对于的主键id
</p>
</li>
</ul>
</div>
<!-- B+树索引分类start -->
<!-- 覆盖索引与回表操作 start -->
<div>
<h3>覆盖索引与回表操作</h3>
<h4>覆盖索引</h4>
<ul>
<li>
<p>
覆盖索引指的是当一个辅助索引命中了时,并且要查询的字段就在这个以该辅助索引建立的B+树上.
</p>
</li>
<li>
<p>
比如: select name,id form user where name="jkey";
</p>
<p>
此时的 <mark>name</mark>和 <mark>id</mark> 字段都在以 <mark>name</mark>
字段为索引建立的B+树上,所有此时的索引查询操作为覆盖索引效果.
</p>
</li>
</ul>
<h4>回表操作</h4>
<ul>
<li>
<p>
回表操作指的是以表中的一个辅助字段为索引建立的B+树上,没有当前要查询的字段,就需要
先通过辅助索引拿到对应的主键id值,再通过该id去聚集索引取对应的字段记录.
</p>
<p>
这个回到聚集索引创建的B+树上查询字段的操作叫做回表操作.
</p>
</li>
</ul>
</div>
<!-- 覆盖索引与回表操作 end -->
<!-- 联合索引和索引的最左前缀匹配原则 start -->
<div>
<h3>联合索引和索引的最左前缀匹配原则</h3>
<h4>联合索引</h4>
<ul>
<li>
<p>
联合索引指的是由多个字段组成的索引.它的优点是最左匹配原则
</p>
</li>
<li>
<p>
例如:create index inx_id_name_age on t1(id,name,age);
</p>
</li>
<li>
<p>
就建立了一个联合索引,索引为[id,name,age]
</p>
</li>
</ul>
<h4>最左前缀匹配原则</h4>
<ul>
<li>
<p>
最左前缀匹配原则是联合索引中的匹配规则
</p>
</li>
<li>
它可以建立以包含最左边的联合索引中的字段构建出任意搭配的B+树
</li>
<li>
<p>
即: [id]索引B+树
</p>
<p>
[id, name]索引B+树
</p>
<p>
[id,age]索引B+树
</p>
<p>
[id,name,age]索引B+树
</p>
</li>
<li>
<p>
优化了可查询字段的回表操作.
</p>
</li>
</ul>
</div>
<!-- 联合索引和索引的最左前缀匹配原则 end -->
<!-- 索引下推技术 start -->
<div>
<h3>索引下推技术</h3>
<ul>
<li>
索引下推技术指的是mysql会将命中了索引的查询效率和全表查询的效率,和其他索引命中的查询效率做一个
内部选择,会将它觉得最优的方案进行查询,并返回.
</li>
<li>
这里可能会牵连到mysql的优化器,它可能并不会将命中了索引的当作此次的查询方案,当你的索引命中之后的查询效率和全表查询
差不多时,它可能也会用全表查询当作此次查询方案.
</li>
</ul>
</div>
<!-- 索引下推技术 end -->
<!-- 索引应该怎么建立? start -->
<div>
<h3>索引应该怎么建立?</h3>
<ul>
<li>
注意: 命中了索引不一定会有明显加速查询的提示
</li>
<li>
<ul>
<li>
一般可以取字段索引印象的rows范围小的
</li>
<li>
索引字段占空间少的,即索引值字节数少的
</li>
<li>
索引字段区分度高的
</li>
<li>
索引不要参与运算
</li>
<li>
索引字段不要放入函数中
</li>
</ul>
</li>
</ul>
</div>
<!-- 索引应该怎么建立? end -->
<!-- explain 查询计划 start -->
<div>
<h3>
explain 查询计划
</h3>
<ul>
<li>
可以通过 explain 命令来查看该sql语句使用的B+树,即查询计划.
</li>
<li>
例如: 查看user 表中的 查询计划
</li>
<li>
explain select name from user where name='jkey';
</li>
<li>
<pre>
+----+-------------+-------+------+---------------+----------+---------+-------+------+--------------------------+
| id | select_type | table | type | possible_keys | key | key_len | ref | rows | Extra |
+----+-------------+-------+------+---------------+----------+---------+-------+------+--------------------------+
| 1 | SIMPLE | user | ref | inx_name | inx_name | 99 | const | 1 | Using where; Using index |
+----+-------------+-------+------+---------------+----------+---------+-------+------+--------------------------+
</pre>
</li>
</ul>
</div>
<!-- explain 查询计划 end -->
<!-- 数据库常识 start -->
<div>
<h3>数据库常识</h3>
<ul>
<li>
单表300w条记录 ===> 硬盘空间大概200m.
</li>
<li>
UV(Unique visitor): 是指通过互联网访问、浏览这个网页的自然人。
<br>
访问您网站的一台电脑客户端为一个访客。00:00-24:00内相同的客户端只被计算一次。
<br>
一天内同个访客多次访问仅计算一个UV。
<br>
<mark>一天企业大概有2-5w的uv范围量</mark>
</li>
<li>
<p>
<strong>IP(Internet Protocol):</strong>
</p>
<p>
独立IP是指访问过某站点的IP总数,以用户的IP地址作为统计依据。00:00-24:00内相同IP地址之被计算一次。
</p>
</li>
<li>
<p>
<strong>UV和IP的区别</strong>
</p>
<p>
如: 你和你哥哥用各自的账户在同一台计算机上登录了新浪微博,则IP数 <mark>+1</mark>
但是UV数 <mark>+2</mark> 同一台计算机的ip是不变的,而用户即账号是可变的.
</p>
</li>
<li>
<p>
<b> PV (Page View) </b>
</p>
<p>
即页面浏览量或点击量,<mark>用户每一次对网站中的每个网页访问均被记录1个PV</mark>. 用户对同一页面的多次访问,访问量累计,用以衡量网站用户访问的网页数量。
一般企业:pv量20w-50w
</p>
</li>
<li>
<p>
<b> VV (Visit View) </b>
</p>
<p>
用以统计所有用户访问1天内访问网站的次数. 当访问完成 <mark>所有浏览并最终关闭该网站的所有页面时便完成了一次访问</mark>.
这就可能导致同一个用户当天内可能会多次访问,并且访问的次数是累积的.
</p>
</li>
<li>
<p>
<b>PV与VV区别:</b>
</p>
<p>
如: 你今天14点打开了百度,访问了它的三个页面; 18点你又打开了百度,访问了它的来个网页
.则pv数为 3 + 2 = 5 次,vv数为 1 + 1 =2 ,俩次.
<mark>PV是指页面的浏览次数,VV是指你访问网站的次数</mark>
</p>
</li>
<li>
<p>
最大并发(同时在线人数最大多少): 一天内,某一时刻的并发量.
</p>
<p>
一般为1000人左右同时在线
</p>
</li>
<li>
<p>
数据库(读多写少)
</p>
<p>
累计2w的uv,平均每人往数据库中写入一条数据.
</p>
<p>
那么单日新增数据条数为2w条
</p>
<p>
2w条数据库-》占用空间大概2M
</p>
<p>
结论:以2-5w uv为例,单日数据库空间增长量从几M到几十M不等
</p>
</li>
</ul>
</div>
<!-- 数据库常识 end -->
<!-- 数据库事务开始 -->
<!-- 事务的ACID开始-->
<div>
<h3>举例说明事务特性ACID</h3>
<ul>
<li>
<p>
A:表示的是事务的原子性,即把事务中做的所有事情看成一个整体,要么同时成功,要么同时失败
</p>
<p>
如: A(有500)给B(有500)转账100元,A用户的钱减100,B用户的钱加100,而当A减钱失败或者B加钱失败都应该不扣除此次交易.
都应该还剩500,而都操作成功就A变为400,B变为600.
</p>
</li>
<li>
<p>
C:表示的是事务的一致性,即事务中做的所有事务中对数据的操作是符合实际操作的.
</p>
<p>
如: 和上面的转账一样,我们可以发现,不管是转账前还是转账后,钱的总数都应该是1000,这就是一致性.
</p>
</li>
<li>
<p>
I: 表示事务的隔离性,表示俩个事务之间互相不影响.
</p>
<p>
如: 事务1和事务2同时开启,事务1对表进行修改操作,那么事务2中,查询到的是开启时的状态,不会是修改后的操作.
</p>
</li>
<li>
<p>
D:表示事务的永久性
</p>
<p>
如: 事务1对数据进行了修改操作,如果提交了,就永久保存到了数据库.
</p>
</li>
</ul>
</div>
<!-- 事务的ACID结束-->
<!-- 事务的开启3种方式start -->
<div>
<h3>开启事务的三种方式</h3>
<h4>隐式开启,隐式提交</h4>
<ul>
<li>
<p>
mysql数据库默认使用的就是显示开启显示,即它会自动给你开启事务和提交事务.所有你执行了sql语句
直接就影响了数据库中的数据.
</p>
<p>
mysql中通过下面的语句来关闭或者开启自动提交事务模式
</p>
<p>
set session/global autocommit=0; -- 0 表示关闭,1表示开启,session表示当前会话有效,global表示全局有效.
</p>
</li>
</ul>
<h4>显示开启,显示提交</h4>
<ul>
<li>
<p>
显示开始和显示提交的意思是手动开启事务和手动提交事务.
</p>
<p>
开启事务的方式有: <mark>start transaction;/begin;</mark>
</p>
<p>
关闭事务的方式有: <mark>rollback;/commit</mark>
</p>
<p>
其中rollback;表示回滚操作,撤销此次事务影响.
</p>
<p>
commit表示提交次事务影响
</p>
</li>
</ul>
<h4>隐式开启,显示提交</h4>
<ul>
<li>
<p>
隐式开启,显示提交即自动开启事务,但是要手动提交.
pymysql是采用的此方案
</p>
<p>
开启和关闭也是:
</p>
<p>
set autocommit=0/1 禁止/开启自动提交
</p>
</li>
</ul>
</div>
<!-- 事务的开启3种方式end -->
<!-- 事务设置保存点 start -->
<div>
<h4>事务可以设置保存点</h4>
<ul>
<li>
<p>
设置保存点 <mark>savepoint</mark> 关键字,可以通过rollback 保存点名,来进行
事务状态跳转.如果要返回事务的初始状态,rollback;
但是需要注意的是: 事务跳转只能往前跳转,不可以往后跳转
</p>
<p>
比如: 我可以跳转到事务保存点2,之后可以跳转到保存点1,但是此时不可以跳转到3或者2之后.
</p>
<p>
开启事务: <mark>begin;</mark>
</p>
<p>
对数据进行更新: update emp set name=concat(name,"_dsb") where name="liu";
</p>
<p>
设置保存点: savepoint one;
</p>
<p>
对数据进行更新: update emp set name=concat(name,"_dsb") where name="song";
</p>
<p>
设置保存点: savepoint two;
</p>
<p>
对数据进行更新: update emp set name=concat(name,"_nb") where name="jkey";
</p>
<p>
设置保存点: savepoint three;
</p>
<p>
进行保存点跳转: rollback two;
</p>
<p>
跳到事务的初始状态: rollback;
</p>
</li>
</ul>
</div>
<!-- 事务设置保存点 end -->
<!-- 数据库事务结束-->
<!-- 数据库读现象 start-->
<div>
<h3>数据库读现象</h3>
<p>
数据库的读现象都是在并发的情况下导致的.
</p>
<h4>脏读</h4>
<p>
脏读是指俩次事务并发开启,其中事务1俩次对数据的查询时,另外一个事务又对该数据进行了修改操作
但是没保存,这时候事务1又查询了一边.此时的数据是一条脏数据,因为被回滚了.
</p>
<h4>不可重复读</h4>
<p>
不可重复读指的是俩次事务并发开启,其中事务1俩次对数据进行查询时,另外一个事务对该数据进行了修改操作
并且提交了,那么就导致了事件1俩次相同的sql产生了不同的查询结果.这种现象就叫做不可重复读现象.
</p>
<h4>幻读</h4>
<p>
幻读指的是俩次事务并发开启,其中事务1对数据进行了范围操作时,另外一个事务也对该数据进行了操作,并且提交了.
那么事务1再次查询时,发现有一部分数据改成功了,而还有一些数据没有改成功,就和幻觉一样.这就是幻读.
</p>
</div>
<!-- 数据库读现象 end-->
<!-- 数据库锁 start -->
<div>
<h3>数据库锁</h3>
<p>
按照粒度分: 行级锁< 页级锁 < 表级锁
</p>
<p>
级别越高并发效果越低,考虑到性能,innodb默认支持行级锁,但是只有命中索引的情况下才是真正的锁行,
否则锁住所有的行,本质虽然是行锁,但是此刻相当于表锁了.
</p>
<h4>行级锁有三种算法</h4>
<ul>
<li>
<p>Record Lock 行锁</p>
</li>
<li>
<p>
Gap Lock 间隙锁
</p>
</li>
<li>
Next-key Lock innodb存储引擎默认使用, 相当于 Record Lock + Gap Lock ==> 解决了幻读问题
</li>
</ul>
<p>
按照级别分为: 排他锁和共享锁
</p>
<ul>
<li>
<p>
排它锁: 就是我们说的互斥锁,它表示的是当事务一起并发时,对同一个数据同一时刻只能有一个能抢到锁,
对其进行更改操作,其他的事务只能等到该事务将这个排它锁释放掉之后,才能进行抢锁和修改数据的操作.
</p>
<p>
排它锁更专注于写操作
</p>
</li>
<li>
<p>
共享锁,就相当于python中的信号量.并发情况下,一次可以有多个事务对相同的数据进行加锁操作.
</p>
<p>
但是一条数据如果被加了共享锁,其他事务也只能加共享锁或者不加锁.当被被事务之间都加了共享锁,那么此时
多个事务之间只能进行读操作.
</p>
<p>
所有说共享锁更专注于读
</p>
</li>
</ul>
<h4>悲观锁,乐观锁</h4>
<ul>
<li>
<p>
悲观锁: 指的是我们sql中的锁机制,它是比较悲观的,因为它会把所有事务都会和它进行修改同一条数据处理,
它会把数据在事务前就加上锁(一般是排它锁),让其他事务对该数据操作时,只能等锁释放.
</p>
<p>
数据更安全了,但是并发效率变低了
</p>
</li>
<li>
<p>
乐观锁: 指的是一种加锁思想,它是比较乐观的,它会等到提交事务的时候才看看对应的数据版本和自己当前的
版本是否一致,如果不一样,比自己高,那么就有人在这个时间段对这个数据进行了操作,就会出现事务提交失败,重新提交的
思想
</p>
<p>
虽然事务的提交失败率加大了,但是不是上来就加锁,并发效率提升了.(推荐)
</p>
</li>
</ul>
</div>
<!-- 数据库锁 end -->
<!-- 事务隔离机制start -->
<div>
<h3>事务隔离机制</h3>
<p>它分为四种隔离机制</p>
<p>
innodb默认使用的是RR机制,它解决了脏读和不可重复读,再加上默认使用的next-key lock算法
其实是还解决了幻读.所有读现象都解决了.
</p>
</div>
<!-- 事务隔离机制end-->
<!-- MVCC start -->
<div>
<h3>MVCC</h3>
<p>
MVCC分为快照读和当前读
</p>
<ul>
<li>
<p>
快照读为: 普通的select 查询语句
</p>
</li>
<li>
<p>
当前读: 为sql中的锁
select ... for update;
</p>
<p>
select ... lock in share mode;
</p>
<p>
update ....
</p>
<p>
delete ...
</p>
</li>
</ul>
</div>
<!-- MVCC end-->
</div>
</body>
</html>