1.MySQL
MySQL系列的文章从这里开始只写三部分,一部分是基础,一部分是初阶,一部分是高阶
2.MySQL基础
1.已经有了文件可以对数据进行存储,为什么还要搞出一个数据库,我们即使不学数据库也应该知道,一定是数据库有着文件无法满足的特点,文件,只提供读写执行的三种权限,划分过于粗糙,无法进行精细粒度的划分,文件不利于进行查询和管理,为什么这么说,学过数据库就知道数据库的查询是通过主键构建B+树来进行查询的,效率高效,而且B+树的叶子节点就是链表,也便于遍历,提供标准的SQL语句便于对数据进行精准的查询,实现海量数据的存储与查询。文件的并发性差,当多个用户同时访问一个文件的时候,无法做到很好的并发,数据库的诞生就是为了解决文件的这些痛点,数据库的水平可以看出一个程序员的水平(不是我说的)。
MySQL以免费和强大的泛用性占据了很大的数据库市场,几乎很少场景在MySQL解决不了的。
它免费,是广泛传播的一个重要因素。
安装一个数据库,就是在机器上安装了一个数据库管理系统
用户通过SQL语句在客户端对数据进行操作,一个数据库就是一个DB,一个DB里面可以有许多表,客户端通过发送标准SQL语句对服务端进行请求的发送,服务端会帮助用户自动对这些库和表进行一个管理


学懂MySQL的数据库架构,学其他的架构也是比较简单的,它们之间都是类似的,MySQL的存储引擎是INNODB,INNODB是最主流的存储引擎,所以学习数据库,主要就是学习MYSQL和INNODB,看一下它的架构,就是客户端发SQL给服务端,服务端会对客户端发来的SQL语句进行解析,优化,执行的操作,

客户端可以发SQL语句给服务端,那数据无非就是增删查改吗,我们对SQL语句还会进行分类,定义数据库的语言叫DDL,对数据进行操作的语言就是DML,DCL主要是负责权限方面的问题。
我们一直说客户端通过SQL语句给服务端发送请求,服务端进行接受,解析,优化,执行,然后就到了存储引擎,那么什么是存储引擎呢?
存储引擎是:数据库管理系统如何存储数据、如何为存储的数据建立索引和如何更新、查询数据等技术的实现方法。通俗来讲,存储引擎几乎就是数据库最核心的部分,它决定了数据的存储方式,以及解决各种问题,比如并发,恢复,备份等等核心操作都是存储引擎来做的。它就类似于我们电脑的操作系统,承上启下。理解了吗?
好了说了这么多,像数据库的这些底层都是数据库开发应该知道的,但是对于我们用户来说,我们仅仅就是想要一个软件来进行数据的存储,便于用户对数据的存储查询等操作,所以我们对我们用户来说,我们只需要了解SQL语句就可以让数据库完成我们想要完成的任务了。
我们知道,我们可以有许多数据库,一个数据库可以有许多表,比如,学校的数据库,里面可以有许多班级的表,对吗?所以对数据库的操作无非就是对库操作和对表进行操作,操作是什么,增删查改吗?
下面再说一下就是MySQL支持备份恢复,mysqldump进行一个备份恢复。
再说一下元数据这个概念,元数据就和一本书的目录一样,数据就是一本完整的书

所以综合说我们接下来的基础学习都是学习用户的操作,用户的操作就是对库和表的增删查改,如果再统一一下概念,某种意义上,库是不是一种特殊的表呢?只不过这个表的数据是其他表,所以统一概念,Linux下一切皆文件,数据库下一切皆表!
学了对库和表的增删查改,还要学习对表的约束,方便我们对数据进行一个规范,保证数据的合法性。
约束是什么?就是约束,字面意思,我约束你不准玩switch,我约束你不准打游戏,这就叫约束,比如我们注册任天堂账号的时候,他会让我们输入我们的邮箱,在后台邮箱必须唯一,所以添加一个约束来保证邮箱的唯一,也就保证了账号的合法性。
MySQL里面空有两个表示个是Null,一个是not null。
default可以定义默认值

列描述,我们可以添加comment对这个列进行描述

