1.基本操作

查看数据库支持的存储引擎

\G格式化展示

1.1 数据库操作


在/var/lib/mysql创建目录,mysql不会增加数据库;通过删除目录操作,删除mysql中创建的数据库,不会删除mysql中的数据库,但是目录没了,这种情况不能直接drop d1;来删除数据库,要通过drop database if exists d1;(在ubuntu 24.04环境的测试结果)



1.2 编码集 校验集


创建库的时候也可以指定编码集,库里所有的表如果不指定编码集,和所在库的编码集一致

1.3 表操作




打开d1.sql可以看到存储的是d1的操作,包括建库,建表,操作,也就是如果重建d1


增/改/删表的列

2.数据类型
2.1 tinyint
1B -128-127

tinyint usigned 0-255

tinyint, small int, medium int, int, big int分别占1,2,3,4,8B
2.2 bit

2.3 float

float(4,2)默认是signed,范围(-99.99, 99.99)

float(4, 2) unsigned 范围(0, 99.99)

2.5 decimal
decimal类型用法和float类似,但是float最多保留5位,decimal测试保留10位是可以的,应该还能

应该最多m是65,d最多是30

如果不指明m和d,m为10,d为0

2.6 char varchar
char(L),L是长度,声明为char(L),会给成员分配L字节,L是0-255
varchar(L),上限是L,如果存储内容不到L,用多少开辟多少空间,最多65535B,utf8mb4编码一个字符4B,就是16383(1-2B用来存实际用的字节数,2^16-1肯定够了)


这里是为了测试,如果大文本一般用BLOB或者TEXT
因为MySQL是按行存的,一行的长度是有限制的(一般是65535B),所以从而限制一行中字段的长度,下面虽然varchar(L)的L最大是16383,但是因为同一行存了id int,给name留的位置就不够16383

如果是gbk,一个字符编码为2B,varchar(L),L理论上最大是(65535-2)/2向下取整是32766(1B最大是255,2B最大是65535,unsigned)

一般情况下如果存储的字段长度浮动不大,采用char(L),如果浮动比较大,采用varchar(L)
2.7 date datetime timestamp
timestamp是时间戳,显示的时候显示为年月日时分秒,时间戳一般用于需要根据表操作实时更新的字段,比如评论时间,每次修改或者增加评论都需要更新时间

只有加入了default current_timestamp on update current_timestamp才会在插入其它列的时候自动更新为当前时间戳

date类型是日期,格式为xxxx-yy-zz(xxxx是年份,yy是月份,zz是日),datetime精确到秒,格式是xxxx-yy-zz aa:bb:cc aa是时,bb是分,cc是秒
date和datetime一般用于存储之后不会变的值,比如出生日期可以用date,上班打卡时间可以用datetime,选择date还是datetime根据具体应用场景对时间精度的要求


update修改数据行某一列的值
2.8 enum
enum(val1, val2,...),插入的时候可以直接使用val1,也可以使用下标,从1开始

text文本类型

2.9 set

上面查出来的是只有羽毛球的行,如果我们想查hobby包含羽毛球的行呢?

同时可以判断是否查找成功

也可以使用数值插入,1,2,4,分别表示是否有第一个,第二个set中的值
本质是一个二进制串,从低到高下标+1表示set中第几个值,为1表示插入这个值,为0不插


3.表的约束
约束本质是通过技术手段,倒逼程序员插入正确的、符合预期的数据
NULL就是NULL,和任何操作数作用结果都是NULL
''进行算术运算会进行类型转化,转化后的数值为0,C++一般使用""作为空串,mysql单双引号都可以,而且一般用单引号

3.1 comment

3.2 default
有defautl值的列可以缺省,使用default值

3.3 not null


3.4 zerofill
被弃用了
核心是定义时int(m),表示显示位数,比如int(6) zerofill,如果插入的值位数小于6,显示的时候前面就会补0,否则就是正常显示数值,但是使用where条件筛选时,使用11能筛选出来000011,底层存的还是数值
3.5 主键

