数据库概述
数据(Data)
对于计算机来说,数据就是计算机存储的(有用的)信息。既然是计算机存储的,那么实质上它就是一串二进制数据(0、1),但是人始终还是要用这个信息的,所以数据存储不能是毫无章法的,它必须遵循一定的规则,数据格式定义了这种规则。
我们之前说过的数据类型就是一种数据格式,比如它规定了整型的数在内存中应该是什么的,浮点型的应该是什么样子。
我们讲操作系统的时候,操作系统的文件系统也是一种数据格式。操作系统在处理读写磁盘文件的时候,肯定要先知道这个文件是什么大概什么格式的,比如word文档、文本文档等。
对于数据来说,最重要的就是它的格式,如果格式定义不清晰,数据就没法存储和处理。
但是即使我们定义清晰了,也还是会有问题。比如,Windows操作系统下的文件,复制到Linux操作系统下,就可能识别不出来。也就是说,数据格式还一个系统兼容性问题。对于文件系统这一类数据表示形式来说,它是高度依赖于操作系统的。如果我们写了一个软件,这个软件是把数据存储到了比如Windows上,那么即使这个软件我们是使用的Java(跨平台)语言开发的,迁移到Linux上,也不一定能够使用。因为Java跨平台,它解决的只是程序设计语言的跨平台,对于数据,它无能为力。所以就要有一种机制,来实现数据格式跨平台兼容。
数据库(DataBase)
数据库,顾名思义,就是遵循一定数据格式的数据集合,可以认为它是对文件系统的改进。它解决了不同操作系统之前,数据格式的兼容性问题。也就是说,只要是同一个数据库的数据文件,即使是从Windows迁移到了Linux上,也可以正常处理的。
数据库管理系统(DataBaseManagementSystem)
最靠近我们用户(开发人员)的其实不是数据库(这个概念要更接近硬件,更底层),我们通常说数据库,其实是说数据库管理系统(DBMS)。
DB是DBMS的一个组成部分(底层实现),对于一个DBMS来说,除了底层数据(DB),还有其他组成部分。比如存储引擎,比如服务器组件,比如UI(命令行,图形化界面),还有其他的一些类似SQL解释器,连接认证系统等。
一些应用广泛的DBMS
画红框的是我们重点关注的。
Oracle,这是一种关系型数据库。它的特点是闭源收费,但是功能强大稳定,而且有一支专业的技术支撑团队。使用这种数据库的一般是大型企业、银行业、金融业。
MySQL,它也是关系型数据库。它的特点是开源免费,功能还是不错的,也比较稳定。通常使用这种数据库的,是中小企业等。因为它是开源的,所以我们有些企业可以对它做定制化、二次开发,以支持自己特殊的业务。比如阿里。说它是目前应用最广泛的DBMS是不夸张的。
Memcached、Redis,这2者是非关系型数据库(Not Only SQL)。它们通常用K-V键值对的形式存储数据,使用场景为缓存(可做为关系型数据库的补充)。它俩都是基于内存存储的,它们的数据都是在内存中,所以访问速度要快。Redis还提供了定期向磁盘中进行序列化的机制,它的IO性能也是非常高的。
MongoDB,这个也是NOSQL数据库。它使用文档形式存储数据。使用场景,通常是大数据量高并发访问。它的性能也非常高。
DDL Data Definition Language,数据定义语言
use - 库
use test1; -- 使用库
desc
desc hello; -- 查看表结构
show
show tables; -- 查询当前库的所有表
show databases; -- 查询当前连接的数据库服务器的所有数据库
show create table student --查看建表语句
show create database test1 --查看建库语句
select - 方法
select version();-- 展示已连接数据库版本
select database();-- 展示当前使用的数据库,可用use切换
create - 建库、表
create database test1; -- 建库
create table hello( -- 建表
age int,
`name` varchar(30),
money decimal(18,2)
)ENGINE=InnoDNB DEFAULT CHARSET=utf8;
create index idx_sc_c on student_score(cname); 建索引
drop - 删,库、表、字段 慎重删除!!!
drop database test; -- 删库
drop table ss; -- 删表
alter table teacher drop birthday;
drop index idx_sc_c on student_score; 删索引
alter - 修改 慎重!!!
rename
alter table hello
rename hello_info; -- 改表名
change
alter table hello_info
change money money_copy varchar(20); -- 改表的字段名,顺带改型类
add
alter table hello_info
add address varchar(500); -- 给表加字段,在最后加
alter table hello_info
add address varchar(500) after teacher_name; -- 少用
alter table student_score add key idx_sc_s_c(sname,cname)
ALTER table student
add PRIMARY KEY(teacher_id,id,name); --加主键
ALTER table student
add FOREIGN KEY(teacher_id)references teacher(teacher_id); --加外键
drop
alter table teacher drop birthday; --删字段
alter table student_score drop key idx_sc_s_c; 删除索引
modify
alter table hello_info
modify address varchar(600) comment'家庭住址'; -- 改类型,改大小
type
alter table teacher type=InnoDB; -- 修改表的存储引擎
注意事项
通常,对于SQL语法中的关键字比如CREATE等,使用大写,其他可以用小写。
改类型的时候要注意,尽量把类型扩大(比如varchar(100)改成varchar(500)),不要反过来改。尽量不要跨类型改(比如把一个decimal改成了int)。
反引号(`):它的作用是为了区分开表名/字段名与SQL语法关键字的。
primary key -主键 实体完整性
表建立时添主键:
create table teacher(
id int not null default 0,
name varchar(20) not null default ' ',
teacher_id int not null default 0,
PRIMARY KEY(teacher_id)
);
表建完时添主键:
create table student(
id int not null default 0,
`name` varchar(100) not null default '',
teacher_id int not null default 0
);
ALTER table student
add PRIMARY KEY(teacher_id,id,name);
foreign key - 外键 关联完整性
表建立时添外键:
create table student_2(
id int not null default 0,
`name` varchar(100) not null default ' ',
student_id int not null default 0,
teacher_id int not null default 0,
PRIMARY KEY(student_id),
foreign key(teacher_id)references teacher(teacher_id)
);
表建完时添外键:
create table student(
id int not null default 0,
`name` varchar(100) not null default ' ',
teacher_id int not null default 0,
PRIMARY KEY(id)
);
ALTER table student
add FOREIGN KEY(teacher_id)references teacher(teacher_id);
DML Data Manipulation Language数据操纵语言
insert
insert into student_2(id,name,student_id,teacher_id)
values(1,'东邪',1,2);
insert into student -- 不建议使用
values(103,'陆军','男','1974-06-03','95031');
update
update person -- 更新数据,一定要加where限制!!否则会全表删除
set id = 19,
`name` = '南帝',
salary = 99.0,
address = '大理'
where `name` = '南海';
delete
delete from teacher
where name='老王'; -- 删除数据,一定要加where限制!!否则会全表删除
DQL(Data Query Language)数据查询语言
查表的某字段
union / union all
它俩的作用是把两张表或者更多表合并成一张表
前者会去重(去重的依据是,UNION时SELECT出来的字段如果对应相等则认为是同一条记录,这的逻辑我们可以参考Java equals)
后者则不会去重,它会保留两张表中的所有记录,但是它性能高(因为去重操作要花时间),
尽量使用union all,把去重这个工作交给代码去完成,这样可以减少MYSQL服务器的压力
使用union / union all的时候要注意:
1.参与合并的表,它们SELECT出来的字段数量必须一致(强制规则)
2.参与合并的表,它们SELECT出来的字段的类型建议一一对应(非强制,但是最好遵循这条规则)
3.参与合并的表,它们SELECT出来的字段的顺序建议一致(非强制,但是最好遵循这条规则)
单表查询
常用的where条件
查询语句select * from student where A and B;筛选student表中既满足条件A又满足条件B的记录。
而select * from student where A or B;筛选的则是只要满足A或者B其中一个条件即可的记录。
命令select * from student where score > 80;筛选的是student表中score字段值大于80的记录。
大于等于使用 >=。小于等于使用 <=。小于使用 <。等于使用 =。不等于使用 <>。
命令select * from student where score>=60 and score<=80;等价于
命令select * from student where score between 60 and 80;
命令select * from student where score=10 or score=20;等价于
命令select * from student where score in (10, 20);
对score in (10, 20);的逻辑否是 score not in (10, 20);
命令select * from student where score is null;筛选student表中score字段值为null的记录。对is null的逻辑否是 is not null。
去重distinct
select distinct teacher_id from student; - - 单一去重
select distinct teacher_id,id,name - - 联合去重
from student;
排序order by 字段名 [desc|asc]
select * from student order by score desc;按score字段降序排序。升序则使用 asc。
select * from student order by teacher_id asc, score desc;先按teacher_id升序排序,如果teacher_id相同,再按score降序排序。
限制条数limit 条数。通常与order by 配合使用
select * from student order by score desc limit 3;求成绩在前三名的学生
统计函数/聚合函数/分组查询
MYSQL中有一类特殊的函数,用于统计,或者分组统计。常用的有求数量的count(1)(等价于count(*)),求最大值的max(字段名),求最小值的min(字段名),求平均值的avg(字段名),求总和的sum(字段名)。
如select count(*) from student;用于计算student表中一共有多少条记录。
而select count(*) from student where A;用于计算student表中满足条件A的记录的条数
子查询
子查询又叫嵌套查询。它通常可以位于SELECT后面 FROM后面 WHERE后面。3种使用场景。
select嵌套
当位于SELECT后面时,要注意
1.一定要在两个表之间找好对应关系(teacher.id必须是主键或者必须保证teacher.id在teacher表中是唯一的)
2.子查询中只能有一个字段(子查询的结果必须是一行一列)
使用子查询的时候,建议大家养成使用别名的好习惯,这样可以让我们的查询语句更加清晰。别名可以用来命令新字段,也可以用来命名新表。
from嵌套
当位于FROM后面时,要注意
1.我们可以把子查询当成一张表
2.必须要有别名
where嵌套
当位于WHERE后面时,要注意
1.用in而不要用=,可使用exists优化
2.子查询中的SELECT后面只能有一个字段(多个字段的话会报错,但可以使用条件限制其在特点条件只有一条)
表连接查询
交叉连接(完全笛卡儿积)
from t1,t2
from t1 join t2
内连接
隐式 from t1,t2 where
显示 from t1 innerjoin t2 on t1.xx=t2.xx
外连接
左外连接 left join table on
右外连接 right join table on
子查询和连接查询性能比较
连接查询优于子查询(因为数据库对连接查询有优化),子查询和连接查询都能实现二点情况下使用连接查询
避免查询语句中多个子查询,最好一个,且子查询结果最最好一个字段(记录越少越好)
不得已必须使用子查询还得用子查询
MySQL常用系统函数
系统信息类
select version();显示当前MySQL软件的版本
SELECT DATABASE();显示当前所处数据库是哪个。(可以使用use <数据库名>切换所在库)
字符类
如select char_length('中国');返回字符个数。
如select length('中国');返回字符所占字节数。MySQL中,一个UTF8编码的汉字占3个字节。
如select concat( 'a', 'b', 'c', 'd');返回 'abcd'。字符串拼接函数(可变元参数)。
如select concat_ws( '=', 'a', 'b', 'c');返回 'a=b=c'。字符串拼接函数(可变元参数,但是第一个参数必须是拼接间隔符)。但是可以指定拼接间隔符。
如select upper('abcd');返回ABCD。将参数中所有小写字母转换为大写。
如select lower('ABCD');返回abcd。将参数中所有大写字母转换为小写。
如select substring( '系统信息类', 1, 3 );返回 系统信。第2个参数代表从1开始的第几个字符,第3个参数代表截取字符个数。
如select trim(' abc ');返回 abc。用于删去参数左右的所有空格。
日期时间类
select curdate();返回当前日期
select curtime();返回当前时间
select CURRENT_TIME();
select CURRENT_DATE();
select now();返回当前日期时间
select unix_timestamp();返回当前日期时间对应的时间戳(单位秒)
select unix_timestamp('2018-05-24 20:00:00');返回参数指定的日期时间对应的时间戳(单位秒)
select from_unixtime(1527163397);返回参数指定时间戳(单位秒)对应的日期时间
select datediff( '2018-05-23', now() );返回两个参数对应日期相差的天数(用第一个参数减第二个参数)
select adddate( now(), -2 );返回指定天数前/后的日期时间(第一个参数是日期时间,第二个参数是天数,向后加是正数,向前减是负数)
条件判断类
select if( <判断条件>, <条件为真时的返回值>, <条件为假时的返回值> );相当于Java中的三目运算符<判断条件> ? <条件为真的返回值> : <条件为假的返回值>。
如select if(1=1, 2, 3);返回2。
select ifnull(<表达式或者字段>, <表达式或者字段为NULL时的返回值>);通常用于给有可能有NULL的情况下的提供默认值。
MySQL扩展
sql注入
所谓SQL注入,就是通过把SQL命令插入到Web表单提交或输入域名或页面请求的查询字符串,最终达到欺骗服务器执行恶意的SQL命令。具体来说,它是利用现有应用程序,将(恶意的)SQL命令注入到后台数据库引擎执行的能力,它可以通过在Web表单中输入(恶意)SQL语句得到一个存在安全漏洞的网站上的数据库,而不是按照设计者意图去执行SQL语句。
性能优化
慢查询原因(通常DBMS都有自己的慢查询日志):
外部原因:内存太小,本地I/O瓶颈,网络I/O瓶颈;
内部原因:程序本身DB设计不合理,SQL语句使用不合理,无索引或者有索引但未充分利用。
学会使用explain分析简单的查询。
数据库设计层面的优化
遵循数据库设计三范式(通俗地讲就是每个字段不可再拆分,尽量减少冗余字段);
字段类型设计上,能使用数值就不要使用字符串,能使用日期时间就不要使用字符串,最好把字段声明为not null default 默认值;
为了避免表连接查询,必要的冗余字段是可以设置的;
能提前建立的索引要提前建好(经常用在where、group by、order by中的字段最好建索引)。
创建索引方式一:alter table student_score add key idx_sc_s_c(sname,cname);
删除索引方式一:alter table student_score drop key idx_sc_s_c;
创建索引方式二:create index idx_sc_c on student_score(cname);
删除索引方式二:drop index idx_sc_c on student_score;
如下图,联合索引,idx_sc_s_c,在红框中的用法,是使用不到联合索引的。
要想使用联合索引,要么把联合索引中的字段按顺序全都用上(sname= and cname= ),要么就使用最左边的字段(sname=)
SQL语句使用层面的优化
尽量不使用 select *,而是要具体指定字段,比如select id, name...;
尽量不使用不等于<>;
不使用is null/is not null(虽然也会使用索引,但是性能损耗是由于default null的字段要比not null的字段多出额外的存储空间来标识这个字段的值是不是null);
不使用or连接不同的字段;
不使用not in;
不在条件字段上使用函数;
不使用前置模糊查询(like '%a');等。
因为上面的使用方式都会产生全表扫描(当然,如果实在没办法优化,全表扫描就扫描吧)
索引优化
建立索引的字段从内容上要有差异要有区分度。
索引提升的是读性能,如果一张表的写操作更多,则尽量不建或者少建索引。
使用where、group by、order by时,尽量充分利用建立索引的字段。
数据导入和导出
先将老表数据导出为本地磁盘文件:
select * from student into outfile 'stu_bak';
导出时,如果outfile使用相对路径,则基准目录是/var/lib/mysql/库名
如果是windows 7,则是C:/ProgramData/MySQL/MySQL Server 5.5/data/库名
再创建一个和老表结构相同的新表:
create table student4 select * from student where 1=2; 创建表结构但不复制数据
或者新创建一个库
create database aaa;
use aaa;
create table student_score select * from lhl_test.student_score where false;
修改表的存储引擎alter table student_score type=innodb;
后将导出的本地磁盘文件导入新表:
load data local infile '/var/lib/mysql/lhl_test/stu_bak' into table student4;
如果是新创建库的则是
load data local infile '/var/lib/mysql/lhl_test/student_score_bak' into table student_score;
导入时,如果infile使用相对路径,则基准目录是家目录。
如果是windows 7,则infile后面必须使用绝对路径,\使用/代替
PS:
create table student4 select * from student where 1=1; 创建表结构同时复制数据
行转列
创建一张表,用于练习:
CREATE TABLE `student_score` (
`id` int(11) NOT NULL DEFAULT '0',
`sname` varchar(100) NOT NULL DEFAULT '',
`cname` varchar(100) NOT NULL DEFAULT '',
`score` decimal(18,2) NOT NULL DEFAULT '0.00',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
初始化数据后如下:
场景一(多行转为一行多列):
可以使用下面的SQL语句(group by 与 case when结合使用即可实现):
select sname,
max(
case cname
when 'Java' then score
else 0
end
) Java,
max(
case cname
when 'MySQL' then score
else 0
end
) MySQL
from student_score
group by sname;
---------------------------------------------------
select sname,
max(case cname
when 'Java' then score
end) Java,
max(case cname
when 'MySQL' then score
end) MySQL
from student_score
group by sname;
就得到最终结果啦。
场景二(多行转为一行一列):
第一步:拆分问题,先按分组的思路,只处理一个学员,如小李
select sname, cname, score
from student_score
where sname = '小李';
第二步:将课程名与成绩拼接成一列
select sname,
concat(cname,'=',score) '各科成绩'
from student_score
where sname = '小李';
第三步:利用group_concat函数将多行压扁到一行
select sname,
group_concat(cname,'=',score) '各科成绩'
from student_score
where sname = '小李';
第四步:修改分隔符(默认是逗号)
select sname,
group_concat(cname,'=',score separator ' | ') '各科成绩'
from student_score
where sname = '小李';
第五步:按课程名称排序
select sname,
group_concat(cname,'=',score order by cname asc separator ' | ') '各科成绩'
from student_score
where sname = '小李';
最后一步:对全表应用group by
select sname,
group_concat(cname,'=',score order by cname asc separator ' | ') '各科成绩'
from student_score
group by sname;