下面就是关键的了,主键约束,这个非常关键,看一下这个例子,主键约束,表示这列的数值不能为空,全局唯一,不能重复,且一个表最多有一个主键,必须有,没有MySQL会默认添加一个主键约束,用户看不到,用户定义了就不再默认添加主键约束,主键是构建聚簇索引B+树的一个关键,支持了高效的数据查找。
表创建好后如果没有主键可以添加主键,也可以修改主键,不过根据我们知道的表的底层高效查找是根据主键构建B+树,所以一旦对主键进行修改,就会对底层的B+树进行修改,这会导致大量的消耗,所以我们尽量不要对主键进行修改

也有一个概念叫复合主键。
复合主键也是主键的一种,它们这一组的组合必须是唯一的,作为主键存储的。
MySQL内还提供了一些内置函数进行调用,比如看一下当前的时间。就是一个概念
还有概念是内连接和外连接,这个的话内连接相当于两个where子句进行筛选,外连接分为左外连接和右外连接
下面就是重点了,MySQL的索引
索引可以大幅加速查询的速度,但是同时如果频繁的更改表结构,会导致底层的索引频繁变化,反而效率降低,所以我们需要选择合适的主键来构建主键索引,K值要选的好,只要不频繁修改K值,K值所在的行的数据一般位置不会变化,对于效率的影响很小,所以一般要选择不频繁变更的值作为主键索引,来进行海量的数据一个查询
索引有:
主键索引 (primary key)
唯一索引 (unique)
普通索引 (index)
全文索引 (fulltext)-- 解决中子文索引问题
背景:MySQL提供数据的存储与管理服务,数据主要在磁盘这个外设中存储,所以数据的读取要设计到磁盘IO,而磁盘距离CPU的距离太远了,效率相对于CPU的速度来说实在太慢了,所以如何提高效率是数据库的一个重点的难题。
数据库文件本质也是存储在了磁盘上面,下面这个磁盘看一下,距离圆心越近,扇区越小,越远,扇区越大,一般的数据库文件都要占据多个扇区,数据库也可以看做磁盘上的保存的一个文件,找到一个数据库就要找到它在磁盘上占据的所有扇区就可以了。我们能定位一个扇区,我们就能定位所有的扇区。
而操作系统读磁盘是4KB一次的,但是磁盘的扇区规定是512字节,所以操作系统和磁盘不是强相关的。我们上层可以人为规定单位,就好比我们规定1米等于10厘米一样,这叫规定,我们规定,电流的方向与负电荷移动的方向相反,规定就是人为规定的。
而我们的MySQL数据库肯定不会选择512字节作为读取的基本单位,但也没有选择操作系统提供的4KB,而是自己选择了一个单位是16KB

MySQL认为,它的文件保存是以一个page为单位的,MySQL的CRUD都需要经过计算,设计计算就要CPU介入,而我们知道CPU只和内存交互,所以想要进行计算,必须先把数据从磁盘移动到内存里,我们推断,在特定时间内,一定是存在数据在内存和磁盘不一致的,我们需要把内存修改的数据提交到磁盘,就需要内存和磁盘进行IO了,为了更好的进行操作,MySQL会申请一块自己的buffer pool,来和内存进行交互。
MySQL认为自己访问和读写都是以page为单位进行的,这样做的好处是不用每次访问都去和磁盘进行IO了,我们不能保证下一次访问的数据一定在这个加载的page里,但是概率很大,因为程序的局部性原理,MySQL要管理它的一个一个的文件,怎么管理,先描述,再组织,所以是先用page进行描述,再选用响应的数据结构进行管理,那么是否可以理解为一个文件的基本单位在MySQL看来就是一个一个的page,page与page之间用链表进行链接
一个page是16KB,我们要知道不论是操作系统还是MySQL都是可编程的, 只不过操作系统是承上启下是计算机的核心,对硬件和软件做统一的管理,而MySQL这种数据库是在操作系统上面做开发,我们要知道MySQL只不过是在软件层面进行开发的

当我们把数据存入page的时候,我们一般会对数据进行排序,优化查询效率,B+树就是依赖这个顺序进行的,我们page就是通过双链表连接起来的,我们知道的,链表都是修改快,查询慢。
但是问题来了,如果我有10000w条数据,我们需要多个page来保存,那我们查询就是遍历链表,效率很低,那么我们怎么办,我们就要利用我们数据的有序性了呀,如果没有有序性,那和无序没啥区别,我们还排序干啥,所以我们怎么办,对于单页page我们引入目录,一个page16kb,我们去遍历16kb肯定很慢,所以我们就对一个范围引入目录。

