为什么要讲数据库 现在有一个场景,公司需要从旗下产品中采集到的10万用户信息进行保存,你要怎么做? 数据是不可能一直运行在程序中的,万一断电或出现其它故障,数据就会丢失。 当今时代,丢了信息就是丢了命。 所以能将数据进行持久化存储,并能快速的查找,成了很重要的需求。 数据库出现就是来解决这个问题的。 数据库也是在程序开发中非常重要的一块内容。
数据:
数据用来描述事物的特征,行为,记录事物的状态,过程,结果等等。
比如 一个人的姓名,姓别,年龄,职业,特长等等,这些都是数据。从计算机角度来看,数据体现在记录现实数据信息的组织方式。比如:文本,图片,表格,视频,音频等等。以下我们所说的数据,都是将现实信息转换为计算机角度的表示形式。
数据库
当数据越来越多的时候,就面临着一个具大的问题,就是如何去管理这些数据,数据量越大,管理难度就越大。
比如,你在你的桌面上建立了 N 个文件夹,文件夹又保存了 N 个文件,当你想找一个很久以前创建的文件时,就很难找到,需要花费大量的查找时间。
这时,就需要一种管理组织方式,分门别类的将数据进行保存,方便使用时的查找。
将个桌面级的数据管理放大到千万级,亿万级的数据量时,问题更加突出。
数据库就产生了。数据库就是数据的仓库,数据库按照一定的数据格式,结构来存储数据。方便数据和操作和管理。
数据库管理数据和其它方式管理数据区别
内存管理数据
优点:存取速度快 缺点:断电后数据不能保存
文件管理数据
优点:数据可以持久化保存 缺点:读取速度慢,数据组织格式不好控制
数据库管理数据
优点:统一的数据组织格式 读取速度快 容量大 缺点:需要专门的管理软件 需要一定的学习成本
数据库如何管理数据
实际数据库在管理数据时,也是管理文件的形式来进行管理,只是这些文件具有特定的文件格式。
如果要操作这些数据文件,需要通过专门的数据库管理软来进行操作。
数据库管理软件:DBMS Database Management System 数据库管理系统,简称DBMS ,是用来管理数据的专用软件。
数据库在管理时,通过 DBMS 来操作数据库文件。
类似于 MS Office Excel 可以操作 .xls 的文件一样
数据库概念
数据库 真正用来存储数据的仓库。
数据库服务管理软件 用来操作数据库中文件的管理软件,也就是 DBMS, 但在使用过程中经常的提及的数据库常指 DBMS
数据库客户端软件 用户在需要使用数据时,需要向数据库服务管理软件按照一定的格式申请,提交申请的软件软件称为数据库客户端软件
常用数据库管理软件
MySQL 中小型数据库,跨平台,开源,免费,应用范围广
Oracle 大型数据库,跨平台,稳定,安全,收费
MS SQL Server MS 针对 NT系统开发的数据库,只能运行在 NT 系统上
Sqlite 微型数据库,一般用在移动端开发
关系型数据库 RDBMS
RDBMS Relational Database Management System 关系数据库管理系统
关系型数据库系统是指以行和列的形式存储数据,将数据组织为相关的行和列的系统
这种方式便于用户理解,类似于一张表格。
一组行列关系组成的表构成一个数据文件。
一组表组成了数据库。
用户通过查询(Query)来检索数据库中的数据。
数据库操作语言
数据库在操作时,需要使用专门的数据库操作规则和语法,这个语法称为 SQL。
SQL Structured Query Language 结构化查询语言
SQL 的主要功能是和数据库建立连接,提供增删改查的操作。
按照ANSI(美国国家标准协会)的规定,SQL被作为关系型数据库管理系统的标准语言。
SQL语言共分为四大类:
数据定义语言DDL Data Definition Language
用于定义数据库中要存储的现实世界实体的语言。
主要提供数据库,数据表的创建操作。
数据操作语言DML Data Manipulation Language 用于数据表的操作语言 主要提供数据表中数据的插入,更新,删除操作
数据查询语言DQL Data Query Language
主要提供对数据表中数据的查询操作。
数据控制语言DCL Data Control Language
主要用来设置或更改数据库用户或角色权限的语句。
数据库服务器工作流程
数据库服务在运行时,也和其它服务一样,即然提供了服务,那么就需要有一个服务器存在。
服务器的作用是用来接受用户的操作请求,并将结果返回给用户。
提供数据操作服务的称为服务器
连接数据库服务器发起操作请求的称为客户端
执行过程:
客户端发生请求
服务器接收请求
服务器将请求发给DBMS查询数据
DBMS 将查询结果返回给服务器
服务器将结果返回给客户端
客户端接收使用数据
服务器安装命令
sudo apt-get install mysql-server
sudo apt-get install mysql-client
sudo apt-get install libmysqlclient-dev
一般情况下只需要安装第一个就可以了,MySQL自的客户端软件并不好用。
MySQL 服务管理
查看服务是否开启
ps aux | grep mysql
MySQL 服务管理命令
启动服务
sudo service mysql start
停止服务
sudo service mysql stop
重新启动服务
sudo service mysql restart
查看服务状态
sudo service mysql status
1.数据库操作介绍
在数据库操作中,基本操作都是围绕增删改查来操作。简称CRUD
创建数据库
create database 数据库名
create database testdb;
create database 数据库名
create database testdb2 character set utf8;
显示数据库创建信息 show create database 数据库名
show create database testdb;
修改数据库编码 alter database 数据库名 character set utf8
alter database testdb charset=utf8;
显示所有数据库
show databases;
切换、使用数据库 use 数据库名
use testdb
显示当前数据库 :select database();
删除数据库 drop database 数据库名
drop database testdb2;
不要随便删库,删库只能跑路,追杀你到天涯海角
创建表:create table stu(sid int,sname char(20),sage int);
显示创建表:show craete table stu;
增加字段 作用:为已存在的表添加一个新字段 语法:alter table 表名 add 列名 数据类型
alter table stu add gender char(4);
修改字段的数据类型 作用:修改表中现有字段的类型 语法:alter table 表名 modify 字段名 数据类型
alter table stu modify sname varchar(20);
修改列的数据类型并且改名 作用:修改表中现有字段的字段名和类型 语法:alter table 表名 change 原字段名 新字段名 数据类型
alter table stu change sid snumber smallint;
删除字段 作用:在表中删除一个已存在的字段 语法:alter table 表名 drop 列名
alter table stu drop gender;
查询数据
作用:查询数据就是通过客户端通过 DBMS 从数据库中取出满足条件的数据。
语法:select 字段名列表 from 表名;
select * from stu;
插入数据
作用:向数据库中插入数据
语法:insert into 表名 [(字段名列表, …)] values(值列表, …),…
插入所有字段数据
可以不指定插入的字段,直接写入插入的数据
insert into tStudent values(1,‘tom’,20)
插入指定字段
可以插入指定的字段,指定字段顺序和列中顺序可以不同,值顺序要和指定的列顺序相同。
insert into stu(sage,sname) values(20,‘jack’); 字段和值要一一对应
select * from stu; 查看插入效果
插入多条数据
insert into stu values(2,‘rose’,20),(3,‘tony’,22);
修改数据
作用: 可以根据指定的条件,修改更新满足条件的数据
语法: update 表名 set 字段=值 [条件]
更新所有的数据
update stu set sAge=25;
更新满足条件的数据
update stu set sname=‘alice’ where name=‘tony’;
select * from stu; 查看更新效果
删除数据
作用:将数据从数据表中删除
语法1:truncate 表名
语法2:delete from 表名 [条件]
删除全部数据 truncate
truncate stu;
不需要加条件,也不能加条件,删除全部数据,重置自动编号到默认值,没有事务,速度快。
delete from stu;
相当于 truncate stu,但是delete操作有事务操作,所以速度慢,而且不会重置自动编号。
用法:
1、当你不再需要该表时, 用 drop;
2、当你仍要保留该表,但要删除所有记录时, 用 truncate;
3、当你要删除部分记录或者有可能会后悔的话, 用 delete。
删除程度可从强到弱如下排列:
drop table tb;
drop 是直接将表格删除,无法找回。例如删除 user 表:
drop table user;
truncate (table) tb;
truncate 是删除表中所有数据,但不能与where一起使用;
TRUNCATE TABLE user;
delete from tb (where);
delete 也是删除表中数据,但可以与where连用,删除特定行;
– 删除表中所有数据
delete from user;
– 删除指定行
delete from user where username =‘Tom’;
truncate 和 delete 的区别:
事物
truncate删除后不记录mysql日志,因此不可以rollback,更不可以恢复数据;而 delete 是可以 rollback ;
原因:truncate 相当于保留原mysql表的结果,重新创建了这个表,所有的状态都相当于新的,而delete的效果相当于一行行删除,所以可以rollback;
效果
效率上 truncate 比 delete快,而且 truncate 删除后将重建索引(新插入数据后id从0开始记起),而 delete不会删除索引 (新插入的数据将在删除数据的索引后继续增加)
truncate 不会触发任何 DELETE触发器;
返回值
delete 操作后返回删除的记录数,而 truncate 返回的是0或者-1(成功则返回0,失败返回-1);
delete 与 delete from 区别:
如果只针对一张表进行删除,则效果一样;如果需要联合其他表,则需要使用from :
delete tb1 from tb1 m where id in (select id from tb2)
看看手册中关于 OPTIMIZE 的描述:
OPTIMIZE [LOCAL | NO_WRITE_TO_BINLOG] TABLE tbl_name [, tbl_name] …
如果您已经删除了表的一大部分,或者如果您已经对含有可变长度行的表(含有VARCHAR, BLOB或TEXT列的表)进行了很多更改,则应使用
OPTIMIZE TABLE。被删除的记录被保持在链接清单中,后续的INSERT操作会重新使用旧的记录位置。您可以使用OPTIMIZE TABLE来重新
利用未使用的空间,并整理数据文件的碎片。
在多数的设置中,您根本不需要运行OPTIMIZE TABLE。即使您对可变长度的行进行了大量的更新,您也不需要经常运行,每周一次或每月一次
即可,只对特定的表运行。
OPTIMIZE TABLE只对MyISAM, BDB和InnoDB表起作用。
注意,在OPTIMIZE TABLE运行过程中,MySQL会锁定表。
第一步:先查看了一下占用磁盘空间较多的几张表
SELECT TABLE_NAME, CONCAT(TRUNCATE(data_length/1024/1024,2),’ MB’) AS data_size,
CONCAT(TRUNCATE(index_length/1024/1024,2),’ MB’) AS index_size
FROM information_schema.tables WHERE TABLE_SCHEMA = ‘database_name’
GROUP BY TABLE_NAME ORDER BY data_length DESC
(PS:以后要仔细研究,详细说明,不马虎了,打脸打脸打脸)
mysql在安装成功后,会有四个系统自带的database(可以通过mysql命令>show databases;查看):
| information_schema |
| mysql |
| performance_schema |
| sys |
而查看数据库占用空间(包括数据data和索引index)则是查看information_schema。而关于这四张表的解释,可以参考
https://blog.csdn.net/dj673344908/article/details/80482844
第二步:针对占用空间大的表做清理删除
由于直接操作服务器,非常卡,所以我都是拷在本地,在本地操作,就是试着玩儿的……
Linux服务器备份MySQL数据库:
mysqldump -u userName -p dataBaseName > fileName.sql
将备份的sql文件下载到本地,Windows下MySQL数据库恢复:
mysql -u 用户名 -p 数据库名 < 保存文件.sql
如果上述命令不成功,换一个,好吧,我用的下面的(mysql命令不是一次性执行的,分开执行!):
mysql>
show databases;
create database 数据库名;
use 数据库名;
source 保存文件.sql;
再次显示mysql>即成功
言归正传
之前关于删除数据库数据首先想到是delete,但是delete执行完成后不会释放磁盘空间。(还有一个truncate table语句,但是这个是删除整张表的数据,由于整个操作不会记录日志,因为时间要比delete删除整张表快。且delete可以添加删除条件,truncate不支持)。
因此通过drop表的方式删除。一大批命令呈上来:
create table temp_table like drop_table;
insert into temp_table select * from drop_table where time >= ‘2019-01-01 00:00:00’;
drop table drop_table;
alter table temp_table rename to drop_table;
首先创建一个新表(temp_table),格式和要删除的表(drop_table)一样
然后将要保留的数据放在临时表(temp_table)中
将旧表删掉
将临时表重命名为旧表名称
表中可能会有外键,删除的时候会ERROR提示,以下:
SELECT @@FOREIGN_KEY_CHECKS;
SET FOREIGN_KEY_CHECKS=0;
SET FOREIGN_KEY_CHECKS=1;
第一行命令查看外键约束是否打开,1是打开,0是关闭,删除的时候如果是1会报错,通过第二行命令修改为0,删除成功之后再改回1。
第三步:执行完成
执行完成后如果想查看某个表空间占用是否变小:
select concat(round(sum(data_length/1024/1024),2),‘MB’) as data_length_MB, concat(round(sum(index_length/1024/1024),2),‘MB’) as index_length_MB from information_schema.tables where table_schema=‘database_name’ AND table_name=‘drop_table’;
查看当前占用空间,可与之前记录下来的做比较。
数据完整性
存储在数据库中的所有数据值均正确的状态。
如果数据库中存储有不正确的数据值,则该数据库称为已丧失数据完整性。
数据完整性包括:
域完整性
实体完整性
参考完整性
数据库通过约束来保证数据完整性。
它通过对表的行或列的数据做出限制,来确保表的数据的准确性,完整性、唯一性,可靠性、联动性。
数据常见问题
2.1 数据冗余-
2.2 失去数据完整性
2.3 数据缺少唯一标识
2.3 失去实体完整性
2.4 失去引用完整性
数据库常用约束
主键约束 作用:让数据具有唯一标识 语法:primary key
create table tpk(id int primary key , name char(10));
字段被设置了主键约束,同时也具有了唯一性约束和非空约束。 在字段中插入重复数据时,或不给数据时会报错。
自动增长 作用:让数字值自动累加 语法:auto_increment 自动增长设置在数值型字段上,需要配合主键约束一起使用。 如果字段没有设置主键约束,是不允许设置自动增长的。
create table tai(id int auto_increment primary key,name varchar(10));
唯一性约束 作用:保证数据的准确性,不会出现重复数据 语法:unique
create table tuni(id int unique,name char(10);
一个表中可以给多个字段设置唯一性,如果有需要的话。
非空约束 作用:不允许字段为空,添加数据时必须给值 语法:not null
create table tnn(id int,name char(10) not null);
默认约束 作用:在添加数据时,如果没有给定有默认约束字段的数据,该字段使用默认值填充 语法:default
create table tdt(id int,name char(10) default ‘NoName’);
外键约束 作用:让两表之间产生联动关系 语法:foreign key(字段名) references 表名(字段名)
– 表1
create table fClass(id int primary key,name char(10));
– 表2
create table fStudent(id int primary key auto_increment, name char(10), cid int, foreign key(cid) references fClass(id));
设置外键约束字段所关联的字段,必须是主键约束字段。
想要删除有设置外键的表,必须先删除外键所关联的表。
drop table fStudent;
drop table fClass;
导入数据库使用 mysql 命令完成
语法:mysql -uroot -p 数据库名 < 要导入的文件.sql
导入数据库前需要先创建一个空数据库
mysql -uroot -p sch < school_bak.sql
准备工作
在查询之前,首先要有数据表和相应的数据。
导入之前需要先创建一个数据库
使用新创建的数据库
使用 source 文件地址 导入数据
查询指定字段
查询数据库使用 select 命令。 这个命令相对比较复杂。可变化样式较多,这里分功能依次讲解。
查询数据表中所有数据 语法:select * from 表名
select * from t_student;
查询指定字段的显示 语法:select 字段1,字段2,… from 表名
select c_id,c_name,c_address from t_student;
在查询时,默认结果显示的字段和表中字段名相同,可以通过别名来修改显示的样式 语法:select 字段1 as 别名,字段2 别名,… from 表名
select c_id as 学号 ,c_name as 姓名 ,c_address 地址 from t_student;
在给字段起别名时,可以使用 as ,也可以直接在字段后跟别名,省略 as 。
消除重复数据
在查询数据时,查询结果可能会有很多重复的数据,如果不想重复,可以使用 distinct 来实现去重。 语法:select distinct 字段名 from 表名
select distinct c_address from t_student;
注意:distinct 在去重时,会比较所有的指定字段,只有完全相同时才认为是重复的。
条件查询 where 子句
查询数据时,需要根据不同的需求设置条件,通过where 子句来设置查询条件
语法: select 字段,… from 表名 [where 字段 运算符 值];
select * from t_student where c_gender=‘男’;
运算符
where 条件中可以使用的运算符。
比较运算符
等于: =
大于: >
大于等于: >=
小于: <
小于等于: <=
不等于: != 或 <>
如:select * from t_student where c_age < 20;
逻辑运算符
and
or
not
如:select * from t_student where c_age < 20 and c_gender = ‘女’;
模糊查询
like
% 表示任意多个任意字符
_ 表示一个任意字符
select * from t_student where c_name like ‘孙’;
select * from t_student where c_name like ‘孙%’;
select * from t_student where c_name like ‘孙_’;
范围查询
in 表示在一个非连续的范围内 , 可以使用 or 实现
select * from t_students where id in(1,3,8);
between … and … 表示在一个连续的范围内,可以使用 and 实现
空判断
在数据库中,允许在数据添加是没有数据,使用空值来表示。 空值不等于0,也不等于‘’,需要使用特殊的判断方式
判断空值
语法:is null
select * from t_student where c_age is null;
判断非空值 语法:is not null
查询结果排序
排序是一个在查询数据时非常重要的操作。比如买东西时,想按一定的条件进行有序显示。就需要使用排序
asc(默认) 升序 / desc 降序 语法:select * from 表名 order by 列1 asc|desc [,列2 asc|desc,…]
单字段排序
select * from t_student order by c_age;
select * from t_student order by c_age asc;
默认使用就是升序排序,可以不指定 asc ,效果相同。
多字段排序 可以对多个字段进行排序,只需将字段的排序方式依次写在 order by 后面即可,字段间使用逗号分隔
select * from t_student order by c_age desc,c_id asc;
分页查询
查询数据库时,由于数据较多,在显示过程中不可能将数据全部显示。 可以使用分页查询,只显示指定的一部分数据 语法:select from 表名 limit start=0,count *说明
从start开始,获取count条数据
start默认值为0
需要获取数据的前n条的时候可以直接写 limit n
select * from t_student limit 3;
select * from t_student limit 2,3;
查询第 N 页 M 条数据,可以通过公式算出:(N - 1) * M
在MySQL中提供了一些定义好的函数,利用这些函数提供对数据的统计功能。 常用的聚合函数如图:
能够对多张数据表进行查询操作
在数据库操作中,数据往往不是存在一张表中的,同一个项目中,根据设计范式,数据可能分散在不同的多张表中,这时查询数据时,就需要多表查询。
普通多表查询(无意义)
作用:直接将表放在from后面,进行读取。 语法:select 表名.字段 … from 表名1,表名2…
select * from t_student,t_class;
这种查询方式没有任何意义。
在查询时,数据库会将表1中的数据逐条和表2中的所有数据连接,组成一条新记录。
查询的结果为 M * N 条,实际就是笛卡尔积结果。
多表查询连接条件
在多表个表进行查询时,表与表之间应该是有有关系的,一般会以外键的形式来建立表间的关系。 查询时按照条件建立记录的匹配规则。 比如学生表中保存了学生的信息和所在班级的ID,班级表中保存了班级的信息。 在查询学生的班级信息时,可以通过学生表中的班级ID和班级表中的ID匹配进行查询
select t_student.c_name,t_class.c_name from t_student,t_class where t_student.c_class_id = t_class.c_id;
表别名
在多表操作时,由于表的名字比较长,在写SQL语句时非常不方便。可以在查询 时,给表起个别名,代替表名来操作
语法:select 别名.字段名… from 表1 as 表1别名,表2 表2别名… [条件]
select ts.c_name as ‘姓名’ , tc.c_name ‘班级名’ from t_student as ts,t_class tc where ts.c_class_id = tc.c_id;
内连接查询
作用:查询的结果为两个表匹配到的数据 语法:select * from 表1 inner join 表2 on 表1.列 运算符 表2.列
数据库默认的连接方式就是内连接查询, inner join 可以不显示的写出来。 这种连接方式会以笛卡尔积的形式进行连接。 所以在连接时,必须要给定连接条件。 连接条件使用 on 进行指定。尽量不要使用 where,where在其它连接方式时,指定的连接条件无效。
select ts.c_name, tc.c_name from t_student as ts inner join t_class tc on ts.c_class_id = tc.c_id;
左连接查询
作用:查询的结果为根据左表中的数据进行连接,如果右表中没有满足条件的记录,则连接空值。
语法:select * from 表1 left join 表2 on 表1.列 运算符 表2.列
select ts.c_name, tc.c_name from t_student as ts left join t_class tc on ts.c_class_id = tc.c_id;
右连接查询(实际工作中用的少)
作用:查询的结果为根据右表中的数据进行连接,如果左表中没有满足条件的记录,则连接空值。
语法:select * from 表1 right join 表2 on 表1.列 运算符 表2.列
select ts.c_name, tc.c_name from t_student as ts right join t_class tc on ts.c_class_id = tc.c_id;
在实际工作中,右连接使用的非常少,因为左连接完全可以替代右连接,在连接过程中,只需要调整表的顺序即可。
子查询
作用:在一个 select 语句中,嵌入了另外一个 select 语句, 那么被嵌入的 select 语句称之为子查询语句
语法:select * from 表1 where 条件 运算符 (select 查询)
外部那个select语句则称为主查询
主查询和子查询的关系
列级子查询 作用:子查询返回的结果是一列(一列多行) 语法:主查询 where 条件 in (列子查询) 示例:查询所有学生所在班级的班级名称
找出学生表中所有的班级 id
找出班级表中对应的名字
行级子查询 作用:子查询返回的结果是一行(一行多列) 语法:主查询 where (字段1,2,…) = (行子查询) 示例:查找班级年龄最大,所在班号最小的的学生
找出最大年龄和最小班号
找出年龄和班号满足条件的学生
select * from t_student where(c_age,c_class_id) = (select max(c_age),min(c_class_id) from t_student);
自连接查询
作用:在查询数据时,只有一张表,查询时使用自己连接自己。 语法:select * from 表 as 表别名1 inner join 表 表别名2 on 表别名1.列 运算符 表别名2.列 where 条件
为什么需要自连接?
现要设计表结构来存储全国所有的省份和全国所有的市
设计省信息的表结构provinces
id 省的编号
ptitle 省名称
设计市信息的表结构citys
id 市编号
ctitle 市名称
proid 市所属的省的编号
citys表的proid表示城市所属的省,对应着provinces表的id值
如果需要查询一个省 比如广东省对应的所有的时的信息 ,我们可以使用两个表连接查询。
问题: 能不能将两个表合成一张表呢?
观察两张表发现,citys表比provinces表多一个列proid,其它列的类型都是一样的。存储的都是地区信息,而且每种信息的数据量有限,没必要增加一个新表,或者将来还要存储区、乡镇信息,都增加新表的开销太大。
那么将可以将两张表合为一张表来存储数据。
定义表areas,结构如下
id
atitle
pid
关于这个表的说明:
因为省没有所属的省份,所以可以填写为null
城市所属的省份pid,填写省所对应的编号id
这就是自关联,表中的某一列,关联了这个表中的另外一列,但是它们的业务逻辑含义是不一样的,城市信息的pid引用的是省信息的id
在这个表中,结构不变,可以添加区县、乡镇街道、村社区等信息
思考: 如果还是要查询广东省对应的所有的市的信息,咱们应该怎么做呢?
areas表和自身进行连接这种形式的连接 就成为自连接。
准备数据
创建areas表的语句如下: 注意,表所在的数据库字符集必须是utf8的,如果不是会导入数据出错
create table areas(
aid int primary key,
atitle varchar(20),
pid int
);
从sql文件中导入数据
source /home/python/Desktop/areas.sql;
示例1:查询一共有多少个省
select count() from areas where pid is null; -----(这里pid表示的是城市,除去城市,就是省份)
示例2: 查询省的名称为“山西省”的所有城市
select city. from areas as city inner join areas as province on city.pid=province.aid where province.atitle=‘山西省’;
示例3:查询市的名称为“广州市”的所有区县
select dis.* from areas as dis inner join areas as city on city.aid=dis.pid where city.atitle=‘广州市’;
这个过程和实际仓库操作相同,比如现在要去一个实际的仓库。
3. pymysql 常用方法
在 Pyhton 中,使用 Pymysql 模块来对数据库进行编程。
导入模块
from pymysql import connect
Connection 对象
创建连接对象 conn=connect(参数列表)
参数host:连接的mysql主机,如果本机是’localhost’
参数port:连接的mysql主机的端口,默认是3306
参数database:数据库的名称
参数user:连接的用户名
参数password:连接的密码
参数charset:通信采用的编码方式,推荐使用utf8
关闭连接 conn.close()
提交数据 conn.commit()
撤销数据 conn.rollback()
通过连接获取游标 cur = conn.cursor()返回Cursor对象,用于执行sql语句并获得结果
Cursor游标对象
获取Cursor对象 cur = conn.cursor()
使用游标执行SQL语句 cur.execute(operation , [parameters]) 执行SQL语句,返回受影响的行数,主要用于执行insert、update、delete语句
获取结果集中的一条 cur.fetchone() 返回一个元组 如 (1,‘妲己’,18)
获取结果集中的一条 cur.fetchmany(2) 返回一个元组 如 ((1,‘妲己’,18),2,‘公孙离’,20))
获取结果集中的所有 cur.fetchall() 返回结果集的所有行,一行构成一个元组,再将这些元组装入一个元组返回. 如((1,‘妲己’,18),(2,‘公孙离’,20),(3,‘姜子牙’,28))
关闭游标 cur.close()
知道如何使用 pymysql 对数据库进行CRUD
-- 创建数据库
create database python_db charset=utf8;
-- 使用数据库
use python_db;
-- students表
create table students(
id int unsigned primary key auto_increment not null,
name varchar(20) default '',
age tinyint unsigned default 0,
height decimal(5,2),
gender enum('男','女','中性','保密') default '保密',
cls_id int unsigned default 0,
is_delete int default 0
);
-- classes表
create table classes (
id int unsigned auto_increment primary key not null,
name varchar(30) not null
);
-- 向students表中插入数据
insert into students values
(0,'小明',18,180.00,2,1,0),
(0,'小月月',18,180.00,2,2,1),
(0,'彭于晏',29,185.00,1,1,0),
(0,'刘德华',59,175.00,1,2,1),
(0,'黄蓉',38,160.00,2,1,0),
(0,'凤姐',28,150.00,4,2,1),
(0,'王祖贤',18,172.00,2,1,1),
(0,'周杰伦',36,NULL,1,1,0),
(0,'程坤',27,181.00,1,2,0),
(0,'刘亦菲',25,166.00,2,2,0),
(0,'金星',33,162.00,3,3,1),
(0,'静香',12,180.00,2,4,0),
(0,'郭靖',12,170.00,1,4,0),
(0,'周杰',34,176.00,2,5,0);
-- 向classes表中插入数据
insert into classes values (0, "python_01期"), (0, "python_02期");
# 导入模块
from pymysql import connect
# 连接数据库
conn = connect(host='localhost', port=3306, database='python_db', user='root', password='123123',charset='utf8')
# 获取游标
cur = conn.cursor()
# 以字符串形式书写SQL语句,因为SQL语句中也会出现字符串,所以建议使用 ```引号形式将SQL诗句引起来
sql_str = '''select * from students;'''
# 执行SQL语句
row_count = cur.execute(sql_str)
# 显示执行 SQL 语句影响的行数
print(row_count)
# 获取一条记录
row_one = cur.fetchone()
# 显示获取的记录
print(row_one)
# 获取多条记录
row_many = cur.fetchmany(4)
# 遍历输出所有的结果
for t in row_many:
print(t)
# 获取所有的数据
row_all = cur.fetchall()
# 遍历输出所有的结果
for t in row_all:
print(t)
# 关闭游标
cur.close()
# 关闭数据库
conn.close()
注意:因为在获取数据时,游标是移动的,所以前面取过的数据,后面不会再取了。
# 导入模块
from pymysql import connect
# 连接数据库
conn = connect(host='localhost', port=3306, database='python_db', user='root', password='123123',charset='utf8')
# 获取游标
cur = conn.cursor()
# 以字符串形式书写SQL语句
# sql_str = '''insert into students values(0,'新来的',20,180,'男',1,1)'''
# sql_str = '''update students set name = '王钢蛋' where name = '新来的'; '''
sql_str = '''delete from students where name='王钢蛋'; '''
# 执行SQL语句
row_count = cur.execute(sql_str)
# 在执行增删改操作时,需要向数据库提交操作,否则操作不成功
conn.commit()
# 关闭游标
cur.close()
# 关闭数据库
conn.close()
# 导入模块
from pymysql import connect
# 连接数据库
conn = connect(host='localhost', port=3306, database='python_db', user='root', password='123123',charset='utf8')
# conn.autocommit(True)
# 获取游标
cur = conn.cursor()
# 以字符串形式书写SQL语句
sql_str = '''insert into students values(0,'新来的',20,180,'男',1,1)'''
#插入10条数据
for i in range(10):
# 执行SQL语句
row_count = cur.execute(sql_str)
# 在执行增删改操作时,如果不想提交前面的修改操作,可以使用 rollback 回滚取消操作
conn.rollback()
# 关闭游标
cur.close()
# 关闭数据库
conn.close()
学习目标
知道什么是SQL注入
知道如何避免SQL注入
产生原因: 后台对用户提交的带有恶意的数据和 SQL 进行字符串方式的拼接,得到了脱离原意的 SQL 语句,从而影响了 SQL 语句的语义,最终产生数据泄露的现象
如何防止: SQL 语句的参数化, 将 SQL 语句的所有数据参数存在一个列表中传递给 execute 函数的第二个参数
注意:
此处不同于python的字符串格式化,必须全部使用%s占位
# 导入模块
from pymysql import connect
find_name = input("请输姓名:")
# 连接数据库
conn = connect(host='localhost', port=3306, database='python_db', user='root', password='123123', charset='utf8')
# 获得Cursor对象
cur = conn.cursor()
# # # 非安全的方式
# # # 输入 "小明" or 1
# sql = '''select * from students where name=%s''' % find_name
# print("""sql===>%s<====""" % sql)
# # 执行select语句,并返回受影响的行数:查询所有数据
# count = cur.execute(sql)
# 安全的方式
# 构造参数列表
params = [find_name]
sql = '''select * from students where name=%s;'''
print("""sql===>%s<====""" % sql)
# 执行select语句,execute方法在内部实现了防SQL注入的功能,但具体如何实现并不清楚,隐藏了细节
count = cur.execute(sql, params)
# 注意:
# 如果要是有多个参数,需要进行参数化
# 那么params = [数值1, 数值2....],此时sql语句中有多个%s即可
# 打印受影响的行数
print(count)
# 获取查询的结果
result = cur.fetchall()
# 打印查询的结果
print(result)
# 关闭Cursor对象
cur.close()
# 关闭Connection对象
conn.close()
练习
使用 pymysql 模块完成练习
1. 准备工作
准备数据
创建数据库
create database JDDB charset=utf8;
use JDDB
导入数据
source JDDB.sql
2. 代码结构
功能列表
print(“1查询所有商品信息”)
print(“2查询所有商品所在种类信息”)
print(“3查询所有商品所在品牌信息”)
print(“4添加商品种类”)
print(“5根据id查询商品信息”)
print(“6根据id查询商品信息安全方式”)
方法命名
#查询所有商品信息
def fetch_all_info():
pass
#查询种类信息
def fetch_cate():
pass
#查询品牌信息
def fetch_brand():
pass
# 添加一个商品类型
def add_info(type):
pass
# 通过ID 查找商品
def find_info(id):
pass
# 通过ID 查找商品 防SQL注入
def find_info_safe(id):
pass
class JD(object):
"""JD 类,提供商品查询服务"""
# 将数据库连接操作放到初化方法中,对象创建时,自动连接数据库
def __init__(self):
# 连接数据库
self.__conn = connect(host='localhost', port=3306, database='JDDB', user='root', password='123123', charset='utf8')
# 获取游标
self.__cur = self.__conn.cursor()
# 将数据库关闭操作放到 __del__方法中,当对象销毁时,自动关闭数据库
def __del__(self):
# 关闭游标
self.__cur.close()
# 关闭数据库
self.__conn.close()
实现 run 方法 因为需要重复选择,所以要用死循环
# run 方法,提供显示接口
def run(self):
while True:
print("1查询所有商品信息")
print("2查询所有商品在种类信息")
print("3查询所有商品在品牌信息")
print("4添加商品种类")
print("5根据id查询商品信息")
print("6根据id查询商品信息安全方式")
selectID = input('请输入要执行的功能编号:')
if selectID == '1':
# 查询所有商品信息
self.fetch_all_info()
elif selectID == '2':
# 查询种类信息
self.fetch_cate()
elif selectID == '3':
# 查询品牌信息
self.fetch_brand()
elif selectID == '4':
# 添加一个商品类型
self.add_info()
elif selectID == '5':
# 通过ID 查找商品
self.find_info()
elif selectID == '6':
# 通过ID 查找商品 防SQL注入
self.find_info_safe()
else:
print('输入行号不正确!')
实现一个显示方法,用来输出结果
#用来显示结果的方法,私有,对外不可见
def __show_result(self, result):
for t in result:
print(t)
实现查询所有商品方法
#查询所有商品信息
def fetch_all_info(self):
sql_str = ''' select * from goods;'''
self.__cur.execute(sql_str)
self.__show_result(self.__cur.fetchall())
查询种类信息
# 查询种类信息
def fetch_cate(self):
sql_str = ''' select * from goods_cates;'''
self.__cur.execute(sql_str)
self.__show_result(self.__cur.fetchall())
查询品牌信息
# 查询品牌信息
def fetch_brand(self):
sql_str = ''' select * from goods_brands;'''
self.__cur.execute(sql_str)
self.__show_result(self.__cur.fetchall())
添加商品类型
# 添加商品类型
def add_info(self):
new_type = input('请输入商品类型:')
sql_str = ''' insert into goods_cates values(0,"%s");'''% new_type #注意,这里占位符要加引号
self.__cur.execute(sql_str)
self.__conn.commit() # 修改操作要手动提交
根据id 查找商品
# 根据id查找商品
def find_info(self):
id = input('请输入ID:')
sql_str = ''' select * from goods where id=%s;'''%id
self.__cur.execute(sql_str)
self.__show_result(self.__cur.fetchall())
根据id 查找商品,案例防注入
# 根据id查找商品,案例防SQL注入
def find_info_safe(self):
id = input('请输入ID:')
sql_str = ''' select * from goods where id=%s;'''
self.__cur.execute(sql_str,(id,))
self.__show_result(self.__cur.fetchall())
导语
视图是一种基于查询结果产生的虚拟表。
为什么要有视图?
当在执行查询操作时,经常会出现查询频率高并且查询语句非常复杂的情况。 每次都要将复杂的SQL语句重新书写,非常不便。
比如:显示每种商品的名字以及对应的商品类型名和品牌名
select goods.name,goods_cates.name,goods_brands.name from goods inner join goods_cates on goods.cate_id = goods_cates.id inner join goods_brands on goods.brand_id = goods_brands.id;
上面的功能实际很简单,但是书写起来很头疼,如果每天还要执行很多次。头大。。。
如果能将这个查询的结果保存下来,下次在使用时,直接使用结果就非常完美了。
这个需求通过视图就可以完成。
视图的特性
视图是一个虚拟表,实际它就是一条被封装起来的 SQL 查询语句。
在使用视图时,就相当执行了被封装的复杂SQL查询语句。
视图不存储具体的数据。
视图的基本表发生变化,那么视图也随之变化
视图操作
定义视图 视图在定义时,建议视图名称以 v_xxx 形式命名,以便和普通的表区分。
语法:create view 视图名称 as select语句;
create view v_goods_info as select goods.name as gname,goods_cates.name as gcname,goods_brands.name as gbname from goods inner join goods_cates on goods.cate_id = goods_cates.id inner join goods_brands on goods.brand_id = goods_brands.id;
查看视图
视图以表的形式体现,通过 show tables 即可查看视图。
使用视图
视图的作用就是用来查询,视图的作用类似将子查询做了封装。
select * from v_goods_info;
删除视图
语法: drop view 视图名称
drop view v_goods_info;
视图小结
视图封装了对多张基本表的复杂操作,简化用户操作
视图只是一个虚表,并不存储任何基本表的表数据,当用户使用视图的时候 视图会从基本表中取出
通过视图可以对用户展示指定字段从而屏蔽其他字段数据,更加安全
什么是索引?
能够快速查询数据的线索就称之为索引。
为什么需要索引?
思考: 如何在一个图书馆中找到一本书的?
在图书馆中如果没有其他辅助手段只能一条道走到黑,一本书一本书的扫。
终于经过1个小时的连续扫描发现你需要看的那本书在一分钟之前被人借走了。
这种方式是顺序查找。
图书馆管理员发现这个问题,于是决定减少这样的悲剧故事。 为同学们购置了一套图书馆管理系统。
大家要找书籍先在系统上查找到书籍所在的房屋编号和货架编号,然后就可以直接大摇大摆的去取书了。
这个房屋编号和货架编号就是索引。
除了词典,生活中随处可见索引的例子,如火车站的车次表、图书的目录等,它们的原理都是一样的。 通过不断的缩小想要获得数据的范围来筛选出最终想要的结果。
索引原理
索引的原理就是为无序存放的数据建立一个有序的对应关系,通过这个关系可以快速查找数据。
索引的目的在于提高查询效率。
索引的使用
查看表中已有索引 show index from 表名
创建索引 create index 索引名称 on 表名(字段名称(长度))
如果指定字段是字符串,需要指定长度,建议长度与定义字段时的长度一致
字段类型如果不是字符串,可以不填写长度部分
删除索引: drop index 索引名称 on 表名;
验证索引的效率
创建一个新表(不带索引)
create table test_index(title varchar(10));
准备10万条数据
from pymysql import connect
def main():
# 创建Connection连接
conn = connect(host='localhost',port=3306,database='jddb',user='root',password='123123',charset='utf8')
# 获得Cursor对象
cursor = conn.cursor()
# 插入10万次数据
for i in range(100000):
cursor.execute("insert into test_index values('ha-%d')" % i)
# 提交数据
conn.commit()
if __name__ == "__main__":
main()
查询验证
开启运行时间监测 set profiling=1;
查询 ha-99999 的数据 select * from test_index where title='ha-99999;
为表 test_index 创建索引 create index title_index on test_index(title(10));
再次查询 ha-99999 的数据 select * from test_index where title='ha-99999;
查看执行时间 show profiles;
使用 desc 命令也可以查看索引的效率 desc select * from test_index where title=‘ha-99999’;
6. 索引小结:
索引可以明显提高某些字段的查询效率。
但不是所有的表都需要建立索引
如果表中数据很少,没有必要建立索引
如果一个表中的数据增删很频繁,不能建立索引 ,因为只要数据发生增减,索引就要重新建立。增加了系统开销,反而慢了。
索引只适合查询操作频繁的表。
知道事务的作用
什么是事务?
事务 Transaction 是指作为一个基本工作单元执行的一系列SQL语句的操作,要么完全地执行,要么完全地都不执行。
为什么要有事务?
以下内容出自《高性能MySQL》第三版,了解事务的ACID有助于我们更好的理解事务运作。 下面举一个银行应用是解释事务必要性的一个经典例子。假如一个银行的数据库有两张表:支票表(checking)和储蓄表(savings)。现在要从用户Jane的支票账户转移200美元到她的储蓄账户,那么至少需要三个步骤:
上述三个步骤的操作必须打包在一个事务中,任何一个步骤失败,则必须回滚所有的步骤。
可以用START TRANSACTION语句开始一个事务,然后要么使用COMMIT提交将修改的数据持久保存,要么使用ROLLBACK撤销所有的修改。
事务SQL的样本如下:
1. start transaction;
2. select balance from checking where customer_id = 10233276;
3. update checking set balance = balance - 200.00 where customer_id = 10233276;
4. update savings set balance = balance + 200.00 where customer_id = 10233276;
5. commit;
事务的四大特性 ACID
原子性(Atomicity)
一致性(Consistency)
隔离性(Isolation)
持久性(Durability)
- 一旦事务提交,则其所做的修改会永久保存到数据库。(此时即使系统崩溃,修改的数据也不会丢失。)
事务操作
MySQL使用的InnoDB引擎支持事务操作。
开启事务 开启事务后执行修改命令,变更会维护到本地缓存中,而不维护到物理表中
begin; 或 start transaction;
提交事务
将缓存中的数据变更维护到物理表中
commit;
回滚事务
放弃缓存中变更的数据 表示事务执行失败 应该回到开始事务前的状态
rollback;
验证事务提交
为了查看效果,需要打开两个终端窗口,使用同一个数据库,操作同一张表。
step 1: 两个终端同时连接同一个数据库并查询同一张表 select * from goods_cates;
step 2: 终端1开启事务,插入数据,查看
begin;
insert into goods_cates(name) values('手机')
select * from goods_cates; -- 插入的数据显示插入成功了
setp 3: 终端2查询数据
select * from goods_cates; -- 并没有看到新数据,因为终端1的操作缓存在本地,还没有提交。隔离性
step 4: 终端1提交数据,并查询
commit; -- 提交后,事务完成,原子性操作结束
select * from goods_cates;
step 5: 终端2查询
select * from goods_cates; -- 可以查到新数据,一致性,持久性。
step 1: 两个终端同时连接同一个数据库并查询同一张表 select * from goods_cates;
step 2: 终端1开启事务,删除所有数据,查看
begin;
insert into goods_cates(name) values('HIFI播放器');
select * from goods_cates; -- 插入的数据显示插入成功了
setp 3: 终端2查询数据
select * from goods_cates; -- 并没有看到新数据,因为终端1的操作缓存在本地,还没有提交。隔离性
step 4: 终端1因为各种原因不想或不能提交数据,取消之前的操作
rollback;
step 5: 终端2查询
select * from goods_cates; -- 没有查到新数据,一致性,持久性。
用户管理
之前在登陆数据时,一直使用的都是 root 用户。
root用户是数据库系统中的超级管理员,可以对数据库做任何操作。
在生产环境中,开发人员一般只是对数据做读取操作。root 用户对于普通人来说,权限太大了,如果不小心做了一些不可逆的操作。那么后果是非常严重的。
所以 root 用户不会让开发人员使用,一般会由DBA或运维人员统一管理。给开发人员针对项目的数据库建立专门的用户来操作。
查看所有用户
MySQL中所有的用户及权限信息都存储在MySQL数据库的user表中。
通过 desc user; 可以查看user表的结构
主要字段:
show grants for testuser@localhost;
修改权限
语法:grant 权限名称 on 数据库 to 账户@主机 with grant option;
– 将testuser权限改为对所有库有所有权限
grant all privileges on . to ‘testuser’@‘localhost’ with grant option;
flush privileges;
修改用户密码
直接使用 update 对user 表中的用户进行修改密码。 注意: 密码需要使用 password(‘新密码’) 来进进加密
update user set authentication_string=password(‘新密码’) where user=‘用户名’;
删除用户
语法:drop user 用户名@主机地址 drop user ‘testuser’@‘localhost’; 或 delete from user where user=‘用户名’;
小结
用户管理是MySQL当中一块非常重要的内容,它涉及到了数据的安全问题。
但是对于开发人员来讲,这块内容在实际工作中涉及又不多,因为数据库的维护会有专门的运维人员或DBA来完成。
一般在操作数据时,不会使用root用户。DBA会针对不同项目需求给你专门创建用户,甚至是视图。
数据库高级部分的内容,大家主要做一个了解即可。当遇到问题时,要知道如何解决。或者可以快速找到解决办法。
对于数据库编程。对表的CURD才是重要的。
主从配置
主从同步使得数据可以从一个数据库服务器复制到其他服务器上,在复制数据时,一个服务器充当主服务器(master),其余的服务器充当从服务器(slave)。因为复制是异步进行的,所以从服务器不需要一直连接着主服务器,从服务器甚至可以通过拨号断断续续地连接主服务器。通过配置文件,可以指定复制所有的数据库,某个数据库,甚至是某个数据库上的某个表。
使用主从同步的好处:
提供服务可用性
通过增加从服务器来提高数据库的性能,在主服务器上执行写入和更新,在从服务器上向外提供读功能,可以动态地调整从服务器的数量,从而调整整个数据库的性能。
提高数据安全,因为数据已复制到从服务器,从服务器可以终止复制进程,所以,可以在从服务器上备份而不破坏主服务器相应数据
主从同步的机制
Mysql服务器之间的主从同步是基于二进制日志机制,主服务器使用二进制日志来记录数据库的变动情况,从服务器通过读取和执行该日志文件来保持和主服务器的数据一致。
在使用二进制日志时,主服务器的所有操作都会被记录下来,然后从服务器会接收到该日志的一个副本。从服务器可以指定执行该日志中的哪一类事件(譬如只插入数据或者只更新数据),默认会执行日志中的所有语句。
每一个从服务器会记录关于二进制日志的信息:文件名和已经处理过的语句,这样意味着不同的从服务器可以分别执行同一个二进制日志的不同部分,并且从服务器可以随时连接或者中断和服务器的连接。
主服务器和每一个从服务器都必须配置一个唯一的ID号(在my.cnf文件的[mysqld]模块下有一个server-id配置项),另外,每一个从服务器还需要通过CHANGE MASTER TO语句来配置它要连接的主服务器的ip地址,日志文件名称和该日志里面的位置(这些信息存储在主服务器的数据库里)
备份恢复-配置主从需要了解的一个小知识
备份
运行mysqldump命令
mysqldump –uroot –p 数据库名 > python.sql;
按提示输入mysql的密码
恢复
连接mysql,创建新的数据库
退出连接,执行如下命令
mysql -uroot –p 新数据库名 < python.sql
配置主从同步的步骤
有很多种配置主从同步的方法,可以总结为如下的步骤:
在主服务器上,必须开启二进制日志机制和配置一个独立的ID
在每一个从服务器上,配置一个唯一的ID,创建一个用来专门复制主服务器数据的账号
在开始复制进程前,在主服务器上记录二进制文件的位置信息
如果在开始复制之前,数据库中已经有数据,就必须先创建一个数据快照(可以使用mysqldump导出数据库,或者直接复制数据文件)
配置从服务器要连接的主服务器的IP地址和登陆授权,二进制日志文件名和位置
主和从的身份可以自己指定,我们将虚拟机Ubuntu中MySQL作为主服务器,可以考虑将Windows中的MySQL作为从服务器。 在主从设置前,要保证Ubuntu与Windows间的网络连通,都使用桥接连接即可-请确认你的虚拟机工作正常。
备份主服务器原有数据到从服务器
如果在设置主从同步前,主服务器上已有大量数据,可以使用mysqldump进行数据备份并还原到从服务器以实现数据的复制。
在主服务器Ubuntu上进行备份,执行命令:
mysqldump -uroot -pmysql --all-databases --lock-all-tables > ~/master_db.sql
说明
-u :用户名
-p :示密码
–all-databases :导出所有数据库
–lock-all-tables :执行操作时锁住所有表,防止操作时有数据修改
~/master_db.sql :导出的备份数据(sql文件)位置,可自己指定
在从服务器Windows上进行数据还原
找到Windows上mysql命令的位置
新打开的命令窗口,在这个窗口中可以执行类似在Ubuntu终端中执行的mysql命令
将从主服务器Ubuntu中导出的文件复制到从服务器Windows中,可以将其放在上面mysql命令所在的文件夹中,方便还原使用
在刚打开的命令黑窗口中执行还原操作:
mysql –uroot –pmysql < master_db.sql
配置主服务器master(Ubuntu中的MySQL)
编辑设置mysqld的配置文件,设置log_bin和server-id
sudo vim /etc/mysql/mysql.conf.d/mysqld.cnf
重启mysql服务
sudo service mysql restart
登入主服务器Ubuntu中的mysql,创建用于从服务器同步数据使用的帐号
mysql –uroot –pmysql
GRANT REPLICATION SLAVE ON . TO ‘slave’@’%’ identified by ‘slave’;
FLUSH PRIVILEGES;
获取主服务器的二进制日志信息
SHOW MASTER STATUS;
File为使用的日志文件名字,Position为使用的文件位置,这两个参数须记下,配置从服务器时会用到
配置从服务器slave(Windows中的MySQL)
找到Windows中MySQL的配置文件
编辑my.ini文件,将server-id修改为2,并保存退出。
打开windows服务管理
可以在开始菜单中输入services.msc找到并运行
在打开的服务管理中找到MySQL57,并重启该服务
进入windows的mysql,设置连接到master主服务器
change master to master_host=‘10.211.55.5’, master_user=‘slave’, master_password=‘slave’,master_log_file=‘mysql-bin.000006’, master_log_pos=590;