使用DDL语句alter删除主键列

把已存在的列设置为主键

复合主键

如果增加某列为主键的话,保证表中现有行该列的值都不一样


不推荐在表建好插入数据之后增加主键,此时如果该列有重复值,删谁?
3.6 自增长


3.7 唯一键

3.8 外键
数据库三范式NF Normal Form
1NF 列属性需是原子性,也就是不可拆分,如下表id属性就是姓名+id的混合,查找起来会很困难

拆开之后用id检索就非常方便

第二范式
不满足2NF就是存在非主属性对候选键的部分依赖
候选键:能够标识一行属性的唯一性,且不含多余属性的属性集,候选键不一定是主键,主键一定是候选键
主属性:是候选键中的一个属性,剩下的就是非主属性、
部分依赖属于函数依赖的一种,函数依赖是指X->Y,X的值确定后,Y的值就能唯一确定
我们看下面这种表,问题在于1.如果探春老师新开了一门课叫做C语言程序设计,但是因为没有学生选,就没有办法插入,学生+课程名作为复合主键,插入异常 ;
删除异常 :如果贾宝玉、惜春结课了,就要删除这两条记录,导致Python程序设计的课程记录没了,再有学生选,插入就会面临插入异常
数据冗余 :可以看到贾宝玉和惜春选了Python程序设计这门课,那么这门课课程名和授课老师信息要存两次,就会构成数据冗余;
更新异常:如果Python程序设计课程改名为高级程序设计,要把表中所有涉及高级程序设计的记录都改了,如果漏改就会导致数据不一致

解决办法还是拆
拆为主表(被引用的表)和从表(引用表)

3NF
传递依赖
候选键A决定非主属性B,非主属性B决定非主属性C,但是B不能反决定A,C传递依赖候选键A
设置外键的列也就是constraint,一般有对应的名称,可以自己设置,不设置系统会自动生成

CRUD
Create, Read, Update, Delete
C
4.CRUD
4.1 C
4.1.1 多/单行 指定列/全列插入

into可以省略

4.1.2 update


row_count()查被影响的行

4.1.3 replace
可以直接使用replace进行替代,如果不存在唯一键冲突就插入,存在就删除旧行,插入新行

4.2 R
4.2.1 全/指定列查询

4.2.2 where






如果没有order by,得到的结果和数据分布有关,依赖插入删除顺序,不推荐直接查询的结果




limit s,n 和limit n offset s一样的效果

4.3 U



4.4 D
4.4.1 删除

把整张表都删掉之后,AUTO_INCREMENT的值并没有变


其实delete from table_name和truncate table_name从效果上看都是一样的,删除表的内容
但是原理上不太一样,从测试上看,可以推测一下,truncate给人的感觉是恢复出厂设置,delete给人的感觉是一条条删,因为AUTO_INCREMENT没变
原理上,delete走的是事务,一条条删,可以回滚
但是truncate不走事务,不能回滚,因此比较快
binlog,存储对于数据的逻辑操作,主从同步,默认binlog是关闭的
redo log 用于持久化,如果内存掉电,从磁盘恢复
undo log 用于回滚
持久化的方式:1.记录历史sql语句,2.记录数据本身
4.4.2 去重
为什么在插入之后进行重命名?我们要求插入是原子的,但是这么多数据做不到,就先放到临时文件,插入完。重命名可以做到原子
类似如果上传文件,文件很大,上传很慢,可以先上传到临时目录,上传完修改文件名即可,也就是修改目录下文件名和innode号的链接关系

5.聚合函数


Oracle 9i 经典测试表 EMP/DEPT

其实分组的本质是分表,把一张表分为多个组,每个组也是一个表进行聚合统计
所以在分组前的表用having是可以的

但是where不能用在group by中