有序的这种特性就很便于我们引入目录结构啊,但是我们这样做,我们在page之间还是需要进行遍历的,比如我们要找16,我们不知道16在哪个page,呀,我们还是要遍历所有的page,怎么办,给page带上目录,

这样我们通过page目录就可以定位我们的page的大概范围,比如要找16,我们就无需去遍历前3个page了,我们直接从第三个page开始找就可以了。随着page的增多,我们的目录页也需要进行遍历了,怎么办,再加目录,给目录加目录套娃,无需真正理解,这是一种思想,给目录加目录,可以加速定位,这就是B+树


我们需要查找的page数减少了,我们的IO次数就减少了,这样树形结构,很便于我们查找,我们知道的树型查找一般都是干掉一般内容的,这样的B+树,一次可以排除很多不必要的Page io
之所以不选用别的数据结构,是有道理的,链表肯定不行,现行遍历的,数组,不行,虽然它遍历很快,但是它删除增加移动元素都很废,而且它还要求地址连续,随着数据库越来越大,效率会越来越低,AVL树,太高了,B+树是多叉的,哈希,范围查找
B+树就是可以做到很快的查找,也支持了遍历,也支持范围查找,因为它综合了树和链表的优点。既可以进行快速的查找,也可以进行范围的遍历。
INNODB的叶子节点是通过链表连接的,索引和数据是放在一起的,而另一种比较流行的引擎MYSIAM它是没有放在一起的,叫做非聚簇索引。
所以对于MYSIAM来说,建立主键索引和普通索引,查找效率几乎没有区别了,因为都是拿到通过数据行存放的地址去访问这一行的。硬说区别就是主键索引不可以重复?
INNODB的话,建立普通索引是拿到主键的index,再通过index去主键索引去查找了。为什么不能给叶子节点附上数据呢?那我问你,建一个索引数据存一份,如果你有1TB,那么你要存2TB,甚至3TB的数据,磁盘不要钱吗?

了解了MySQL的INNODB存储引擎为什么这么高效,主要就是引入了索引机制。
下面我们进入又一个核心,事务,如果从事数据库相关的工作,事务始终是绕不开的话题,数据库必须保证结果的正确性,比如买票,两个人同时看到还有1张票,同时购买,怎么办,票能减为-1吗?明显不能,如果票数变为负数,说明不符合预期了。这个比较愚蠢,一般到0不会变为负一,一般的场景都是客户端A和B检查的时候都是一张票,然后A买票了,但是还没来得及更新到数据库里,导致B也可以买票,A和B同时买了一张票,这就是经典的一张票被卖了2次的问题
所以我们就要保证,买票的过程是原子的,也就是说,要买就买,不买就不买,不存在中间状态,意思就是你数据库的数据必须是即使更新的,当我A把票买完的时候,买票这个动作结束之前,你B不可以来买票,这叫原子。没有买票中这个状态,同样的,当我B买票之后,你A不能再次买票,不能把一张票进行2次购买,买完票后是永久有效的,买前和卖后都是确定的状态。
说人话就是买票只有买票前和买票后两个状态,不能有买票中这个状态。

