文章目录
-
目录
[3.1 存储引擎](#3.1 存储引擎)
前言
存储数据用文件就可以了,为什么还要弄个数据库 ?
文件保存数据有以下几个缺点:
1.文件的安全性问题
2.文件不利于数据查询和管理
3.文件不利于存储海量数据
4.文件在程序中控制不方便
数据库存储介质:
磁盘和内存
为了解决上述问题,专家们设计出更加利于管理数据的东西 ------ 数据库,它能更有效的管理数据。 数据 库的水平是衡量一个程序员水平的重要指标 。
所以需要数据库的主要原因在于,一般的文件确实提供了数据的存储功能,但是文件并没有提供非常好的数据管理能力(用户角度)
数据库本质:对数据内容存储的一套解决方案,你给我字段或者要求,我直接给你结果就行。
一、主流数据库以及如何登陆数据库
SQL Sever : 微软的产品, .Net 程序员的最爱,中大型项目。
Oracle : 甲骨文产品,适合大型项目,复杂的业务逻辑,并发一般来说不如 MySQL 。
MySQL :世界上最受欢迎的数据库,属于甲骨文,并发性好,不适合做复杂的业务。主要用在电
商, SNS ,论坛。对简单的 SQL 处理效果好。
PostgreSQL : 加州大学伯克利分校计算机系开发的关系型数据库,不管是私用,商用,还是学术研
究使用,可以免费使用,修改和分发。
SQLite : 是一款轻型的数据库,是遵守 ACID 的关系型数据库管理系统,它包含在一个相对小的 C 库中。它的设计目标是嵌入式的,而且目前已经在很多嵌入式产品中使用了它,它占用资源非常的
低,在嵌入式设备中,可能只需要几百 K 的内存就够了。
H2 : 是一个用 Java 开发的嵌入式数据库,它本身只是一个类库,可以直接嵌入到应用项目中。
连接服务器命令:
sql
mysql -h 127.0.0.1 -P 3306 -u root -p
注意:
如果没有写-h 那么默认连接的就是本地,-P是指定端口号,不写默认是3306.(-h就是指明登录部署了mysql服务的主机)
-u代表要登录的用户,后面跟root代表我们要登录的是root,最后的-p是以输入密码的方式登录。
当然也可以修改配置文件进行root免密码登录:
sql
1. vim /etc/my.cnf 打开mysql配置文件
2.在mysqld最后一栏加入skip-grant-tables选项并且保存退出
3.systemctl restart mysqld 重启mysql服务
注意:修改配置文件的操作一定是root权限
先stop暂停mysql服务,然后再启动,当然也可以用上面的一条命令去重启。重新登录后发现出现要输入密码的选项,这个时候直接回车即可。
当然在配置文件中我们也可以干其他事:
比如
1.修改默认的端口号(一般不建议)
2.修改服务端字符集(建议设置为utf-8)
3.修改mysql默认的存储引擎(看需求修改,如果只是学习建议改为innodb)
mysql和mysqld的区别:
1.mysql是数据库服务的客户端
2.mysqld是数据库服务的服务端
3.mysql本质是基于C(mysql)/S(mysqld)模式的一种网络服务
4.mysql是一套给我提供数据存取的服务的网络程序
5.数据库一般指的是在磁盘或内存中存储的特定结构组织的数据,将来在磁盘上存储的一套数据库方案。
服务器,数据库,表关系
所谓安装数据库服务器,只是在机器上安装了一个数据库管理系统程序,这个管理程序可以管理多
个数据库,一般开发人员会针对每一个应用创建一个数据库。
为保存应用中实体的数据,一般会在数据库中创建多个表,以保存程序中实体的数据。
数据库服务器、数据库和表的关系如下:
二、常用命令使用
1.显示所有数据库
sql
show databases;
注意:SQL语言的后面是用;做结尾的。
下面我们先认识一下mysql是如何存储数据的:
首先进入/etc/my.cnf文件,然后我们会看到datadir,这个后面的路径就是数据库数据存储的位置
进入该路径后我们就可以看到mysql中显示出来的数据库了:
所以建立一个数据库的本质就是在linux下创建一个目录。
2.创建数据库
sql
create database test;
test是用于演示的数据库,这个名字根据需要自己设置。
3.使用数据库
sql
use test;
注意:要对某个数据库做操作都需要先进行使用
4.创建数据库表
sql
create table student(
id int,
name varchar(32),
gender varchar(2)
);
意思就是在test这个数据库中创建一个student表,这个表中列名称id为int类型,name为varchar类型,varchar是可变的char最大给了32个字节,后面详细讲,gender的类型也是varchar。
当建立一张表后,我们去该数据库目录下看看有什么区别:
所以在数据库内建表本质就是在linux下创建对应的文件即可。
注意:这个操作是mysqld帮我们做的。所以数据库本质也是文件,只不过这些文件不由程序员主动操作,而是由数据库服务帮我们进行操作。
5.表中插入数据
sql
insert into student (id,name,gender) values(1,'张三','男');
insert into表示在student这张表中插入数据,表后面括号内三个列名称代表向这三列进行插入,后面的values表示这三列对应的值,也就表示插入id为1,name为张三,gender为男的数据。
6.显示表中数据
sql
select * from student;
显示student这张表的数据:
7.显示数据库中所有表
sql
show tables;
8.删除表
sql
drop table student;
9.删除数据库
sql
drop database test;
删掉后我们也可以看到linux下的test目录也被删除了,所以用drop命令一定要小心。
三、SQL分类
DDL 【 data definition language 】 数据定义语言,用来维护存储数据的 结构
代表指令 : create, drop, alter
DML 【 data manipulation language 】 数据操纵语言,用来对 数据 进行操作
代表指令: insert , delete , update
DML 中又单独分了一个 DQL ,数据查询语言,代表指令: select
DCL 【 Data Control Language 】 数据控制语言,主要负责权限管理和事务
代表指令: grant , revoke , commit
3.1 存储引擎
存储引擎是:数据库管理系统如何存储数据、如何为存储的数据建立索引和如何更新、查询数据等技术的实现方法。
MySQL 的核心就是插件式存储引擎,支持多种存储引擎。
查看引擎的代码:
sql
show engines;
我们经常用到的存储引擎实际上也就是上面标红的。
四、创建数据库如何设置编码等问题
sql
CREATE DATABASE [IF NOT EXISTS] db_name [create_specification [,
create_specification] ...]
create_specification:
[DEFAULT] CHARACTER SET charset_name
[DEFAULT] COLLATE collation_name
大写的表示关键字
[] 是可选项
CHARACTER SET: 指定数据库采用的字符集
COLLATE: 指定数据库字符集的校验规则
加入if not exists的区别是再次插入重复的数据库时不会报错。
创建数据库的时候,有两个编码集:
1.数据库编码集(数据库未来存储数据用到的编码) 2.数据库校验集(支持数据库进行字段比较使用的编码,本质也是一种读取数据库中数据的采用的编码格式)
以上两个编码集保证了数据库无论对数据做任何操作,都必须保证操作和编码必须是编码一致的。
sql
1.查看编码集的命令
show variables like 'character_set_database';
sql
2.查看校验集的命令
show variables like 'collation_database';
下面我们看看默认创建数据库时的编码集和校验集:
可以看到不指明的时候是按照mysql配置文件中的编码集和校验集来配置的。
下面我创建一个演示数据库d3,创建库并且指定编码集和校验集的命令如下:
sql
create database d3 charset=gbk collate gbk_chinese_ci;
创建完成后我们可以看到编码集和校验集变成我们指定的了。在创建数据库时为什么要设置编码呢?这是因为数据库内的表的编码和校验集都是继承于数据库的。
4.1操纵数据库
1.查看自己在哪个数据库中
sql
select database();
2.修改数据库
sql
alter database d2 charset=gbk collate gbk_chinese_ci;
将d2这个数据库的编码集和校验集进行修改。
3.显示创建数据库时的命令
sql
show create database d2;
4.备份
cpp
# mysqldump -P3306 -u root -p 密码 -B 数据库名 > 数据库备份存储的文件路径
cpp
示例:将mytest库备份到文件(退出连接)
# mysqldump -P3306 -u root -p123456 -B mytest > D:/mytest.sql
这时,可以打开看看 mytest.sql 文件里的内容,其实把我们整个创建数据库,建表,导入数据的语句
都装载这个文件中。
5.还原
cpp
mysql> source D:/mysql-5.7.22/mytest.sql;
注意事项:
如果备份的不是整个数据库,而是其中的一张表,怎么做?
# mysqldump -u root -p 数据库名 表名1 表名2 > D:/mytest.sql
同时备份多个数据库
# mysqldump -u root -p -B 数据库名1 数据库名2 ... > 数据库存放路径
如果备份一个数据库时,没有带上-B参数, 在恢复数据库时,需要先创建空数据库,然后使用数据
库,再使用source来还原
6.查看连接情况
cpp
show processlist
可以告诉我们当前有哪些用户连接到我们的MySQL,如果查出某个用户不是你正常登陆的,很有可能你的数据库被人入侵了。以后大家发现自己数据库比较慢时,可以用这个指令来查看数据库连接情况。
4.2操纵表
1.创建表
cpp
create table table_name(
field1 datatype,
field2 datatype,
field3 datatype
) charset 字符集 collate 校验规则 engine 存储引擎;
当我们创建成功后看一下这个表在linux目录下是如何显示的:
可以看到在d2目录中多出了三个文件,下面我们再创建一个表并且使用不同的存储引擎再来看看效果:
我们可以发现用InnoDB做存储引擎需要创建两个文件,用MyIsam做存储引擎需要创建三个文件。
当然我们刚开始就说过,如果创建的时候不指明使用哪个存储引擎哪个字符集就会使用我们配置文件中默认的那个。
2.查看表
我查看表的基本步骤是:1.先查看使用的是哪个数据库 2.再查看这个数据库中有哪些表 3.然后选择使用这个表。
cpp
desc 表名
3.查看创建表时的sql语句
cpp
show create table user1(表名) \G(格式化)
4.修改表
1.添加某一列
cpp
alter table 表名 add 添加的列名 添加的列的类型;
当然也可以指定将新列放在某个位置:
cpp
alter table user1 add 列名 列类型 after name
上面代码的意思是将新添加的列放在name列的后面。
2.修改某一列的属性
cpp
alter table user1 modify name varchar(16);
将user1表中的name列的属性修改为varchar(16)
3.删除某一列
cpp
alter table user1 drop path;
注意:删除后整个列都没有了,数据也没了。
4.修改表名
cpp
alter table user1 rename user;
5.修改列名称
cpp
alter table user change name xingming varchar(32);
注意:修改列名必须加上列的属性。
6.删除表
cpp
drop table 表名
注意:轻易不要对表做修改和删除。
五、数据类型
1.数值类型
tinyint:字节数为1,有符号区间为-128~127,无符号区间为0~255
smallint:字节数为2,有符号区间为-32768~32767,无符号区间为0~65535
mediumint:字节数为3,有符号区间为-838608~838607,无符号区间为0~16777215
int:字节数为4,有符号区间为-2147483648~2147483647,无符号区间为0~4294967295
bigint:字节数为8,有符号区间为-9223372036854775808~9223372036854775807,无符号区间为0~18446744073709551615
在MySQL中,整形可以指定是有符号的和无符号的,默认是有符号的。可以通过unsigned来说明某个字段是无符号的。
2.bit类型
bit [(M)] : 位字段类型。 M 表示每个值的位数,范围从 1 到 64 。如果 M 被忽略,默认为 1 。
3.小数类型
float [(m, d)] [ unsigned ] : M 指定显示长度, d 指定小数位数,占用空间 4 个字节
4.decimal
decimal (m, d) [ unsigned ] : 定点数 m 指定长度, d 表示小数点的位数
decimal(5,2) 表示的范围是 -999.99 ~ 999.99
decimal(5,2) unsigned 表示的范围 0 ~ 999.99
decimal 和 float 很像,但是有区别 : float和 decimal表示的精度不一样, float 表示的精度大约是 7 位。
decimal 整数最大位数 m 为 65 。支持小数最大位数 d 是 30 。如果 d 被省略,默认为 0. 如果 m 被省略,
默认是 10。
5.字符串类型
char (L): 固定长度字符串, L 是可以存储的长度,单位为字符,最大长度值可以为 255
char(2) 表示可以存放两个字符,可以是字母或汉字,但是不能超过 2 个, 最多只能是 255
6.varchar
varchar (L): 可变长度字符串, L 表示字符长度,最大长度 65535 个字节
关于 varchar(len),len 到底是多大,这个 len 值,和表的编码密切相关:
varchar 长度可以指定为 0 到 65535 之间的值,但是有 1 - 3 个字节用于记录数据大小,所以说有效字
节数是 65532 。
当我们的表的编码是 utf8 时, varchar(n) 的参数 n 最大值是 65532/3=21844[ 因为 utf 中,一个字符占
用 3 个字节 ] ,如果编码是 gbk , varchar(n) 的参数 n 最大是 65532/2=32766 (因为 gbk 中,一个字符
占用 2 字节)。
char和varchar的比较:
如何选择定长或变长字符串?
如果数据确定长度都一样,就使用定长( char ),比如:身份证,手机号, md5
如果数据长度有变化 , 就使用变长 (varchar), 比如:名字,地址,但是你要保证最长的能存的进去。
定长的磁盘空间比较浪费,但是效率高。
变长的磁盘空间比较节省,但是效率低。
定长的意义是,直接开辟好对应的空间
变长的意义是,在不超过自定义范围的情况下,用多少,开辟多少。
7.日期和时间类型
常用的日期有如下三个:
date : 日期 'yyyy - mm - dd' ,占用三字节
datetime 时间日期格式 'yyyy - mm - dd HH:ii:ss' 表示范围从 1000 到 9999 ,占用八字节
timestamp :时间戳,从 1970 年开始的 yyyy - mm - dd HH:ii:ss 格式和 datetime 完全一致,占用
四字节
8.enum和set
enum :枚举, " 单选 " 类型;
enum(' 选项 1',' 选项 2',' 选项 3',...);
该设定只是提供了若干个选项的值,最终一个单元格中,实际只存储了其中一个值;而且出于效率考虑,这些值实际存储的是" 数字 " ,因为这些选项的每个选项值依次对应如下数字: 1,2,3,.... 最多 65535个;当我们添加枚举值时,也可以添加对应的数字编号。
set :集合, " 多选 " 类型;
set(' 选项值 1',' 选项值 2',' 选项值 3', ...);
该设定只是提供了若干个选项的值,最终一个单元格中,设计可存储了其中任意多个值;而且出于效率考虑,这些值实际存储的是" 数字 " ,因为这些选项的每个选项值依次对应如下数字: 1,2,4,8,16,32 , .... 最多64 个。
注意:不建议在添加枚举值,集合值的时候采用数字的方式,因为不利于阅读。
集合查询使用 find_ in_ set 函数:
find_in_set(sub,str_list) :如果 sub 在 str_list 中,则返回下标;如果不在,返回 0 ;
str_list 用逗号分隔的字符串。
六、表的约束
真正约束字段的是数据类型,但是数据类型约束很单一,需要有一些额外的约束,更好的 保证数据的合 法性 ,从业务逻辑角度保证数据的正确性。比如有一个字段是 email ,要求是唯一的。
表的约束很多,这里主要介绍如下几个: null/not null,default, comment, zerofill , primary
key , auto_increment , unique key 。
表的约束的作用是:通过约束让我们未来插入数据库表中的数据是符合预期的。约束本质是通过技术手段倒逼程序员插入正确的数据。反过来站在mysql的视角,凡是插入进来的数据都是符合数据约束的。约束的最终目标:保证数据的完整性和可预期性。
1.非空约束
两个值: null (默认的)和 not null( 不为空 )
数据库默认字段基本都是字段为空,但是实际开发时,尽可能保证字段不为空,因为数据为空没办
法参与运算。
比如我现在创建一张表,表中班级名称和班级房间设置为不为空:
可以看到我们在插入的时候一旦遇到某个不能为空的列输入了空值就无法继续插入了。
2.默认值
默认值:某一种数据会经常性的出现某个具体的值,可以在一开始就指定好,在需要真实数据的时候, 用户可以选择性的使用默认值。
这个和C++的缺省值一样,如果你插入了那么就不用默认值,如果你没插入就使用默认值。
下面我们插入数据试一下:
可以看到张三成功使用了默认值,李四由于自己插入了age列所以不使用默认值。
default和not null的关系:
如果我们没有指定一列要插入,用的是default,如果建表中,对应列默认没有设置default值,无法直接插入。default和not null不冲突,而是相互补充的。
注意:MySQL是支持default和not null同时设置的,但是一般情况下都是设置了not null后就不再设置default.
3.列描述
我们可以将comment理解为注释,那么为什么将它归为约束呢,我们看一看mysql内部是如何对comment做处理的:
通过查询创建该表时所用的语句我们得出结论,comment是给操纵数据库的管理员或者程序员看的,这是一种软性的约束。
4.zerofill约束
上图是一个普通int类型,下面我们插入数据看一下是如何显示的:
下面我再加上zerofill试一下:
修改成功后我们再看一下数据:
可以看到我们设置的是5位int,当位数不够时会自动在前面帮我们补0。如果够了或者超过指定位数,这个时候就直接显示了。也就是说zerofill是一个至少的行为。
5.主键约束
主键: primary key 用来唯一的约束该字段里面的数据,不能重复,不能为空,一张表中最多只能有一个主键;主键所在的列通常是整数类型。
可以看到主键会自动加非空约束,下面我们验证一下唯一性:
可以看到主键是不允许重复的。当然我们也可以删除主键约束:
alter table user6 drop primary key;
删除主键约束后我们就可以插入相同值了。
复合主键
一个主键不是只能添加给一列,而是可以同时将多个列合成为一个主键。
这个表可以保证id和course合起来不冲突。
从上图我们可以看到只要两列数据不是完全一样就可以成功插入,复合主键保证了复合的这几列不会出现完全一样的情况。
auto_increment约束
下面我们插入数据看看:
我们可以看到id会自动增加。注意:自增主键默认从1插入。当然我们也可以控制让自增主键默认从一个数开始:
注意:只能有一个自增长,并且自增长必须是主键。
6.唯一键约束
一张表中有往往有很多字段需要唯一性,数据不能重复,但是一张表中只能有一个主键:唯一键就可以解决表中有多个字段需要唯一性约束的问题。
唯一键的本质和主键差不多,唯一键允许为空,而且可以多个为空,空字段不做唯一性比较。
关于唯一键和主键的区别:
我们可以简单理解成,主键更多的是标识唯一性的。而唯一键更多的是保证在业务上,不要和别的信息出现重复。
下面我们插入演示一下区别:
我们可以看到当我们重复插入id为1234的时候出现了错误,原因是唯一键约束了id不能相同。
当我们插入空时我们发现是不受约束的,所以主键和唯一键的区别是主键不能为空,唯一键可以为空。主键和唯一键并不冲突而是互相补充的,比如员工表中员工id,姓名,电话三者的关系,员工id用主键约束不为空并且不能有重复值,电话用唯一键因为每个人的电话一定不相同,但是有可能为空。
当然,我们是可以将唯一键设置为非空的,唯一键默认是可以为空的。
7.外键
外键用于定义主表和从表之间的关系:外键约束主要定义在从表上,主表则必须是有主键约束或 unique约束。当定义外键后,要求外键列数据必须在主表的主键列存在或为null 。
语法:
foreign key (字段名) references 主表(列)
案例:现在有两张表,一个是学生表,一个是班级表,学生表中每个学生都有一个班级id,而通过班级id又可以找到这个班级,所以两个表的结构如下图所示:
像上图这样的关联关系我们称为外键约束。
下面我们正常插入数据试试:
我们发现如果插入的学生的班级是存在的那么就没有问题,如果插入的学生的班级是不存在的就会报错,而这个实例中的班级就是外键,而上面的报错就是外键约束。
理解外键约束:
首先我们承认,这个世界是数据很多都是相关性的。
理论上,上面的例子,我们不创建外键约束,就正常建立学生表,以及班级表,该有的字段我们都有。 此时,在实际使用的时候,可能会出现什么问题? 有没有可能插入的学生信息中有具体的班级,但是该班级却没有在班级表中? 因为此时两张表在业务上是有相关性的,但是在业务上没有建立约束关系,那么就可能出现问题。 解决方案就是通过外键完成的。建立外键的本质其实就是把相关性交给mysql 去审核了,提前告诉 mysql表之间的约束关系,那么当用户插入不符合业务逻辑的数据的时候,mysql 不允许你插入。
外键约束还有一个有趣的地方:
当一个班级表中还有学生的时候,我们要删除这个班级是不被允许的,因为mysql发现这个班级和某个学生在关联,所以不允许你删除。
当我们将这个班级内的学生删除,让这个班级里没有一个学生这个时候就可以将这个班级删除了。
七、基本查询
单行列插入:
单列插入就是我们选定一列或多列进行数据插入,但是每一列一定要与values相对应。
全列插入:
全列插入就是说我们默认对所有列进行插入,同样每一列必须和values里面的数据一一对应。
插入否则更新:
由于 主键 或者 唯一键 对应的值已经存在而导致插入失败。
-- 主键冲突
INSERT INTO students (id, sn, name) VALUES (100, 10010, '唐大师');
ERROR 1062 (23000): Duplicate entry '100' for key 'PRIMARY'
-- 唯一键冲突
INSERT INTO students (sn, name) VALUES (20001, '曹阿瞒');
ERROR 1062 (23000): Duplicate entry '20001' for key 'sn'
解决方法:
INSERT INTO students (id, sn, name) VALUES (100, 10010, '唐大师')
ON DUPLICATE KEY UPDATE sn = 10010, name = '唐大师';
Query OK, 2 rows affected (0.47 sec)
-- 0 row affected: 表中有冲突数据,但冲突数据的值和 update 的值相等
-- 1 row affected: 表中没有冲突数据,数据被插入
-- 2 row affected: 表中有冲突数据,并且数据已经被更新
替换:
在上图中由于id为1的用于已经存在了,所以我们插入失败,下面我们用replace语句试一下:
-- 主键 或者 唯一键 没有冲突,则直接插入;
-- 主键 或者 唯一键 如果冲突,则删除后再插入
REPLACE INTO user VALUES (1, '曹阿瞒');
Query OK, 2 rows affected (0.00 sec)
-- 1 row affected: 表中没有冲突数据,数据被插入
-- 2 row affected: 表中有冲突数据,删除后重新插入
select语句:
基本语法:
SELECT
[DISTINCT] {* | {column [, column] ...}
[FROM table_name]
[WHERE ...]
[ORDER BY column [ASC | DESC], ...]
LIMIT ...
select后面跟*表示筛选所有列,跟一列或者多列代表筛选出多列。from后面跟表名字,where可以指定筛选出每一列的值等于多少的数据,order by可以将筛选出来的数据进行排序。
上图是将user表中id和姓名显示出来,当然select也可以显示表达式,比如:
当然我们也可以修改表达式的名字:
只需要在表达式后面加as,as后面是新的名称。
当然我们也可以用distinct来对查询出来的结果进行去重:
可以看到姓名重复的被自动去重了。
where子句:
在where子句中是需要用到用算符的,下面我们看一下MySQL中的运算符:
比较运算符:
逻辑运算符:
上图演示了当性别为男或为女时显示出来的数据。
下面我们为了演示where语句我们重新构建一张分数表:
这个分数表的结构如上图:
下面我们尝试用更多的要求来练习where语句:
当然我们也可以用用between语句:
当然也可以用或语句:
那如果我们要查询的是姓孙的同学及孙某同学呢?在MySQL中也是有通配符的:
%表示后面通配任意个字符,这就显示了姓孙的同学,如果是孙某呢?也就是说只能是两个字姓孙的同学该如何表示呢?
只需要在后面加一个下划线表示孙后面只有一个字符。
如果要查询语文成绩好于英语成绩的同学呢?
下面我们查询一下总分在200分以下的同学:
语文成绩大于80并且不姓孙的同学:
孙某同学,否则要求总成绩大于200并且语文成绩小于数学成绩并且英语成绩大于80:
order by子句:
order by可以对查询出来的数据做排序,语法如下:
-- ASC 为升序(从小到大)
-- DESC 为降序(从大到小)
-- 默认为 ASC
SELECT ... FROM table_name [WHERE ...]
ORDER BY column [ASC|DESC], [...];
上图是我们将按照math列的大小来做降序排序。
order by默认的排序方式是升序。下面我们用总分降序的方式进行显示:
查询姓孙的同学或者姓曹的同学数学成绩,结果按照数学成绩由高到低显示:
筛选分页结果
-- 起始下标为 0
-- 从 s 开始,筛选 n 条结果
SELECT ... FROM table_name [WHERE ...] [ORDER BY ...] LIMIT s, n
-- 从 0 开始,筛选 n 条结果
SELECT ... FROM table_name [WHERE ...] [ORDER BY ...] LIMIT n;
;
-- 从 s 开始,筛选 n 条结果,比第二种用法更明确,建议使用
SELECT ... FROM table_name [WHERE ...] [ORDER BY ...] LIMIT n OFFSET s;
建议:对未知表进行查询时,最好加一条 LIMIT 1 ,避免因为表中数据过大,查询全表数据导致数据库卡死
直接使用limit默认从0开始,如下图:
而limit s,n表示从s开始显示n个数据:
注意要想从首行开始s应该为0才对。
Update
UPDATE table_name SET column = expr [, column = expr ...]
[WHERE ...] [ORDER BY ...] [LIMIT ...]
update对查询到的结果进行列值更新。
下面我们演示一下将孙悟空同学的成绩改为80分:
下面演示一下将总分倒数前三的学生的数学成绩加30分:
Delete
DELETE FROM table_name [WHERE ...] [ORDER BY ...] [LIMIT ...]
比如我们要删除孙悟空同学的考试成绩:
聚合函数
下面我们统计一下数学成绩不相等的个数:
当然也可以求一下数学成绩的总分:
group by 子句的使用
在 select 中使用 group by 子句可以对指定列进行分组查询
select column1, column2, .. from table group by column;
我们现在有一个雇员信息表,表中有EMP员工表,DEPT部门表,SALGRADE工资等级表,那么我们该如何显示每个部门的平均工资和最高工资呢?
从上图中我们可以看到每一位员工都有不同的部门,也就是说每个部门内都有很多员工,下面我们用group by子句对指定列进行分组查询:
这就是group by子句对指定列进行分组查询的意义。
那么如果我们要查询每个部门的每种岗位的平均工资和最低工资呢?
首先我们要查询的是每个部门内的每种岗位,所以group分组的时候先在部门内分组,然后在部门内将每种岗位进行分组,从而统计出每个部门的每种岗位的平均工资和最低工资。
如何显示平均工资低于2000的部门和它的平均工资?
这里我们需要知道新的语法having,having经常与group by 配合使用,对group by结果进行过滤。
当然我们把条件再苛刻一下,比如要求smith员工不参与统计显示平均工资低于2000的部门和它的平均工资。
八、函数
1.日期函数
当然也可以在日期的基础上加日期或减日期:
select date_add('2017-10-28', interval 10 day);
+-----------------------------------------+
| date_add('2017-10-28', interval 10 day) |
+-----------------------------------------+
| 2017-11-07 |
+-----------------------------------------+
select date_sub('2017-10-1', interval 2 day);
+---------------------------------------+
| date_sub('2017-10-1', interval 2 day) |
+---------------------------------------+
| 2017-09-29 |
+---------------------------------------+
select datediff('2017-10-10', '2016-9-1');
+------------------------------------+
| datediff('2017-10-10', '2016-9-1') |
+------------------------------------+
| 404 |
+------------------------------------+
下面我们创建一个留言表演示一下:
下面我们插入数据试一下:
或者:
如果只想显示日期的话可以用date函数:
2.字符串函数
下面我们演示一下:
剩下的大家可以自行去验证。
那么这些函数有什么意义呢?下面我们给出一些案例:
比如获取emp表的ename列的字符集:
要求显示 exam_result 表中的信息,显示格式: "XXX 的语文是 XXX 分,数学 XXX 分,英语 XXX 分 ":
也可以用替换函数统一替换显示:
3.数学函数
向上取整:就是向比自己大的整数方向进行取整
向下取整:就是向比自己小的整数方向进行取整
4.其他函数
user() 查询当前用户
md5(str) 对一个字符串进行 md5 摘要,摘要后得到一个 32 位字符串
一般我们存放用户密码的时候就会使用此函数
password() 函数, MySQL 数据库使用该函数对用户加密
ifnull ( val1 , val2 ) 如果 val1 为 null ,返回 val2 ,否则返回 val1 的值
九、复合查询
1.查询工资高于500或岗位为MANAGER的雇员,同时还要满足他们的姓名首字母为大写的****J
2.按照部门号升序而雇员的工资降序排序
3.使用年薪进行降序排序
4.显示工资最高的员工的名字和工作岗位
5.显示工资高于平均工资的员工信息
6.显示每个部门的平均工资和最高工资
7.显示平均工资低于2000的部门号和它的平均工资
8.显示每种岗位的雇员总数,平均工资
多表查询
1.显示雇员名、雇员工资以及所在部门的名字因为上面的数据来自EMP和DEPT表,因此要联合查询。
其实我们只要 emp 表中的 deptno = dept 表中的 deptno 字段的记录 。
2.显示部门号为10的部门名,员工名和工资
3.显示各个员工的姓名,工资,及工资级别
自连接
自连接是指在同一张表连接查询。
比如:显示员工FORD的上级领导的编号和姓名(mgr是员工领导的编号--empno)
上面我们使用了子查询,子查询就是指嵌入在其他sql语句中的select语句,也叫嵌套查询。
1.单行子查询
显示 SMITH 同一部门的员工
2.多行子查询
返回多行记录的子查询
in 关键字;查询和 10 号部门的工作岗位相同的雇员的名字,岗位,工资,部门号,但是不包含 10 自
己的
all 关键字;显示工资比部门 30 的所有员工的工资高的员工的姓名、工资和部门号
any 关键字;显示工资比部门 30 的任意员工的工资高的员工的姓名、工资和部门号(包含自己部门
的员工)
3.多列子查询
单行子查询是指子查询只返回单列,单行数据;多行子查询是指返回单列多行数据,都是针对单列而言的,而多列子查询则是指查询返回多个列数据的子查询语句.
比如: 查询和 SMITH 的部门和岗位完全相同的所有雇员,不含 SMITH 本人
在from子句中使用子查询
子查询语句出现在from子句中。这里要用到数据查询的技巧,把一个子查询当做一个临时表使用。 查找每个部门工资最高的人的姓名、工资、部门、最高工资
合并查询
在实际应用中,为了合并多个 select 的执行结果,可以使用集合操作符 union , union all。
union
该操作符用于取得两个结果集的并集。当使用该操作符时,会自动去掉结果集中的重复行。
案例:将工资大于 2500 或职位是 MANAGER 的人找出来
union all
该操作符用于取得两个结果集的并集。当使用该操作符时,不会去掉结果集中的重复行。
案例:将工资大于 25000 或职位是 MANAGER 的人找出来
十、表的内连和外连
1.内连接
内连接实际上就是利用 where 子句对两种表形成的笛卡儿积进行筛选,我们前面学习的查询都是内连接,也是在开发过程中使用的最多的连接查询。
语法
select 字段 from 表1 inner join 表2 on 连接条件 and 其他条件;
显示 SMITH 的名字和部门名称
之前的写法:
内连接写法:
2.外连接
外连接分为左外连接和右外连接
左外连接
如果联合查询,左侧的表完全显示我们就说是左外连接。
语法
select 字段名 from 表名1 left join 表名2 on 连接条件
下面我们创建两张表用来测试,一个是学生表,另一个是成绩表。
查询所有学生的成绩,如果这个学生没有成绩,也要将学生的个人信息显示出来
右外连接
如果联合查询,右侧的表完全显示我们就说是右外连接。
语法
select 字段 from 表名1 right join 表名2 on 连接条件;
对 stu 表和 exam 表联合查询,把所有的成绩都显示出来,即使这个成绩没有学生与它对应,也要
显示出来。
十一、索引
索引:提高数据库的性能,索引是物美价廉的东西了。不用加内存,不用改程序,不用调 sql ,只要执行正确的 create index ,查询速度就可能提高成百上千倍。但是天下没有免费的午餐,查询速度的提高是以插入、更新、删除的速度为代价的,这些写操作,增加了大量的IO 。所以它的价值,在于提高一个海量数据的检索速度。
常见索引分为:
主键索引 (primary key)
唯一索引 (unique)
普通索引 (index)
全文索引 (fulltext)-- 解决中子文索引问题。
MySQL 与存储
MySQL 给用户提供存储服务,而存储的都是数据,数据在磁盘这个外设当中。磁盘是计算机中的一个机械设备,相比于计算机其他电子元件,磁盘效率是比较低的,在加上IO 本身的特征,可以知道,如何提交效率,是 MySQL 的一个重要话题。
扇区
数据库文件,本质其实就是保存在磁盘的盘片当中。也就是上面的一个个小格子中,就是我们经常所说的扇区。当然,数据库文件很大,也很多,一定需要占据多个扇区。
题外话:
从上图可以看出来,在半径方向上,距离圆心越近,扇区越小,距离圆心越远,扇区越大
那么,所有扇区都是默认 512 字节吗?目前是的,我们也这样认为。因为保证一个扇区多大,是由
比特位密度决定的。
不过最新的磁盘技术,已经慢慢的让扇区大小不同了,不过我们现在暂时不考虑。
我们在使用 Linux ,所看到的大部分目录或者文件,其实就是保存在硬盘当中的。 ( 当然,有一些内存文件系统,如: proc , sys 之类,我们不考虑 )
所以,最基本的,找到一个文件的全部,本质,就是在磁盘找到所有保存文件的扇区。
而我们能够定位任何一个扇区,那么便能找到所有扇区,因为查找方式是一样的。
定位扇区
柱面 ( 磁道 ): 多盘磁盘,每盘都是双面,大小完全相等。那么同半径的磁道,整体上便构成了一个柱面。
每个盘面都有一个磁头,那么磁头和盘面的对应关系便是 1 对 1 的
所以,我们只需要知道,磁头( Heads )、柱面 (Cylinder)( 等价于磁道 ) 、扇区 (Sector) 对应的编
号。即可在磁盘上定位所要访问的扇区。这种磁盘数据定位方式叫做 CHS 。不过实际系统软件使用的并不是 CHS (但是硬件是),而是 LBA ,一种线性地址,可以想象成虚拟地址与物理地址。系统将 LBA 地址最后会转化成为 CHS ,交给磁盘去进行数据读取。不过,我们现在不关心转化细节,知道这个东西,让我们逻辑自洽起来即可。
结论
我们现在已经能够在硬件层面定位,任何一个基本数据块了 ( 扇区 ) 。那么在系统软件上,就直接按照扇区 (512字节,部分 4096 字节 ), 进行 IO 交互吗?不是
如果操作系统直接使用硬件提供的数据大小进行交互,那么系统的 IO 代码,就和硬件强相关,换言
之,如果硬件发生变化,系统必须跟着变化
从目前来看,单次 IO 512 字节,还是太小了。 IO 单位小,意味着读取同样的数据内容,需要进行多
次磁盘访问,会带来效率的降低。
之前学习文件系统,就是在磁盘的基本结构下建立的,文件系统读取基本单位,就不是扇区,而是
数据块。 故,系统读取磁盘,是以块为单位的,基本单位是 4KB 。
磁盘随机访问 (Random Access) 与连续访问 (Sequential Access)
随机访问:本次 IO 所给出的扇区地址和上次 IO 给出扇区地址不连续,这样的话磁头在两次 IO 操作之间需要作比较大的移动动作才能重新开始读/ 写数据。
连续访问:如果当次 IO 给出的扇区地址与上次 IO 结束的扇区地址是连续的,那磁头就能很快的开始这次IO操作,这样的多个 IO 操作称为连续访问。
因此尽管相邻的两次 IO 操作在同一时刻发出,但如果它们的请求的扇区地址相差很大的话也只能称为随机访问,而非连续访问。
磁盘是通过机械运动进行寻址的,随机访问不需要过多的定位,故效率比较高。
MySQL 与磁盘交互基本单位
MySQL 作为一款应用软件,可以想象成一种特殊的文件系统。它有着更高的 IO 场景,所以,为了提高基本的IO 效率, MySQL 进行 IO 的基本单位是 16KB ( 后面统一使用 InnoDB 存储引擎讲解 )
显示页大小
SHOW GLOBAL STATUS LIKE 'innodb_page_size';
也就是说,磁盘这个硬件设备的基本单位是 512 字节,而 MySQL InnoDB 引擎 使用 16KB 进行 IO 交互。 即, MySQL 和磁盘进行数据交互的基本单位是 16KB 。这个基本数据单元,在 MySQL 这里叫做 page (注意和系统的page 区分)
MySQL 中的数据文件,是以 page 为单位保存在磁盘当中的。
MySQL 的 CURD 操作,都需要通过 计算 ,找到对应的插入位置,或者找到对应要修改或者查询的数据。 而只要涉及计算,就需要CPU 参与,而为了便于 CPU 参与,一定要能够先将数据移动到内存当中。 所以在特定时间内,数据一定是磁盘中有,内存中也有。后续操作完内存数据之后,以特定的刷新策略,刷新到磁盘。而这时,就涉及到磁盘和内存的数据交互,也就是IO 了。而此时 IO 的基本单位就是Page 。
为了更好的进行上面的操作, MySQL 服务器在内存中运行的时候,在服务器内部,就申请了被称
为 Buffer Pool 的的大内存空间,来进行各种缓存。其实就是很大的内存空间,来和磁盘数据进
行 IO 交互。 为了更高的效率,一定要尽可能的减少系统和磁盘IO 的次数。
为何 IO 交互要是 Page?
为何 MySQL 和磁盘进行 IO 交互的时候,要采用 Page 的方案进行交互呢 ? 用多少,加载多少不香吗 ?
如上面的 5 条记录,如果 MySQL 要查找 id=2 的记录,第一次加载 id=1 ,第二次加载 id=2 ,一次一条记录,那么就需要2 次 IO 。如果要找 id=5 ,那么就需要 5 次 IO 。
但,如果这 5 条 ( 或者更多 ) 都被保存在一个 Page 中 (16KB ,能保存很多记录 ), 那么第一次 IO 查找 id=2 的时候,整个Page 会被加载到 MySQL 的 Buffer Pool 中,这里完成了一次 IO 。但是往后如果在查找 id=1,3,4,5等,完全不需要进行IO 了,而是直接在内存中进行了。所以,就在单 Page 里面,大大减少了 IO 的次数。
你怎么保证,用户一定下次找的数据,就在这个 Page 里面?我们不能严格保证,但是有很大概率,因为有局部性原理。
往往 IO 效率低下的最主要矛盾不是 IO 单次数据量的大小,而是 IO 的次数。
理解单个 Page
MySQL 中要管理很多数据表文件,而要管理好这些文件,就需要 先描述,在组织 , 我们目前可以简单理解成一个个独立文件是有一个或者多个Page 构成的。
不同的 Page ,在 MySQL 中,都是 16KB ,使用 prev 和 next 构成双向链表
因为有主键的问题, MySQL 会默认按照主键给我们的数据进行排序,从上面的 Page 内数据记录可以看出,数据是有序且彼此关联的。
为什么数据库在插入数据时要对其进行排序呢?我们按正常顺序插入数据不是也挺好的吗?
插入数据时排序的目的,就是优化查询的效率。
页内部存放数据的模块,实质上也是一个链表的结构,链表的特点也就是增删快,查询修改慢,所以优化查询的效率是必须的。
正式因为有序,在查找的时候,从头到后都是有效查找,没有任何一个查找是浪费的,而且,如果运气好,是可以提前结束查找过程的。
理解多个 Page
通过上面的分析,我们知道,上面页模式中,只有一个功能,就是 在查询某条数据的时候直接将一
整页的数据加载到内存中,以减少硬盘 IO 次数,从而提高性能。 但是,我们也可以看到,现在的页
模式内部,实际上是采用了链表的结构,前一条数据指向后一条数据,本质上还是通过数据的逐条
比较来取出特定的数据。
如果有 1 千万条数据,一定需要多个 Page 来保存 1 千万条数据,多个 Page 彼此使用双链表链接起
来,而且每个 Page 内部的数据也是基于链表的。那么,查找特定一条记录,也一定是线性查找。这效率也太低了。
页目录
我们在看C语言 书的时候,如果我们要看 < 指针章节 > ,找到该章节有两种做法
从头逐页的向后翻,直到找到目标内容。
通过书提供的目录,发现指针章节在 234 页 ( 假设 ) ,那么我们便直接翻到 234 页。同时,查找目录的
方案,可以顺序找,不过因为目录肯定少,所以可以快速提高定位。
本质上,书中的目录,是多花了纸张的,但是却提高了效率 ,所以,目录,是一种" 空间换时间的做法 "。
单页情况
针对上面的单页 Page ,我们能否也引入目录呢?当然可以
那么当前,在一个 Page 内部,我们引入了目录。比如,我们要查找 id=4 记录,之前必须线性遍历 4 次,才能拿到结果。现在直接通过目录2[3] ,直接进行定位新的起始位置,提高了效率。现在我们可以再次正式回答上面的问题了,为何通过键值 MySQL 会自动排序? 可以很方便引入目录。
多页情况
MySQL 中每一页的大小只有 16KB ,单个 Page 大小固定,所以随着数据量不断增大, 16KB 不可能存下所有的数据,那么必定会有多个页来存储数据。
在单表数据不断被插入的情况下, MySQL 会在容量不足的时候,自动开辟新的 Page 来保存新的数据,然后通过指针的方式,将所有的Page 组织起来。
需要注意,上面的图,是理想结构,大家也知道,目前要保证整体有序,那么新插入的数据,不一定会在新Page 上面,这里仅仅做演示。
这样,我们就可以通过多个 Page 遍历, Page 内部通过目录来快速定位数据。可是,貌似这样也有效率问题,在Page 之间,也是需要 MySQL 遍历的,遍历意味着依旧需要进行大量的 IO ,将下一个 Page 加载到内存,进行线性检测。这样就显得我们之前的Page 内部的目录,有点杯水车薪了。
那么如何解决呢?解决方案,其实就是我们之前的思路,给 Page 也带上目录。
使用一个目录项来指向某一页,而这个目录项存放的就是将要指向的页中存放的最小数据的键值。
和页内目录不同的地方在于,这种目录管理的级别是页,而页内目录管理的级别是行。
其中,每个目录项的构成是:键值 + 指针。图中没有画全。
存在一个目录页来管理页目录,目录页中的数据存放的就是指向的那一页中最小的数据。有数据,就可通过比较,找到该访问那个Page ,进而通过指针,找到下一个 Page 。
其实 目录页的本质也是页,普通页中存的数据是用户数据,而目录页中存的数据是普通页的地址。
可是,我们每次检索数据的时候,该从哪里开始呢?虽然顶层的目录页少了,但是还要遍历啊?不用担心,可以在加目录页。
这货就是传说中的 B+ 树啊!没错,至此,我们已经给我们的表 user 构建完了主键索引。
随便找一个 id= ?我们发现,现在查找的 Page 数一定减少了,也就意味着 IO 次数减少了,那么效率也就提高了。
总结:
Page 分为目录页和数据页。目录页只放各个下级 Page 的最小键值。
查找的时候,自顶向下找,只需要加载部分目录页到内存,即可完成算法的整个查找过程,大大减
少了 IO 次数。
InnoDB 在建立索引结构来管理数据的时候,其他数据结构为何不行?
链表?线性遍历
二叉搜索树?退化问题,可能退化成为线性结构
AVL && 红黑树?虽然是平衡或者近似平衡,但是毕竟是二叉结构,相比较多阶 B+ ,意味着树整体
过高,大家都是自顶向下找,层高越低,意味着系统与硬盘更少的 IO Page 交互。虽然你很秀,但
是有更秀的。
Hash ?官方的索引实现方式中, MySQL 是支持 HASH 的,不过 InnoDB 和 MyISAM 并不支持 .Hash 跟进其算法特征,决定了虽然有时候也很快(O(1)) ,不过,在面对范围查找就明显不行,另外还有其他差别,有兴趣可以查一下。
B+ vs B
B树:
B+树
这两棵树,对我们最有意义的区别是:
B 树节点,既有数据,又有 Page 指针,而 B+ ,只有叶子节点有数据,其他目录页,只有键值和
Page 指针。
B+ 叶子节点,全部相连,而 B 没有。
为何选择 B+
节点不存储 data ,这样一个节点就可以存储更多的 key 。可以使得树更矮,所以 IO 操作次数更少。
叶子节点相连,更便于进行范围查找
聚簇索引 VS 非聚簇索引
MyISAM 存储引擎 - 主键索引
MyISAM 引擎同样使用 B+ 树作为索引结果,叶节点的 data 域存放的是数据记录的地址。下图为 MyISAM表的主索引, Col1 为主键
其中, MyISAM 最大的特点是,将索引Page和数据Page分离,也就是叶子节点没有数据,只有对应数据的地址。
相较于 InnoDB 索引, InnoDB 是将索引和数据放在一起的。
其中, MyISAM 这种用户数据与索引数据分离的索引方案,叫做非聚簇索引。
其中, InnoDB 这种用户数据与索引数据在一起索引方案,叫做聚簇索引。
当然, MySQL 除了默认会建立主键索引外,我们用户也有可能建立按照其他列信息建立的索引,一般这种索引可以叫做辅助(普通)索引。
对于 MyISAM , 建立辅助(普通)索引和主键索引没有差别,无非就是主键不能重复,而非主键可重复。 下图就是基于 MyISAM 的 Col2 建立的索引,和主键索引没有差别。
同样, InnoDB 除了主键索引,用户也会建立辅助(普通)索引,我们以上表中的 Col3 建立对应的辅助索引如下图:
可以看到, InnoDB 的非主键索引中叶子节点并没有数据,而只有对应记录的 key 值。
所以通过辅助(普通)索引,找到目标记录,需要两遍索引:首先检索辅助索引获得主键,然后用主键到主索引中检索获得记录。这种过程,就叫做回表查询
为何 InnoDB 针对这种辅助(普通)索引的场景,不给叶子节点也附上数据呢?原因就是太浪费空间了。
总结:
如何理解硬盘
如何理解柱面,磁道,扇区,磁头
InnoDB 主键索引和普通索引
MyISAM 主键索引和普通索引
其他数据结构为何不能作为索引结构,尤其是B+和B
聚簇索引 VS 非聚簇索引
索引操作
1.创建主键索引
第一种方式:
-- 在创建表的时候,直接在字段名后指定 primary key
create table user1(id int primary key, name varchar(30));
第二种方式:
-- 在创建表的最后,指定某列或某几列为主键索引
create table user2(id int, name varchar(30), primary key(id));
第三种方式:
create table user3(id int, name varchar(30));
-- 创建表以后再添加主键
alter table user3 add primary key(id);
主键索引的特点:
一个表中,最多有一个主键索引,当然可以使复合主键
主键索引的效率高(主键不可重复)
创建主键索引的列,它的值不能为 null ,且不能重复
主键索引的列基本上是 int
唯一索引的创建
第一种方式:
-- 在表定义时,在某列后直接指定unique唯一属性。
create table user4(id int primary key, name varchar(30) unique);
第二种方式:
-- 创建表时,在表的后面指定某列或某几列为unique
create table user5(id int primary key, name varchar(30), unique(name));
第三种方式:
create table user6(id int primary key, name varchar(30));
alter table user6 add unique(name);
唯一索引的特点:
一个表中,可以有多个唯一索引
查询效率高
如果在某一列建立唯一索引,必须保证这列不能有重复数据
如果一个唯一索引上指定 not null ,等价于主键索引
普通索引的创建
第一种方式 :
create table user8(id int primary key,
name varchar(20),
email varchar(30),
index(name) --在表的定义最后,指定某列为索引
);
第二种方式 :
create table user9(id int primary key, name varchar(20), email
varchar(30));
alter table user9 add index(name); --创建完表以后指定某列为普通索引
第三种方式 :
create table user10(id int primary key, name varchar(20), email
varchar(30));
-- 创建一个索引名为 idx_name 的索引
create index idx_name on user10(name);
普通索引的特点:
一个表中可以有多个普通索引,普通索引在实际开发中用的比较多
如果某列需要创建索引,但是该列有重复的值,那么我们就应该使用普通索引
全文索引的创建
当对文章字段或有大量文字的字段进行检索时,会使用到全文索引。 MySQL 提供全文索引机制,但是有要求,要求表的存储引擎必须是MyISAM ,而且默认的全文索引支持英文,不支持中文。如果对中文进行全文检索,可以使用sphinx 的中文版 (coreseek).
比如:
CREATE TABLE articles (
id INT UNSIGNED AUTO_INCREMENT NOT NULL PRIMARY KEY,
title VARCHAR(200),
body TEXT,
FULLTEXT (title,body)
)engine=MyISAM;
INSERT INTO articles (title,body) VALUES
('MySQL Tutorial','DBMS stands for DataBase ...'),
('How To Use MySQL Well','After you went through a ...'),
('Optimizing MySQL','In this tutorial we will show ...'),
('1001 MySQL Tricks','1. Never run mysqld as root. 2. ...'),
('MySQL vs. YourSQL','In the following database comparison ...'),
('MySQL Security','When configured properly, MySQL ...');
查询有没有 database 数据
如果使用如下查询方式,虽然查询出数据,但是没有使用到全文索引
mysql> select * from articles where body like '%database%';
+----+-------------------+------------------------------------------+
| id | title | body |
+----+-------------------+------------------------------------------+
| 1 | MySQL Tutorial | DBMS stands for DataBase ... |
| 5 | MySQL vs. YourSQL | In the following database comparison ... |
+----+-------------------+------------------------------------------+
可以用 explain 工具看一下,是否使用到索引:
mysql> explain select * from articles where body like '%database%'\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: articles
type: ALL
possible_keys: NULL
key: NULL <== key为null表示没有用到索引
key_len: NULL
ref: NULL
rows: 6
Extra: Using where
1 row in set (0.00 sec)
如何使用全文索引呢?
mysql> SELECT * FROM articles
-> WHERE MATCH (title,body) AGAINST ('database');
+----+-------------------+------------------------------------------+
| id | title | body |
+----+-------------------+------------------------------------------+
| 5 | MySQL vs. YourSQL | In the following database comparison ... |
| 1 | MySQL Tutorial | DBMS stands for DataBase ... |
+----+-------------------+------------------------------------------+
通过 explain 来分析这个 sql 语句:
mysql> explain SELECT * FROM articles WHERE MATCH (title,body) AGAINST
('database')\G
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: articles
type: fulltext
possible_keys: title
key: title <= key用到了title
key_len: 0
ref:
rows: 1
Extra: Using where
查询索引
第一种方法: show keys from 表名
mysql> show keys from goods\G
*********** 1. row ***********
Table: goods <= 表名
Non_unique: 0 <= 0表示唯一索引
Key_name: PRIMARY <= 主键索引
Seq_in_index: 1
Column_name: goods_id <= 索引在哪列
Collation: A
Cardinality: 0
Sub_part: NULL
Packed: NULL
Null:
Index_type: BTREE <= 以二叉树形式的索引
Comment:
1 row in set (0.00 sec)
第二种方法 : show index from 表名 ;
第三种方法(信息比较简略): desc 表名;
删除索引
第一种方法 - 删除主键索引: 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
索引创建原则
比较频繁作为查询条件的字段应该创建索引
唯一性太差的字段不适合单独创建索引,即使频繁作为查询条件
更新非常频繁的字段不适合作创建索引
不会出现在 where 子句中的字段不该创建索引
十二、事务
CURD 不加控制,会有什么问题?
CURD 满足什么属性,能解决上述问题?
-
买票的过程得是原子的吧
-
买票互相应该不能影响吧
-
买完票应该要永久有效吧
-
买前,和买后都要是确定的状态
什么是事务?
事务就是一组 DML 语句组成,这些语句在逻辑上存在相关性,这一组 DML 语句要么全部成功,要么全部失败,是一个整体。MySQL 提供一种机制,保证我们达到这样的效果。事务还规定不同的客户端看到的数据是不相同的。
事务就是要做的或所做的事情,主要用于处理操作量大,复杂度高的数据。假设一种场景:你毕业了,学校的教务系统后台 MySQL 中,不在需要你的数据,要删除你的所有信息 ( 一般不会 :) ), 那么要删除你的基本信息( 姓名,电话,籍贯等 ) 的同时,也删除和你有关的其他信息,比如:你的各科成绩,你在校表现,甚至你在论坛发过的文章等。这样,就需要多条 MySQL 语句构成,那么所有这些操作合起来,就构成了一个事务。
正如我们上面所说,一个 MySQL 数据库,可不止你一个事务在运行,同一时刻,甚至有大量的请求被包装成事务,在向 MySQL 服务器发起事务处理请求。而每条事务至少一条 SQL ,最多很多 SQL , 这样如果大家都访问同样的表数据,在不加保护的情况,就绝对会出现问题。甚至,因为事务由多条 SQL 构成,那么,也会存在执行到一半出错或者不想再执行的情况,那么已经执行的怎么办呢? 所有,一个完整的事务,绝对不是简单的 sql 集合,还需要满足如下四个属性:
原子性: 一个事务( transaction )中的所有操作,要么全部完成,要么全部不完成,不会结束在中
间某个环节。事务在执行过程中发生错误,会被回滚( Rollback )到事务开始前的状态,就像这个
事务从来没有执行过一样。
一致性: 在事务开始之前和事务结束以后,数据库的完整性没有被破坏。这表示写入的资料必须完
全符合所有的预设规则,这包含资料的精确度、串联性以及后续数据库可以自发性地完成预定的工
作。
隔离性: 数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务
并发执行时由于交叉执行而导致数据的不一致。事务隔离分为不同级别,包括读未提交( Read
uncommitted )、读提交( read committed )、可重复读( repeatable read )和串行化
( Serializable )
持久性: 事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。
上面四个属性,可以简称为 ACID 。
原子性(Atomicity,或称不可分割性)
一致性(Consistency)
隔离性(Isolation,又称独立性)
持久性(Durability)
为什么会出现事务?
事务被 MySQL 编写者设计出来 , 本质是为了当应用程序访问数据库的时候 , 事务能够简化我们的编程模型 , 不需要我们去考虑各种各样的潜在错误和并发问题. 可以想一下当我们使用事务时 , 要么提交 , 要么回滚 , 我们不会去考虑网络异常了, 服务器宕机了 , 同时更改一个数据怎么办对吧 ? 因此事务本质上是为了应用层服 务的 . 而不是伴随着数据库系统天生就有的 .
备注:我们后面把 MySQL 中的一行信息,称为一行记录
事务的版本支持
在 MySQL 中只有使用了 Innodb 数据库引擎的数据库或表才支持事务, MyISAM 不支持。
查看数据库引擎:
事务提交方式
事务的提交方式常见的有两种:
自动提交
手动提交
下面是查看事务提交方式的命令
用 SET 来改变 MySQL 的自动提交模式:
mysql> SET AUTOCOMMIT=0; #SET AUTOCOMMIT=0 禁止自动提交
mysql> SET AUTOCOMMIT=1; #SET AUTOCOMMIT=1 开启自动提交
下面为了方便演示我们将mysql的默认隔离级别设置为读未提交:
当然设置完需要重启mysql客户端才能生效
接下来我们创建一张表用来演示事务的开始与回滚
首先我们如下图查看事务是否自动提交。我们故意设置成自动提交,看看该选项是否影响begin
接下来我们开始一个事务:
开始一个事务也可以用begin,当然我们也推荐使用begin开启一个事务。
接下来我们在一个事务里面设置一个保存点:
然后我们在表结构中插入数据
我们在另一个客户端也可以看到刚刚插入的数据。接下来我们再设置一个保存点:
然后我们继续插入:
接下来我们回滚到保存点2:
可以看到李四那条记录没有了,下面我们继续回滚到一开始:
当回滚到一开始我们发现一条数据也没有了。
下面我们进行两条非正常演示:
非正常演示 1 - 证明未 commit ,客户端崩溃, MySQL 自动会回滚(隔离级别设置为读未提交)
-- 终端A
mysql> select * from account; -- 当前表内无数据
Empty set (0.00 sec)
mysql> show variables like 'autocommit'; -- 依旧自动提交
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| autocommit | ON |
+---------------+-------+
1 row in set (0.00 sec)
mysql> begin; --开启事务
Query OK, 0 rows affected (0.00 sec)
mysql> insert into account values (1, '张三', 100); -- 插入记录
Query OK, 1 row affected (0.00 sec)
mysql> select * from account; --数据已经存在,但没有commit,此时同时查看
终端B
+----+--------+--------+
| id | name | blance |
+----+--------+--------+
| 1 | 张三 | 100.00 |
+----+--------+--------+
1 row in set (0.00 sec)
mysql> Aborted -- ctrl + \ 异常终止MySQL
--终端B
mysql> select * from account; --终端A崩溃前
+----+--------+--------+
| id | name | blance |
+----+--------+--------+
| 1 | 张三 | 100.00 |
+----+--------+--------+
1 row in set (0.00 sec)
mysql> select * from account; --数据自动回滚
Empty set (0.00 sec)
非正常演示 2 - 证明 commit 了,客户端崩溃, MySQL 数据不会在受影响,已经持久化
--终端 A
mysql> show variables like 'autocommit'; -- 依旧自动提交
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| autocommit | ON |
+---------------+-------+
1 row in set (0.00 sec)
mysql> select * from account; -- 当前表内无数据
Empty set (0.00 sec)
mysql> begin; -- 开启事务
Query OK, 0 rows affected (0.00 sec)
mysql> insert into account values (1, '张三', 100); -- 插入记录
Query OK, 1 row affected (0.00 sec)
mysql> commit; --提交事务
Query OK, 0 rows affected (0.04 sec)
mysql> Aborted -- ctrl + \ 异常终止MySQL
--终端 B
mysql> select * from account; --数据存在了,所以commit的作用是将数据持久
化到MySQL中
+----+--------+--------+
| id | name | blance |
+----+--------+--------+
| 1 | 张三 | 100.00 |
+----+--------+--------+
1 row in set (0.00 sec)
非正常演示 3 - 对比试验。证明 begin 操作会自动更改提交方式,不会受 MySQL 是否自动提交影响
-- 终端 A
mysql> select *from account; --查看历史数据
+----+--------+--------+
| id | name | blance |
+----+--------+--------+
| 1 | 张三 | 100.00 |
+----+--------+--------+
1 row in set (0.00 sec)
mysql> show variables like 'autocommit'; --查看事务提交方式
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| autocommit | ON |
+---------------+-------+
1 row in set (0.00 sec)
mysql> set autocommit=0; --关闭自动提交
Query OK, 0 rows affected (0.00 sec)
mysql> show variables like 'autocommit'; --查看关闭之后结果
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| autocommit | OFF |
+---------------+-------+
1 row in set (0.00 sec)
mysql> begin; --开启事务
Query OK, 0 rows affected (0.00 sec)
mysql> insert into account values (2, '李四', 10000); --插入记录
Query OK, 1 row affected (0.00 sec)
mysql> select *from account; --查看插入记录,同时查看终端B
+----+--------+----------+
| id | name | blance |
+----+--------+----------+
| 1 | 张三 | 100.00 |
| 2 | 李四 | 10000.00 |
+----+--------+----------+
2 rows in set (0.00 sec)
mysql> Aborted --再次异常终止
-- 终端B
mysql> select * from account; --终端A崩溃前
+----+--------+----------+
| id | name | blance |
+----+--------+----------+
| 1 | 张三 | 100.00 |
| 2 | 李四 | 10000.00 |
+----+--------+----------+
2 rows in set (0.00 sec)
mysql> select * from account; --终端A崩溃后,自动回滚
+----+--------+--------+
| id | name | blance |
+----+--------+--------+
| 1 | 张三 | 100.00 |
+----+--------+--------+
1 row in set (0.00 sec)
非正常演示 4 - 证明单条 SQL 与事务的关系
--实验一
-- 终端A
mysql> select * from account;
+----+--------+--------+
| id | name | blance |
+----+--------+--------+
| 1 | 张三 | 100.00 |
+----+--------+--------+
1 row in set (0.00 sec)
mysql> show variables like 'autocommit';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| autocommit | ON |
+---------------+-------+
1 row in set (0.00 sec)
mysql> set autocommit=0; --关闭自动提交
Query OK, 0 rows affected (0.00 sec)
mysql> insert into account values (2, '李四', 10000); --插入记录
Query OK, 1 row affected (0.00 sec)
mysql> select *from account; --查看结果,已经插入。此时可以在查
看终端B
+----+--------+----------+
| id | name | blance |
+----+--------+----------+
| 1 | 张三 | 100.00 |
| 2 | 李四 | 10000.00 |
+----+--------+----------+
2 rows in set (0.00 sec)
mysql> ^DBye --ctrl + \ or ctrl + d,终止终
端
--终端B
mysql> select * from account; --终端A崩溃前
+----+--------+----------+
| id | name | blance |
+----+--------+----------+
| 1 | 张三 | 100.00 |
| 2 | 李四 | 10000.00 |
+----+--------+----------+
2 rows in set (0.00 sec)
mysql> select * from account; --终端A崩溃后
+----+--------+--------+
| id | name | blance |
+----+--------+--------+
| 1 | 张三 | 100.00 |
+----+--------+--------+
1 row in set (0.00 sec)
-- 实验二
--终端A
mysql> show variables like 'autocommit'; --开启默认提交
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| autocommit | ON |
+---------------+-------+
1 row in set (0.00 sec)
mysql> select * from account;
+----+--------+--------+
| id | name | blance |
+----+--------+--------+
| 1 | 张三 | 100.00 |
+----+--------+--------+
1 row in set (0.00 sec)
mysql> insert into account values (2, '李四', 10000);
Query OK, 1 row affected (0.01 sec)
mysql> select *from account; --数据已经插入
+----+--------+----------+
| id | name | blance |
+----+--------+----------+
| 1 | 张三 | 100.00 |
| 2 | 李四 | 10000.00 |
+----+--------+----------+
2 rows in set (0.00 sec)
mysql> Aborted --异常终止
--终端B
mysql> select * from account; --终端A崩溃前
+----+--------+----------+
| id | name | blance |
+----+--------+----------+
| 1 | 张三 | 100.00 |
| 2 | 李四 | 10000.00 |
+----+--------+----------+
2 rows in set (0.00 sec)
mysql> select * from account; --终端A崩溃后,并不影响,已经持久化。autocommit
起作用
+----+--------+----------+
| id | name | blance |
+----+--------+----------+
| 1 | 张三 | 100.00 |
| 2 | 李四 | 10000.00 |
+----+--------+----------+
2 rows in set (0.00 sec)
结论:
只要输入begin或者start transaction,事务便必须要通过commit提交,才会持久化,与是
否设置set autocommit无关。
事务可以手动回滚,同时,当操作异常,MySQL会自动回滚。
对于 InnoDB 每一条 SQL 语言都默认封装成事务,自动提交。(select有特殊情况,因为
MySQL 有 MVCC )
从上面的例子,我们能看到事务本身的原子性(回滚),持久性(commit)。
事务操作注意事项
如果没有设置保存点,也可以回滚,只能回滚到事务的开始。直接使用 rollback(前提是事务
还没有提交)
如果一个事务被提交了(commit),则不可以回退(rollback)
可以选择回退到哪个保存点
InnoDB 支持事务, MyISAM 不支持事务
开始事务可以使 start transaction 或者 begin
事务隔离级别
如何理解隔离性 1
MySQL 服务可能会同时被多个客户端进程 ( 线程 ) 访问,访问的方式以事务方式进行
一个事务可能由多条 SQL 构成,也就意味着,任何一个事务,都有执行前,执行中,执行后的阶
段。而所谓的原子性,其实就是让用户层,要么看到执行前,要么看到执行后。执行中出现问题,
可以随时回滚。所以单个事务,对用户表现出来的特性,就是原子性。
但,毕竟所有事务都要有个执行过程,那么在多个事务各自执行多个 SQL 的时候,就还是有可能会
出现互相影响的情况。比如:多个事务同时访问同一张表,甚至同一行数据。
就如同你妈妈给你说:你要么别学,要学就学到最好。至于你怎么学,中间有什么困难,你妈妈不
关心。那么你的学习,对你妈妈来讲,就是原子的。那么你学习过程中,很容易受别人干扰,此
时,就需要将你的学习隔离开,保证你的学习环境是健康的。
数据库中,为了保证事务执行过程中尽量不受干扰,就有了一个重要特征:隔离性
数据库中,允许事务受不同程度的干扰,就有了一种重要特征:隔离级别
隔离级别
读未提交【 Read Uncommitted 】 : 在该隔离级别,所有的事务都可以看到其他事务没有提交的
执行结果。(实际生产中不可能使用这种隔离级别的),但是相当于没有任何隔离性,也会有很多
并发问题,如脏读,幻读,不可重复读等,我们上面为了做实验方便,用的就是这个隔离性。
读提交【 Read Committed 】 :该隔离级别是大多数数据库的默认的隔离级别(不是 MySQL 默
认的)。它满足了隔离的简单定义 : 一个事务只能看到其他的已经提交的事务所做的改变。这种隔离级别会引起不可重复读,即一个事务执行时,如果多次 select , 可能得到不同的结果。
可重复读【 Repeatable Read 】 : 这是 MySQL 默认的隔离级别,它确保同一个事务,在执行
中,多次读取操作数据时,会看到同样的数据行。但是会有幻读问题。
串行化【 Serializable 】 : 这是事务的最高隔离级别,它通过强制事务排序,使之不可能相互冲突,
从而解决了幻读的问题。它在每个读的数据行上面加上共享锁,。但是可能会导致超时和锁竞争
(这种隔离级别太极端,实际生产基本不使用)
隔离级别如何实现:隔离,基本都是通过锁实现的,不同的隔离级别,锁的使用是不同的。常见有,表锁,行锁,读锁,写锁,间隙锁(GAP),Next-Key 锁 (GAP+ 行锁 ) 等。不过,我们目前现有这个认识就行, 先关注上层使用。
查看与设置隔离性
1.查看
-- 查看
mysql> SELECT @@global.tx_isolation; --查看全局隔级别
+-----------------------+
| @@global.tx_isolation |
+-----------------------+
| REPEATABLE-READ |
+-----------------------+
1 row in set, 1 warning (0.00 sec)
mysql> SELECT @@session.tx_isolation; --查看会话(当前)全局隔级别
+------------------------+
| @@session.tx_isolation |
+------------------------+
| REPEATABLE-READ |
+------------------------+
1 row in set, 1 warning (0.00 sec)
mysql> SELECT @@tx_isolation; --默认同上 查看会话(当前)全局隔级别
+-----------------+
| @@tx_isolation |
+-----------------+
| REPEATABLE-READ |
+-----------------+
1 row in set, 1 warning (0.00 sec)
--设置
-- 设置当前会话 or 全局隔离级别语法
SET [SESSION | GLOBAL] TRANSACTION ISOLATION LEVEL {READ UNCOMMITTED | READ
COMMITTED | REPEATABLE READ | SERIALIZABLE}
--设置当前会话隔离性,另起一个会话,看不多,只影响当前会话
mysql> set session transaction isolation level serializable; -- 串行化
Query OK, 0 rows affected (0.00 sec)
mysql> SELECT @@global.tx_isolation; --全局隔离性还是RR
+-----------------------+
| @@global.tx_isolation |
+-----------------------+
| REPEATABLE-READ |
+-----------------------+
1 row in set, 1 warning (0.00 sec)
mysql> SELECT @@session.tx_isolation; --会话隔离性成为串行化
+------------------------+
| @@session.tx_isolation |
+------------------------+
| SERIALIZABLE |
+------------------------+
1 row in set, 1 warning (0.00 sec)
mysql> SELECT @@tx_isolation; --同上
+----------------+
| @@tx_isolation |
+----------------+
| SERIALIZABLE |
+----------------+
1 row in set, 1 warning (0.00 sec)
--设置全局隔离性,另起一个会话,会被影响
mysql> set global transaction isolation level READ UNCOMMITTED;
Query OK, 0 rows affected (0.00 sec)
mysql> SELECT @@global.tx_isolation;
+-----------------------+
| @@global.tx_isolation |
+-----------------------+
| READ-UNCOMMITTED |
+-----------------------+
1 row in set, 1 warning (0.00 sec)
mysql> SELECT @@session.tx_isolation;
+------------------------+
| @@session.tx_isolation |
+------------------------+
| READ-UNCOMMITTED |
+------------------------+
1 row in set, 1 warning (0.00 sec)
mysql> SELECT @@tx_isolation;
+------------------+
| @@tx_isolation |
+------------------+
| READ-UNCOMMITTED |
+------------------+
1 row in set, 1 warning (0.00 sec)
-- 注意,如果没有现象,关闭mysql客户端,重新连接。
读未提交【 Read Uncommitted 】
--几乎没有加锁,虽然效率高,但是问题太多,严重不建议采用
--终端A
-- 设置隔离级别为 读未提交
mysql> set global transaction isolation level read uncommitted;
Query OK, 0 rows affected (0.00 sec)
--重启客户端
mysql> select @@tx_isolation;
+------------------+
| @@tx_isolation |
+------------------+
| READ-UNCOMMITTED |
+------------------+
1 row in set, 1 warning (0.00 sec)
mysql> select * from account;
+----+--------+----------+
| id | name | blance |
+----+--------+----------+
| 1 | 张三 | 100.00 |
| 2 | 李四 | 10000.00 |
+----+--------+----------+
2 rows in set (0.00 sec)
mysql> begin; --开启事务
Query OK, 0 rows affected (0.00 sec)
mysql> update account set blance=123.0 where id=1; --更新指定行
Query OK, 1 row affected (0.05 sec)
Rows matched: 1 Changed: 1 Warnings: 0
--没有commit哦!!!
--终端B
mysql> begin;
mysql> select * from account;
+----+--------+----------+
| id | name | blance |
+----+--------+----------+
| 1 | 张三 | 123.00 | --读到终端A更新但是未commit的数据[insert,
delete同样]
| 2 | 李四 | 10000.00 |
+----+--------+----------+
2 rows in set (0.00 sec)
--一个事务在执行中,读到另一个执行中事务的更新(或其他操作)但是未commit的数据,这种现象叫做脏读
(dirty read)
根据上面的演示我们发现,在处于读未提交级别中,当A客户端更新了数据但是没提交时,我们的B客户端立马就能读取到修改后的数据,而B客户端读取到A客户端未提交的数据的这种行为就被称为读未提交。读到别人没有提交的数据的行为也被称为脏读。一个事务在执行中,读到另一个执行中事务的更新(或其他操作)但是未commit的数据,这种现象叫做脏读。注意上面这句话中两个事物都是执行状态,也就是并发状态。
读提交【 Read Committed 】
-- 终端A
mysql> set global transaction isolation level read committed;
Query OK, 0 rows affected (0.00 sec)
--重启客户端
mysql> select * from account; --查看当前数据
+----+--------+----------+
| id | name | blance |
+----+--------+----------+
| 1 | 张三 | 123.00 |
| 2 | 李四 | 10000.00 |
+----+--------+----------+
2 rows in set (0.00 sec)
mysql> begin; --手动开启事务,同步的开始终端B事务
Query OK, 0 rows affected (0.00 sec)
mysql> update account set blance=321.0 where id=1; --更新张三数据
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
--切换终端到终端B,查看数据。
mysql> commit; --commit提交!
Query OK, 0 rows affected (0.01 sec)
--切换终端到终端B,再次查看数据。
--终端B
mysql> begin; --手动开启事务,和终端A一前一后
Query OK, 0 rows affected (0.00 sec)
mysql> select * from account; --终端A commit之前,查看不到
+----+--------+----------+
| id | name | blance |
+----+--------+----------+
| 1 | 张三 | 123.00 | --老的值
| 2 | 李四 | 10000.00 |
+----+--------+----------+
2 rows in set (0.00 sec)
--终端A commit之后,看到了!
--but,此时还在当前事务中,并未commit,那么就造成了,同一个事务内,同样的读取,在不同的时间段
(依旧还在事务操作中!),读取到了不同的值,这种现象叫做不可重复读(non reapeatable read)!!
(这个是问题吗??)
mysql> select *from account;
+----+--------+----------+
| id | name | blance |
+----+--------+----------+
| 1 | 张三 | 321.00 | --新的值
| 2 | 李四 | 10000.00 |
+----+--------+----------+
2 rows in set (0.00 sec)
读提交这个隔离级别我们可以很明显看到,当两个事物都在执行的时候,一方一旦进行修改并且提交后,尽管另一方还处于执行状态依旧会看到刚刚更新的内容,而这是不符合常理的。比如现在有一份员工表分为几个工资等级,A客户端将张三的工资等级修改为另一个级别,而B客户端在执行的过程中突然显示有两个等级都有张三的名字,这就不符合常理了因为一个员工只能有一个工资等级。
可重复读【 Repeatable Read 】
--终端A
mysql> set global transaction isolation level repeatable read; --设置全局隔离级别
RR
Query OK, 0 rows affected (0.01 sec)
--关闭终端重启
mysql> select @@tx_isolation;
+-----------------+
| @@tx_isolation |
+-----------------+
| REPEATABLE-READ | --隔离级别RR
+-----------------+
1 row in set, 1 warning (0.00 sec)
mysql> select *from account; --查看当前数据
+----+--------+----------+
| id | name | blance |
+----+--------+----------+
| 1 | 张三 | 321.00 |
| 2 | 李四 | 10000.00 |
+----+--------+----------+
2 rows in set (0.00 sec)
mysql> begin; --开启事务,同步的,终端B也开始事务
Query OK, 0 rows affected (0.00 sec)
mysql> update account set blance=4321.0 where id=1; --更新数据
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
--切换到终端B,查看另一个事务是否能看到
mysql> commit; --提交事务
--切换终端到终端B,查看数据。
--终端B
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from account; --终端A中事务 commit之前,查看当前表中数据,数据未更新
+----+--------+----------+
| id | name | blance |
+----+--------+----------+
| 1 | 张三 | 321.00 |
| 2 | 李四 | 10000.00 |
+----+--------+----------+
2 rows in set (0.00 sec)
mysql> select * from account; --终端A中事务 commit 之后,查看当前表中数据,数据未更新
+----+--------+----------+
| id | name | blance |
+----+--------+----------+
| 1 | 张三 | 321.00 |
| 2 | 李四 | 10000.00 |
+----+--------+----------+
2 rows in set (0.00 sec)
--可以看到,在终端B中,事务无论什么时候进行查找,看到的结果都是一致的,这叫做可重复读!
mysql> commit; --结束事务
Query OK, 0 rows affected (0.00 sec)
mysql> select * from account; --再次查看,看到最新的更新数据
+----+--------+----------+
| id | name | blance |
+----+--------+----------+
| 1 | 张三 | 4321.00 |
| 2 | 李四 | 10000.00 |
+----+--------+----------+
2 rows in set (0.00 sec)
--如果将上面的终端A中的update操作,改成insert操作,会有什么问题??
--终端A
mysql> select *from account;
+----+--------+----------+
| id | name | blance |
+----+--------+----------+
| 1 | 张三 | 321.00 |
| 2 | 李四 | 10000.00 |
+----+--------+----------+
2 rows in set (0.00 sec)
mysql> begin; --开启事务,终端B同步开启
Query OK, 0 rows affected (0.00 sec)
mysql> insert into account (id,name,blance) values(3, '王五', 5432.0);
Query OK, 1 row affected (0.00 sec)
--切换到终端B,查看另一个事务是否能看到
mysql> commit; --提交事务
Query OK, 0 rows affected (0.00 sec)
--切换终端到终端B,查看数据。
mysql> select * from account;
+----+--------+----------+
| id | name | blance |
+----+--------+----------+
| 1 | 张三 | 4321.00 |
| 2 | 李四 | 10000.00 |
| 3 | 王五 | 5432.00 |
+----+--------+----------+
3 rows in set (0.00 sec)
--终端B
mysql> begin; --开启事务
Query OK, 0 rows affected (0.00 sec)
mysql> select * from account; --终端A commit前 查看
+----+--------+----------+
| id | name | blance |
+----+--------+----------+
| 1 | 张三 | 4321.00 |
| 2 | 李四 | 10000.00 |
+----+--------+----------+
2 rows in set (0.00 sec)
mysql> select * from account; --终端A commit后 查看
+----+--------+----------+
| id | name | blance |
+----+--------+----------+
| 1 | 张三 | 4321.00 |
| 2 | 李四 | 10000.00 |
+----+--------+----------+
2 rows in set (0.00 sec)
mysql> select * from account; --多次查看,发现终端A在对应事务中insert的数据,在终端B的事
务周期中,也没有什么影响,也符合可重复的特点。但是,一般的数据库在可重复读情况的时候,无法屏蔽其
他事务insert的数据(为什么?因为隔离性实现是对数据加锁完成的,而insert待插入的数据因为并不存
在,那么一般加锁无法屏蔽这类问题),会造成虽然大部分内容是可重复读的,但是insert的数据在可重复读
情况被读取出来,导致多次查找时,会多查找出来新的记录,就如同产生了幻觉。这种现象,叫做幻读
(phantom read)。很明显,MySQL在RR级别的时候,是解决了幻读问题的(解决的方式是用Next-Key锁
(GAP+行锁)解决的。这块比较难,有兴趣同学了解一下)。
+----+--------+----------+
| id | name | blance |
+----+--------+----------+
| 1 | 张三 | 4321.00 |
| 2 | 李四 | 10000.00 |
+----+--------+----------+
2 rows in set (0.00 sec)
mysql> commit; --结束事务
Query OK, 0 rows affected (0.00 sec)
mysql> select * from account; --看到更新
+----+--------+----------+
| id | name | blance |
+----+--------+----------+
| 1 | 张三 | 4321.00 |
| 2 | 李四 | 10000.00 |
| 3 | 王五 | 5432.00 |
+----+--------+----------+
3 rows in set (0.00 sec)
可重复读的隔离级别解决了刚刚读提交的那个问题,即使我们A客户端对数据库进行了更新等操作,B客户端读到的数据依旧是更新之前的,这就解决了不可重复读的问题。
串行化【 serializable 】
--对所有操作全部加锁,进行串行化,不会有问题,但是只要串行化,效率很低,几乎完全不会被采用
--终端A
mysql> set global transaction isolation level serializable;
Query OK, 0 rows affected (0.00 sec)
mysql> select @@tx_isolation;
+----------------+
| @@tx_isolation |
+----------------+
| SERIALIZABLE |
+----------------+
1 row in set, 1 warning (0.00 sec)
mysql> begin; --开启事务,终端B同步开启
Query OK, 0 rows affected (0.00 sec)
mysql> select * from account; --两个读取不会串行化,共享锁
+----+--------+----------+
| id | name | blance |
+----+--------+----------+
| 1 | 张三 | 4321.00 |
| 2 | 李四 | 10000.00 |
| 3 | 王五 | 5432.00 |
+----+--------+----------+
3 rows in set (0.00 sec)
mysql> update account set blance=1.00 where id=1; --终端A中有更新或者其他操作,会阻
塞。直到终端B事务提交。
Query OK, 1 row affected (18.19 sec)
Rows matched: 1 Changed: 1 Warnings: 0
--终端B
mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from account; --两个读取不会串行化
+----+--------+----------+
| id | name | blance |
+----+--------+----------+
| 1 | 张三 | 4321.00 |
| 2 | 李四 | 10000.00 |
| 3 | 王五 | 5432.00 |
+----+--------+----------+
3 rows in set (0.00 sec)
mysql> commit; --提交之后,终端A中的update才会提交。
Query OK, 0 rows affected (0.00 sec)
在串行化这个隔离级别中,我们发现只要A客户端有更新等操作,客户端B就会被阻塞,直到事务提交,虽然安全性很高,但是效率却很低。
总结:
其中隔离级别越严格,安全性越高,但数据库的并发性能也就越低,往往需要在两者之间找一个平
衡点。
不可重复读的重点是修改和删除:同样的条件 , 你读取过的数据 , 再次读取出来发现值不一样了
幻读的重点在于新增:同样的条件 , 第 1 次和第 2 次读出来的记录数不一样
说明: mysql 默认的隔离级别是可重复读 , 一般情况下不要修改
上面的例子可以看出,事务也有长短事务这样的概念。事务间互相影响,指的是事务在并行执行的
时候,即都没有 commit 的时候,影响会比较大。
一致性 (Consistency)
事务执行的结果,必须使数据库从一个一致性状态,变到另一个一致性状态。当数据库只包含事务
成功提交的结果时,数据库处于一致性状态。如果系统运行发生中断,某个事务尚未完成而被迫中
断,而改未完成的事务对数据库所做的修改已被写入数据库,此时数据库就处于一种不正确(不一
致)的状态。因此一致性是通过原子性来保证的。
其实一致性和用户的业务逻辑强相关,一般 MySQL 提供技术支持,但是一致性还是要用户业务逻辑做支撑,也就是,一致性,是由用户决定的。
十三、视图
视图是一个虚拟表,其内容由查询定义。同真实的表一样,视图包含一系列带有名称的列和行数据。视图的数据变化会影响到基表,基表的数据变化也会影响到视图。
基本使用
创建视图
create view 视图名 as select语句;
比如:
create view v_ename_dname as select ename, dname
from EMP, DEPT where EMP.deptno=DEPT.deptno;
select * from v_ename_dname order by dname;
+--------+------------+
| ename | dname |
+--------+------------+
| CLARK | ACCOUNTING |
| KING | ACCOUNTING |
| MILLER | ACCOUNTING |
| SMITH | RESEARCH |
| JONES | RESEARCH |
| SCOTT | RESEARCH |
| ADAMS | RESEARCH |
| FORD | RESEARCH |
| ALLEN | SALES |
| WARD | SALES |
| MARTIN | SALES |
| BLAKE | SALES |
| TURNER | SALES |
| JAMES | SALES |
+--------+------------+
修改了视图,对基表数据有影响
select emp.ename,dept.dname,dept.deptno from emp,dept where
emp.deptno=dept.deptno order by dname;
update v_ename_dname set ename='TEST' where ename='CLARK';
select * from EMP where ename='CLARK';
select * from EMP where ename='TEST';
修改了基表,对视图有影响
mysql> update EMP set deptno=10 where ename='JAMES'; -- 修改基表
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
mysql> select * from v_ename_dname where ename='JAMES';
+-------+----------+
| ename | dname |
+-------+----------+
| JAMES | RESEARCH | <== 视图中的数据也发生了变化
+-------+----------+
可以看到修改视图确实是会影响基表的。
删除视图
drop view 视图名;
视图规则和限制
1.与表一样,必须唯一命名(不能出现同名视图或表名)
2.创建视图数目无限制,但要考虑复杂查询创建为视图之后的性能影响
3.视图不能添加索引,也不能有关联的触发器或者默认值
4.视图可以提高安全性,必须具有足够的访问权限
5.order by 可以用在视图中,但是如果从该视图检索数据 select 中也含有 order by , 那么该视图
中的 order by 将被覆盖
6.视图可以和表一起使用
十四、 用户管理
如果我们只能使用root用户,这样存在安全隐患。这时,就需要使用MySQL的用户管理。
1.用户信息
MySQL 中的用户,都存储在系统数据库 mysql 的 user 表中
host : 表示这个用户可以从哪个主机登陆,如果是 localhost ,表示只能从本机登陆
user : 用户名
authentication_string : 用户密码通过 password 函数加密后的
*_priv : 用户拥有的权限
2.创建用户
语法
create user '用户名'@'登陆主机/ip' identified by '密码';
当遇到密码等级的报错时通过上图中的命令进行查看。
3.删除用户
语法
drop user '用户名'@'主机名'
4.修改用户密码
自己改自己的密码
set password=password('新的密码');
root 用户修改指定用户的密码
set password for '用户名'@'主机名'=password('新的密码');
5.数据库的权限
6.给用户授权
刚创建的用户没有任何权限。需要给用户授权
grant 权限列表 on 库.对象名 to '用户名'@'登陆位置' [identified by '密码']
权限列表,多个权限用逗号分开
grant select on ...
grant select, delete, create on ....
grant all [privileges] on ... -- 表示赋予该用户在该对象上的所有权限
*.* : 代表本系统中的所有数据库的所有对象(表,视图,存储过程等)
库 .* : 表示某个数据库中的所有数据对象 ( 表,视图,存储过程等 )
identified by 可选。 如果用户存在,赋予权限的同时修改密码 , 如果该用户不存在,就是创建用户
7.回收权限
语法
revoke 权限列表 on 库.对象名 from '用户名'@'登陆位置';
十五、连接MySQL
要使用 C 语言连接 mysql ,需要使用 mysql 官网提供的库。
我们使用 C 接口库来进行连接
要正确使用,我们需要做一些准备工作:
保证mysql服务有效
在官网上下载合适自己平台的mysql connect库,以备后用
Connector/C 使用
我们下下来的库格式如下:
[hb@MiWiFi-R1CL-srv lib]$ tree .
.
├── include
│ ├── big_endian.h
│ ├── byte_order_generic.h
│ ├── byte_order_generic_x86.h
│ ├── decimal.h
│ ├── errmsg.h
│ ├── keycache.h
│ ├── little_endian.h
│ ├── m_ctype.h
│ ├── m_string.h
│ ├── my_alloc.h
│ ├── my_byteorder.h
│ ├── my_compiler.h
│ ├── my_config.h
│ ├── my_dbug.h
│ ├── my_dir.h
│ ├── my_getopt.h
│ ├── my_global.h
│ ├── my_list.h
│ ├── my_pthread.h
│ ├── mysql
│ │ ├── client_authentication.h
│ │ ├── client_plugin.h
│ │ ├── client_plugin.h.pp
│ │ ├── get_password.h
│ │ ├── plugin_auth_common.h
│ │ ├── plugin_trace.h
│ │ ├── psi
│ │ │ ├── mysql_file.h
│ │ │ ├── mysql_idle.h
│ │ │ ├── mysql_mdl.h
│ │ │ ├── mysql_memory.h
│ │ │ ├── mysql_ps.h
│ │ │ ├── mysql_socket.h
│ │ │ ├── mysql_sp.h
│ │ │ ├── mysql_stage.h
│ │ │ ├── mysql_statement.h
│ │ │ ├── mysql_table.h
│ │ │ ├── mysql_thread.h
│ │ │ ├── mysql_transaction.h
│ │ │ ├── psi_base.h
│ │ │ ├── psi.h
│ │ │ └── psi_memory.h
│ │ ├── service_my_snprintf.h
│ │ └── service_mysql_alloc.h
│ ├── mysql_com.h
│ ├── mysql_com_server.h
│ ├── mysqld_ername.h
│ ├── mysqld_error.h
│ ├── mysql_embed.h
│ ├── mysql.h
│ ├── mysql_time.h
│ ├── mysql_version.h
│ ├── my_sys.h
│ ├── my_xml.h
│ ├── sql_common.h
│ ├── sql_state.h
│ ├── sslopt-case.h
│ ├── sslopt-longopts.h
│ ├── sslopt-vars.h
│ └── typelib.h
└── lib
├── libmysqlclient.a
├── libmysqlclient_r.a -> libmysqlclient.a
├── libmysqlclient_r.so -> libmysqlclient.so
├── libmysqlclient_r.so.18 -> libmysqlclient.so.18
├── libmysqlclient_r.so.18.3.0 -> libmysqlclient.so.18.3.0
├── libmysqlclient.so -> libmysqlclient.so.18
├── libmysqlclient.so.18 -> libmysqlclient.so.18.3.0
└── libmysqlclient.so.18.3.0
其中 include 包含所有的方法声明, lib 包含所有的方法实现(打包成库)
尝试链接 mysql client
通过 mysql_get_client_info() 函数,来验证我们的引入是否成功
#include <stdio.h>
#include <mysql.h>
int main()
{
printf("mysql client Version: %s\n", mysql_get_client_info());
return 0;
}
[hb@MiWiFi-R1CL-srv lib]$ gcc -o test test.c -I./include -L./lib -lmysqlclient
[hb@MiWiFi-R1CL-srv lib]$ ls
include lib test test.c
[hb@MiWiFi-R1CL-srv lib]$ ./test
./test: error while loading shared libraries: libmysqlclient.so.18: cannot open
shared object file: No such file or directory
[hb@MiWiFi-R1CL-srv lib]$ export LD_LIBRARY_PATH=./lib #动态库查找路径,讲解ldd命令
[hb@MiWiFi-R1CL-srv lib]$ ./test
mysql client Version: 6.1.6
至此引入库的工作已经做完,接下来就是熟悉接口 。
mysql 接口介绍
要使用库,必须先进行初始化!
MYSQL *mysql_init(MYSQL *mysql) ;
如: MYSQL *mfp = mysql_init(NULL)
初始化完毕之后,必须先链接数据库,在进行后续操作。( mysql 网络部分是基于 TCP/IP 的)
MYSQL *mysql_real_connect(MYSQL *mysql, const char *host,
const char *user,
const char *passwd,
const char *db,
unsigned int port,
const char *unix_socket,
unsigned long clientflag);
//建立好链接之后,获取英文没有问题,如果获取中文是乱码:
//设置链接的默认字符集是utf8,原始默认是latin1
mysql_set_character_set(myfd, "utf8");
第一个参数 MYSQL 是 Capi 中一个非常重要的变量( mysql_init 的返回值),里面内存非常丰富,有port,dbname,charset等连接基本参数。它也包含了一个叫 st_mysql_methods 的结构体变量,该变量里面保存着很多函数指针,这些函数指针将会在数据库连接成功以后的各种数据操作中被调用。 mysql_real_connect函数中各参数,基本都是顾名思意。
下发mysql命令mysql_query
int mysql_query(MYSQL *mysql, const char *q);
第一个参数上面已经介绍过,第二个参数为要执行的 sql 语句 , 如 "select * from table" 。
获取执行结果mysql_store_result
sql 执行完以后,如果是查询语句,我们当然还要读取数据,如果 update , insert 等语句,那么就看下操作成功与否即可。我们来看看如何获取查询结果: 如果mysql_query 返回成功,那么我们就通过mysql_store_result这个函数来读取结果。原型如下:
MYSQL_RES *mysql_store_result(MYSQL *mysql);
该函数会调用 MYSQL 变量中的 st_mysql_methods 中的 read_rows 函数指针来获取查询的结果。同时该函数会返回MYSQL_RES 这样一个变量,该变量主要用于保存查询的结果。同时该函数 malloc 了一片内存空间来存储查询过来的数据,所以我们一定要记的 free(result), 不然是肯定会造成内存泄漏的。 执行完mysql_store_result 以后,其实数据都已经在 MYSQL_RES 变量中了,下面的 api 基本就是读取MYSQL_RES 中的数据
获取结果行数mysql_num_rows
my_ulonglong mysql_num_rows(MYSQL_RES *res);
获取结果列数mysql_num_fields
unsigned int mysql_num_fields(MYSQL_RES *res);
获取列名mysql_fetch_fields
MYSQL_FIELD *mysql_fetch_fields(MYSQL_RES *res);
比如:
int fields = mysql_num_fields(res);
MYSQL_FIELD *field = mysql_fetch_fields(res);
int i = 0;
for(; i < fields; i++){
cout<<field[i].name<<" ";
}
cout<<endl;
获取结果内容mysql_fetch_row
MYSQL_ROW mysql_fetch_row(MYSQL_RES *result);
它会返回一个 MYSQL_ROW 变量, MYSQL_ROW 其实就是 char **. 就当成一个二维数组来用吧。
i = 0;
MYSQL_ROW line;
for(; i < nums; i++){
line = mysql_fetch_row(res);
int j = 0;
for(; j < fields; j++){
cout<<line[j]<<" ";
}
cout<<endl;
}
关闭mysql链接mysql_close
void mysql_close(MYSQL *sock);
另外, mysql C api 还支持事务等常用操作,这里就不再演示了。
my_bool STDCALL mysql_autocommit(MYSQL * mysql, my_bool auto_mode);
my_bool STDCALL mysql_commit(MYSQL * mysql);
my_bool STDCALL mysql_rollback(MYSQL * mysql);
总结
本篇文章只作为自己学习MySQL的笔记,其中有错误的地方还希望大家能够指出来。对于想要入门MySQL的同学看这篇文章就足够了,将这篇文章中的案例都进行演示后就可以去刷MySQL的题了。本篇文章的面试重点在于约束,索引,事务这三部分,面试前突击的可以优先看这部分。