MySQL数据库--从创建数据库到删库跑路
MySQL数据库
家人们!删库是危险操作,慎用!!!
前言: 今天开始正式进入MySQL的学习,希望这篇文章能够帮助到大家!
MySQL安装:
下载链接:
下载链接
打开链接之后直接点击No thanks, just start my download. 即可下载
如果该版本不是你想要的,也可以选择去Oracle官网或者MySQL官网下载其他版本
安装步骤就是一路next,这里不多介绍,需要注意的是:学习过程中,MySQL设置的密码尽量简单一点,容易记住
关于MySQL需要知道的:
-
MySQL是 “客户端-服务器” 结构的程序
-
1个MySQL服务器上,可以包含多个数据库,
-
1个数据库利用包含多个数据表,
-
1个数据表可以包含多个数据行
-
1个数据行可以包含多个数据列,一个数据行也可以叫一条记录
如图:
1. 数据库基本操作
1.1 创建数据库
创建数据库(如果数据库名字存在,会报错):
create database 数据库名;
如果不存在该名称的数据库,就创建数据库;如果存在,就什么的不做,也不会报错,但是有警告:
create database if not exists 数据库名;
显示警告:
show warnings;
设置字符编码:
MySQL里面的utf8是阉割版的utf8,在MySQL中真正的utf8是utfmb4
create database if not exists 数据库名 charset utf8mb4;
1.2 显示当前数据库
语法:
show databases;
例如:
以上这四个数据库是系统自带的,安装好就有的数据库
1.3 删除数据库
删库非常危险,慎重使用!!!
drop database 数据库名;
也可以添加条件:
drop database if not exists 数据库名;
1.4 使用数据库/选中数据库
use 数据库名;
2. SQL中的数据类型
2.1 数值类型
数据类型 | 大小 | 说明 | 对应Java中的类型 |
---|---|---|---|
bit(M) | 默认为1,M表示位数 | 表示二进制数,M取1~64 | |
tinyint | 1字节 | Byte | |
smallint | 2字节 | Short | |
int | 4字节 | Integer | |
bigint | 8字节 | Long | |
float(M,D) | 4字节 | 单精度浮点数,M表示总长度,D表示小数点位,不能精确表示小数 | Float |
double(M,D) | 8字节 | 双精度浮点数,M表示总长度,D表示小数点位,不能精确表示小数 | Double |
decimal(M,D) | M/D最大值+2 | M表示总长度,D表示小数点位,能精确表示小数 | BigDecimal |
numeric(M,D) | M/D最大值+2 | M表示总长度,D表示小数点位,能精确表示小数 | BigDecimal |
以上类型都是有符号类型(能够表示负数),常用的有int、bigint、double、decimal
2.2 字符串类型
数据类型 | 大小 | 说明 | 对应Java类型 |
---|---|---|---|
varchar(size) | 0~65535字节 | 可变长度字符串,size表示字符个数 | String |
char(size) | 0~65535字节 | 定长字符串,size表示字符个数 | String |
text | 0~65535字节 | 长文本数据(可变长,无需指定最大长度,是自适应的) | String |
mediumtext | 0~16777215字节 | 中等长度文本数据 | String |
blob | 0~65535字节 | 二进制数据 | byte[] |
可变长:根据存储的数据自动调整存储空间,例如char(10)与varchar(10),能保存的最大长度是10个字符,如果只存储了5个字符,char申请的存储空间是10个字符,可变长的varchar申请的存储空间是5个字符。
常用的有varchar
2.3 时间类型
数据类型 | 大小 | 说明 | 对应Java类型 |
---|---|---|---|
datetime | 8字节 | 能表示范围:1000年到9999年 | java.util.Date、java.sql.Timestamp |
timestamp | 4字节 | 能表示范围:1970年到2038年 | java.util.Date、java.sql.Timestamp |
timestamp(时间戳):当前时间与1970年1月1日0时0分0秒的时间之差(单位可以是秒/毫秒/微秒…),只能表示到2038年,不适用
常用的有:datetime
总结: 上述类型并不是数据库全部的类型,不同的数据库支持的类型也有差别,掌握以下类型:int、bigint、double、decimal、varchar、datetime
3. 表的操作
大前提:先选中某个数据库!!!!
3.2 创建表
语法:
create table 表名(列名 类型,列名 类型.....);
例如:
create table test(id int,name varchar(20));
在命令行中执行SQL语句,执行成功会提示Query OK
3.1 显示数据库中的表
show tables;
此时会展示已经创建好的表
3.3 查看表的详细情况
desc 表名;
例如:
desc是describe的缩写,desc只能查看表的结构(有哪些列,每个列的情况),不能查看表中具体的内容
Field: 字段(列的名字)
Type: 列的类型,int(11),11表示位宽,最多只显示11个字符长度
NULL: 列是否允许为空,yes表示允许
key: 主键/外键(本文后面会介绍到)
Default: 默认值,可以手动指定
Extra: 补充信息
3.4 删除表
drop table 表名;
整个表的内容都会删除,包括数据
慎重使用!!!
3.5 注释
使用comment、–、#
如:
comment: comment比较特殊,用法如下(创建表的时候使用)
3. 修改列(了解即可)
关键字alter
-
添加列:
alter table 表名 add 新增的列名 类型;
-
修改列的类型:
alter table 表名 modify 要修改的列名 类型;
-
删除列:
alter table 表名 drop 要删除的列名;
-
修改表名:
alter table 要修改的表名 rename to 新表名;
-
添加主键:
alter table 表名 add primary key(列名);
-
删除主键:
alter table 表名 drop primary key;
-
添加外键:
alter table 表名 add constraint 添加的外键约束名 foreign key (设为外键的列名) references 参考的表名;
-
删除外键:
alter table 表名 drop foreign key 外键约束名;
4. 表的增删查改
4.1 插入(insert)
基本语法:
insert into 表名 values(值,值,值...);
插入的数据类型要和表对应列类型匹配
插入数据需要和列的个数、数据类型、顺序匹配
如果要插入的数据包含中文,必须保证创建数据库时指定的字符集为utf8/gbk
例子
我们先准备好一个学生表,表的结构如下:
- 基本插入方法
insert into student values(1,'张三');
- 类型转换
例如:
insert into student values('111',222);
上图代码中,虽然插入第一列的是字符串类型,但是系统会尝试自动转成int;第二列插入的是整数类型,系统会尝试转成字符串。
- 指定某些列名进行插入
insert into 表名(列名,列名...) values(值,值...);
为指定的列的值:如果允许为空值,则为空值;如果不允许为空值,则为默认值;如果没有指定默认值插入失败
insert into student(id) values(2);
- 插入多行
insert into 表名 values(值,值...),(值,值...),(值,值...);
如:
insert into student values(1,'张三'),(2,'李四'),(3,'王五');
- 插入时间类型
遵循格式:
年-月-日空格时:分:秒
insert into test values('2024-05-11 15:10:30');
获取系统当前时间,使用函数now(),如:
insert into test values(now());
- 插入和查询结合
也就是将查询的结果插入表中(可以指定列)
insert into 表名 select语句;
要求:select得到的结果集合要和insert into的表的列数、类型、顺序匹配
例如:将student1中年龄小于20的记录插入到student2中
insert into student2 select * from student1 where age < 20;
4.2 查询(select)
关键字:select
生成临时表,不会改变原始数据
全列查询
select * from 表名;
例如
select * 的操作慎用,因为实际工作中,如果表非常大,数据非常多,此时使用select 操作可能会将硬盘、网卡带宽吃满,其他客户端访问数据库就无法正常进行
查询指定列
select 列名,列名... from 表名;
查询时指定表达式
select 表达式 from 表名;
遍历每一行,取出想要的列代入表达式,结果为临时表,不会改变原来的数据
例:
有表grades如下:
查询每个同学的英语成绩和数学成绩的总和,语句如下
select math+english from grades;
查询结果:
查询时给列取别名
如果表达式比较复杂无法直接观察,此时可以取别名
select 列名 as 别名 from 表名;
as 可以省略(不建议省略,容易看混)
例如:
去重查询
多个行的数据如果出现相同的值,就只保留一份(针对也是临时表)
select distinct 列名 from 表名;
例如:
如果要查询多列,多列都重复才能触发去重效果
例如:
查询的是math和name,虽然math重复了,但是name没有重复,没有触发去重效果
排序查询
针对查询结果进行顺序/降序排列,使用order by
select 列名 from 表名 order by 想要进行排序的列名 desc;-- 降序
select 列名 from 表名 order by 想要进行排序的列名 asc;-- order by默认的就是升序,asc 可以省略
desc,asc分别是descend、ascend的缩写
例:按数学成绩降序:
按数学成绩升序(asc可以省略不写)
另外,可以指定多个列,先按第一列排序,第一列相同则按照第二列排序以此类推
例如:按照数学成绩排序,如果数学成绩一样则按照英语成绩排序
条件查询
查询过程可以指定筛选条件,查询结果会保留满足条件的记录
select 列名 from 表名 where 条件;
条件指的是条件表达式
比较运算符:
比较运算符 | 说明 |
---|---|
>、>=、<、<= | 大于、大于等于、小于、小于等于 |
= | 等于,null不安全,null=null的结果为null |
<=> | 等于,null安全,null<==>null结果是 true |
!=、<> | 不等于 |
between a and b | 表示连续的范围,[a,b) |
in(a1,a2,…) | 表示非连续的范围(离散的) |
is null | 是null |
is not null | 不是null |
like | 字符匹配 |
逻辑运算符:
逻辑运算符 | 说明 |
---|---|
and | 多个条件都为真,结果才是真 |
or | 任意一个条件为真,结果就为真 |
and 优先级高于or
执行顺序问题(大部分解释是这样,但是并不靠谱):
-
遍历表,取出每一行
-
把当前行,代入到where条件,根据条件真假决定这一行是否需要查询出来
-
再根据select后面写的列名/表达式,来进行筛选列/进行计算/别名
-
如果有order by则进行排序
字符匹配
like、not like
%表示任意长度
_代表任意一个字符
例如:
查询学生表(student)中以李开头的学生的所有内容
select * from student where name like '李%';
查询课程表(course)中,以’结构’结尾的课程名
select cname from course where cname like '%结构';
%表示可以是任意长度的字符串
查询学生表中姓名倒数第二个字为’德’的学生的所有内容
select * from student where name like '%德_';
null的比较
is null、is not null、<=>
看例子
原始数据如下
null=null和null<=>null的区别
分页查询
限制一次查询最多能查多少个记录
select 列名 from 表名 limit n;
从下标m开始查n条记录(原来的表中的第一条记录的下标是0)
select 列名 from 表名 limit m offset n;
select 列名 from 表名 limit m,n;
聚合查询
聚合是针对行和行之间的操作,需要搭配聚合函数
聚合函数
函数 | 说明 |
---|---|
count([distinct] 表达式) | 返回查询结果的数量(行数) |
sum([distinct] 表达式) | 返回查询结果的总和 |
avg([distinct] 表达式) | 返回查询结果的平均值 |
max([distinct] 表达式) | 返回查询结果的最大值 |
min([distinct] 表达式) | 返回查询结果的最小值 |
distinct是可选项,表示去重,前面介绍过
- count
select count(*) from 表名;
-- 表示查询这张表有多少个记录(多少行),如果某个记录存在空值null,查询结果也会包含这列
select count(列名) from 表名;
-- 查询这一列的记录个数(多少行),如果这列存在空值null,查询结果不会包含空值(重名也计算)
- sum 针对指定列进行求和
select sum(列名/表达式) from 表名;
sum操作的这一列的类型必须是数字类型,遇到空值null直接跳过不参与运算
3. avg 针对指定列进行求和
select avg(列名/表达式) from 表名;
avg操作的这一列的类型必须是数字类型,遇到空值null直接跳过不参与运算
- max 针对指定列进行求最大值
select max(列名/表达式) from 表名;
max操作的这一列的类型必须是数字类型,遇到空值null直接跳过不参与运算
5. min 针对指定列进行求最小值
select min(列名/表达式) from 表名;
min操作的这一列的类型必须是数字类型,遇到空值null直接跳过不参与运算
group by :分组查询
使用group by时,指定一个列,把列的值相同的行归到一组中
语法如下
select 列名 from 表名 ... group by 想要分组的列名;
例:
有员工薪资表如下
使用group by分组
将role值相同的归为一组,进行group by分组时,select后面的列名一般写group by分组的列或者搭配聚合函数的列
例如:
统计每个部门(role)的平均薪资:
统计每个部门的人数:
与order by搭配使用
having
当需要指定分组的条件时,可以使用having(不能使用where)
例:统计薪资平均值,以role分组,但是除去老板
注意:having是分组的条件,而where是没有分组时的条件
联合查询
联合查询也就是多表查询,查询设计多个表
笛卡尔积:简单来说,就是拿着第一个表的每一行依次和第二个表进行匹配
如:
有学生表和班级表,以及这两个表的笛卡尔积
也就是将第一张表(学生表)中每一条记录,依次和第二张表(班级表)依次进行排列组合,多表查询就是基于笛卡尔积,但是在笛卡尔积中,有一些记录是不合理的,例如上述的张三的班级编号既有1又有2,但真实情况是张三的班级编号是2,如果两个表不存在某个列具有对应关系(对应关系如上述的班级编号),则这两个表进行笛卡尔积是无意义的。
在SQL语句中,如何进行笛卡尔积?
我们创建上述例子的两个表
笛卡尔积的SQL语句为:
select * from 表1,表2;
例如
内连接
语法:
select 列名 from 表1 [inner]join 表2 on 连接条件 and 其他条件;-- 表1join表2就相当于表1和表2进行笛卡尔积,inner表示内连接,可以省略
select 列名 from 表1,表2 where 连接条件 and 其他条件;-- 也就是把join换成逗号,把on换成where
只包含两个表中同时具备的数据,
外连接
不支持from多个表,只能使用join,MySQL中也不支持全外连接
左外连接,表1完全显示
select 字段名 from 表名1 left join 表名2 on 连接条件; -- 表1为基准
右外连接,表2完全显示
select 字段 from 表名1 right join 表名2 on 连接条件; -- 表2为基准
例如:有两张表
左外连接:左表为基准,会确保左表中的每个记录都出现在,如果左表中的记录在右边中没有对应的记录,会把右表的字段填成null
右外连接和左外连接逻辑是一样的:确保右表中所有的记录都出现在结果中,如果右边中的记录在左边中没有对应的相关字段,则填成null
外连接和内连接可以总结为下面这张图:
自连接
自连接,就是一张表自己和自己进行笛卡尔积,自连接本质上是把行关系转换成列关系,自连接的语法如下
select 列名 from 表1 as 别名1,表1 as 别名2;
表1和表1是同一个表
例如:
如果不取别名,就会出现下面的报错:
同样的,自连接也需要指定连接条件(否则会出现许多无效记录):
总结:如果发现查询的条件是针对两行而不是两列,可以使用自连接,但是如果表的数据量非常大,要谨慎操作
子查询
子查询简单来说就是套娃(嵌套次数过多容易脑瓜子嗡嗡,不推荐使用)
例如:查询和张三同班的同学,
select * from student
where classid = (select classid from student where name = '张三');
多行子查询,使用in
例如:查询语文和英语的成绩信息
select * from score
where courseid in( select id from course where name = '语文' or name = '英语');
合并查询
合并查询使用union关键字,可以把多个select查询结果合并成一个
select 列名 from 表名 条件 union select 列名 from 表名 条件;
可以把两个查询的结果合并在一起,针对不同表查询也可以
如果不想去重,可以使用union all
select 列名 from 表名 条件 union select 列名 from 表名 条件;
要求:合并的两个集合类型、个数、顺序、要匹配
4.3 修改(update)
update 表名 set 列名 = 值,列名 = 值 ...;
后面可以加上条件where/limit/order by
例:
4.4 删除(delete)
删除的是硬盘上的一条符合条件的记录,如果没有规定条件,则是删除所有的记录(不会删除表)
delete from 表名 条件;
5. 约束
约束是数据库针对表中的数据,进行的一系列校验检查,约束类型有not null、unique、default、primary key、foreign key等
5.1 not null
表示某列不能为空值,语法:
create table 表名 (属性)
例:创建表时,指定某列的值不能为空
在插入/修改数据时,插入/修改id属性就不能为空,如果有空则插入/修改失败
5.2 unique
unique表示唯一,某列的每行必须有唯一值,在插入、修改时,不能插入/修改成存在过的值
unique如何知道数据是否存在重复?插入、修改之前会进行一次查询操作
例:指定学生表的name为unique
插入一条姓名为张三的记录后,又一次插入姓名为张三的记录,此时插入失败
5.3 default
default表示默认,指定某列的默认值,插入一条记录,如果不指定某行的值,会设置为默认值,例如:
创建学生表时,指定name的默认值为‘无名’
查看一下student的详细信息:default为‘无名’,如果创建表时不指定默认值,则默认是null
此时插入数据如果不指定该列的值,这列的值就会被设置为我们指定的默认值,如:
5.4 primary key
primary key表示主键,主键是一行唯一标识一条数据的属性,例如一个学校中,学生的学号就可以设置为主键,因为一个学校的学生学号不可能重复
primary key可以看成是not null和unique的结合,主键的值不能为空,也不能重复
例:创建学生表,将学号设为主键
查看表的详细信息:PRI指的就是primary 表示该属性是主键
在进行插入/修改之前,会进行一次查询,查看是否存在重复的主键值,如果存在,插入失败/修改
如果主键的类型是整型,可以搭配auto_increment使用,表示自增主键,此时插入记录可以不指定主键的值,MySQL会自动分配主键值,每次都会记录自增主键的最大值,后续插入数据是按最大值进行分配,同时也可以手动指定其他的值
创建学生表
如:
此时表中数据为
5.5 foreign key
foreign key表示外键,外键涉及两个表,语法格式
create table 表名 (列名 类型,...... foreign key(列名) references 表名(列名));
创建class表
创建student表,student中外键为classid,引用自class表
此时外键约束出现了,如果要往student表中插入数据,必须保证插入的数据的classid在class表中存在,否则插入失败
父表:制约的表(class表),子表:被约束的表(student表)
如何知道classid存不存在?插入时会触发对class表的查询(数据库内部完成)
如果父表的某个记录被子表引用了,此时不能删除/修改父表中对应的记录了
删除表:需确保先删除子表,再删除父表否则出错:
场景:商品表:(goodsid,goodsname,price),订单表:(orderid,goodsid);如果某个需要商品下架,如何删除?
逻辑删除:额外添加一列,表示标记
6. 表的设计
- 一对一关系:如一个班级只能有一个班长,一个班长也只能在一个班级中任职,设计思路:可以设计成一个表,同时包含两个属性;也可以弄两个表利用用主键来进行两个表的关联,如:班级班长表(班级id,班级名,班长id)或者 班级班长表(班级id,班级名,班长id)
- 一对多关系:如一个学生只能属于一个班级,一个班级可以包含多个学生,设计思路:在“一”表中添加“多”表的主键,如:学生表(学生id,学生姓名,班级id),班级表(班级id,班级名称)
- 多对多关系:一个学生能选择多门课程学习,一门课程也能被多个学生选择,设计思路:创建一个新的关联表,通过关联表把多对多的关系表示出来,如:学生表(学生id,学生姓名),课程表(课程id,课程名),创建一个关联表:学生-课程表(学生id,课程id)
7. 索引
索引,相当于一个特殊的“目录”,有了索引的存在,能够加快查询速度,尽可能避免对表数据的遍历。
引入索引的缺点:
-
需要消耗额外的存储空间
-
引入索引,虽然可以提高查询速度,但是可能会影响增、删、改的效率(增删改的时候需要同步更新维护索引)
操作索引的SQL语句
查看索引
show index from 表名;
如果建表时指定了主键,会自动生成主键索引,索引是按照列的方式创建的,可以给某个列创建索引。如果以某个列创建索引,当查询的时候以该列为条件时,索引才能生效,提高查询速度,否则无法生效。
除了主键外,unique、foreign key也会自带索引
创建索引
可以给非主键、外键、unique的列手动创建索引,语法如下:
create index 索引名 on 表名(列名);
创建索引,也是危险操作~因为一旦数据量比较大,就会触发大量的硬盘IO,所以在最初建表时就提前规划好建立哪些索引
删除索引
语法
drop index 索引名 on 表名;
只能删除自己手动创建的索引,自动生成的索引无法删除,删除索引也是危险操作~~
索引背后的秘密 - B+树
构建索引,也就是引入一些数据结构,对数据进行存储,从而提高查找速度
数据库中,索引保存的数据结构主要为B+树,为啥不用别的数据结构?
顺序表、链表不适合查找,栈、队列也不适合查找,
二叉搜索树:可以提高查找效率,但是问题在于保存数据较多时,树的高度就会变高,比较的次数也就会变高,会造成非常大的硬盘IO
哈希表:只能进行"相等"的查询,不能进行范围查询(比如>、<、between and),也无法进行like这样的模糊查询
适合给数据库做索引的数据结构,就是B+树,什么是B+树?
想要了解B+树,得先知道B树(也可以写作B-树)
B树: 平衡的n(n>2)叉搜索树(每个结点可以有多个子树),这样树的高度就大大降低了,如图
树的高度降低了,每个结点的比较次数变多了(比较逻辑:和某个结点比较的时候,先进行一次硬盘IO,把所有的结点内容读取出来),但是硬盘IO减少了,
B+树: 针对B树改进的数据结构
B+树的优点:
- N叉搜索树,高低低,硬盘IO次数少
- 叶子结点是数据的全集,并且用链表结构连接,方便范围查询
- B+树,所有的查询都要落到叶子结点上完成,任何一次查询经历的IO次数和比较次数是差不多的,查询开销稳定
- 叶子结点是数据的全集,所以非叶子结点不必存储数据行,只需要存储索引列的key值即可,使得非叶子结点消耗空间小这样甚至
关于索引的总结(找工作面试常问!!!)
- 索引是什么?
索引相当于书的目录,能够提高查询速度
- 索引付出了什么代价?
-
消耗了更多的存储空间
-
可能会影响增、删、改的效率(可能提高也可能降低,也可能没有影响)
- 使用SQL语句操作索引
-
show index from 表名; – 查看表的索引(主键、外键、unique会自动生成索引)
-
create index 索引名 on 表名(列名); – 给指定的列创建索引
-
drop index 索引名 on 表名; – 删除索引(只能删除自己手动创建的索引)
- 关于索引的注意事项
- 索引是针对列创建的,后续查询时,只有查询条件包含了这列,索引才能提高效率
- 针对数据量比较大的表,最好不要在中途添加和删除索引,因为这样可能会触发大量的硬盘IO,可能把数据库搞崩溃
- 索引背后的数据结构是B+树,特点如下
- N叉搜索树,每个结点上包含n(n>2)个key,划分出n个区间
- 每个父结点都会在下一层多存一份
- 叶子结点这层包含了数据集合的全集
- 叶子结点使用链表将所有的叶子结点连起来
- 使用B+树的优点
- n叉搜索树,降低了树的高度,减少了硬盘的IO次数
- 进行范围查询时非常方便
- 所有查询都落在叶子结点上,开销稳定,成本容易评估
- 叶子结点存储数据行,非叶子结点存储索引的key值,非叶子结点占据空间小,甚至可以加载到内存中,进一步减少了IO的访问次数
8. 事务
事务是用来解决某些特定场景的问题的,有些场景完成某个操作,需要多个SQL语句配合完成~
想象一个场景:
张三在玩王者农药,想要充648点券买皮肤,于是给马花藤的账户充了648
先创建表~~
create table account( id int primary key auto_increment,
name varchar(20) comment '账户名称',
money decimal(11,2) comment '金额');
insert into account(name, money) values
('马花藤', 99999999),
('张三', 1000);
充值逻辑:
update account set money = money-648 where name = '张三';
update account set money = money+648 where name = '马花藤';
两个sql必须都执行完毕转账才能完成,假设第一条update执行成功后,突然出现网络问题,主机断电了或者数据库崩了,后面的语句无法执行,导致张三的账户里减少了648,而马花藤的账户金额没有增加。这种情况显然是不对的,如何避免这种情况?使用事务,保证上面两条语句要么全部执行成功,要么全部都执行失败
语法:
start transaction; -- 开启事务
sql语句
rollback; -- 回滚,表示sql语句全部执行成失败
start transaction;
sql语句
commit;-- 提交,表示sql语句全部执行成功
事务的特性(面试常问!!!)
所谓事务:就是将多个需要执行的sql语句,包装成一个整体,要么这个整体都执行完,要么都不执行(不是真的不执行,而是还原成出现问题之前的状态,这样的机制也叫回滚(rollback))
1、原子性:上述的包装成的整体也可以称为一个原子,表示这个整体不能拆分了,这样的特征叫原子性
2、一致性:事务执行前和执行后,数据库中的数据都是合法状态,不会出现非法的临时结果状态(比如转账,不能出现:A转账给B,A的账户余额少了,而B账户余额没有增加)
3、持久性:事务执行完毕后,会修改硬盘上的数据,事务都是持久生效的
4、隔离性:隔离性描述的是:多个事务并发执行时,相互之间的影响是怎样的。什么是并发?MySQL是客户端–服务器结构的程序,一个服务器,可能会给多个客户端同时提供服务。所以,如果这些同时执行的事务恰好也是针对同一个表,进行增删改查就会出现下面问题
- 脏读:有两个事务:A、B,其中A在针对某个表中的数据进行修改,A执行过程的同时,B也去读取这个表中的数据,B读取完之后,A则把表中的数据改成别的了,导致B读取到的不是最终的正确的数据,而是临时性的脏数据(旧的数据)
解决办法:约定好:A在修改数据时,B不能读取数据 --> 给写加锁
- 不可重复读:有三个事务A、B、C,首先事务A执行一个修改操作,A执行完毕提交数据,接下来事务B执行,事务B读取A提交的数据,B读取的过程中,又来了一个事务C,C对A修改过的数据再次修改,对B来说后续再读取的数据和之前读取的数据不一样了这个过程就叫不可重复读(一个事务多次读取的结果不一样,叫不可重复读)举个例子:A同学写了代码,将代码提交到github上了,B同学想要学习一下A同学的代码,于是在github上看A同学写的代码,在B同学看的过程中,A同学发现自己写的代码有问题,就修改了一下,重新提交,此时B同学的视角为:代码看着看着就变了
解决办法:约定好:一个事务在读取数据的时候,其他事务不能修改正在读取的数据 --> 给读加锁
- 幻读:事务A正在读取数据,读的过程中,另一个事务B新增/删除了一些其他数据(不是A读取的数据)对于A来说多次读取的数据输入内容是一样的,但是结果集合不一样
解决办法:约定:只要有事务在读取数据,其他事务就不要干任何操作 --> 串行化,也就是说多个客户端同时提交多个事务给服务器,服务器是一个一个执行事务,而不是同时执行
隔离性描述的是针对上述问题如何解决的,在mysql中提供了4个隔离级别,可以通过设置配置文件来设置当前服务器的隔离级别
- read uncommited 读取未提交:一个事务可以读取另一个事务为提交的数据,此时会产生脏读、不可重复读、幻读,并发程度高,速度快
- read commited 读已提交:一个事务只能读取另一个事务提交之后的数据(给写操作加锁了)解决了脏读问题
- repeatable read 可重复读:给写操作和读操作都加锁,,解决了脏读和不可重复读(mysql默认的级别)
- seriallizable可串行化:所有事务在服务器上都是一个接一个执行的,并发程度最低,执行速度最慢,隔离性最高,数据最准确
9. MySQL与JDBC
实际工作中,很少直接使用命令行/图形化界面 的客户端来操作数据库,更多的是使用C++、Java、Python等语言来操作数据库。数据库提供了API供我们使用代码来操作数据库。数据库有很多种Oracle、MySQL、SQL server这些数据库提供的API是不一样的,于是Java自身制定了一套标准,要求这些数据库都要把自己的API转换成和兼容Java的API,而JDBC就是由Java制定的针对数据库操作的API。
MySQL本身的API是C语言的API,把C语言的API转换成兼容JDBC的Java的API需要MySQL官方提供的mysql-connector,如何下载mysql-connector?
下载方法:去Maven中央仓库Maven中央仓库
输入mysql,点击Search
我们可以看到有两个选项,第一个选项的版本比较新,第二个选项是老的经典的版本
选择对应版本(根据自己的mysql的版本来选择,如果mysql是5系列的版本,那么connector也必须是5系列的只要是5开头的就可以)
我们随便选一个版本,点击jar按钮即可下载
把下载好的connector导入到项目中,才能编写我们的代码来进行数据库的操作
随便取个名字
将下载好的jar文件复制到刚才创建好的lib路径
直接选中lib然后Ctrl+v粘贴,点击OK
复制好之后还有一步,右键刚才的lib,点击Add as Library(告诉lib是一个库目录)
点击OK
添加完成之后的效果是这样的
以上方法是简单粗暴的方法导入jar包,实际开发并不会这样操作,如何操作,后续再介绍~
接下来,就可以开始写java代码了
JDBC使用步骤
-
- 准备工作(创建数据源对象,描述了数据从哪里来,数据库服务器所在位置)
方法1:利用了向上转型和向下转型
DataSource dataSource = new MysqlDataSource();//MysqlDataSource是刚才导入的.jar包里面的 ((MysqlDataSource)dataSource).setURL("jdbc:mysql://127.0.0.1:3306/jdbctest?characterEncoding=utf8&useSSL=false");//数据源,要访问的数据库在哪里 ((MysqlDataSource)dataSource).setUser("root");//访问数据库的账户 ((MysqlDataSource)dataSource).setPassword("1234");//访问数据库的密码,这里假设密码是1234
上述字符串
"jdbc:mysql://127.0.0.1:3306/jdbctest?characterEncoding=utf8&useSSL=false"中,127.0.0.1是一个特殊的IP地址,表示本机的地址,因为我们用的MySQL服务器基本上都是在自己的电脑上,如果服务器在其他地方,填对应的IP地址即可,3306表示指的是端口号,用于区分一个主机上的应用程序方法2:
MysqlDataSource dataSource = new MysqlDataSource(); dataSource.setURL("jdbc:mysql://127.0.0.1:3306/jdbctest?characterEncoding=utf8&useSSL=false"); dataSource1.setUser("root"); dataSource1.setPassword("1234");
-
- 和数据库服务器建立网络连接
Connection connection = dataSource.getConnection();
-
- 构造SQL语句
String sql = "insert into student values(?,?)";//变量sql表示要执行的sql语句,问号表示占位符 PreparedStatement statement = connection.prepareStatement(sql);//
-
- 处理结果
int n = statement.executeUpdate(); //处理的是更新操作相关的语句(包括create,delete,update等除查询外的操作),返回值n表示几行受到了影响
ResultSet resultSet = statement.executeQuery(); //处理查询操作相关的SQL,返回值是ResultSet,可以理解为数据集合
-
- 释放资源
statement.close(); connection.close(); resultSet.close();//如果处理了查询操作相关的SQL,需要加上这句,如果没有则不加
例如:
public static void main(String[] args) throws SQLException {
Scanner scanner = new Scanner(System.in);
//1.创建数据源对象
MysqlDataSource dataSource = new MysqlDataSource();
dataSource.setURL("jdbc:mysql://127.0.0.1:3306/jdbctest?characterEncoding=utf8&useSSL=false");
//字符串的内容表示("jdbc:mysql://数据库服务器的IP地址/数据库名?characterEncoding=utf8&useSSL=false");
dataSource.setUser("root");
dataSource.setPassword("111111");
//2.和数据库建立网络连接
Connection connection = dataSource.getConnection();
//3.构造SQL语句
String sql = "insert into student values(?,?)";//问号表示占位符,
PreparedStatement statement = connection.prepareStatement(sql);
int id = scanner.nextInt();
String name = scanner.next();
//设置占位符的相关值也就是设置上述?号的值
statement.setInt(1, id);
statement.setString(2, name);
// 4. 把 SQL 发送到服务器上进行执行
int n = statement.executeUpdate();
System.out.println(n + "行受到影响");
//5.关闭资源
statement.close();
connection.close();
}
实用案例:
在jdbctest数据库中有学生表student,初始状态表中没有数据
使用代码,通过控制台输入数据,并插入到student表中
public static void main(String[] args) throws SQLException {
// 1. 创建数据源
MysqlDataSource dataSource = new MysqlDataSource();
dataSource.setUrl("jdbc:mysql://127.0.0.1:3306/jdbctest?characterEncoding=utf8&useSSL=false");
dataSource.setUser("root");//账号
dataSource.setPassword("111111");//密码
// 2. 建立连接
Connection connection = dataSource.getConnection();
// 3. 构建 sql
String sql = "insert into student values(?,?)";
PreparedStatement statement = connection.prepareStatement(sql);
//3.1 在控制台输入要插入的值
Scanner scanner = new Scanner(System.in);
System.out.println("请输入学生id");
int id = scanner.nextInt();
System.out.println("请输入学生姓名");
String name = scanner.next();
statement.setInt(1, id);//1表示下标,从1开始!!,id表示列
statement.setString(2, name);
// 4. 执行 sql
int n = statement.executeUpdate();
// 5. 关闭对应的资源
statement.close();
connection.close();
}
运行程序,分别输入1,张三
再次查询学生表
可以看见学生表中多了一条记录,
另外还可以使用java代码进行查询语句的操作
如:
初始数据:
使用java代码查询
public static void main(String[] args) throws SQLException {
// 1. 创建数据源
MysqlDataSource dataSource = new MysqlDataSource();
dataSource.setUrl("jdbc:mysql://127.0.0.1:3306/jdbctest?characterEncoding=utf8&useSSL=false");
dataSource.setUser("root");//账号
dataSource.setPassword("111111");//密码
// 2. 建立连接
Connection connection = dataSource.getConnection();
// 3. 构建 sql
String sql = "select * from student";
PreparedStatement statement = connection.prepareStatement(sql);
// 4. 执行 sql
ResultSet resultSet = statement.executeQuery();//返回值是ResultSet,可以理解为数据集合
// 5. 遍历结果集合
while (resultSet.next()) {
// 循环里针对一行进行处理
System.out.println(resultSet.getInt("id") + "\t" + resultSet.getString("name"));
}
// 6. 关闭对应的资源
resultSet.close();
statement.close();
connection.close();
}
输出结果:
10. 完结撒花
关于MySQL的内容就介绍到这了,以上内容能应付大部分的面试和笔试,感谢大家支持~~
开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!
更多推荐
所有评论(0)