MySQL 学习笔记
操作数据库
-- 创建数据库
CREATE DATABASE IF NOT EXISTS school;
-- 删除数据库
DROP DATABASE IF EXISTS school;
-- 使用数据库(如果表名是特殊字段,需要加``)
USE school;
-- 查看数据库
SHOW DATABASES;
数据库的列类型
数值:
1. tinyint 十分小的数据 1个字节
2. smallint 较小的数据 2个字节
3. mediumint 中等大小的数据 3个字节
4. int 标准的整数 4个字节 对应java中int、Integer类型
5. bigint 较大的数据 8个字节
6. float 单精度浮点数 4个字节
7. double 双精度浮点数 8个字节
8. decimal 字符串形式的浮点数 金融计算的时候,一般用decimal
字符串:
1. char 固定大小字符串 0 - 255
2. varchar 可变字符串 0 - 65535 对应java中String类型
3. tinytext 微型文本 2 ^ 8 - 1
4. text 文本 2 ^ 16 - 1 保存大文本
时间日期:
1. date YYYY-MM-DD 日期格式
2. time HH:MM:SS 时间格式
3. datetime YYYY-MM-DD HH:MM:SS 最常用的时间格式
4. timestrap 时间戳 1970.1.1 到现在的毫秒数
5. year 年份表示
null:
1. 没有值 未知
2. 不要用NULL进行计算,结果为NULL
数据库的字段属性(重点)
1. Unsignd
无符号整数
该列不能声明为负数
2. Zerofill
0填充的
不足的位数,用0来填充
3. Auto_increment
自动在上一条记录的基础上 + 1 (默认)
通常用来设计主键,必须是整数类型
可以自定义设计主键的起始值和步长
4. NOT NULL
NOT NULL,如果不给它赋值就会报错
NULL,如果不赋值,默认为NULL
5. DEFAULT
默认的值
创建数据库表
CREATE TABLE IF NOT EXISTS `student`(
`id` INT(3) NOT NULL AUTO_INCREMENT COMMENT '学号',
`name` VARCHAR(30) NOT NULL DEFAULT '匿名' COMMENT '姓名',
`password` VARCHAR(20) NOT NULL DEFAULT '123456' COMMENT '密码',
`sex` VARCHAR(2) NOT NULL DEFAULT '女' COMMENT '性别',
`birthday` DATETIME DEFAULT NULL COMMENT '出生日期',
`address` VARCHAR(100) DEFAULT NULL COMMENT '家庭住址',
`e-mail` VARCHAR(50) DEFAULT NULL COMMENT '邮箱',
PRIMARY KEY(`id`)
)ENGINE=INNODB DEFAULT CHARSET=utf8
SHOW CREATE DATABASE school --查看创建数据库的语句
SHOW CREATE TABLE student --查看创建表的语句
DESC student --查看表的结构
数据表的类型
MYISAM | INNODB | |
---|---|---|
事务支持 | 不支持 | 支持 |
数据行锁定 | 不支持 | 支持 |
外键 | 不支持 | 支持 |
全文索引 | 支持 | 不支持 |
表空间的大小 | 较小 | 较大 |
常规使用操作:
- MYISAM 节约空间,速度较快
- INNODB 安全性高,支持事务处理,多表多用户操作
在物理空间存在的位置:所有的数据库文件都存在data目录下,本质是文件存储
MySQL引擎在物理文件上的区别:
- INNODB在数据库表中只有一个 *.frm 文件,以及上级目录 ibdata1 文件
- MYISAM对应文件:
- *.frm 文件 表结构的定义文件
- *.MYD 文件 数据文件(data)
- *.MYI 索引文件(index)
-- 设置数据库表的字符集编码
CHARSET=utf8
-- 不设置的话会是默认的Latin1,不支持中文
修改删除表
-- 修改表名
ALTER TABLE student RENAME AS student_renamed
-- 增加表的字段
ALTER TABLE student_renamed ADD age INT(3)
-- 修改表的字段
ALTER TABLE student_renamed MODIFY age VARCHAR(3) -- 修改约束
ALTER TABLE student_renamed CHANGE age age_renamed INT(3) -- 重命名
-- 删除表的字段
ALTER TABLE student_renamed DROP age_renamed
-- 删除表
DROP TABLE IF EXISTS student_renamed
MySQL数据管理
外键
方式一:创建表的时候设置外键
-- 删除有外键关系的表时,必须先删除引用别人的表(从表),再删除被引用的表(主表)
CREATE TABLE `grade`(
`grade_id` INT(3) NOT NULL AUTO_INCREMENT COMMENT '年级id',
`grade_name` VARCHAR(30) NOT NULL COMMENT '年级名称',
PRIMARY KEY(`grade_id`)
)ENGINE=INNODB DEFAULT CHARSET=utf8
-- 学生表的 grade_id 字段要去引用年纪表的 grade_id
-- 定义外键KEY
-- 给这个外键添加约束
CREATE TABLE IF NOT EXISTS `student`(
`id` INT(3) NOT NULL AUTO_INCREMENT COMMENT '学号',
`grade_id` INT(3) NOT NULL COMMENT '年级id',
`name` VARCHAR(30) NOT NULL DEFAULT '匿名' COMMENT '姓名',
`password` VARCHAR(20) NOT NULL DEFAULT '123456' COMMENT '密码',
`sex` VARCHAR(2) NOT NULL DEFAULT '女' COMMENT '性别',
`birthday` DATETIME DEFAULT NULL COMMENT '出生日期',
`address` VARCHAR(100) DEFAULT NULL COMMENT '家庭住址',
`e-mail` VARCHAR(50) DEFAULT NULL COMMENT '邮箱',
PRIMARY KEY(`id`),
KEY `FK_grade_id` (`grade_id`),
CONSTRAINT `FK_grade_id` FOREIGN KEY (`grade_id`) REFERENCES `grade`(`grade_id`)
)ENGINE=INNODB DEFAULT CHARSET=utf8
方式二:
ALTER TABLE student
ADD CONSTRAINT `FK_grade_id` FOREIGN KEY (`grade_id`) REFERENCES `grade`(`grade_id`);
以上操作都是物理外键,即数据库级别的外键,不建议使用
最佳实现:
- 数据库就是单纯的表,只用来存数据,只有行(数据)和列(字段)
- 我们想使用多张表的数据、想使用外键,就用程序去实现(应用层)
DML语言(重点)
DML语言:数据操作语言
- insert
INSERT INTO `grade`(`grade_name`) VALUES('大三')
-- 由于主键自增,可以省略主键(如果不写表的字段,会自行一一匹配)
INSERT INTO `grade`(`grade_name`) VALUES('大一'),('大二')
INSERT INTO `student`(`name`,`password`,`sex`, `grade_id`)
VALUES('李四', 'aaaaaa', '男', 3),('王五', 'aaaaaa', '男', 3)
- update
-- 语法:UPDATE 表名 SET 字段名 = 值 WHERE 条件
UPDATE `student` SET `name` = '无名', `e-mail` = '51623@qq.com' WHERE `id` = 1
UPDATE `student` SET `name` = '无名', `e-mail` = '51623@qq.com' WHERE `sex` = '男' AND `name` = '李四'
UPDATE `student` SET `birthday` = CURRENT_TIME WHERE `name` = '王五'
- delete
-- 删除数据(避免这样写,会全部删除)
DELETE FROM `student`
-- 删除指定数据
DELETE FROM `student` WHERE `id` = 1
-- TRUNCATE 命令:完全清空一个数据库表,表的结构和索引约束不会变
TRUNCATE `student`
TRUNCATE
student
和 DELETE FROMstudent
:
相同点:都能删除数据,都不会删除表结构
不同点:
TRUNCATE 重新设置 自增列 ,计数器归零
TRUNCATE 不会影响事务
DELETE 删除问题,重启数据库,现象:
INNODB:自增列会重新从 1 开始(存在内存当中,断电即失)
MYISAM:继续从上一个自增量开始(存在文件中,不会丢失)
DQL查询语句(重点)
DQL
Data Query Language 数据查询语言
- 所有查询操作都用Select
- 简单的查询、复杂的查询都能做
- 数据库中最核心的语言
- 使用频率最高的语言
查询指定字段
-- 查询全部学生
SELECT * FROM `student`
-- 查询指定字段,可以给字段、表起别名
SELECT `studentno` AS 学号,`studentname` AS 学生姓名 FROM `student`
-- 函数 CONCAT(a, b)
SELECT CONCAT('姓名:', `studentname`) AS 新名字 FROM `student`
去重 DISTINCT:去除SELECT查询出来结果中重复的数据,只显示一条
SELECT * FROM `result` -- 查询全部的考试成绩
SELECT DISTINCT `studentno` FROM `result` -- 查询哪些学生参加了考试
数据库的列(表达式)
SELECT VERSION() -- 查询系统版本(函数)
SELECT 100 * 3 - 9 AS 计算结果 -- 用于计算(表达式)
SELECT @@auto_increment_increment -- 查询自增的步长(变量)
SELECT `studentno`, `studentresult` + 1 AS 加一分后的成绩 FROM `result` -- 学员考试成绩 + 1分查看
--数据库中的表达式:文本值、列、Null、函数、计算表达式、系统变量……
WHERE条件子句
WHERE 条件子句:检索数据中符合条件的值
搜索的条件由一个或多个表达式组成 结果是布尔值
WHERE是约束声明,后面不能写聚合函数
HAVING是过滤声明,后面可以写聚合函数
逻辑运算符(尽量使用英文字符)
运算符 | 语法 | 描述 |
---|---|---|
AND 或 && | a AND b 或 a && b | 与 |
OR 或 || | a OR b 或 a || b | 或 |
NOT 或 ! | NOT a 或 !a | 非 |
-- 查询考试成绩在95~100的
SELECT `studentno`, `studentresult` FROM `result`
WHERE `studentresult` >= 95 AND `studentresult` <= 100
SELECT `studentno`, `studentresult` FROM `result`
WHERE `studentresult` BETWEEN 95 AND 100
-- 查询1000号学生之外的学生成绩
SELECT `studentno`, `studentresult` FROM `result`
WHERE NOT `studentno` = 1000
模糊查询:比较运算符
IS NULL ---------- 如果操作符为NULL,结果为真
IS NOT NULL ---------- 如果操作符不为NULL,结果为真
BETWEEN AND ---------- 如果 a 在 b 和 c 之间,结果为真
LIKE ---------- SQL匹配,如果 a 匹配 b ,结果为真
IN ---------- 如果 a 是 a1、a2…… 中的某一个值,结果为真
-- 查询姓赵的同学的学号和姓名
-- LIKE 结合 %(代表0到任意个字符)
SELECT `studentno`, `studentname` FROM `student`
WHERE `studentname` LIKE '赵%'
-- 查询姓赵并且“赵”后面只有一个字的同学的学号和姓名
-- LIKE 结合 _(一个字符)
SELECT `studentno`, `studentname` FROM `student`
WHERE `studentname` LIKE '赵_'
-- 查询姓赵并且“赵”后面只有两个字的同学的学号和姓名
-- LIKE 结合 _(一个字符)
SELECT `studentno`, `studentname` FROM `student`
WHERE `studentname` LIKE '赵__'
-- 查询名字中有“强”字的同学的学号和姓名
SELECT `studentno`, `studentname` FROM `student`
WHERE `studentname` LIKE '%强%'
-- 查询1001,1002,1003号同学的学号和姓名
SELECT `studentno`, `studentname` FROM `student`
WHERE `studentno` IN (1000, 1001, 1002)
-- 查询地址为空的同学的学号和姓名 NULL ''
SELECT `studentno`, `studentname` FROM `student`
WHERE `address` IS NULL OR `address` = ''
联表查询
JOIN 对比
/* 思路
1. 分析需求,分析查询的字段来自哪些表
2. 确定使用哪种连接查询(共7种)
确定交叉点(两个表种哪个数据相同)
判断的条件:`student` 表中 `studentno` = `result` 表中 `studentno`
*/
-- 查询参加了考试的同学的学号、姓名、科目编号、成绩
-- INNER JOIN
SELECT s.`studentno`, `studentname`, `subjectno`, `studentresult`
FROM `student` AS s
INNER JOIN `result` AS r
ON s.`studentno` = r.`studentno`
-- LEFT JOIN 左表为主表
SELECT s.`studentno`, `studentname`, `subjectno`, `studentresult`
FROM `student` AS s
LEFT JOIN `result` AS r
ON s.`studentno` = r.`studentno`
-- RIGHT JOIN 右表为主表
SELECT s.`studentno`, `studentname`, `subjectno`, `studentresult`
FROM `student` AS s
RIGHT JOIN `result` AS r
ON s.`studentno` = r.`studentno`
操作 | 描述 |
---|---|
INNER JOIN | 如果表中有一条记录匹配,返回该记录 |
LEFT JOIN | 从左表中返回所有的记录,即使右表中没有匹配 |
RIGHT JOIN | 从右表中返回所有的记录,即使左表中没有匹配 |
-- 先查询两张表然后慢慢增加
SELECT s.`studentno`, `studentname`, `subjectname`, `studentresult`
FROM student AS s
RIGHT JOIN result AS r
ON s.`studentno` = r.`studentno`
INNER JOIN `subject` AS sub
ON r.`subjectno` = sub.`subjectno`
LEFT JOIN 和 RIGHT JOIN 时 ON 和 WHERE 的区别(INNER JOIN 时无影响)
在多表联接查询时,ON 比 WHERE 更早起作用。
例如 LEFT JOIN 时:
- ON 是在生成临时表时使用的条件,不管记录是否满足 ON 中条件,都会返回左表中的记录
- WHERE 是在临时表生成好后,再对临时表进行过滤的条件,这时候已经和 LEFT JOIN 没有关系(即不是必须返回左表中记录数据),条件不满足的会被全部过滤掉
自连接:自己的表和自己的表连接,核心:一张表拆两张一样的表即可
categoryid | categoryname |
---|---|
2 | 信息技术 |
3 | 软件开发 |
5 | 美术技术 |
pid | categoryid | categoryname |
---|---|---|
3 | 4 | 数据库 |
2 | 8 | 办公信息 |
3 | 6 | web开发 |
5 | 7 | ps技术 |
父类 | 子类 |
---|---|
信息技术 | 办公信息 |
软件开发 | 数据库 |
软件开发 | web开发 |
美术技术 | ps技术 |
-- 查询父子信息
SELECT a.`categoryname` AS 父栏目, b.`categoryname` AS 子栏目
FROM `category` AS a, `category` AS b
WHERE a.`categoryid` = b.`pid`
分页和排序
排序
-- 查询结果升序排序ASC 或 降序排序DESC
SELECT s.`studentno`, `studentname`, `subjectname`, `studentresult`
FROM `student` AS s
INNER JOIN `result` AS r
ON s.`studentno` = r.`studentno`
INNER JOIN `subject` AS sub
ON sub.`subjectno` = r.`subjectno`
ORDER BY `studentresult` DESC
为什么要分页?
缓解数据库压力,给人更好的体验
瀑布流
SELECT s.`studentno`, `studentname`, `subjectname`, `studentresult`
FROM `student` AS s
INNER JOIN `result` AS r
ON s.`studentno` = r.`studentno`
INNER JOIN `subject` AS sub
ON sub.`subjectno` = r.`subjectno`
ORDER BY `studentresult` DESC
LIMIT 0, 2 -- 语法:LIMIT 起始页, 显示的记录条数
子查询
本质:在 WHERE 语句中嵌套一个查询语句
-- 查询 高等数学-1 的所有考试结果(学号,科目编号,成绩),降序排列
-- 方式一:使用联表查询
SELECT r.`studentno`, r.`subjectno`, `studentresult`
FROM `result` AS r
INNER JOIN `subject` AS sub
ON r.`subjectno` = sub.`subjectno`
WHERE `subjectname` = '高等数学-1'
ORDER BY `studentresult` DESC
-- 方式二:使用子查询(先执行内部再执行外部)
SELECT `studentno`, `subjectno`, `studentresult`
FROM `result`
WHERE `subjectno` = (
SELECT `subjectno` FROM `subject`
WHERE `subjectname` = '高等数学-1'
)
ORDER BY `studentresult` DESC
MySQL中的函数
常用函数(大概知道有这么个东西,要用的时候上官网查就完事了)
-- 数学运算
SELECT ABS(-1) -- 绝对值
SELECT CEILING(9.4) -- 向上取整
SELECT FLOOR(9.6) -- 向下取整
SELECT RAND() -- 返回一个 0 ~ 1 之间的随机数
SELECT SIGN(-10) -- 返回参数的符号(负数返回 -1,正数返回 1,0 返回 0)
-- 字符串函数
SELECT CHAR_LENGTH('返回字符串长度') -- 返回字符串长度
SELECT CONCAT('拼接', '字符串') -- 拼接字符串
SELECT INSERT('查询与替换', 3, 1, '和') -- 查询,从某个位置开始替换某个长度的字符串
SELECT LOWER('HSDASD') -- 转小写字母
SELECT UPPER('sdasdlas') -- 转大写字母
SELECT INSTR('sdhsdua', 'h') -- 返回第一次出现字符的索引
SELECT REPLACE('这才是替换', '才', '也') -- 替换
SELECT REVERSE('反转') -- 反转
SELECT CURRENT_DATE() -- 获取当前日期
SELECT CURRENT_TIME() -- 获取当前时间
SELECT NOW() -- 获取当前日期 + 时间
SELECT LOCALTIME() -- 获取本地时间
SELECT SYSDATE() -- 获取系统时间
-- 系统
SELECT USER()
SELECT VERSION()
聚合函数
函数名称 | 描述 |
---|---|
COUNT() | 计数 |
AVG() | 平均数 |
MAX() | 最大值 |
MIN() | 最小值 |
SUM() | 求和 |
SELECT COUNT(`studentname`) FROM `student` -- 会忽略所有 NULL 值
SELECT COUNT(*) FROM `student` -- 不忽略 NULL 值
SELECT COUNT(1) FROM `student` -- 不忽略 NULL 值
SELECT SUM(`studentresult`) FROM `result`
SELECT AVG(`studentresult`) FROM `result`
SELECT MAX(`studentresult`) FROM `result`
SELECT MIN(`studentresult`) FROM `result`
-- 查询不同课程的平均分
SELECT ANY_VALUE(`subjectname`), AVG(`studentresult`)
FROM `result` AS r
INNER JOIN `subject` AS s
ON r.`subjectno` = s.`subjectno`
GROUP BY r.`subjectno`
数据库级别的 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, '张三', '123456'),(2, '李四', '123456'),(3, '王五', '123456')
-- 加密
UPDATE `testmd5` SET `pwd` = MD5(`pwd`)
-- 插入时加密
INSERT INTO `testmd5` VALUES(4, '赵六', MD5('123456'))
-- 校验;将用户传进来的密码进行加密,然后比对
SELECT * FROM `testmd5` WHERE `name` = '赵六' AND `pwd` = MD5('123456')
事务
什么是事务?
将一组 SQL 放在一个批次中执行
SQL 执行 :A 给 B 转账 A 1000 B 200
SQL 执行:B 收到 A 的钱 A 200 B 1000
事务原则:ACID
- 原子性(两个步骤一起成功,或者一起失败,不能只发生其中一个动作)
- 一致性(A 1000 B 200 不管怎么转账,总金额都是1200,事务前后数据完整性保持一致)
- 隔离性(A 给 B 赚钱 不影响 C 给 B 赚钱)
- 持久性(事务一旦提交就不可逆)
隔离导致的问题:
脏读:一个事务读取了另一个事务未提交的数据
不可重复读:在一个事务内读取表中某一行数据,多次读取的结果不同(不一定错误,只是某些场合不对)
幻读:一个事务内读取到了别的事务插入的数据,导致前后读取不一致(一般是行影响,多了一行)
-- MySQL 是默认开启事务自动提交的
SET autocommit = 0 -- 关闭
SET autocommit = 1 -- 开启(默认的)
-- 手动处理事务
SET autocommit = 0 -- 关闭自动提交
-- 事务开启
START TRANSACTION -- 标记一个事务的开启,从这个之后的SQL都在同一个事务内
-- 提交:持久化(成功)
COMMIT
-- 回滚:回到原来的样子(失败)
ROLLBACK
-- 事务结束
SET autocommit = 1 -- 开启自动提交
-- 了解即可
SAVEPOINT 保存点名 -- 设置一个事务的保存点
ROLLBACK TO SAVEPOINT 保存点名 -- 回滚到保存点
RELEASE SAVEPOINT 保存点名 -- 撤销保存点
-- 转账
CREATE DATABASE shop CHARACTER SET utf8 COLLATE utf8_general_ci
USE shop
CREATE TABLE `account`(
`id` INT(3) NOT NULL AUTO_INCREMENT,
`name` VARCHAR(4) 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'
UPDATE `account` SET `money` = `money` + 500 WHERE `name` = 'B'
COMMIT -- 提交
ROLLBACK -- 回滚
SET autocommit = 1 -- 开启自动提交
索引
MySQL 官方对索引的定义为:索引(index)是帮助MySQL高效获取数据的数据结构。
提取句子主干,就可以得到索引的本质,索引是数据结构。
索引的分类
- 主键索引(PRIMARY KEY):唯一的标识,主键不可重复,只能有一个列作为主键
- 唯一索引(UNIQUE KEY):避免重复的列出现,唯一索引可以重复,多个列都可以标识为唯一索引
- 常规索引(KEY/INDEX):默认的
- 全文索引(FULLTEXT):在特定的数据库引擎下才有,用于快速定位数据
-- 显示所有索引信息
SHOW INDEX FROM `student`
-- 增加一个全文索引 (索引名)列名
ALTER TABLE `school`.`student` ADD FULLTEXT INDEX `studentname`(`studentname`);
-- EXPLAIN 分析 SQL 执行的状况
EXPLAIN SELECT * FROM `student`; -- 非全文索引
EXPLAIN SELECT * FROM `student` WHERE MATCH(`studentname`) AGAINST('赵');
测试索引
CREATE TABLE `app_user` (
`id` BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
`name` VARCHAR(50) DEFAULT'' COMMENT'用户昵称',
`email` VARCHAR(50) NOT NULL COMMENT'用户邮箱',
`phone` VARCHAR(20) DEFAULT'' COMMENT'手机号',
`gender` TINYINT(4) UNSIGNED DEFAULT '0'COMMENT '性别(0:男;1:女)',
`password` VARCHAR(100) NOT NULL COMMENT '密码',
`age` TINYINT(4) DEFAULT'0' COMMENT '年龄',
`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 COMMENT = 'app用户表'
-- 插入 100 万条数据
DELIMITER $$ -- 写函数之前必须要写,标志
CREATE FUNCTION mock_data()
RETURNS INT
BEGIN
DECLARE num INT DEFAULT 1000000;
DECLARE i INT DEFAULT 0;
WHILE i < num DO
-- 插入语句
INSERT INTO `app_user`(`name`,`email`,`phone`,`gender`,`password`,`age`)
VALUES(CONCAT('用户',i),'gdadas@email',
CONCAT('18',FLOOR(RAND()*((999999999-100000000)+100000000))),
FLOOR(RAND()*2),UUID(),FLOOR(RAND()*100));
SET i = i + 1;
END WHILE;
RETURN i;
END;
SELECT mock_data()
SELECT * FROM `app_user` WHERE `name` = '用户9999' -- 总耗时: 2.109 sec
EXPLAIN SELECT * FROM `app_user` WHERE `name` = '用户9999' -- rows = 991749
CREATE INDEX id_app_user_name ON `app_user`(`name`)
SELECT * FROM `app_user` WHERE `name` = '用户9999' -- 总耗时: 0.001 sec
EXPLAIN SELECT * FROM `app_user` WHERE `name` = '用户9999' -- rows = 1
索引在小数据量的时候区别不大,但在大数据的时候区别十分明显
索引原则
- 索引不是越多越好
- 不要对经常变动的数据加索引
- 小数据量的表不需要索引
- 索引一般加在经常查询的字段上
索引的数据结构
Hash类型的索引
BTREE:INNODB的默认数据结构
权限管理和备份
用户管理
SQL 命令操作
-- 创建用户
CREATE USER a_user IDENTIFIED BY '123456'
-- 修改密码(修改当前用户密码)
SET PASSWORD = PASSWORD('111111')
-- 修改密码(修改指定用户密码)
SET PASSWORD FOR a_user = PASSWORD('111111')
-- 重命名
RENAME USER a_user TO another_user
-- 用户授权
-- 除了给别人授权
GRANT ALL PRIVILEGES ON *.* TO another_user
-- 查看权限
SHOW GRANTS FOR another_user -- 查看指定用户权限
SHOW GRANTS FOR root@localhost -- 查看 root 权限
-- root 的权限:GRANT ALL PRIVILEGES ON *.* TO 'root'@'localhost' WITH GRANT OPTION
-- 撤销权限
REVOKE ALL PRIVILEGES ON *.* FROM another_user
-- 删除用户
DROP USER another_user
MySQL备份
为什么要备份?
- 保证重要的数据不丢失
- 数据转移
MySQL备份方式:
- 直接拷贝物理文件
- 在 Sqlyog 这种可视化工具中手动导出
- 使用命令行导出 mysqldump 命令行使用
导出:
mysqldump -hlocalhost -uroot -p123456 school student > D:/a.sql
导入(在登陆的情况下,切换到指定数据库):
source d:/a.sql
规范数据库设计
当数据库比较复杂的时候,我们就需要设计
糟糕的数据库设计:
- 数据冗余,浪费空间
- 数据库插入和删除都麻烦、会异常(避免使用物理外键)
- 程序的性能差
良好的数据库设计:
- 节省内存空间
- 保证数据库完整性
- 方便开发系统
软件开发中关于数据库的设计
- 分析需求:分析业务与需要处理的数据库的需求
- 概要设计:设计关系图 E - R 图
设计数据库步骤(个人博客)
- 收集信息,分析需求:
用户表(用户登陆注销,用户的个人信息,写博客,创建分类)
分类表(文章分类,谁创建的)
文章表(文章表)
评论表
友链表(友链信息)
自定义表(系统信息,某个关键的字,或者一些主字段)key : value- 标识实体(把需求落地到每个字段)
- 标识实体之间的关系
user —> blog 写博客
user —> category 创建分类
user —> user 关注
linkes 友链
user — user — blog 评论
三大范式
为什么要数据规范化?
- 信息重复
- 更新异常
- 插入异常(无法正常显示信息)
- 删除异常(丢失有效的信息)
三大范式
第一范式
- 原子性(保证每一列不可再分)
第二范式(必须满足第一范式)
- 每张表只描述一件事情
第三范式(必须满足第一范式、第二范式)
- 确保数据表中的每一列数据都和主键直接相关,而不能间接相关
规范 和 性能
- 考虑商业化的需求和目标(用户体验,性能),数据库性能更加重要
- 在考虑性能问题的时候,适当考虑规范性
- 故意给某些表增加一些冗余的字段(把多表查询变为单表查询)
- 故意增加一些计算列(把大数据量的查询降低为小数据量查询)
JDBC(重点)
数据库驱动
JDBC
SUN 公司为了简化开发人员对数据库的统一操作,提供了一个(Java 操作数据库)的规范,就叫 JDBC,这些规范的实现由具体厂商去做
对于开发人员来说,只需要掌握 JDBC 接口的操作即可
第一个 JDBC 程序
package ind.jdbc.lesson01;
import java.sql.*;
public class JDBCFirstDemo {
public static void main(String[] args) throws ClassNotFoundException, SQLException {
// 1. 加载驱动
Class.forName("com.mysql.jdbc.Driver"); // 固定写法,加载驱动
// 2. 用户信息 和 url
// useUnicode=true&characterEncoding=utf8&useSSL=true
String url = "jdbc:mysql://localhost:3306/jdbcstudy?useUnicode=true&characterEncoding=utf8&useSSL=true";
String username = "root";
String password = "123456";
// 3. 连接成功,数据库对象
Connection connection = DriverManager.getConnection(url, username, password);
// 4. 执行 SQL 的对象 statement 执行 SQL 的对象
Statement statement = connection.createStatement();
// 5. 执行 SQL 的对象 执行 SQL,可能存在结果,查看返回结果
String sql = "SELECT * FROM `users`";
ResultSet resultSet = statement.executeQuery(sql); // 返回的结果集,结果集中封装了所有我们查询出来的结果
while(resultSet.next()){
System.out.println("id = " + resultSet.getObject("id"));
System.out.println("name = " + resultSet.getObject("NAME"));
System.out.println("pwd = " + resultSet.getObject("PASSWORD"));
System.out.println("email = " + resultSet.getObject("email"));
System.out.println("birthday = " + resultSet.getObject("birthday"));
System.out.println("====================");
}
// 6. 释放连接
resultSet.close();
statement.close();
connection.close();
}
}
DriverManager
// DriverManager.registerDriver(new com.mysql.jdbc.Driver());
Class.forName("com.mysql.jdbc.Driver"); // 固定写法,加载驱动
Connection connection = DriverManager.getConnection(url, username, password);
// connection 代表数据库
// 数据库设置自动提交、事务提交、事务回滚
connection.rollback();
connection.commit();
connection.setAutoCommit();
URL
String url = "jdbc:mysql://localhost:3306/jdbcstudy?useUnicode=true&characterEncoding=utf8&useSSL=true";
// mysql 默认 3306
// jdbc:mysql://主机地址:端口号/数据库名?参数1&参数2&参数3
// oracle 默认 1521
// jdbc:oracle:thin:@localhost:1521:sid
Statement 执行 SQL 的对象
PrepareStatement 执行 SQL 的对象
statement.executeQuery(); // 执行查询操作,返回 ResultSet 结果集
statement.execute(); // 执行任何 SQL
statement.executeUpdate(); // 更新、插入、删除都使用这个,返回受影响的行数
结果集:封装了所有查询的结果
// 获得指定类型
resultSet.getObject()
resultSet.getShort()
resultSet.getInt()
resultSet.getString()
resultSet.getBoolean()
resultSet.getDouble()
resultSet.getFloat()
resultSet.beforeFirst(); // 移动到最前面
resultSet.afterLast(); // 移动到最后面
resultSet.next(); // 移动到下一个数据
resultSet.previous(); // 移动到前一行
resultSet.absolute(row); // 移动到指定行
释放资源
resultSet.close();
statement.close();
connection.close(); // 耗资源,用完关掉
Statement 对象
编写工具类
package ind.jdbc.lesson02.utils;
import java.io.IOException;
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");
// 驱动只用加载一次
Class.forName(driver);
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
// 获取连接
public static Connection getConnection() throws SQLException {
return DriverManager.getConnection(url, username, password);
}
// 释放连接
public void release(Connection connection, Statement statement, ResultSet resultSet) throws SQLException {
if (resultSet != null) {
resultSet.close();
}
if (statement != null) {
statement.close();
}
if (connection != null) {
connection.close();
}
}
}
测试增、删、改 executeUpdate()
package ind.jdbc.lesson02;
import ind.jdbc.lesson02.utils.JDBCUtils;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
public class TestInsert {
public static void main(String[] args) throws SQLException {
JDBCUtils jdbcUtils = new JDBCUtils();
Connection connection = jdbcUtils.getConnection();
Statement statement = connection.createStatement();
String sql = "INSERT INTO `users`(`id`,`NAME`,`PASSWORD`,`email`,`birthday`)" +
"VALUES(4,'Jarvis','123456','1121212@qq.com','2000-1-15')";
int i = statement.executeUpdate(sql);
if (i > 0) {
System.out.println("插入成功");
}
jdbcUtils.release(connection, statement, statement.getResultSet());
}
}
package ind.jdbc.lesson02;
import ind.jdbc.lesson02.utils.JDBCUtils;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
public class TestDelete {
public static void main(String[] args) throws SQLException {
JDBCUtils jdbcUtils = new JDBCUtils();
Connection connection = jdbcUtils.getConnection();
Statement statement = connection.createStatement();
String sql = "DELETE FROM `users` WHERE `id` = 4";
int i = statement.executeUpdate(sql);
if (i > 0) {
System.out.println("删除成功");
}
jdbcUtils.release(connection, statement, statement.getResultSet());
}
}
package ind.jdbc.lesson02;
import ind.jdbc.lesson02.utils.JDBCUtils;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
public class TestUpdate {
public static void main(String[] args) throws SQLException {
JDBCUtils jdbcUtils = new JDBCUtils();
Connection connection = jdbcUtils.getConnection();
Statement statement = connection.createStatement();
String sql = "UPDATE `users` SET `NAME` = 'jarvis' WHERE `id` = 1";
int i = statement.executeUpdate(sql);
if (i > 0) {
System.out.println("修改成功");
}
jdbcUtils.release(connection, statement, statement.getResultSet());
}
}
测试查 executeQuery()
package ind.jdbc.lesson02;
import ind.jdbc.lesson02.utils.JDBCUtils;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
public class TestQuery {
public static void main(String[] args) throws SQLException {
JDBCUtils jdbcUtils = new JDBCUtils();
Connection connection = jdbcUtils.getConnection();
Statement statement = connection.createStatement();
String sql = "SELECT * FROM `users` WHERE `id` = 1";
ResultSet resultSet = statement.executeQuery(sql);
while(resultSet.next()){
System.out.println("id = " + resultSet.getInt("id"));
System.out.println("NAME = " + resultSet.getString("NAME"));
System.out.println("PASSWORD = " + resultSet.getString("PASSWORD"));
System.out.println("email = " + resultSet.getString("email"));
System.out.println("birthday = " + resultSet.getDate("birthday"));
}
jdbcUtils.release(connection, statement, resultSet);
}
}
SQL 注入问题
SQL 存在漏洞,会被攻击,导致数据泄露
package ind.jdbc.lesson02;
import ind.jdbc.lesson02.utils.JDBCUtils;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
public class SQLInject {
public static void main(String[] args) throws SQLException {
// 正常登陆
// login("jarvis", "123456");
// "SELECT * FROM `users` WHERE `NAME` = ' ' OR '1 = 1' AND `PASSWORD` = ' ' OR '1 = 1'";
login(" ' OR '1 = 1", " ' OR '1 = 1");
}
public static void login(String username, String password) throws SQLException {
JDBCUtils jdbcUtils = new JDBCUtils();
Connection connection = jdbcUtils.getConnection();
Statement statement = connection.createStatement();
String sql = "SELECT * FROM `users` WHERE `NAME` = '" + username + "'" + "AND `PASSWORD` = '" + password + "'";
ResultSet resultSet = statement.executeQuery(sql);
while(resultSet.next()){
System.out.println(resultSet.getString("NAME"));
System.out.println(resultSet.getString("PASSWORD"));
}
}
}
PreparedStatement 对象
可以防止 SQL 注入,并且效率更高
增
package ind.jdbc.lesson03;
import ind.jdbc.lesson02.utils.JDBCUtils;
import java.sql.Connection;
import java.util.Date;
import java.sql.PreparedStatement;
import java.sql.SQLException;
public class TestInsert {
public static void main(String[] args) throws SQLException {
Connection connection = null;
PreparedStatement preparedStatement = null;
try {
connection = JDBCUtils.getConnection();
// 使用 ? 占位符代替参数
String sql = "INSERT INTO `users`(`id`,`NAME`,`PASSWORD`,`email`,`birthday`) VALUES(?,?,?,?,?)";
// 预编译 SQL,先写 SQL 但不执行
preparedStatement = connection.prepareStatement(sql);
// 手动给 SQL 赋值
preparedStatement.setInt(1,4);
preparedStatement.setString(2,"369");
preparedStatement.setString(3, "369369");
preparedStatement.setString(4, "25262@qq.com");
// 注意:sql.Date 数据库 java.sql.Date()
// util.Date Java new Date().getTime() 获得时间戳
preparedStatement.setDate(5, new java.sql.Date(new Date().getTime()));
int i = preparedStatement.executeUpdate();
if(i > 0) System.out.println("插入成功");
} catch (SQLException e) {
e.printStackTrace();
} finally {
JDBCUtils.release(connection, preparedStatement, null);
}
}
}
删
package ind.jdbc.lesson03;
import ind.jdbc.lesson02.utils.JDBCUtils;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
public class TestDelete {
public static void main(String[] args) throws SQLException {
Connection connection = JDBCUtils.getConnection();
String sql = "DELETE FROM `users` WHERE `id` = ?";
PreparedStatement preparedStatement = connection.prepareStatement(sql);
preparedStatement.setInt(1, 3);
int i = preparedStatement.executeUpdate();
if(i > 0){
System.out.println("删除成功");
}
JDBCUtils.release(connection, preparedStatement, null);
}
}
改
package ind.jdbc.lesson03;
import ind.jdbc.lesson02.utils.JDBCUtils;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
public class TestUpdate {
public static void main(String[] args) throws SQLException {
Connection connection = JDBCUtils.getConnection();
String sql = "UPDATE `users` SET `NAME` = ? WHERE `id` = ?";
PreparedStatement preparedStatement = connection.prepareStatement(sql);
preparedStatement.setString(1, "TheShy");
preparedStatement.setInt(2, 2);
int i = preparedStatement.executeUpdate();
if(i > 0) System.out.println("更新成功");
JDBCUtils.release(connection, preparedStatement, null);
}
}
查
package ind.jdbc.lesson03;
import ind.jdbc.lesson02.utils.JDBCUtils;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
public class TestQuery {
public static void main(String[] args) throws SQLException {
Connection connection = JDBCUtils.getConnection();
String sql = "SELECT * FROM `users` WHERE `id` = ?";
PreparedStatement preparedStatement = connection.prepareStatement(sql);
preparedStatement.setInt(1, 2);
ResultSet resultSet = preparedStatement.executeQuery();
while(resultSet.next()){
System.out.println(resultSet.getInt("id"));
System.out.println(resultSet.getString("NAME"));
System.out.println(resultSet.getString("PASSWORD"));
System.out.println(resultSet.getString("email"));
System.out.println(resultSet.getDate("birthday"));
}
JDBCUtils.release(connection, preparedStatement, resultSet);
}
}
SQL 注入
package ind.jdbc.lesson03;
import ind.jdbc.lesson02.utils.JDBCUtils;
import java.sql.*;
public class SQLInject {
public static void main(String[] args) throws SQLException {
// 正常登陆
login("jarvis", "123456");
// "SELECT * FROM `users` WHERE `NAME` = ' ' OR '1 = 1' AND `PASSWORD` = ' ' OR '1 = 1'";
// login(" ' OR '1 = 1", " ' OR '1 = 1");
}
public static void login(String username, String password) throws SQLException {
JDBCUtils jdbcUtils = new JDBCUtils();
Connection connection = jdbcUtils.getConnection();
String sql = "SELECT * FROM `users` WHERE `NAME` = ? AND `PASSWORD` = ?";
// PreparedStatement 防止 SQL 注入的本质就是把传进来的参数当作字符
// 假设其中存在转义字符,就直接忽略,比如 ‘ 会被直接转义
PreparedStatement statement = connection.prepareStatement(sql);
statement.setString(1, username);
statement.setString(2, password);
ResultSet resultSet = statement.executeQuery();
if(resultSet.next()){
System.out.println(resultSet.getString("NAME"));
}
}
}
事务
package ind.jdbc.lesson04;
import ind.jdbc.lesson02.utils.JDBCUtils;
import jdk.nashorn.internal.scripts.JD;
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 connection = null;
PreparedStatement preparedStatement = null;
ResultSet resultSet = null;
try {
connection = JDBCUtils.getConnection();
// 关闭数据库的自动提交
connection.setAutoCommit(false);
String sql1 = "UPDATE account SET money = money - 100 WHERE NAME = 'A'";
preparedStatement = connection.prepareStatement(sql1);
preparedStatement.executeUpdate();
String sql2 = "UPDATE account SET money = money + 100 WHERE NAME = 'B'";
preparedStatement = connection.prepareStatement(sql2);
preparedStatement.executeUpdate();
// 提交事务
connection.commit();
System.out.println("Success");
} catch (SQLException throwables) {
// try {
// connection.rollback();// 如果失败则回滚
// } catch (SQLException e) {
// e.printStackTrace();
// }
throwables.printStackTrace();
}finally {
JDBCUtils.release(connection,preparedStatement, null);
}
}
}
数据库连接池
数据库连接 — 执行完毕 — 释放
连接 — 释放 是十分浪费系统资源的
池化技术:准备一些预先的资源,过来就连接预先准备好的
编写连接池,实现一个接口 DataSource
开源数据实现
DBCP
C3P0
Druid(阿里巴巴)
使用了这些数据库连接池后,我们在项目开发中就不需要编写连接数据库的代码了
DBCP
要用到的 jar 包
- commons-pool-1.6.jar
- commons-dbcp-1.4.jar
C3P0
要用到的 jar 包
- c3p0-0.9.5.5.jar
- mchange-commons-java-0.2.19.jar
无论用什么数据源,本质还是一样的,DataSource接口不会变,getConnection()和release()方法也不会变