一个事物是由很多条SQL语句组成的,一个MySQL中会同时运行多个事务的,那么这个时候就会设计到多个事务同时对一张表的数据进行操作,一个事物必须要保证不出问题,所以事物是原子的,要么完成,要么不完成,不存在完成中这个状态,一致性,保证数据的安全,隔离性,MySQL是允许多个事务并发的,说人话就是并发就是允许多个事务同时进行对一个表结构进行操作,包括读未提交,
读提交,
可重复读,
串行化
也必须保证事务的持久性,事务提交之后,不能丢失,你要么完成,要么就被完成,别完成之后数据丢失。
事务隔离分为不同级别,包括读未提交( Read
uncommitted )、读提交( read committed )、可重复读( repeatable read )和串行化
( Serializable )
理解隔离性是理解MySQL事物非常重要的一个点,看一下,什么是读未提交,就是另一个事物能够读到没有提交的事务,这个几乎在生产中不会进行使用,太过危险
读提交,能读到别的事务已经提交的事务,但会造成不可重复读的问题,一个事物A,一个事物B, 当A的事务提交的时候,事务B没结束单身能读到已经提交的事务A的数据,所以就会导致,事务B在不同时间段查询会出现不同的情况,叫不可重复读。可重复读,是修复了这个问题,MySQL的默认隔离级别就是可重复读,可重复读,就是事务B没有结束,即使在事务B期间,事务A提交了但是不会影响事务B,事务B读出来的数据不受影响,注意的点是INSERT比较特殊,一般的数据库没有解决insert的问题,即事务A插入提交, 事务B还是会读到不同的数据,我们把这种情况叫做幻读。原因是我们无法对不存在的事务进行加锁。
事务是具有原子性的,不存在执行中这个概念,要么全部完成,要么全部干掉。要么成功,要么失败。
这是隔离性的大概,隔离性的场景有读-读,读-写,写-写,读读的时候没有问题,对数据不做修改,想怎么读就怎么读,怎么舒服怎么来,读-写,举要考虑问题了,会遇到脏读,幻读,不可重复读,脏读就是读未提交的,可以读到别的事务未提交的数据,幻读,就是可重复读的级别,当事务Ainsert数据的时候,事务B可以读到事务A提交的Insert数据,不可重复读,就是读提交的隔离级别导致的问题。
数据库并发就是读-读,读-写,写-写,什么意思,读读就是所有的线程都在读,没有对数据进行修改,读-写就是即对数据进行读也对数据进行写,同时进行,写写就是同时对数据进行修改,如保证并发的安全就是一个很大问题。
读-写并发解决的方法是无锁解决并发,MVCC很神奇,
为事务分配单向增长的事务 ID ,为每个修改保存一个版本,版本与事务 ID 关联,读操作只读该事务开始 前的数据库的快照。 所以 MVCC 可以为数据库解决以下问题 在并发读写数据库时,可以到在读操作时不用阻塞写操作,写操作也不用阻塞读操作,提高了数据库并发读写的性能 同时还可以解决脏读,幻读,不可重复读等事务隔离问题,但不能解决更新丢失问题
理解MVCC,知道一下UNDO日志,事务ID,隐藏主键,删除flag。
MySQL的服务是以进程的方式在内存中运行的,
索引,事务,隔离性,日志等,都是在内存中完成的,即在 MySQL 内部的相关缓冲区
中,保存相关数据,完成各种判断操作。然后在合适的时候,将相关数据刷新到磁盘当中的。
理解Undo可以理解为在内存的日志来实现mvcc
现在有一个事务 10( 仅仅为了好区分 ) ,对 student 表中记录进行修改 (update) :将 name( 张三 ) 改成
name( 李四 ) 。
事务 10, 因为要修改,所以要先给该记录加行锁。
修改前,现将改行记录拷贝到 undo log 中,所以, undo log 中就有了一行副本数据。 ( 原理就是写
时拷贝 )
所以现在 MySQL 中有两行同样的记录。现在修改原始记录中的 name ,改成 ' 李四 ' 。并且修改原始
记录的隐藏字段 DB_TRX_ID 为当前 事务 10 的 ID, 我们默认从 10 开始,之后递增。而原始记录的回
滚指针 DB_ROLL_PTR 列,里面写入 undo log 中副本数据的地址,从而指向副本记录,既表示我的
上一个版本就是它。
事务 10 提交,释放锁。
事务10执行完毕后,要修改Name,就有2条记录,本质就是写实拷贝,当修改的时候单开一块空间出来,这样通过链表实现了mvcc多版本控制,所以现在我们可以知道,它是怎么做到可重复读,在读的时候写,在写的时候读了。

现在又有一个事务 11 ,对 student 表中记录进行修改 (update) :将 age(28) 改成 age(38) 。
事务 11, 因为也要修改,所以要先给该记录加行锁。(该记录是那条?)
修改前,现将改行记录拷贝到 undo log 中,所以, undo log 中就又有了一行副本数据。此时,新的
副本,我们采用头插方式,插入 undo log 。
现在修改原始记录中的 age ,改成 38 。并且修改原始记录的隐藏字段 DB_TRX_ID 为当前 事务 11 的
ID 。而原始记录的回滚指针 DB_ROLL_PTR 列,里面写入 undo log 中副本数据的地址,从而指向副
本记录,既表示我的上一个版本就是它。
事务 11 提交,释放锁。
也是类似的,所以我们知道了它是怎么做到读的时候写,写的时候读,就是通过版本链来实现的,基本的事实是,如果你没有原来的数据,你是无法读的,豁然开朗,回滚的时候怎么回滚就是让历史的版本链覆盖现在的数据就可以实现roll back了