MySQL学习笔记
1.操作数据库
1.1数据库的列类型
数值:
-
tinyint 1个字节
-
smallint 2个字节
-
mediumint 3个字节
-
int 4个字节
-
bigint 8个字节
-
float 浮点数 4个字节
-
double 浮点数 8个字节
-
decimal 字符串形式的浮点数,金融计算是用到
字符串:
-
char 字符串固定大小 0-255
-
varchar 可变字符串 0-65535
-
tinytext 微型文本 0-255
-
text 文本串 0-65535
时间日期
-
date YYYY-MM-DD 日期格式
-
time HH: mm: ss 时间格式
-
datetime YYYY-MM-DD HH: mm: ss 常用的时间格式
-
timestamp 时间戳
-
year 年份
1.2数据库的字段属性
Unsigned:无符号整数,声明该列不能为负数.
zerofill:0填充,不足的位数使用0填充.
Auto_Increment自增长:自动在上一条记录基础上加1
拓展
-- 每一个表都应该存在以下五个字段
id 主键
varsion 乐观锁
is_delete 伪删除
gmt_create 创建时间
gmt_update 修改时间
1.3创建数据库表
CREATE TABLE IF NOT EXISTS `student`(
`id` INT(4) NOT NULL AUTO_INCREMENT COMMENT '学生id',
`name` VARCHAR(20) NOT NULL DEFAULT '匿名' COMMENT '学生名字',
`pwd` VARCHAR(20) NOT NULL DEFAULT '123456' COMMENT '密码',
`sex` VARCHAR(2) NOT NULL DEFAULT '男' COMMENT '性别',
`birthday` DATETIME DEFAULT NULL COMMENT '出生日期',
PRIMARY KEY (`id`)-- 主键
)ENGINE=INNODB DEFAULT CHARSET=utf8 -- 引擎和字符集编码
常用查询创建语句:
SHOW CREATE DATABASE kuang; -- 查看创建数据库的语句
SHOW CREATE TABLE student; -- 查看student数据表的定义语句
DESC student; -- 查看表结构
1.4数据库的类型
数据库引擎:
INNODB 默认使用
MYISAM 早些年使用5.5之前
MYISAM | INNODB | |
---|---|---|
事务支持 | 不支持 | 支持 |
数据行锁定 | 不支持 | 支持 |
外键约束 | 不支持 | 支持 |
全文索引 | 支持 | 不支持 |
表空间的大小 | 较小 | 较大,约为2倍 |
-
MYISAM 节约空间,速度较快
-
MYISAM 对应文件
.frm 表结构的定义文件
.MYD 数据文件(data)
.MYI 索引文件(index)
-
INNODB 安全性高,事务的处理,多表多用户操作
-
INNODB 在只有一个.frm文件,以及上级目录下的ibdata1文件
1.5修改删除表
修改
-- 修改表名,ALTER TABLE 旧表名 RENAME AS 新表名;
ALTER TABLE student RENAME AS student1;
-- 添加表字段,ALTER TABLE 表名 ADD 字段名 属性;
ALTER TABLE student ADD age INT(3);
-- 修改表的字段 (重命名,修改约束)
ALTER TABLE student MODIFY age VARCHAR(11);-- 修改约束
ALTER TABLE student CHANGE age age1 INT(3);-- 既能重命名也能修改约束
-- 删除字段
ALTER TABLE student DROP age1;
删除表:
-- 删除表
DROP TABLE IF EXISTS teacher;-- 如果存在
2.数据管理
2.1 外键
创建时添加外键:
key 外键名称
(添加外键的字段名
)
CONSTRAINT 外键名
FOREIGN KEY (添加外键的字段名
) REFERENCES 被引用的表名
(被引用的字段名
)
-- 定义外键key
-- key `外键名称` (`添加外键的字段名`)
-- 给这个外键添加约束(执行引用) references 引用
-- CONSTRAINT `外键名` FOREIGN KEY (`添加外键的字段名`) REFERENCES `被引用的表名`(`被引用的字段名`)
CREATE TABLE `grade`(
`grade_id` INT(4) NOT NULL AUTO_INCREMENT COMMENT '年级id',
`grade_name` VARCHAR(20) NOT NULL DEFAULT '一年级' COMMENT '年级名字',
PRIMARY KEY (`grade_id`)
)ENGINE=INNODB DEFAULT CHARSET=utf8
CREATE TABLE `student1` (
`id` INT(4) NOT NULL AUTO_INCREMENT COMMENT '学生id',
`name` VARCHAR(20) NOT NULL DEFAULT '匿名' COMMENT '学生名字',
`pwd` VARCHAR(20) NOT NULL DEFAULT '123456' COMMENT '密码',
`sex` VARCHAR(2) NOT NULL DEFAULT '男' COMMENT '性别',
`birthday` DATETIME DEFAULT NULL COMMENT '出生日期',
`grade_id` INT(4) NOT NULL COMMENT '年级id',
PRIMARY KEY (`id`),
KEY `FK_gradeid` (`grade_id`),
CONSTRAINT `FK_gradeid` FOREIGN KEY (`grade_id`) REFERENCES `grade`(`grade_id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8
删除表示要先删除引用表,再删除被引用表
已经创建表后再添加外键:
ALTER TABLE 表名
ADD CONSTRAINT 外键名
FOREIGN KEY (添加外键的字段名
) REFERENCES 被引用的表名
(被引用的字段名
);
CREATE TABLE `grade`(
`grade_id` INT(4) NOT NULL AUTO_INCREMENT COMMENT '年级id',
`grade_name` VARCHAR(20) NOT NULL DEFAULT '一年级' COMMENT '年级名字',
PRIMARY KEY (`grade_id`)
)ENGINE=INNODB DEFAULT CHARSET=utf8
CREATE TABLE `student1` (
`id` INT(4) NOT NULL AUTO_INCREMENT COMMENT '学生id',
`name` VARCHAR(20) NOT NULL DEFAULT '匿名' COMMENT '学生名字',
`pwd` VARCHAR(20) NOT NULL DEFAULT '123456' COMMENT '密码',
`sex` VARCHAR(2) NOT NULL DEFAULT '男' COMMENT '性别',
`birthday` DATETIME DEFAULT NULL COMMENT '出生日期',
`grade_id` INT(4) NOT NULL COMMENT '年级id',
PRIMARY KEY (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8
-- ALTER TABLE `表名` ADD CONSTRAINT `外键名` FOREIGN KEY (`添加外键的字段名`) REFERENCES `被引用的表名`(`被引用的字段名`);
ALTER TABLE `student1`
ADD CONSTRAINT `FK_gradeid` FOREIGN KEY (`grade_id`) REFERENCES `grade`(`grade_id`);
2.2 DML(数据操作语言)
数据库意义:数据存储,数据管理
DML语言:数据操作语言
-
insert
-
update
-
delete
2.21添加
-
insert into 表名(字段名1,字段名2,....) values('值1','值2',...)字段名和值要一一对应
-- 插入数据 insert into 表名(字段名1,字段名2,....) values('值1','值2',...) 字段名和值要一一对应
INSERT INTO `grade` (`grade_name`) VALUES('大一');
-- 不写字段名则自动匹配
INSERT INTO `grade` VALUES(2,'大二');
-- 插入多个值 INSERT INTO 表名 (字段名1,字段名2) VALUES(值1,值2),(值1,值2),(值1.值2)....;
INSERT INTO `grade` (`grade_name`) VALUES('大三'),('大四');
INSERT INTO `grade` (`grade_id`,`grade_name`) VALUES(5,'大五'),(6,'大六');
2.22修改
-
update 表名 set 字段名1=值1 where 作为条件的字段名=值;
UPDATE `grade` SET `grade_name`='研一' WHERE `grade_id`=5;
UPDATE `grade` SET `grade_name`='研二' WHERE `grade_name`='大六';
-
修改多个值 update 表名 set 字段名1=值1,字段名2=值2,字段名3-值3 where 作为条件的字段名 = 值;
UPDATE `grade` SET `grade_id`=7,`grade_name`='研三' WHERE `grade_name`='研二';
-
不指定条件会改变整个表的所有的数据!(不建议使用)
-
操作符
操作符 | 含义 |
---|---|
= | 等于 |
<>或!= | 不等于 |
> | 大于 |
< | 小于 |
<= | 小于等于 |
>= | 大于等于 |
BETWEEN ... AND ... | 在某个范围 |
AND && | 同时为真,结果才为真 |
OR || | 一个为真,结果为真 |
Not ! | 非 |
2.23删除
-
delete from 表名 where 条件
DELETE FROM `grade` WHERE `grade_id`=7;
-
不写条件则删除所有数据(不建议)
-
清空表数据 TRUNCATE 表名
TRUNCATE `grade`;
DELETE 和 TRUNCATE 区别:
-
相同点: 都能删除数据且不会删除表结构
-
不同:
-
TRUNCATE 重新设置自增长 计数器会归零
-
TRUNCATE 不会影响事务
-
DELETE 自增长不变
-
DELETE 会影响事务
-
-
DELETE 删除重启数据库现象:
-
InnoDB 自增长会从1开始 (存在内存当中的,断电即失)
-
MyISAM 继续从上一个自增长开始 (存在文件中的,不会丢失)
-
3. DQL(Data Query LANGUAGE)查询数据
3.1 简单查询
-- 查询全部信息
SELECT * FROM `grade`;
-- 查询指定字段
SELECT `grade_id`,`grade_name` FROM `grade`;
-- 别名,给结果字段另起一个名字 AS
SELECT `grade_id` AS 年级编号,`grade_name` AS 年级名字 FROM `grade`;
-- 函数 Concat(a,b)拼接字符串
SELECT CONCAT('年级: ',`grade_name`) AS 新名字 FROM `grade`;-- 结果 年级: 大一
去重 DISTINCT
-- 去掉查询出来的结构中重复的数据,重复的数据只显示一条
SELECT DISTINCT `grade_name` FROM `grade`;
模糊查询
like 结合 %(代表任意字符) _(代表一个字符)
-- 查询名称包含'一'的数据
SELECT * FROM `grade` WHERE `grade_name` LIKE '%一%';
-- 查询名称以'大'开头的数据的数据
SELECT * FROM `grade` WHERE `grade_name` LIKE '大%';
-- 查询名称以'一'结尾的数据
SELECT * FROM `grade` WHERE `grade_name` LIKE '%一';
-- 查询名称'大'后面只有一个字符的数据
SELECT * FROM `grade` WHERE `grade_name` LIKE '大_';
in查询 in里面的值是一个具体的值,不能使用%
-- 查询id在(1,2,3)中的数据,即id=1或id=2或id=3的数据
SELECT * FROM `grade` WHERE `grade_id` IN (1,2,3);
还有is null和in not null关键字,为空和不为空
3.2 联表查询
join on查询: select 表名.字段名,... from 表1 xxx join 表2 on 条件
-- inner join 只返回两个表所匹配的数据
SELECT s.`id`,s.`name`,r.`score` FROM `student1` AS s INNER JOIN `result` AS r ON s.`id`=r.`id`;
-- left join 返回左表的全部数据,如果右表不匹配的则为null,即不匹配也显示出来
SELECT s.`id`,s.`name`,r.`score` FROM `student1` AS s LEFT JOIN `result` AS r ON s.`id`=r.`id`;
-- right join 返回右表的全部数据,如果左表不匹配的则为null,即不匹配也显示出来
SELECT s.`id`,s.`name`,r.`score` FROM `student1` AS s RIGHT JOIN `result` AS r ON s.`id`=r.`id`;
-- 查询没有成绩的学生
SELECT s.`id`,s.`name`,r.`score` FROM `student1` AS s LEFT JOIN `result` AS r ON s.`id`=r.`id` WHERE score IS NULL;
操作 | 描述 |
---|---|
inner join | 只返回两个表条件匹配的数据 |
left join | 返回左表的全部数据,如果右表不匹配的则为null,即条件不匹配也显示出来 |
right join | 返回右表的全部数据,如果左表不匹配的则为null,即条件不匹配也显示出来 |
3.3 分页和排序
-
升序ASC(默认)
-
降序DESC
-- 默认升序
SELECT * FROM result ORDER BY score;
-- 降序
SELECT * FROM result ORDER BY score DESC;
-
limit 数据条数 (limit 2)显示5条数据
-
limit 起始值,数据条数 (limit 0,5)从第1条开始显示5条数据,即1到5条
-- 显示2条数据
SELECT * FROM result LIMIT 2;
-- 从第2条数据开始显示5条数据,即2到6
SELECT * FROM result LIMIT 1,5;
-
分页
-
第一页 limit 0,5 (1-1)*5
-
第二页 limit 5,5 (2-1)*5
-
第三页 limit 10,5 (3-1)*5
-
第N页 limit (N-1)*S,5
-
页面大小 S
-
当前页 N
-
起始值 (N-1)*S
-
总页数 数据总数/页面大小 向上取整 CEILING()
-
3.4 分组和过滤
GROUP BY 需要分组的字段名 HAVING 条件
-- 查询年级平均分大于等于80的年级名字和平均分
SELECT `grade_name`,AVG(`score`) FROM `grade` AS g
INNER JOIN `student1` AS s ON s.`grade_id`=g.`grade_id`
INNER JOIN `result` AS r ON s.`id`=r.`id`
GROUP BY g.`grade_name`
HAVING AVG(`score`)>=80;
查询顺序:
select 去重 要查询的字段 from 表
xxx join 要连接的表 on 等值判断
where 条件
group by 分组的字段名
having 条件
order by 需要排序的字段 DESC(降序) ASC(默认升序)
limit 起始值,数据条数
3.5 子查询
在where语句中嵌套一个子查询
-- 查询年级为大一的学生,先从grade表中查询名字为'大一'的grade_id再等于student1的grade_id
SELECT * FROM student1 WHERE `grade_id`=(SELECT `grade_id` FROM `grade` WHERE `grade_name`='大一');
子查询和联合查询可以互换
-- 等同于上面的子查询
SELECT s.* FROM student1 AS s LEFT JOIN `grade` AS g ON s.`grade_id`=g.`grade_id` WHERE `grade_name`='大一';
4. MYSQL函数
4.1 常用函数
-- 数学运算
SELECT ABS(-2); -- 绝对值
SELECT CEILING(2.3); -- 向上取整
SELECT FLOOR(2.3); -- 向下取整
SELECT RAND(); -- 返回一个0-1之间的随机数
SELECT SIGN(-4); -- 判断一个数的正负 正数返回1 负数返回-1
-- 字符串函数
SELECT CHAR_LENGTH('查询字符串的长度'); -- 查询字符串的长度,返回结果为8
SELECT CONCAT('拼','接','字符串'); -- 拼接字符串
SELECT INSERT('hello,world',1,5,'HELLO'); -- 返回结果为HELLO,world,从第一个字符开始将后面5个字符替换成新的字符
SELECT LOWER('HELLO,world'); -- 转为小写字母
SELECT UPPER('HELLO,world'); -- 转为大写写字母
SELECT INSTR('HELLO,world','L'); -- 返回第一次出现的索引3
SELECT REPLACE('HELLO,world','world','WORLD'); -- 替换相应的字符,将world替换成WORLD
SELECT SUBSTR('HELLO,world',7,5); -- 截取字符串,截取从第7个字符开始往后5个字符
SELECT REVERSE('HELLO,world'); -- 反转字符串,结果dlrow,OLLEH
-- 时间和日期函数
SELECT CURRENT_DATE(); -- 获取当前日期 2021-06-22
SELECT CURDATE(); -- 获取当前日期 2021-06-22
SELECT NOW(); -- 获取当前时间日期 2021-06-22 09:30:18
SELECT LOCALTIME(); -- 本地时间 2021-06-22 09:31:13
SELECT SYSDATE(); -- 系统时间 2021-06-22 09:31:35
SELECT YEAR(NOW()); -- 年
SELECT MONTH(NOW()); -- 月
SELECT DAY(NOW()); -- 日
SELECT HOUR(NOW()); -- 时
SELECT MINUTE(NOW()); -- 分
SELECT SECOND(NOW()); -- 秒
-- 系统
SELECT SYSTEM_USER(); -- root@localhost
SELECT USER(); -- root@localhost
SELECT VERSION(); -- 5.6.31-log
4.2 聚合函数
函数名称 | 描述 |
---|---|
count() | 计数 |
sum() | 求和 |
avg() | 平均值 |
max() | 最大值 |
min() | 最小值 |
SELECT COUNT(`grade_id`) FROM `grade`; -- count(字段) 会忽略null
SELECT COUNT(*) FROM `grade`; -- count(*) 都是统计记录总数,但是这个不会忽略null
SELECT SUM(`score`) FROM `result`;-- 求总和
SELECT AVG(`score`) FROM `result`;-- 平均值
SELECT MAX(`score`) FROM `result`;-- 最大值
SELECT MIN(`score`) FROM `result`;-- 最小值
4.3 数据库级别的MD5加密
-
MD5('需要加密的内容')
-
MD5主要增强算法的复杂度和不可逆性.
-- 创建测试表
CREATE TABLE `testmd5`(
`id` INT(4) NOT NULL,
`name` VARCHAR(20) NOT NULL,
`pwd` VARCHAR(50) NOT NULL,
PRIMARY KEY(`id`)
)ENGINE=INNODB DEFAULT CHARSET=utf8
-- 插入数据,明文密码
INSERT INTO `testmd5` VALUES(1,'Lance','123456'),(2,'Lee','123456'),(3,'L','123456');
-- MD5加密
UPDATE `testmd5` SET `pwd`=MD5(`pwd`);
-- 插入时加密
INSERT INTO `testmd5` VALUES(4,'LLL',MD5('123456'));
-- 如何校验: 将用户传递过来的密码先进行MD5加密后,再进行比对
SELECT * FROM `testmd5` WHERE `name`='Lee' AND `pwd`=MD5('123456');
5. 事务
5.1 事务管理(ACID)
博客参考链接:事务ACID理解_dengjili的专栏-优快云博客_acid
-
事务原则 ACID原则
-
原子性
-
一致性
-
隔离性
-
持久性
-
-
原子性(Atomicity)
要么都成功,要么都失败
-
一致性(Consistency)
事务前后的数据完整性要保持一致
-
持久性(Durability)
事务一旦提交则不可逆,被持久化到数据库中
-
隔离性(Isolation)
事务的隔离性是多个用户并发访问数据库是,数据库为每个用户开启的事务,不能被其他事务的操作数据所干扰,多个并发事务之间要相互隔离.
隔离所导致的问题
-
脏读
指一个事务读取了另一个事务未提交的数据
-
不可重复读
在一个事务内读取表中的某一行数据,多次读取的结果不同.(这个不一定是错误,只是某些场合不对)
-
虚读(幻读)
是指在一个事务内读取到了别的事务插入的数据,导致前后读取不一致.
5.2 事务的执行
执行流程:
-- mysql 是默认开启事务自动提交的
-- 手动处理事务
SET autocommit =0; -- 关闭自动提交
-- 事务开启
START TRANSACTION -- 标记一个事务的开启
INSERT xxx -- 插入数据
INSERT xxx
-- 提交: 持久化
COMMIT
-- 回滚: 回到事务开始是的数据
ROLLBACK
SAVEPOINT -- 保存点 (设置一个事务的保存点,可以通过回滚恢复到这里的数据)
ROLLBACK TO SAVEPOINT -- 回到保存点
RELEASE SAVEPOINT -- 删除保存点
-- 事务结束
SET autocommit =1; -- 开启自动提交
实例
CREATE DATABASE shop CHARACTER SET utf8 COLLATE utf8_general_ci;
USE shop;
CREATE TABLE `account`(
`id` INT(3) NOT NULL AUTO_INCREMENT,
`name` VARCHAR(30) NOT NULL,
`money` DECIMAL(9,2) NOT NULL,
PRIMARY KEY (`id`)
)ENGINE=INNODB DEFAULT CHARSET=utf8
INSERT INTO account (`name`,`money`) VALUES('A',2000.00),('B',10000.00);
-- 转账:事务
SET autocommit =0; -- 关闭自动提交
START TRANSACTION -- 开启事务
UPDATE `account` SET `money`=`money`-500 WHERE `name`='A'; -- A转500
UPDATE `account` SET `money`=`money`+500 WHERE `name`='B'; -- B收500
-- 可以选择回滚,也可以选择提交,提交后就无法回滚了.
COMMIT; -- 提交事务,被持久化了 A1500 B10500
ROLLBACK; -- 回滚 A2000 B10000
-- 事务结束
SET autocommit =1; -- 开启自动提交
6. 索引
MySQL官方对索引的定义为: 索引 (index) 是帮助MySQL高效获取数据的数据结构.
提取句子主干,就可以得到索引的本质:索引是数据结构.
6.1 索引的分类
-
主键索引 (PRIMARY KEY)
-
唯一的标识,主键不可重复,只能有一个列作为主键
-
-
唯一索引 (UNIQUE KEY)
-
避免重复的列出现,唯一索引可以重复,多个列都可以作为唯一索引
-
-
常规索引 (KEY / INDEX)
-
默认的,index,key关键字开设置
-
-
全文索引 (FullText)
-
在特定的数据库引擎下才有,MyISAM
-
快速定位数据
-
6.2 索引的创建和使用
-- 索引的创建
-- 1.在创建表的时候给字段添加索引 索引类型 索引名(索引字段)
UNIQUE KEY `name_unique`(`name`)
-- 2.创建完毕后,添加索引 ALTER TABLE 表名 ADD 索引类型 索引名(索引字段)
ALTER TABLE `student1` ADD UNIQUE KEY `name_unique`(`name`);
-- 3.创建完毕后,添加索引 create 索引类型 索引名 on 表(字段)
CREATE INDEX Id_app_user_name ON app_user(`name`);
-- 显示所有的索引信息
SHOW INDEX FROM `student`;
-- EXPLAIN 分析sql执行的状况
EXPLAIN SELECT * FROM `student1`;
测试:
CREATE TABLE `app_user` (
`id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
`name` VARCHAR(50) DEFAULT '',
`eamil` VARCHAR(50) NOT NULL,
`phone` VARCHAR(20) DEFAULT '',
`gender` TINYINT(4) UNSIGNED DEFAULT '0',
`password` VARCHAR(100) NOT NULL DEFAULT '',
`age` TINYINT(4) DEFAULT NULL,
`create_time` DATETIME DEFAULT CURRENT_TIMESTAMP,
`update_time` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8;
-- 插入1000000条数据
SET GLOBAL log_bin_trust_function_creators=1; -- 开启创建函数功能
/*
第一个语句 delimiter 将 mysql 解释器命令行的结束符由”;” 改成了”$$”,
让存储过程内的命令遇到”;” 不执行
*/
DELIMITER $$
CREATE FUNCTION mock_data()
RETURNS INT
BEGIN
DECLARE num INT DEFAULT 1000000;
DECLARE i INT DEFAULT 0;
WHILE i
索引在小数据时不明显,在大数据时就很明显.
6.3 索引原则
-
索引不是越多越好
-
不要对经常变动数据加索引
-
小数据的表不需要加索引
-
索引一般加在常用来查询的字段上
7. 权限管理和备份
7.1 用户管理
用户表: mysql/user
sql命令:
-- 创建用户 CREATE USER 用户名 IDENTIFIED BY '密码';
CREATE USER Lance IDENTIFIED BY '123456';
-- 修改密码 (修改当前用户密码)
SET PASSWORD = PASSWORD('123456');
-- 修改密码 (修改制定用户密码)
SET PASSWORD FOR Lance = PASSWORD('111111');
-- 重命名 RENAME USER 用户名 TO 新用户名;
RENAME USER Lance TO Lance2;
-- 用户授权 ALL PRIVILEGES全部权限,除了给别人授权,其他都行
GRANT ALL PRIVILEGES ON *.* TO Lance2;
-- 查看权限
SHOW GRANTS FOR Lance2; -- 查看制定用户的权限
SHOW GRANTS FOR root@localhost; -- 查看root的权限
-- 撤销权限 REVOKE
REVOKE ALL PRIVILEGES ON *.* FROM Lance2;
-- 删除用户
DROP USER Lance2;
7.2 MySQL备份
-
保证重要的数据不丢失
-
数据转移
使用命令行导出 mysqldump 命令行使用
# mysqldump -h 主机 -u 用户名 -p 密码 数据库 表1 表2 > 物理磁盘位置/文件名
mysqldump -hlocalhost -uroot -p123456 school student result >D:/a.sql
# mysqldump -h 主机 -u 用户名 -p 密码 数据库 > 物理磁盘位置/文件名
mysqldump -hlocalhost -uroot -p123456 school >D:/a.sql
#导入
# 登录的情况下,切换到指定的数据库
# source 备份文件
source D:/a.sql
# 不登录情况(不建议使用)
# mysql -u用户名 -p密码 库名< 备份文件
8. 规范数据库设计
8.1 为什么需要设计
当数据库比较复杂的时候就需要设计
糟糕的数据库设计:
-
数据冗余,浪费空间
-
数据库插入和删除都会麻烦,异常[避免使用物理外键]
-
程序的性能差
良好的数据库设计:
-
节省内存空间
-
保证数据库的完整性
-
方便我们开发系统
软件开发中,关于数据库的设计
-
分析需求:分析业务和需要处理的数据库需求
-
概要设计:设计关系图E-R图
设计数据库的步骤:(个人博客)
-
收集信息,分析需求
-
用户表(id,用户名,密码,性别,年龄,签名)
-
分类表 (id,分类标题)
-
文章表(id,标题,作者id,分类,内容,创建时间,修改时间)
-
评论表(id,所属文章,评论人id,评论内容,评论时间,回复人id)
-
友链表 (id,网站名称,网站链接,排序)
-
关注表(id,被关注的id,关注人的id)
-
自定义表 (系统信息,某个关键的字,或者一些主字段) key: value
-
-
标识实体(把需求落地到每个字段) 创建表
-
标识实体之间的关系
-
写博客: user --> blog
-
创建分类: user --> category
-
关注: user --> user
-
友链: links
-
评论: user-user-blog
-
8.2 三大范式
为什么需要数据规范化?
-
信息重复
-
更新异常
-
插入异常
-
无法正常显示信息
-
-
删除异常
-
丢失有效的信息
-
三大范式
-
第一范式(1NF)
-
原子性:保证每一列都不可再分
-
-
第二范式(2NF)
-
前提满足第一范式
-
每张表只描述一件事情
-
-
第三范式(13NF)
-
前提满足第一,第二范式
-
需要确保数据表中的每一列都和主键直接相关,而不能间接相关
-
规范性 和 性能的问题
关联查询的表不得超过三张表
-
考虑商业化的需求和目标,(成本,用户体验) 数据库的性能更加重要
-
在规范性能的问题的时候,需要适当的考虑一下规范性
-
故意给某些表增加一些冗余的字段.(从多表查询变为单表查询)
-
故意增加一些计算列(从大数量降低为小数据量的查询:索引)
9. JDBC
9.1、 第一个JDBC程序
mysql-connector-java-5.1.47.jar包下载链接:MySQL :: Download MySQL Connector/J (Archived Versions)
-
1.创建项目
-
2.导入数据库驱动
-
先创建一个lib目录
-
复制mysql-connector-java-5.1.47.jar包到lib目录
-
右键lib包选择Add as Library
-
-
3.编写测试代码
package JDBCTest; import java.sql.*; public class JDBC1 { public static void main(String[] args) throws ClassNotFoundException, SQLException { //1.加载驱动 Class.forName("com.mysql.jdbc.Driver");//固定写法,加载驱动 //2.用户信息和url //jdbc:mysql://localhost:3306/数据库?useUnicode=true&characterEncoding=utf8&useSSl=true String url="jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf8&useSSl=true"; String username="root";//用户名 String password="123456";//密码 //3.连接数据库 数据库对象 connection 代表数据库 Connection connection = DriverManager.getConnection(url, username, password); //4.执行SQL对象 statement 执行SQL的对象 Statement statement = connection.createStatement(); //5.执行SQL的对象去执行SQL,可能存在结果,查看返回结果 String sql="select * FROM grade"; ResultSet resultSet = statement.executeQuery(sql);//执行查询并返回结果集 while (resultSet.next()){//如果有元素则执行 System.out.println("id="+ resultSet.getObject("grade_id"));//获取grade_id字段的值 System.out.println("name="+ resultSet.getObject("grade_name"));//获取grade_name字段的值 System.out.println("=========================================="); } //6. 释放连接 resultSet.close(); statement.close(); connection.close(); } }
String sql="select * FROM grade";//编写SQL语句 statement.executeQuery(sql);//执行查询并返回结果集 statement.execute(sql);//可以执行所有的SQL语句 statement.executeUpdate(sql);//增删改 都可以用这个,返回受影响的行数
//获取数据 //不知道类型使用Object resultSet.getObject(); //知道类型则使用相应的 resultSet.getString(); resultSet.getInt(); resultSet.getFloat(); resultSet.getDate();
//遍历,指针 resultSet.next();//移动到下一个数据 resultSet.beforeFirst();//移动到最前面 resultSet.afterLast();//移动到最后面 resultSet.previous();//移动到前一行 resultSet.absolute(row);//移动到指定行
9.2 statement对象
JDBC中的statement对象用于向数据库发送SQL语句,想完成对数据库的增删改查,只需要通过这个对象向数据库发送增删改查语句即可.
statement对象的executeUpdate方法,用于向数据库发送增,删,改的SQL语句,executeupdate执行完后,将返回一个整数(受影响行数).
statement.executeQuery方法用于向数据库发送查询语句,返回结果集ResultSet对象.
使用executeUpdate(String sql)方法添加数据:
Statement st=conn.createStatement();
String sql="insert into user(...) values(...)";
int num=st.executeUpdate(sql);
if(num>0){
System.out.println("插入数据成功");
}
使用executeUpdate(String sql)方法删除数据:
Statement st=conn.createStatement();
String sql="delete from user where id = 1";
int num=st.executeUpdate(sql);
if(num>0){
System.out.println("删除数据成功");
}
使用executeUpdate(String sql)方法更新数据:
Statement st=conn.createStatement();
String sql="update user set name='a' where name ='b'";
int num=st.executeUpdate(sql);
if(num>0){
System.out.println("更新数据成功");
}
使用executeQuery(String sql)方法查询数据:
Statement st=conn.createStatement();
String sql="select * from user";
resultSet rs=st.executeQuery(sql);
while(rs.next()){
}
测试
1.创建db.properties配置文件
driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf8&useSSl=true
username=root
password=123456
2.创建工具类JdbcUtils,用于存放创建连接和释放连接的方法
package JDBCTest.utils;
import java.io.InputStream;
import java.sql.*;
import java.util.Properties;
public class JdbcUtils {
private static String driver=null;
private static String url=null;
private static String username=null;
private static String password=null;
static {
try {
//读取配置文件,获得输入流
InputStream in = JdbcUtils.class.getClassLoader().getResourceAsStream("db.properties");
Properties properties = new Properties();
properties.load(in);//从字节输入流中读取键值对
driver = properties.getProperty("driver");//通过键读取属性值
url = properties.getProperty("url");
username = properties.getProperty("username");
password = properties.getProperty("password");
//1.加载驱动,只加载一次
Class.forName(driver);
} catch (Exception e) {
e.printStackTrace();
}
}
//获取连接
public static Connection getConnection() throws SQLException {
return DriverManager.getConnection(url,username,password);//连接数据库
}
//释放资源
public static void release(Connection conn, Statement st, ResultSet rs){
if (rs!=null){
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (st!=null){
try {
st.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (conn!=null){
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
3.使用executeUpdate方法执行增,删,改操作
package JDBCTest;
import JDBCTest.utils.JdbcUtils;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
public class JdbcInsert {
public static void main(String[] args) {
Connection conn=null;
Statement st=null;
ResultSet rs=null;
try {
conn = JdbcUtils.getConnection();//获取连接
st=conn.createStatement();//获得sql的执行对象
String sql="INSERT INTO grade VALUES(6,'研二');";
int i = st.executeUpdate(sql);//执行插入操作
if (i>0){
System.out.println("插入成功");
}
} catch (SQLException e) {
e.printStackTrace();
}finally {
JdbcUtils.release(conn,st,rs);
}
}
}
package JDBCTest;
import JDBCTest.utils.JdbcUtils;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
public class JdbcDelete {
public static void main(String[] args) {
Connection conn=null;
Statement st=null;
ResultSet rs=null;
try {
conn = JdbcUtils.getConnection();//获取连接
st=conn.createStatement();//获得sql的执行对象
String sql="DELETE FROM grade WHERE `grade_id` =6;";
int i = st.executeUpdate(sql);//执行插入操作
if (i>0){
System.out.println("删除成功");
}
} catch (SQLException e) {
e.printStackTrace();
}finally {
JdbcUtils.release(conn,st,rs);
}
}
}
package JDBCTest;
import JDBCTest.utils.JdbcUtils;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
public class JdbcUpdate {
public static void main(String[] args) {
Connection conn=null;
Statement st=null;
ResultSet rs=null;
try {
conn = JdbcUtils.getConnection();//获取连接
st=conn.createStatement();//获得sql的执行对象
String sql="UPDATE grade SET `grade_name` ='研一' WHERE `grade_id`=5;";
int i = st.executeUpdate(sql);//执行插入操作
if (i>0){
System.out.println("更新成功");
}
} catch (SQLException e) {
e.printStackTrace();
}finally {
JdbcUtils.release(conn,st,rs);
}
}
}
4.使用executeQuery语句执行查询操作
package JDBCTest;
import JDBCTest.utils.JdbcUtils;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
public class JdbcSelect {
public static void main(String[] args) {
Connection conn=null;
Statement st=null;
ResultSet rs=null;
try {
conn= JdbcUtils.getConnection();//获取连接
st=conn.createStatement();//获取SQL的执行对象
String sql="SELECT * FROM grade;";
rs=st.executeQuery(sql);//执行查询,返回结果集
while (rs.next()){//如果还有下一条数据
System.out.println(rs.getInt("grade_id"));
System.out.println(rs.getString("grade_name"));
System.out.println("==============================");
}
} catch (SQLException e) {
e.printStackTrace();
}finally {
JdbcUtils.release(conn,st,rs);//释放资源
}
}
}
sql注入问题
SQL存在漏洞,会被攻击导致数据泄露,SQL会被拼接 or.
通过拼接or '1=1'可查询出所有的数据
package JDBCTest;
import JDBCTest.utils.JdbcUtils;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
public class JdbcSelect {
public static void main(String[] args) {
//login("1");正常使用
login("1 or '1=1'");//SQL注入,通过拼接or '1=1'可查询出所有的数据
}
public static void login(String id){
Connection conn=null;
Statement st=null;
ResultSet rs=null;
try {
conn= JdbcUtils.getConnection();//获取连接
st=conn.createStatement();//获取SQL的执行对象
String sql="SELECT * FROM grade where grade_id = "+id;
rs=st.executeQuery(sql);//执行查询,返回结果集
while (rs.next()){//如果还有下一条数据
System.out.println(rs.getInt("grade_id"));
System.out.println(rs.getString("grade_name"));
System.out.println("==============================");
}
} catch (SQLException e) {
e.printStackTrace();
}finally {
JdbcUtils.release(conn,st,rs);//释放资源
}
}
}
9.3 preparedStatement对象
preparedStatement 可以防止SQL注入,效率更高.
1.新增
package JdbcTest2;
import JDBCTest.utils.JdbcUtils;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class JdbcInsert2 {
public static void main(String[] args) {
Connection conn=null;
PreparedStatement st=null;
ResultSet rs=null;
try {
conn=JdbcUtils.getConnection();
//区别
//使用?占位符代替参数
String sql="INSERT INTO grade VALUES(?,?);";
st=conn.prepareStatement(sql);//预编译SQL,先写SQL,然后不执行
//手动给参数赋值
st.setInt(1,6);//给第一个占位符设置值为6
st.setString(2,"研二");//给第二个占位符设置值为研二
//执行
int i = st.executeUpdate();
if(i>0){
System.out.println("插入成功");
}
} catch (SQLException e) {
e.printStackTrace();
}finally {
JdbcUtils.release(conn,st,rs);
}
}
}
2.删除
package JdbcTest2;
import JDBCTest.utils.JdbcUtils;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class JdbcDelete2 {
public static void main(String[] args) {
Connection conn=null;
PreparedStatement st=null;
ResultSet rs=null;
try {
conn= JdbcUtils.getConnection();
//区别
//使用?占位符代替参数
String sql="DELETE FROM grade WHERE grade_id = ?;";
st=conn.prepareStatement(sql);//预编译SQL,先写SQL,然后不执行
//手动给参数赋值
st.setInt(1,6);//给第一个占位符设置值为6
//执行
int i = st.executeUpdate();
if(i>0){
System.out.println("删除成功");
}
} catch (SQLException e) {
e.printStackTrace();
}finally {
JdbcUtils.release(conn,st,rs);
}
}
}
3.更新
package JdbcTest2;
import JDBCTest.utils.JdbcUtils;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class JdbcUpdate2 {
public static void main(String[] args) {
Connection conn=null;
PreparedStatement st=null;
ResultSet rs=null;
try {
conn= JdbcUtils.getConnection();
//区别
//使用?占位符代替参数
String sql="update grade set grade_name=? where grade_id = ?";
st=conn.prepareStatement(sql);//预编译SQL,先写SQL,然后不执行
//手动给参数赋值
st.setString(1,"研二");//给第一个占位符设置值为研二
st.setInt(2,5);//给第二个占位符设置值为5
//执行
int i = st.executeUpdate();
if(i>0){
System.out.println("更新成功");
}
} catch (SQLException e) {
e.printStackTrace();
}finally {
JdbcUtils.release(conn,st,rs);
}
}
}
4.查询
package JdbcTest2;
import JDBCTest.utils.JdbcUtils;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class JdbcSelect2 {
public static void main(String[] args) {
Connection conn=null;
PreparedStatement st=null;
ResultSet rs=null;
try{
conn=JdbcUtils.getConnection();
String sql="SELECT * FROM grade where grade_id =?";//使用?占位符代替参数
st=conn.prepareStatement(sql);//预编译SQL,先写SQL,然后不执行
//手动赋值
st.setInt(1,1);//给第一个占位符设置值为1
//执行
rs = st.executeQuery();
while (rs.next()){
System.out.println(rs.getInt("grade_id"));
System.out.println(rs.getString("grade_name"));
System.out.println("====================================");
}
} catch (SQLException e) {
e.printStackTrace();
}finally {
JdbcUtils.release(conn,st,rs);
}
}
}
5.防止SQL注入
package JdbcTest2;
import JDBCTest.utils.JdbcUtils;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class Sql注入 {
public static void main(String[] args) {
//s("大一");//正常查询
s("'' or 1=1");//注入拼接or 1=1 查询结果为空
//PreparedStatement把('' or 1=1)当成一个字符串,而不再是语句,所以or不起作用
}
public static void s(String id){
Connection conn=null;
PreparedStatement st=null;
ResultSet rs=null;
try{
conn= JdbcUtils.getConnection();
//PreparedStatement防止SQL注入的本质,把传递的参数当成字符
String sql="SELECT * FROM grade where grade_name =?";//使用?占位符代替参数
st=conn.prepareStatement(sql);//预编译SQL,先写SQL,然后不执行
//手动赋值
st.setString(1,id);//给第一个占位符设置值为变量id
//执行
rs = st.executeQuery();
while (rs.next()){
System.out.println(rs.getInt("grade_id"));
System.out.println(rs.getString("grade_name"));
System.out.println("====================================");
}
} catch (SQLException e) {
e.printStackTrace();
}finally {
JdbcUtils.release(conn,st,rs);
}
}
}
9.4 事务
要么都成功,要么都失败
-
ACID原则
-
原子性:要么都成功,要么都失败.
-
一致性:结果总数不变
-
隔离性:多个进程互不干扰
-
持久性:一旦提交就不可逆,持久化到数据库了
-
-
隔离性的问题:
-
脏读:一个事务读取到了另一个没提交的事务
-
不可重复读:在同一个事务内,重复读取表中的数据,表数据发生了改变
-
虚读(幻读):在一个事务内,读取到了别人插入的数据,导致前后读出来的结果不一致
-
测试:
1.开启事务conn.setAutoCommit(false);//开启事务
2.业务执行完毕,提交事务conn.commit();
3.可以在catch语句中显示的定义回滚语句,但默认失败就会回滚
package JdbcTransaction;
import JDBCTest.utils.JdbcUtils;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class TestTransaction1 {
public static void main(String[] args) {
Connection conn=null;
PreparedStatement st=null;
ResultSet rs=null;
try{
conn=JdbcUtils.getConnection();
//关闭数据库的自动提交功能
conn.setAutoCommit(false);//开启事务
String sql1="update account set money=money-500 where name= 'A'";
st=conn.prepareStatement(sql1);
st.executeUpdate();
int i=1/0;//报错
String sql2="update account set money=money+500 where name= 'B'";
st=conn.prepareStatement(sql2);
st.executeUpdate();
//业务完毕,提交事务
conn.commit();
System.out.println("成功");
} catch (SQLException e) {
try {
conn.rollback();//如果失败则回滚事务,不写也是默认回滚
} catch (SQLException ex) {
ex.printStackTrace();
}
e.printStackTrace();
}finally {
JdbcUtils.release(conn,st,rs);
}
}
}
9.5 数据库连接池
数据库连接--执行完毕--释放
连接--释放 十分浪费系统资源
池化技术:准备一些预先的资源,过来就连接预先准备好的
最小连接数:10
最大连接数:15 业务承载上限,超过最大连接数则排队等待,
等待超时:100ms 过时就断开
编写连接池,实现一个接口 DataSource
-
开源数据源实现
-
DBCP
-
C3P0
-
Druid:阿里巴巴
-
使用了这些数据库连接池之后,我们在项目开发中就不需要编写连接数据库的代码了!
DBCP
需要用到的jar包
commons-dbcp-1.4 commons-pool-1.6
dbcpconfig.properties配置文件:
#连接设置
driverClassName=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf8&useSSL=true
username=root
password=123456
#!-- 初始化连接 --
initialSize=10
#最大连接数量
maxActive=50
#!-- 最大空闲连接 --
maxIdle=20
#!-- 最小空闲连接 --
minIdle=5
#!-- 超时等待时间以毫秒为单位 6000毫秒/1000等于60秒 --
maxWait=60000
#JDBC驱动建立连接时附带的连接属性属性的格式必须为这样:【属性名=property;】
#注意:user 与 password 两个属性会被明确地传递,因此这里不需要包含他们。
connectionProperties=useUnicode=true;characterEncoding=UTF8
#指定由连接池所创建的连接的自动提交(auto-commit)状态。
defaultAutoCommit=true
#driver default 指定由连接池所创建的连接的只读(read-only)状态。
#如果没有设置该值,则“setReadOnly”方法将不被调用。(某些驱动并不支持只读模式,如:Informix)
defaultReadOnly=
#driver default 指定由连接池所创建的连接的事务级别(TransactionIsolation)。
#可用值为下列之一:(详情可见javadoc。)NONE,READ_UNCOMMITTED, READ_COMMITTED, REPEATABLE_READ, SERIALIZABLE
defaultTransactionIsolation=READ_UNCOMMITTED
工具类JdbcUtils_DBCP
package pool.utils;
import org.apache.commons.dbcp.BasicDataSourceFactory;
import javax.sql.DataSource;
import java.io.InputStream;
import java.sql.*;
import java.util.Properties;
public class JdbcUtils_DBCP {
private static DataSource dataSource=null;
static {
try {
InputStream in = JdbcUtils_DBCP.class.getClassLoader().getResourceAsStream("dbcpconfig.properties");//读取配置文件
Properties properties = new Properties();
properties.load(in);
//创建数据源 工厂模式--> 创建对象
dataSource=BasicDataSourceFactory.createDataSource(properties);
} catch (Exception e) {
e.printStackTrace();
}
}
//获取连接
public static Connection getConnection() throws SQLException {
return dataSource.getConnection();//从数据源中获取连接
}
//释放资源
public static void release(Connection conn, Statement st, ResultSet rs){
if (rs!=null){
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (st!=null){
try {
st.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if (conn!=null){
try {
conn.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
测试TestDBCP1
package pool;
import pool.utils.JdbcUtils_DBCP;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class TestDBCP1 {
public static void main(String[] args) {
Connection conn=null;
PreparedStatement st=null;
ResultSet rs=null;
try {
conn= JdbcUtils_DBCP.getConnection();
String sql="select * from grade where grade_id=?";
st=conn.prepareStatement(sql);
st.setInt(1,1);
rs=st.executeQuery();
while (rs.next()){
System.out.println(rs.getInt("grade_id"));
System.out.println(rs.getString("grade_name"));
}
} catch (SQLException e) {
e.printStackTrace();
}finally {
JdbcUtils_DBCP.release(conn,st,rs);
}
}
}
狂神说MySQL : 【狂神说Java】MySQL最新教程通俗易懂_哔哩哔哩_bilibili