目录
1.mysql在centos7环境上的安装
2.数据库基础
1. 什么是数据库
2.基本认识
3.库的操作
1.创建数据库
2.查看数据库列表
3.删除数据库
4.修改数据库
5.库的备份与修复
4.表的操作
1.创建表
2.查看表
3.修改表
修改表名
给表增加属性。
修改表中的某个属性(modify 直接跟修改的类型等,change 加上名字,可以修该名字)
删除表中的某个行属性
5.删除表
5.mysql的数据类型
6.表的约束
1.空属性
2.default(默认/缺省值)
3.comment(列描述)
4.zerofile
5.primary key(主键)
6.自增长(auto_increment)
7.唯一键(unique)
8.外键(Foreign Ke)
表的内容的增删查改
1.插入元素
单行数据+全列插入
替换
2.restrieve(取回元素)
比较操作符
1.全列查询
2.单列查询
3.表达式查询
4.where 子句
比较运算符
结果排序
limit offset 筛选
3.updata修改
4.删除delete
5 截断表
分组查询
聚合函数
函数
1.日期函数
2.字符串函数
3.数学函数
4.其他函数
复合查询
多表查询
子查询
多行子查询
合并查询
union
表的内外连接
内连接
外连接
左外连接
右外连接
Mysql索引
理解单与多page时的文件
聚簇索引与非聚簇索引
索引操作
1.添加索引
2.查询索引
3.删除索引
事务
1.什么是事务
2.事务的属性
3.为什么要有事务
4.事务的两种提交方式
5.常见事务操作
事务的开始与回滚
事务隔离级别
理解隔离级别
设置隔离级别
隔离的本质
读未提交:
读提交:
可重复读
串行化
编辑mysql读写并发
视图
创建视图
mysql用户管理
1.创建新用户
2.删除子用户
3.设置修改密码
4.权限设置
1.给用户授权
2.回收权限
Mysql Connect
mysql的接口
连接池原理
这里我们以云服务器上的cenmtos7上的数据库的安装为例:
1.mysql在centos7环境上的安装
我们先查看是否有相关的mysql或者mariadb(mysql的另一种开源分支)进程,一般来说服务器默认会有这两个,这里我们在重新安装一下(切换到root),一步步来:
1.关掉mysql的相关服务。
systemctl stop mysqld
2.一般在安装的时候会同时拉取有mysql rpm风格的安装包,我们找到并删除数据库。
rpm -qa |grep mysql |xargs yum -y remove
即使卸载了一般也会留下mysql的配置文件(要删除掉)和上一个数据库的数据。我们可以选择删除,也可以不用删除前数据库信息。可以用指令查看相关内容:
ls /etc/my.cnf //配置文件
ls var/lib/mysql/ //前前数据库遗留下的数据
3.获取官方yum源
先查看自己系统的版本
cat /etc/redhat-release
去官网找稍微比自己系统新一点的版本的mysql :repo.mysql.com.
官网信息看起来不是很全,我们可以看页面源码来看找对应的版本。
我们这里就选用版本为,mysql57-community-release-el-,(mysql选用常用的5.7版本),之后后面的版本后根据自己的服务器选择,我这里是7.6的,但时官网没有,就选用版本为7的就行。
4.本地上传并安装解压
创建一个目录用来存放数据库的解压的东西:
rz -E 选取解压的rpm文件 //上传到该路径下
在使用指令解压安装yun源:
rpm -ivh +rpm文件
这样我们就有了对于mysql的一系列安装包。
之后你可以选择删除掉rpm文件。
5.开始安装mysql
使用指令一键安装
yum install -y mysql-community-server
系统会自行选择合适的版本的mysql安装(虽然这里只右server但是安装时,会安装一系列的插件服务端等)。
安装过程中遇到密钥保存使用下面指令更新密钥(GPK KEY)
rpm --import https://repo.mysql.com/RPM-GPG-KEY-mysql-2022
之后重新安装。
6.确认安装成功
安装完成会存在两个东西,一是配置文件 .第二个是存在服务 mysqld。
//查看以下三个是否存在
ls /etc/my.cnf //配置文件
which mysqld //服务端
which mysql //客户端
都存在时,说明安装成功。
7.启动服务器
在使用前,我们还需要启动mysql的服务器,一般超级管理员启动服务端,我们一般(普通用户)使用客户端即可。
systemctl start mysqld
之后我们可以使用ps指令或者netstat来查看mysql是否启动了。
8.开始登陆
如果没有学习任何sql语句,我们这里可以直接修改配置文件(/etc/my.cnf)在最后一行加入skip-grant-tables.
跳过授权表,然后登陆上数据库。
mysql -uroot -p
重新启动服务器
systemctl restart mysqld
回车之后再回车,就可以进入到数据库中了。
为了之后使用正常,我们这里会先修改mysql的配置文件。
我们会添加三个配置选项;
port=3306 //端口号
character-set-server=utf8 //编码格式
default-storage-engine=innodb //引擎
之后也可以设置开机自启动服务器
systemctl enable mysqld
systemctl daemon-reload
2.数据库基础
1. 什么是数据库
2.基本认识
1.登录上服务器
mysql -h ip地址 -p 端口号 -u 用户 -p
//一般我们就简单点
mysql -u用户 -p //默认本地环回
2.管理服务器
mysql是一个给我们提供存取数据服务的一个网络程序。
那么我们一般所指的数据库就是在磁盘下存储的数据组织。
数据库服务就指的是mysqld。
systemctl stop mysqld //关闭服务
systemctl start mysqld //启动服务
systemctl restart mysqld //重启服务
整个mysql服务由三部分组成:
一.第一部分就是链接池
二.第二部分对sql语句进行词法分析,语法分析对代码优化。
三.第三部分是存储着一个个与之匹配的存储引擎。
这三部分我们可以插入到mysql中,也可以去掉,需要用哪一种就插入哪一种拿来使用。
3.sql语言的分类。
sql语言分为三类:
第一种是DDL,数据定义语言,数据库的创建,表的定义,如alter,drop,create
第二种是DML,数据操纵语言,增删查改,sselect ,insert,delete ,update.
第三种是DCL,数据控制语言,负责全县管理与实务管理,grant,revoke,commit.
4.存储引擎
mysql中支持插件式的引擎管理,就跟插件一样,想要哪一类特殊数据,就下载对应的插件引擎,我们可以使用指令show engine来查看库中的引擎。
大部分情况我们使用的较为频繁的两个存储引擎式就是Innodb,MyIsAm,这两个。
3.库的操作
1.创建数据库
CREATE DATABASE [IF NOT EXISTS] db_name
中括号里面代表的时可选项。
2.查看数据库列表
SHOW DATABASES;
SELECT DATABASE();//查看在哪一个数据库
SHOW CREATE DATABASE name;
3.删除数据库
DROP DATABASE [IF NOT EXISTS] db_name
4.数据库的编码问题
当我们创建数据库的时候没有指定字符集与校验规则时,系统默认使用字符集‘utf-8’,检验规则是:uft-8_general_ci。
查看系统支持的字符集:
show variables like 'character_set_database';
检验集:
show variables like 'collation_%';
查看数据库支持的字符集:
show charset;
当然我们也可以在创建数据库的时候加上character /collation set utf8/uft-8_general_ci;
也可以同时指明:
create database db_name charset=utf8 collate utf8_general_ci;
系统配置与数据库配置采用就近原则,如果数据库设置了,就使用数据库的。
4.修改数据库
ALTER DATABASE name charset=utf8 collate utf8_general_ci;
修改数据库的字符集与校验集。
5.库的备份与修复
备份语法:
mysqldump -p3306 -uroot -p 密码 -B 数据库名 > 要拷贝到的库的路径
修复语法:
mysql> source 路径.sql;
4.表的操作
1.创建表
create table t_name(
filed1 datatype,
filed2 datatype,
filed3 datatype
)character set 字符集 collate 校验规则 engine 存储引擎;
filed是列名,datatype表示类型 ,之后就是指定字符集与校验规则。
2.查看表
desc tab_nme; //查看表的结构
show tables; //查看当前库下的表
show create table tab_name \G;查看创建时表的信息
3.修改表
修改表名
alter table tab_name rename to new_name;
给表增加属性。
可以指明在哪一列后面添加
alter table tab_name ADD filed datatype,filed1 datatype after x ;
修改表中的某个属性(modify 直接跟修改的类型等,change 加上名字,可以修该名字)
alter table tab_name modify newfiled newdatatype,..;
alter table tab_name change 列名 新列名 属性
其中,增加和修改需要指明新的列表属性。
删除表中的某个行属性
alter table tab_name 列名
alter table tab_name drop 列名;
5.删除表
drop table tab_name
以上操作不要轻易做修改与删除。
5.mysql的数据类型
先看看数据库类型分类:
bit[(M)] : 位字段类型。M表示每个值的位数,范围从1到64。如果M被忽略,默认为1
charchar(2) 表示可以存放两个字符,可以是字母或汉字,但是不能超过 2 个, 最多只能是 255。varcharchar (L): 固定长度字符串, L 是可以存储的长度,单位为字符,最大长度值可以为 255。
日期和时间类型常用的日期有如下三个:date : 日期 'yyyy - mm - dd' ,占用三字节datetime 时间日期格式 'yyyy - mm - dd HH:ii:ss' 表示范围从 1000 到 9999 ,占用八字节timestamp :时间戳,从 1970 年开始的 yyyy - mm - dd HH:ii:ss 格式和 datetime 完全一致,占用四字节
enum 和 set语法:enum :枚举, “ 单选 ” 类型;enum(' 选项 1',' 选项 2',' 选项 3',...);该设定只是提供了若干个选项的值,最终一个单元格中,实际只存储了其中一个值;而且出于效率考 虑,这些值实际存储的是“ 数字 ” ,因为这些选项的每个选项值依次对应如下数字: 1,2,3,.... 最多 65535 个;当我们添加枚举值时,也可以添加对应的数字编号。set :集合, “ 多选 ” 类型; set('选项值 1',' 选项值 2',' 选项值 3', ...);
6.表的约束
光数据类型要确保数据的合法性太过单一,为此又引入了额外的约束,表的约束很多,这里主要介绍如下几个:
null/not null,default,comment,zerofile,primary_key,auto_increment,unique key.
通过表的约束,我们未来再插入数据库中表的数据时,数据是否符合预期的结果,如果插入错误,说明插入数据有误,以确保数据和合法性。
1.空属性
两个null与not null,字面意思,数据是否为空,null就是空,没有,not null就是非空。
null不参与计算。
在创建表的时候,对列名做限制,not null/null(不做说明时,默认可以为空).不符合我们的限制时就无法插入。
create table myclass(clssroom varchar(20) not null);
2.default(默认/缺省值)
我们可以指定某个具体的值,当我们不去的指定的时候,就直接使用默认给的值,这就是默认的作用。
create table myclass(clssroom varchar(20) default NULL);
例如我们定义表的某个列时,我们给定初始值(default 类型值)。
在我们插入元素时,没有给出对应的列的值,就使用默认的初始值。如果未设置缺省值,在插入元素时无具体值就会报错。
mysql自动添加defalut null的缺省值。
3.comment(列描述)
对列名进行描述,即该列表示是的数据是什么意思,可以理解为注释。
create table myclass(clssroom varchar(20) not null comment '教室的名字');
4.zerofile
我们一般在建表的时候,例如列的类型我iint,创建好后,使用desc查看表的时候Int(10),会出现10,这是什么意思呢?
实际上我们可以理解,这是对我们定义的数据类型做优化了,实际上在创建的时候,mysql给我们带上int(10),因为我们并没有指明宽度。
我们可以通过\G选项来查看创建表时的信息。
当我们给列添加zerofill时,在来查看表,可以看到空余的宽度会被零填充。
alter table myclass modify ing zerofeill not null;
zerofill并不影响数组的存储,只是会影响数据的显示。
5.primary key(主键)
主键用来唯一的约束该字段里面的数据,不能为空,不能重复,一张表中只能有一个。逐渐的列通常都为整型类型(如学生的学号,编号等)。
一般而言我们建表都是要指定主键的,同样是在我们建表的时候对列指定约束。
其次我们也可以对创建好的表进行主键的设置:
alter table student add primary key(id);
但是要确定列是不是都是具有唯一性的。
删除主键
alter table student drop primary key;
虽然一个表只能有一个主键,但实际上还有一种复合主键使得多列都可以有主键属性。
在进行列的定义最后,我们可以指明复合主键primary key(id,num)。
6.自增长(auto_increment)
alter table student modify id int auto_increment;
7.唯一键(unique)
alter table student modify email varchar(20) unique;
8.外键(Foreign Ke)
foreign key (字段名) references 主表(列)
表的内容的增删查改
1.插入元素
单行数据+全列插入
插入分为两种方式:
1.第一种是全列插入,即默认插入所有元素。
insert into student value(2,'马超','2222.com');
2.第二种是给定列的属性插入。
insert into student (id,name,email)value(1,'张飞','1111.com')
属性必须和内容一一对应。
当然我们也可以多行插入,也可多行指定列插入:
insert into student (id,name,email)value(1,'张飞','1111.com'),(3,'刘备','3333.com')
对插入数据做修正:
我们可以使用updata更新新插入的数据,此时主键冲突,使用on duplicate key update,如下:
insert into student value(3,'赵云','300000.com')on duplicate key update id=3,name='赵云',email='30000.com';
替换
插入元素除了用上述插入时是否修改的方法,也可以直接用replace做替换,如:
如没有该数据冲突,就直接插入,如果有删除后再插入(不在原位置上了):
replace into student(id,name,email) values(2,'刘备','222.com');
2.restrieve(取回元素)
在此时钱我们先了解一下mysql的一些基本比较操作符:
比较操作符
取回元素,即查询元素,作为数据操作最为频繁的语句,查询的方式多种多样,我们来先看看查询的语法:
SELECT[DISTINCT] {* | {column [, column] ...}[FROM table_name][WHERE ...][ORDER BY column [ASC | DESC], ...]LIMIT ...
1.全列查询
select * from table_name
2.单列查询
select 列名 列名 .. from table_name;
3.表达式查询
实际上select后面跟的是一个表达式,只要是表中有的,都出查出对应的值并计算。其中我们还可以使用as赋值表达式。
使用distinct进行某一列 去重:
4.where 子句
使用where子句进行条件查询
比较运算符
现在我们据根据案例来使用一下这些运算符:
结果排序
DESC降序
ASC升序(默认值)
limit offset 筛选
为了防止数据太大,查找全部太慢可以进行分页:
limit offset用法类似python里的切片
limit num---从开始0处到num行
limit pos1 step 从指定位置pos1到step行:
limit num offset step 筛选num行从第step开始.
3.updata修改
语法:
UPDATE table_name SET column = expr [, column = expr ...][WHERE ...] [ORDER BY ...] [LIMIT ...]
根据条件进行需要的元素的修改。
根据名字修改数学成绩:例如:
update stu_score set math=90 where name='张飞';
update stu_score set math=math+30 order by math+chinese+english asc limit 3 offset 0
4.删除delete
删除操作一般要慎重删除,确定好后删除。
DELETE FROM table_name [WHERE ...] [ORDER BY ...] [LIMIT ...]
删第一行
delete from stu_score limit 1;
5 截断表
语法:TRUNCATE [TABLE] table_name
分组查询
select column1, column2, .. from table group by column;
注意这里是根据列进行分组的。
聚合函数
函数 | 作用 |
COUNT([DISTINCT] expr)
|
返回查询到的数据的 数量
|
SUM([DISTINCT] expr)
|
返回查询到的数据的 总和,不是数字没有意义
|
AVG([DISTINCT] expr)
| 返回查询到的数据的 平均值,不是数字没有意义 |
MAX(DISTINCT expr)
| 返回查询到的数据的 最大值,不是数字没有意义 |
MIN([DISTINCT] e
xpr)
| f返回查询到的数据的 最小值,不是数字没有意义 |
计数:
select count(id) from stu_score where math=150;
select count(*) from stu_score where math=150;
求和:
select sum(math) from stu_score where math>90;
一般我们将group by语句与聚合函数结合到一起使用:
注意:分组的时候分出来的组,查询时是将一组作为一个整体的。
函数
1.日期函数
函数 | 描述 |
current_date | 当前日期 |
current_time | 当前时间 |
current_timestamp | 当前时间戳 |
date(datetime) | 返回date_time参数中的日期部分 |
date_add | 添加日期或者时间 |
date_sub | 减去日期或者时间 |
date_diff | 两个日期之间的差(天数) |
now | 当前日期时间 |
给日期增加单位 :单位可以是 year, month,day。
2.字符串函数
函数 | 作用 |
charset(str) | 返回字符串字符集 |
concat(str1,str2) | 连接字符串 |
instr(str,substr) | 返回substr后的下标位置 |
ucase(str) | 转换成大写 |
lcase(str) | 转换成小写 |
left(str,length) | 从字符左边开始去n个字符 |
length(str) | 获取字符串长度 |
replace(str,serch_str,replace_str) | 用replace替换serch的字符在str中 |
strcmp(str,str) | 字符串比较 |
substring(str,pos,length) | 从str的pos处取length |
ltrim | 去除前空格与后空格 |
select charset(name)from stu_score;
这些函数基本上我们一看就会用,就不多做赘述了。
3.数学函数
函数 | 作用 |
abs | 求绝对值 |
bin | 十进制转二进制 |
hex | 转十六进制 |
conv | 进制转换 |
ceiling | 向上取整 |
floor | 向下取整 |
format | 格式化,保留小数 |
rand | 随机浮点数 |
mod | 取模 |
4.其他函数
函数 | 作用 |
user() | 查询当前用户 |
md5() | 将字符串转化为32位字符串 |
database() | 当前数据库 |
password | 使用该用户数据库进行了加密 |
isnull(val1,val2) | 判断第一个字段是否为空,返回val1,否则val2 |
复合查询
多表查询
例如查询员工名,员工工资 ,以及所在部门:
查询的本质就将两个表的信息做整合(笛卡尔积)。表名相同的话可以重命名
通过两个表中相同的列建立查询关系,可以认为是外键和主键做关联。
select ename,sal,dname from emp ,dept where emp.deptno=dtp.deptno;
对于表中唯一的列,关联之后可以不加前缀说明。
再例如:找到员工名字为FORD的领导的编号与名字。
先查到领导的工号,在根据工号查对应的名字与编号。
当然也可以多表查询:
将 表做笛卡尔积,在进行两个条件的筛选。
子查询
可以理解为嵌套查询:
例如:查询SMIT同一部门的员工
select * from emp where deptno =(select deptno from emp where name="SMIT");
多行子查询
仔细观察,我们单行子查询在上一层查询时进行筛选是 直接是指明了特定的 列明。
如果将其改为 where 列明 in (子查询结果),那么就是多行查询。
例如:
select ename,job,sal,deptno from emp where job in (select distinct job from
emp where deptno=10) and deptno<>10;
先查出所有相同名字的员工,然后对查到的每一个元素在进行外层查询。
一个子查询的结果本身其实就是一个新表,我们可以在将该表结合其他表进行多表查询。
除了in关键字 还有两个关键字(all)(any)。
显示工资比部门30的所有员工的工资高的员工的姓名、工资和部门号
select ename, sal, deptno from EMP where sal > all(select sal from EMP where
deptno=30);
比最高的高
select ename, sal, deptno from EMP where sal > any(select sal from EMP where
deptno=30);
只要比其中任意一个高就符合条件。
合并查询
union
select ename, sal, job from EMP where sal>2500 union
-> select ename, sal, job from EMP where job='MANAGER';--去掉了重复记录
> select ename, sal, job from EMP where sal>2500 union all
-> select ename, sal, job from EMP where job='MANAGER'
表的内外连接
内连接
select 字段 from 表1 inner join 表2 on连接条件 and 其他条件
例如查询员工SMITH的员工与部门。
-- 用前面的写法
select ename, dname from EMP, DEPT where EMP.deptno=DEPT.deptno and
ename='SMITH';
-- 用标准的内连接写法
select ename, dname from EMP inner join DEPT on EMP.deptno=DEPT.deptno and
ename='SMITH';
外连接
外连接是分为两种的:一种是左外连接,一种是右外连接。
左外连接
语法格式
select 字段 from 表1 left join 表2 on 连接条件
如果是内连接只会保留大于80的学生。
所以左外连接的特点是 已左边的表为侧重点,然后用右边的表进行条件查询,也就是左侧的表会直接先查出来,再根据条件筛选(以右边的表的属性)进行排序。
总结:左边的表完全保留,右边的表按条件拼接。
右外连接
了解了左外连接,那么显而易见,右外连接是以右表为重心,再根据左表按条件筛选排序。
例如还是查成绩大于80的学生名字,这次改为右外连接:
Mysql索引
什么是索引:
主键索引 (primary key)唯一索引 (unique)普通索引 (index)全文索引 (fulltext)-- 解决中子文索引问题。
语法:
alter table 表名 add index(列名)
而 MySQL 作为一款应用软件,可以想象成一种特殊的文件系统。它有着更高的IO场景,所以,为了提高 基本的IO效率, MySQL 进行IO的基本单位是 16KB 。
建立共识MySQL 中的数据文件,是以 page 为单位保存在磁盘当中的。MySQL 的 CURD 操作,都需要通过 计算 ,找到对应的插入位置,或者找到对应要修改或者查询的数据。而只要涉及计算,就需要 CPU 参与,而为了便于 CPU 参与,一定要能够先将数据移动到内存当中。 所以在特定时间内,数据一定是磁盘中有,内存中也有。后续操作完内存数据之后,以特定的刷新 策略,刷新到磁盘。而这时,就涉及到 磁盘和内存的数据交互,也就是IO了 。 而此时IO的基本单位 就是Page。为了更好的进行上面的操作, MySQL 服务器在内存中运行的时候,在服务器内部,就申请了被称 为 Buffer Pool 的的大内存空间,来进行各种缓存。其实就是很大的内存空间,来和磁盘数据进 行IO 交互。 为何更高的效率,一定要尽可能的减少系统和磁盘IO 的次数
其次因为主键,数据会按照索引链式连接起来,当然顺序是按找索引的大小顺序连接的。
理解单与多page时的文件
那么为什么在磁盘与内存进行数据IO交互时用一页作为IO交互的大小,每次读取数据都以以page为单位?
首先是对于增删查改操作,通过更大的缓冲区,减少每次cpu访问主存的次数,大大提高了访问的效率,在加载的时候加载较大的数据量。再者对于文件读写,提高磁盘与主存的IO交互,通过这种方式,也会提高不少效率,因此就规定了IO交互的大小为一页是比较适合的。
其次除了数据的查找增加,存储,还要对这么多数据进行管理,而管理也需要数据结构来进行管理,一个独立的文件可能由一个page或者多个page构成,通过双向循环链表来管理着这些文件,在查找或者访问时,将数据对应得一页加载上去。
但是对于这样的一个文件,在大量数据情况下,肯定时是有许许多多得页,数据小好说,但太多的话,效率提高不了多少,因此对于每一个文件,会增加一个页目录,顾名思义,通过目录更快速得找到该页,从而提高效率。----对于以上设计都是经典的空间换时间。
为了更加高效的去迅速找到对应的页目录,又对页目录构建了一个页页目录,用来查找页目录,页页目录不存储数据,只有对于页目录得数据字段。
看到这里学习过高级数据结构的小伙伴就会发现,这种结构似曾相识,没错就是B+树!
我们先看一下结构图:
叶子节点保存数据,路上的节点值保存目录,叶子节点全用链表连接起来。
我们称这种结构为mysql下的inodb结构.
总结索引设计的三个方面:
第一个方面是:构建数据是随机生成地址,使用随机访问。不使用连续访问,不需要定位。
第二个方面是提供更大的缓冲区进行1page单位的 的IO交互,磁盘。
第三个方面就是设计更好结构实现数据库数据的管理(增删查改)。
这里可能就有好兄弟疑惑,选择B+树,为什么不是用其他数据结构?
显而易见的,感觉链表也可以啊:
首先链表是线性结构,遍历的话是一个个找的,但树不一样,他是一个个分支,效率自然比线性结构高
那二叉搜索树呢?还自带有序。
首先这里的页是非常多的,那就代表节点有非常多,树是又高又瘦的,且每一个节点都要配置页的数据信息,IO效率不是很高,数据足够多,还面对退化问题,退化成线性表。
AVL和红黑树,平衡了二叉树,且有序。
虽然平衡了,但树的整体还是瘦高形状,自底向下查找 IO交互次数也不低。
二哈希则是因为首先搜索引擎(myisam Inodb)不支持该算法,其次在面对范围广的数据是不要行。
除了B+树,还有一个B树,B树与B+树结构类似,区别是B树的路上的节点既存目录项,也存有数据,那为什么人不用B树。
首先既然你非叶子节点都存,那对于一个节点来说,存取的目录项反而就要少了,那么在IO交互时反而次数要多。IO的时间成本是比算法效率高的,其次叶子节点之间是不相连的,范围查找效率会变低,时间成本会高。
聚簇索引与非聚簇索引
上述所说的结构就是mysql下的Inodb搜索引擎的实现,除此之外,还有其他结构的其他搜索引擎--MYISAM(存储引擎与主键索引)。
底层还是使用B+树作为索引结构,区别是Inodb时数据和索引结构是一个整体,而MYISAM是将所应结构与数据分开,叶子节点存储数据的地址。这种方式我们一般称为非聚簇索引,反之Inodb是聚簇索引。
索引操作
1.添加索引
第一种方式在创建表的时候给列指定索引(主键,唯一键,index,fulltext)。
create table user(id int primary key,name varchar(20));
第二种方式在创建表的最后指定列约束。
create table user(id int,name varchar(20),primary key(id));
第三种方式即表已经创建好了,此时要修改表(准确来说是添加)。
alter table user add primary key(id);
2.查询索引
show index from 表名
desc 表名 //比较简陋,不推荐
3.删除索引
第一种方法-删除主键索引: alter table 表名 drop primary key;
第二种方法-其他索引的删除: alter table 表名 drop index 索引名; 索引名就是show keys
from 表名中的 Key_name 字段
mysql> alter table user10 drop index idx_name;
第三种方法方法: drop index 索引名 on 表名
mysql> drop index name on user8;
补充:关于全文索引,首先只有myisam引擎支持,其次只支持英文索引。
事务
为什么要有事务?
以我们买火车票为例,因为买票是一个高并发的情况,就存在数据库数据更新来不及的情况,客户端A和B同时买票,此时票只有一张,A买票成功了,此时票数为零,B也在买,刚才还为1,更新之后为0,此时退回B的购买数据,当作没发生,我们确定买票只有两种结果 成功还是不成功是原子的。
1.什么是事务
实际在面对这种网络服务是,对于数据的处理,不是一条语句就能完成。例如转账,在该数据库中增加你的余额,而其他的数据库中减少你的余额。这样的一组由多个DML组成的我们称作事务。
2.事务的属性
所谓的事务也不单单由一条条sql语句构成,还需要满足以下四个特性:
原子性: 一个事务( transaction )中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节。事务在执行过程中发生错误,会被回滚(Rollback )到事务开始前的状态,就像这个 事务从来没有执行过一样。一致性: 在事务开始之前和事务结束以后,数据库的完整性没有被破坏。这表示写入的资料必须完 全符合所有的预设规则,这包含资料的精确度、串联性以及后续数据库可以自发性地完成预定的工 作。隔离性: 数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务 并发执行时由于交叉执行而导致数据的不一致。事务隔离分为不同级别,包括读未提交( Read uncommitted )、读提交( read committed )、可重复读( repeatable read )和串行化( Serializable )持久性: 事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。
对于mysql只要在Inodb下的搜索引擎才支持事务,myisam是不支持的。
3.为什么要有事务
除了上面说的为了应对高并发时对数据的修改不一致最后造成数据不一致导致数据完整性的破坏而设计的,事物的设计更重要的作用是上层语言在访问数据库时,事务可以简化编程模型,不用考虑由于高并发的访问而产生的一系列问题,因为事务已经防止了这些问题的产生,上层只用调sql语句即可,不用担心原子性,一致性等问题。
4.事务的两种提交方式
自动提交与手动提交
我们可以通过语句查看提交状态:
---show variables like '''autocommit' ON就代表自动
手动提交 我们也可以手动设置事物的提交
set autocommit =0;//非自动提交
set autocommit =1;//自动提交
5.常见事务操作
首先确保数据库服务启动着--端口号3306的mysqld。
其次为了方便演示,我们先将数据的隔离设置为读未提交。
set global transaction isolation level READ UNCOMMITTED;
事务的开始与回滚
开始一个事务:
start transaction; begin也可以
在每编写一条DML语句时,首先创建一个保存节点。
savepoint save1;
此时上述操作都属于一个事务。
此时若多对一个操作后悔了,我们就可以回滚,返回到上一个savepoint。
例如上述每一次插入在此之前创建了savepoint,此时插了四条后,后悔了,想回到第三次插入前的状态:
rollback to save3;//rollback to 节点名
如果没设置保存点,也可以rollback,不过代价是数据全没了。
最后事务完成我们就可以提交事务了。
commit;
之后数据就持久化的保持在mysql中了。
未commit,客户端崩溃,MySQL自动会回滚(隔离级别设置为读未提交)。
例如我们继续启动事务,再插入一条数据,才是ctrl+\,异常退出musql,再查看的时候此时数据回滚到刚开始的时候,因为隔离等级为读未提交,所以异常的时候,没提交,事务作废。
commit了,客户端崩溃,MySQL数据不会在受影响,已经持久化
begin操作会自动更改提交方式,不会受MySQL是否自动提交影响
单条 SQL 与事务的关系 :实际上我们一般写的一条语句也就是一个事务,设置自动commit。
总结:
只要输入 begin 或者 start transaction ,事务便必须要通过 commit 提交,才会持久化,与是否设置 set autocommit 无关。事务可以手动回滚,同时,当操作异常, MySQL 会自动回滚对于 InnoDB 每一条 SQL 语言都默认封装成事务,自动提交。( select 有特殊情况,因为MySQL 有 MVCC )如果没有设置保存点,也可以回滚,只能回滚到事务的开始。直接使用 rollback( 前提是事务还没有提交 )如果一个事务被提交了( commit ),则不可以回退( rollback )
事务隔离级别
对于事物的特性:原子性,持久性,隔离性的实现,因为事务在处理过程中肯定是有过程的,要想实现这些特性,就要在操作时使用某些特性去实现,例如原子性,在有异常时就会回滚,成功时就提交--保证原子性。
读未提交【 Read Uncommitted 】 : 在该隔离级别,所有的事务都可以看到其他事务没有提交的 执行结果。(实际生产中不可能使用这种隔离级别的),但是相当于没有任何隔离性,也会有很多并发问题,如脏读,幻读,不可重复读等我们上面为了做实验方便,用的就是这个隔离性。读提交【 Read Committed 】 :该隔离级别是大多数数据库的默认的隔离级别(不是 MySQL 默认的)。它满足了隔离的简单定义: 一个事务只能看到其他的已经提交的事务所做的改变。这种隔离级别会引起不可重复读,即一个事务执行时,如果多次 select , 可能得到不同的结果。可重复读【 Repeatable Read 】 : 这是 MySQL 默认的隔离级别,它确保同一个事务,在执行 中,多次读取操作数据时,会看到同样的数据行。但是会有幻读问题。串行化【 Serializable 】 : 这是事务的最高隔离级别,它通过强制事务排序,使之不可能相互冲突,从而解决了幻读的问题。它在每个读的数据行上面加上共享锁,。但是可能会导致超时和锁竞争。
理解隔离级别
先看看如何查看隔离等级:
select @@global.tx_isolation; //查看全局隔离级别
select @@session.tx_isolation; //查看会话全局隔离级别
select @@tx_isolation; // 默认同上
设置隔离级别
set [globa/session] transaction isolation level {Read Uncommitted,Read Commiteed,Repeatabel READ,Serializable }
对于数据库他会默认使用就近的那一个session的隔离级别。
seeion的设置不会影响global的,但global的会影响刚刚新创建的session。
隔离的本质
我们先以读提交的隔离等级验证:
我们先对于一个事务做修改,此时未提交,在对另一个事务数据做修改,此时
可以看到上一个事务没提交的话,无法修改的而是进入了一种等待状态,只能等待上一个事务提交后才行,很明显这是通过加锁实现的。隔离等级的实现就是通过给数据加锁而实现的。
读未提交:
即我未提交事务,但是其他事务可以看到我未提交的结果。
而这种其他事务读到了当前事务未提交的结果---这种现象我们称之为脏读。
读提交:
首先设置全局隔离等级
此时我们会发现,其他事务并不会读到你还未commit时候的结果。
但是此时就会出现问题,比如我先commit了,其他事务此时在查询就会查到不同的数据。----这种现象我们叫做不可重复读。(并发情况下不同事务commit导致读到的数据不一致)。
可重复读
针对不可重复读,于是又设计了一种隔离方式-可重复读,即使是在并发情况下,当前事务的操作都是基于刚开始的,即使其他事物对数据进行了操作且提交了,但是不影响我当前数据,还是最初的样子。
但有一种特殊情况:
串行化
因为只能一个一个执行所以我们称作串行化。
总结问题:
mysql读写并发
mysql的读写并发有三种情况:
读--读并发 读---写并发 写--写并发
而解决多版本并发控制就是一种解决读写冲突的一种无所并发控制。
想要解决并发,举要解决事务在处理使得顺序,这是通过给事务赋予了一个单项增长的事务ID,
其次mysql需要对事务进行管理--事物的生命周期,创建,删除等。
因此在此之前,我们还需要了解一些其他东西:
3个隐藏的VCC字段
DB_TRX_ID : 6 byte ,最近修改 ( 修改 / 插入 ) 事务 ID ,记录创建这条记录 / 最后一次修改该记录的事 务ID。DB_ROLL_PTR : 7 byte ,回滚指针,指向这条记录的上一个版本(简单理解成,指向历史版本就 行,这些数据一般在 undo log 中)。DB_ROW_ID : 6 byte ,隐含的自增 ID (隐藏主键),如果数据表没有主键, InnoDB 会自动以 DB_ROW_ID 产生一个聚簇索引。补充:实际还有一个删除 flag 隐藏字段 , 既记录被更新或删除并不代表真的删除,而是删除 flag 变了。
undo日志
undo log是一个内存缓冲区
RR 与 RC 的本质区别正是 Read View 生成时机的不同,从而造成 RC,RR 级别下快照读的结果的不同在 RR 级别下的某个事务的对某条记录的第一次快照读会创建一个快照及 Read View, 将当前系统活 跃的其他事务记录起来此后在调用快照读的时候,还是使用的是同一个 Read View ,所以只要当前事务在其他事务提交更 新之前使用过快照读,那么之后的快照读使用的都是同一个Read View ,所以对之后的修改不可 见;即 RR 级别下,快照读生成 Read View 时, Read View 会记录此时所有其他活动事务的快照,这些事 务的修改对于当前事务都是不可见的。而早于Read View 创建的事务所做的修改均是可见 而在RC 级别下的,事务中,每次快照读都会新生成一个快照和 Read View, 这就是我们在 RC 级别下 的事务中可以看到别的事务提交的更新的原因总之在RC隔离级别下,是每个快照读都会生成并获取最新的Read View ; 而在RR隔离级别下,则是 同一个事务中的第一个快照读才会创建Read View , 之后的快照读获取的都是同一个Read View 。 正是RC 每次快照读,都会形成 Read View ,所以, RC 才会有不可重复读问题。
视图
视图是mysql的一种虚拟表,其内容由查询定义,同真实的表一样,视图也拥有自己的一些列带名称的列和行数据,视图的变化会影响到基表,基表的变化同样会影响视图。
创建视图
create view 视图名 as select 查询语句
之前我们就说过,我们每次查询的结果其实就可以当作一个临时的表,这次为了更好的存储查询的结果,引入了视图对查询结果的保存。
此时修改表中的数据会影响视图的内容,修改视图会影响表中的内容。
mysql用户管理
对于mysql就跟我们服务器一样,也是可以创建多个账号的,一般默认下我们使用的是root账号,很明显,这并不是很安全的方式,因此我们需要用普通账号使用,而root用来用户管理与权限设定。
在我们的数据库中有一个mysql这样的数据库,这里基本就是存储了mysql种所有的配置的数据。
查询可以看到里面有许多表,对应在系统下就是一个个文件,其中最底下就有一个user的表格:
查询发现里面的内容非常之多,因此我们只查询三个列来筛选一下:
以上局势我们当前的用户,这里只有root,既然是一张表,那么我们肯定可以进行表的数据的操作。
1.创建新用户
在此操作前,如果你的etc/my.cof的配置文件还添加了skip-grant-tables,需要注释掉,这样才可以创建新用户。语法:
create user '用户名'@登录主机('localhost')/ip identified by '密码'
如果遇到保存说受限于配置文件种之前的skip-grant-tables,刷新一下
mysql> flush privileges;
子用户登陆之后再查看数据库时,会明显地看到与root查看时的不同,
2.删除子用户
语法:
drop user 用户名@主机/ip
3.设置修改密码
自己改自己密码:
set password=password('新的密码');
root改其他账户密码:
set password for '用户名'@'主机名'=password('新的密码')
4.权限设置
1.给用户授权
grant 权限列表 on 库.对象名 to '用户名'@'登陆位置' [identified by '密码']
这里的库,对象名就指的是数据库下的哪一个表(*.*,库.* 所有库所有表,与库下的所有表),all代表所有权限。
2.回收权限
revoke 权限列表 on 库.对象名 from '用户名'@'登陆位置';
Mysql Connect
我们基本已经了解了mysql的使用了,我们当前使用的是命令行的方式使用数据库,但数据库的访问多种多样,有图形化,有程序访问,有命令行。但是命令行是最直接帮我们掌握sql指令的方式,实际开发当中,我们使用程序访问数据库的,但是如何在我们的程序中去使用mysql呢,此时就需要数据库连接器connect(mysql官方的第三方库)。关于c++的连接我们可以上官网进行下载查看:
MySQL :: MySQL Community Downloads
选择你需要的语言对应的般的的库,下载解压后其实就是一个库提供了头文件与动态库,提供了连接数据的方发以及访问,在程序连接时需要指明库的位置且包含头文件。
但是这个方式我们现在不推荐,存在版本兼容问题,且不方便如果你对动静态库的使用不熟悉。
其实我们在系统下载数据库的时候已经下载了对应的客户端,服务器,以及其他开发库。
sudo yum install -y mysql-community-server
如果没有你也可以去直接安装:
sudo yum install mysql-devel;
环境准备好了,我们就可以在程序中直接去使用了。
在编译的时候,我们不仅需要指明是哪一个库,还需要指明哪一个库中的动态文件。
对于mysql默认库在/lib64/mysql ,其中链接的是libmysqlclien.so(系统规定动态库命名是加了前后缀,而一般是我们将动态库的前缀去掉lib,再去掉后缀.so,就代表库名字)就可以指明库。
-L指明动态库的位置,-l指明具体哪一个库
g++ tets.cc -o test -L/lib64/mysql -lmysqlclient
mysql的接口
当然我们能学习也可以去通过官方文档学习:MySQL :: MySQL 5.7 C API Developer Guide
我这里用的时c的5.4版本的API。mysql的使用类似于文件操作,对比文件操作可以更好的理解。
1.创建mysql对象 mysqlinit
MYSQL * mysql_init(MYSQL * mysql)
MYSQL * mysql=mysql_init(NULL)
成功返回mysql结构体指针对象,失败返会空指针。
2.创建完毕之后,建立了连接,若不使用了就关闭 mysql_close
mysql_close(MYSQL *); //关闭连接
3.链接mysql mysql_real_connet
参数依次分别是 数据库对象指针,主机,用户名,密码,数据库,端口号,套接字,服务标志位。其中域间套接字和标志位我们都可以设置为空。
4.下达mysql指令mysql_query
例如我现在里面就有一个表用来存一个人的个人信息表,此时我对表进行操作。
5.设置字符编码格式mysql_set_character_set
我们在直接使用mysql时是设置了客户端的编码格式为utf8,但是此时我们通过程序访问得时候,再插入数据时,我们的编码格式大概率与mysql客户端的编码格式不匹配,因此在连接数据库之后,我们还需要设置字符集(直接插入中文是会乱码的):
6.查询语句处理 mysql_store_result
我们的程序对于数据库就相当于上层应用了,我们做增删改,操作完,数据库结果就改变了,是没有问题的,但是对于查询我们不仅需要执行查询语句,还需要将我们查询后的结果给到上层应用,再打印出来。
需要使用的接口,作用:提取查询得到的结果并存储到结果集当中。
可以看到返回值是一个MYSQL_RES*类型的结构体,我们称之为结果集得结构体,先存储到结果集当中,再根据去其他句柄读取里面的内容。
之后就是对结果集的内容按列,按行,或者按名称获取:
获取结果集的行数
对于fetch_row我们可以理解是一个迭代器,自动遍历每一行。
获取结果集列数
根据行数与列数,我们就可以利用fetch_row每次打印出一行内容。
除此之外,我们还可以通过接口获取列名
获取结果集中属性的列名
之后就可以遍历结果集,得到查询的结果。
查询完毕之后,我们还需要释放结果集:
代码编写如下:
string command1="select * from person";
//查询的结果集里面是查询结果拆分的多行多列字符串
int n=mysql_query(mysql,command1.c_str());
//根据sql对象获取结果集
MYSQL_RES *ret=mysql_store_result(mysql);
if(ret==nullptr)
{
cout<<"store_result error"<<endl;
}
if(n!=0)
{
std::cout<<"query error"<<std::endl;
}
//根据结果集获取行数,列数,列名
int rows=mysql_num_rows(ret);//行数
int fileds=mysql_num_fields(ret);//列数
cout<<"rows:"<<rows<<",fileds:"<<fileds<<endl;
//读取列名
MYSQL_FIELD*filed=mysql_fetch_fields(ret);//一行属性列
for(int i=0;i<fileds;i++)
{
std::cout<<filed[i].name<<"\t";//打印列名
}
std::cout<<"\n";
//一行行读取内容
for(int i=0;i<rows;i++)
{
MYSQL_ROW row=mysql_fetch_row(ret);//一行元i素
for(int j=0;j<fileds;j++)
{
std::cout<<row[j]<<"\t";
}
std::cout<<std::endl;
}
//查询完毕释放结果集
mysql_free_result(ret);
除了服务器访问,程序访问,当前也有对应的雨杏花界面的客户端来访问数据库,这个大家自行上网就可以搜索到,使用也是比较容易的。
连接池原理
一般我们在使用数据库服务的时候,是多个用户在线同时并发访问数据库服务的,这就代表多个人同时并发的去建立连接,对于数据库来说除了一些缓存技术(mongodb,redis),在编码上还使用了连接池得技术。
所谓的链接池我们就可以这样来认为:线程池中的每一个线程,都可自己的去建立数据库连接。
每一个用户都相当于任务队列中的任务,而我们的线程池,在启动的时候,只要有任务,就回去提供线程,该线程用来建立数据连接。
你可以自己设计,或者去看gthub,gitee上其他人怎么设计的,