表的理解,不仅仅存放在磁盘上表结构导入到mysql,真实存在的表是表,select * from table_name查出来的表是表,where筛出来的表也是表,任何操作过后的表都是表, 比如avg聚合统计过的,中间过程出现的,结果表都是逻辑上的表
单个表的CRUD处理好,sql的场景都可以用统一的方式进行
6.内置函数
6.1 日期类


date_add

date_sub

建国不到三万天,日子是好了不止一点点


6.2 字符串函数





6.3 数学函数


6.4 其它

7.复合查询
多表查询本质是单表的嵌套,想办法分析出单表,一层一层抽丝剥茧


8.内外连接

9.索引
9.1 概念

128MB


乱序插入,但查的时候正序显示

如果id不是主键,查出来不是按照id有序的

1.用户创建的数据库本质都是linux文件系统的目录,表就是目录下的文件,由一个一个page(16kb)组成,mysql的CURD操作本质都是调用文件系统接口读写文件缓冲区,再由OS持久化到磁盘
mysql的CURD操作肯定要先找到数据进行查/删/改,或者找到要插入的位置,所以要加载到内存CPU进行计算
提高算法效率:1.组织数据的方式 2.算法本身
组织数据的方式会影响算法,比如链表适合插入/删除,不适合排序和查找

可以查到buffer pool是128MB,mysql逻辑上读写磁盘单位是16KB,为了减少IO,利用局部性原理,概率上之后访问的数据和当前访问的数据在空间分布上是连续的,每次读取磁盘涉及到磁头的摆动,盘片的转到,比较耗时,所以进行预读取
而128MB包含多个16KB,mysql有很多16KB,先描述,再组织进行管理

可以使用链表进行管理,page内部管理信息+内存块
单页page,引入目录
上面那个问题,为什么基于id进行排序,为了查找效率的提升,如下图,因为一条条记录是用链表管理起来的,如果不排序,查找就是线性线性遍历,O(N),那id不作为主键的时候呢?关键还是索引,id是主键的时候,id列就是索引,没有主键,没有指定索引,系统会生成隐藏列作为索引,按照隐藏列的值进行排序
这样结合目录条目,就可以在单个page进行快速查找,如果查找的数据id=4,那目录1后,目录2前的记录可以跳过,直接通过目录2条目找到id=3记录的地址,之后顺序遍历,找到id=4对应的记录

本质是空间换时间
本质是B+树

page分为目录页和数据页,目录页只存放下级page最小键值和指向下级page的指针
查找的时候,只需加载部分目录页到内存,减少IO次数,提高效率
使用B+树,一方面叶子节点存数据,非叶子节点存目录项,不存数据,就可以存更多目录项,整棵树更加矮胖,查找记录途径的page就会更少,IO次数少;page内依然引入目录,在查找时根据目录定位,提高查找效率
InnoDB建立索引结构管理数据,为什么用B+树

官方索引实现,mysql其实支持hash

MySQL 5.7 官方参考手册(MySQL 5.7 Reference Manual),章节:13.1.14 CREATE INDEX Statement


9.2 聚簇索引 非聚簇索引
MyIsam存储引擎,主键索引
使用B+树作为索引结果,和InnoDB不同的是,InnoDB叶子结点存的是记录本身,MyIsam叶子结点存的是记录的地址,索引page和数据page分离,


MyIsam 非聚簇索引
InnoDB 聚簇索引
非主键建立的索引叫做辅助索引,MyIsam以非主键Col2的列值作为索引的key

InnoDB非主键作为索引,和主键索引不同的是,data放的是主键索引的key值,如果查其它信息,要回表查询,根据主键的key查主键索引B+树找到记录

创建索引
主键索引

唯一键索引

其它索引(普通索引)普通索引在开发中用的比较多,和主键索引、唯一索引相比,可以为NULL或重复

创建复合索引

删索引


全文索引
当对文章字段或有大量文字的字段进行检索时,使用全文索引;mysql提供全文索引机制,要求存储引擎是myisam,默认全文索引支持英文,如果对中文进行全文索引,使用sphinx的中文版(coreseek)

