狂神说MySQL学习笔记

这篇博客详细介绍了MySQL的学习笔记,包括数据库的列类型、字段属性、创建与修改删除表,以及数据管理如外键和DML操作。还涵盖了DQL查询、函数、事务、索引、权限管理、备份、数据库设计规范以及JDBC使用等内容,是全面学习MySQL的宝贵资料。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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之前

MYISAMINNODB
事务支持不支持支持
数据行锁定不支持支持
外键约束不支持支持
全文索引支持不支持
表空间的大小较小较大,约为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 nullin 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

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值