10.事务
10.1 概念

transaction,事务,作为一组逻辑上有关联的mysql语句集,能够完成某种任务,比如A给B转账100=A的余额-100以及B的余额+100,如果因为网络或者其它原因,导致失败,那本来A的余额-100,就要+回来,就是说要么成功,要么失败,只有这两态
事务更多的是站在用户角度来说的,因为用户使用某种服务都是以事务为单位的

10.2 隔离性和隔离级别
10.2.1 隔离性
mysqld服务端可能被多个客户端进程(线程)访问,以事务的方式进行
一个事务可能由多条sql构成,事务有执行前,执行中,执行后的阶段,原子性就是用户层看到执行前,执行后两种状态,如果执行中出现问题,进行回滚,单个事务对用户表现出来的特性就是原子性
事务执行有个过程,多个事务执行多个sql,可能相互影响,可能访问同一张表,同一行记录
数据库中为了保证事务执行过程中尽量不受干扰,将事务的执行隔离开来,隔离性
允许事务受不同程度的干扰,也就是隔离级别
10.2.2 隔离级别

10.2.2.1 read uncommitted

事务A还没结束,事务B就可以看到事务A对数据的修改,叫做脏读(dirty read)





自动提交不是针对手动begin或start transaction开启的事务,而是在除此之外执行的语句,如果自动提交开着,执行之后直接提交,不能回滚;如果关了,必须commit才能生效
InnoDB每一条sql语句被包装成事务


10.2.2.2 read committed

事务执行期间,对同一个读操作可能看到不一样的结果,不可重复读(unrepeatable read)
不可重复读有问题吗?

10.2.2.3 repeatable read



对于插入而言,客户端B事务在客户端A事务commit之后并没有看到插入的数据,也就是客户端B启动的事务在任何时候读得到的结果都是一样,但一般数据库在可重复读的情况,无法屏蔽其它事务insert的数据,因为隔离性是通过加锁实现的,而insert待插入数据在之前并不存在,所以一般加锁无法屏蔽这类问题,结果是大部分内容是可重复读的,但insert数据在可重复读的情况下会被读出来,多次查找时,会出现新的记录,称为幻读(phantom read),mysql在RR隔离级别解决幻读,(Next-key锁(GAP+行锁))
我们看到隔离级别说的都是跟读有关的,为什么?如果多个写事件必须串行,如果读读并发无所谓,反正不会改变数据,如果读写并发,就要考虑写的结果是否让读看到
一般read uncommitted->read committed->repeatable read->serializable,隔离级别越来越强
10.2.2.4 serializable
串行化的效率最低,通过加锁实现,对所有操作加锁,刚开始客户端A事务和B事务都能读,但是客户端A事务插入,客户端B事务删除就会被卡住,直到客户端A事务commit,客户端B事务才能进行删除,如果客户端B事务长时间拿不到锁,就会超时,事务退出




隔离级别越严格,安全性越高,数据库的并发性能越低,往往需要在两者之间找一个平衡点
不可重复读的重点是修改和删除,同样的条件,读取的数据不一致
幻读的重点在新增,同样的条件,读取的记录数不一致
mysql默认隔离级别是RR,一般不推荐修改
事务有长短,事务间相互影响是指事务并发执行的过程中,都没有commit时影响较大

一致性(consistency)
事务执行的结果,必须使数据库从一个一致性状态,变到另一个一致性状态,当数据库只包含事务成功提交的结果时,数据库处于一致性状态。如果系统运行发生中断,某个事务尚未完成而被迫中断,而未改完的事务对数据库所做的修改已被写入数据库,此时数据库处于一种不正确(不一致状态),一致性需要通过原子性来保证
一致性和用户业务逻辑强相关,一般mysql提供技术支持,但一致也需要用户业务逻辑做支持,一致性由用户决定
技术上,通过AID保证C

10.3 隔离性内核理解
数据库并发场景
读读-不存在问题,不需要并发控制
读写-有线程安全问题,可能造成事务隔离性问题,可能遇到脏读,幻读,不可重复读
写写-有线程安全问题,可能存在更新丢失问题,如第一类更新丢失,第二类更新丢失
读-写
MVCC(Multi-version concurrency control,多版本并发控制)是一种用来解决读写冲突的无锁并发控制
未事务分配单项增长的事务id,每个事物有自己的事务Id,ID表明事务到来的先后顺序
mysql面临处理多个事务的情况,事务有自己的生命周期,mysqld对多个事务进行管理,先描述,再组织,mysqld是用C/C++写的,事务用结构体或者类对象来描述
为每个修改保存一个版本,版本与事物ID关联,读操作只读该事物开始前的数据库快照,MVCC可以为数据库解决一下问题:
- 在并发读写数据库时,可以做到在读操作时不用阻塞写操作,写操作不用阻塞读操作,提高数据库并发读写性能
- 解决脏读、幻读、不可重复读等事务隔离问题,但不能解决更新丢失问题
3个记录隐藏列字段
DB_TRX_ID: 6byte,最近修改/插入事务ID,记录创建这条记录/最后一次修改该记录的事务ID
DB_ROLL_PTR: 7byte,回滚指针,指向这条记录的上一个版本(在undo log中)
DB_ROW_ID: 6byte,隐藏的自增ID(隐藏主键),如果数据表没有主键,InnoDB会自动以DB_ROW_ID产生一个聚簇索引
还有一个删除flag隐藏字段,记录被更新/删除不是物理意义上的删除,而是删除flag变了
undo日志
mysqld以服务进程方式在内存中运行,索引、事务、隔离性、日志等机制在内存中完成,在mysql内部相关缓冲区保存相关数据,完成各种判题操作,在合适的时候,将相关数据刷新到磁盘,undo log就是mysql中的一段内存缓冲区


上面是更新,删除呢?我们知道,删除是通过标志位来进行的,那么本质也是修改,插入呢?插入之前没有该条记录,怎么回滚呢?可以通过反操作来进行,放入undo log的是删除操作,可以通过删除进行插入的回滚
如果事务commit,并且没有其它事务访问的undo log历史版本记录就可以删掉
select分为
当前读:读取最新版本
快照读:读取历史版本
进行删改查都是当前读,加锁,如果此时select当前读,要加锁,串行化
快照读,不受加锁限制,并行,提高效率
隔离级别决定select是当前读还是快照读
为什么要有隔离级别?事务都是原子的,有先有后,begin->CURD->commit,有阶段,事务有执行前、执行中、执行后的阶段,多个事物执行中,CURD操作交织在一起,为了保证事物的鲜花,不同事物看到合适的版本,也就是隔离性和隔离级别解决的问题
如何保证不同事物看到不同内容?如何实现隔离级别》
read view
read view就是事务进行快照读操作的时候产生的读视图,在该事物执行快照读那一刻,会生成数据库系统当前的一个快照,记录并维护当前活跃事物的ID(当每个事物开启时,都会被分配一个ID,递增,越新的事物,ID值越大)
read view在mysql源码中,是一个类,本质进行可见性哦安定,当某个事物执行快照读的时候,对该记录创建一个read view,用来判断当前事务能够看到哪个版本的数据,可能是当前最新的数据,也可能是该行记录undo log里面某个版本的数据
readview简化后的结构


实际读取数据版本链时,能读取到每一个版本对应的事务ID,即当前记录的DB_TRX_ID

源码,通过B+树找到最新记录,找到DB_TRX_ID,判断是否可见,如果不可见,沿着回滚指针往前找,找到就停止



RR和RC的区别,RR在第一次进行快照读之后,后续快照读用的都是第一次读生成的快照,所以对第一次快照读之后提交的事务造成的修改不可见(还认为是活跃事务,但已经不是了)
RC每次都生成新的快照