目录
- pymysql
- 1.基本操作
- 1.新建表
- 2.增加数据
- 1.execute添加单个
- 2.executemany添加多个
- 3.删除数据
- 4.更改数据
- 5.查找数据
- 1.fetchone()`偏移寻找一条
- 2.cursor.fetchmany()
- 3.cursor.fetchall()
- 6.断开连接
- 2.sql注入
- 解决方法
- 3.创建大数据库
- 索引
- 定义
- 作用
- 实现原理
- B+树的性质
- 应该尽可能的将数据量小的字段作为索引
- 最左匹配原则*
- B+树的分类
- 聚集索引*
- InnoDB存储引擎表示索引组织表,即表中数据按照主键顺序存放。而聚集索引(clustered index)就是按照每张表的主键构造一棵B+树,同时叶子结点存放的即为整张表的行记录数据,也将聚集索引的叶子结点称为数据页。聚集索引的这个特性决定了索引组织表中数据也是索引的一部分。同B+树数据结构一样,每个数据页都通过一个双向链表来进行链接。
- 如果未定义主键,MySQL取第一个唯一索引(unique)而且只含非空列(NOT NULL)作为主键,InnoDB使用它作为聚簇索引。
- 如果没有这样的列,InnoDB就自己产生一个这样的ID值,它有六个字节,而且是隐藏的,使其作为聚簇索引。
- 由于实际的数据页只能按照一棵B+树进行排序,因此每张表只能拥有一个聚集索引。在多少情况下,查询优化器倾向于采用聚集索引。因为聚集索引能够在B+树索引的叶子节点上直接找到数据。此外由于定义了数据的逻辑顺序,聚集索引能够特别快地访问针对范围值得查询。
- 辅助索引*
- 索引的种类
- 主键索引 primary key
- 唯一索引 unique(name)
- 唯一联合索引 uniqe (name, email)
- 普通索引 index(name)
- 联合索引 index(name,email)
- 索引的优缺点
- 索引不会命中
- explain
- 索引覆盖
- 慢查询日志
- 查看慢sql的相关变量
- 配置慢sql的变量
pymysql
pip安装模块pymysql
1.基本操作
连接 -- 设置游标 -- 执行sql -- 提交或获取结果 -- 关闭游标与连接
游标设置 : pymysql.cursors.DictCursor
游标取值与偏移:fetchone() | fetchmany(n) | scroll(n,'relative|absolute')
防注入:cursor.execute('select * from user where usr=%s and pwd=%s', (usr, pwd))
提交操作结果:conn.commit()
# 模块pymysql
# 按照并导入pymysql: pip3 insatll pymysql
# 通过pymysql操作数据库分四步:
1.新建表
# pymysql 连接数据库的必要操作 主机/端口/用户名/密码/数据库
# 注: pymysql不能提供创建数据库的服务,数据库要提前创建
import pymysql
# 1. 建立数据库连接对象 coon
conn = pymysql.connect(user='root',password='',database='oldboy')
# 输入自身数据库的用户名密码与数据库,建立连接.
# 2. 通过coon创建操作sql的游标对象
cursor = conn.cursor()
# 3.编写sql 交给cursor执行
# 创建表的sql语句
sql = 'create table t1 (id int,x int ,y int);'
cursor.execute(sql)
''' 创建表成功 cmd 中查看
mysql> show tables;
+------------------+
| Tables_in_oldboy |
+------------------+
| t1 |
+------------------+
1 row in set (0.00 sec)'''
2.增加数据
import pymysql
# 1. 建立数据库连接对象 coon
conn = pymysql.connect(user='root',password='',database='oldboy')
# 如果加上autocommit=true 是为不开启事务,后面的conn.commit()可省略直接添加只至数据库(不安全)
# 2. 通过coon创建操作sql的游标对象
cursor = conn.cursor()
# 3.增加数据
1.execute添加单个
sql2 = 'insert into t1 values (%s,%s,%s)'
cursor.execute(sql2,(1,110,200))
cursor.execute(sql2,(2,220,200))
#重点 : 在创建conn对象时,不设置autocommit=true,默认开启事务,增删改操作不会直接映射到数据库中
conn.commit()
2.executemany添加多个
sql2 = 'insert into t1 values (%s,%s,%s)'
cursor.executemany(sql2,[(1,2,3),(4,5,6)]) # 增加多个数据,列表中套元组
conn.commit()
3.删除数据
import pymysql
# 1. 建立数据库连接对象 coon
conn = pymysql.connect(user='root',password='',database='oldboy')
# 如果加上autocommit=true 是为不开启事务,后面的conn.commit()可省略直接添加只至数据库(不安全)
# 2. 通过coon创建操作sql的游标对象
cursor = conn.cursor()
# 3.删除数据
# 删除id = 4 的一行数据
sq3 = 'delete from t1 where id = %s'
cursor.execute(sq3,4)
conn.commit()
4.更改数据
# sql 语句
sq4 = 'update t1 set x=888,y=666 where id = %s'
cursor.execute(sq4,1)
conn.commit()
5.查找数据
如果是查询,通过cursor对象获取结果
# 游标不设置参数,查询结果是元组
fetchall() : 取出所有的数据 返回的是列表套字典
fetchone() : 取出一条数据 返回的是字典
fetchmany(size) : 取出size条数据 返回的是列表套字典
# cursor = conn.cursor(cursor=pymysql.cursors.DictCursor)
游标设置参数 获取字典 cursor=pymysql.cursors.DictCursor
1.fetchone()`偏移寻找一条
import pymysql
# 1. 建立数据库连接对象 coon
conn = pymysql.connect(user='root',password='',database='oldboy')
# 2. 通过coon创建操作sql的 游标对象
cursor = conn.cursor(cursor=pymysql.cursors.DictCursor)
# 3.查询数据
sq5 = 'select * from t1'
res = cursor.execute(sq5)
sq5 = 'select * from t1'
res = cursor.execute(sq5) # 返回值是受影响的行(3行数据)
r1 = cursor.fetchone()
print(r1) # {'id': 1, 'x': 888, 'y': 666}
r2 = cursor.fetchone() # 一起查询会第二个跟着打印
print(r2) # {'id': 2, 'x': 220, 'y': 200}
2.cursor.fetchmany() 指定获取数量
sq5 = 'select * from t1'
res = cursor.execute(sq5) # 返回值是受影响的行(3行数据)
r1 = cursor.fetchmany(2)
print(r1)
# [{'id': 1, 'x': 888, 'y': 666}, {'id': 2, 'x': 220, 'y': 200}]
3.cursor.fetchall() 获取全部
sq5 = 'select * from t1'
res = cursor.execute(sq5) # 返回值是受影响的行(3行数据)
r1 = cursor.fetchall()
print(r1)
# [{'id': 1, 'x': 888, 'y': 666}, {'id': 2, 'x': 220, 'y': 200}, {'id': 1, 'x': 888, 'y': 666}]
6.断开连接
# 操作完毕,端口操作与连接关闭
cursor.close
conn.close
2.sql注入
通过书写sql包含(注释相关的)特殊字符, 让原有的sql执行顺序发生改变, 从而改变执行得到的sql
就是在输入字符串中嵌入SQL指令,在设计程序中忽略了对特殊字符串的检查,这些嵌入的指令便会被误认为正常的SQL指令,在数据库中执行,因此可以对后台数据库进行查看等工作,甚至破坏后台数据库造成严重后果。
我们永远不要信任用户的输入,我们必须认定用户输入的数据都是不安全的,我们都需要对用户输入的数据进行过滤处理。
# sql 注入
import pymysql
# 用户输入
user = input('输入用户名:')
pwd = input('输入密码:')
# 连接数据库的参数
conn = pymysql.connect(host='localhost',user='root',password='',database='oldboy',charset='utf8')
cursor = conn.cursor(cursor=pymysql.cursors.DictCursor)
sql = "select * from t2 where name='%s' and password='%s'" % (user,pwd)
cursor.execute(sql)
# 查找数据
res = cursor.fetchall()
print(res)
# 关闭连接接口
cursor.close()
conn.close()
if res:
print('登录成功')
else:
print('登录失败')
当输入正确用户密码的时候会登录成功,错误的也会失败
但当用户名胡乱输入随意字符串' or 1=1 #
也会登录成功
1=1 为恒真表达式 # 将后面的代码不带执行,
没有对用户输入的字符进行过滤限制
解决方法
更改参数的传入方式,将用户输入的字符,交由`execute`进行校验
...
cursor = conn.cursor(cursor=pymysql.cursors.DictCursor)
sql = "select * from t2 where name=%s and password=%s"
cursor.execute(sql,(user,pwd))
...
3.创建大数据库
//创建user表
create table user(
id int auto_increment primary key ,
name varchar(20) not null default '',
email varchar(50) not null default ''
)charset utf8;
// python 中使用模块
import pymysql
# 1. 建立数据库连接对象 coon
conn = pymysql.connect(user='root',password='',database='aaa')
# 2. 通过coon创建操作sql的 游标对象
cursor = conn.cursor(cursor=pymysql.cursors.DictCursor)
sql = "insert into user(name,email) values (%s,%s)"
# 定义接受字典
data = []
for i in range(3000000):
info = ('zekai'+str(i),'zekai'+str(i)+'@qq.com')
data.append(info)
cursor.executemany(sql,data) # 新增多条数据
conn.commit()
cursor.close()
conn.close()
// 查询表中数据
select * from user; // 会闪屏,慎用
select count(id) from user; // 查看id 计数
/*
+-----------+
| count(id) |
+-----------+
| 3000000 |
+-----------+
1 row in set (1.31 sec)*/
创建索引之前查询速度
select * from user where name='zekai2239999';
/*
+---------+--------------+---------------------+
| id | name | email |
+---------+--------------+---------------------+
| 2240000 | zekai2239999 | zekai2239999@qq.com |
+---------+--------------+---------------------+
1 row in set (1.91 sec)
添加索引
// 添加索引
create index ix_name on user(name);
/*
Query OK, 0 rows affected (16.68 sec)
Records: 0 Duplicates: 0 Warnings: 0
*/
之后查询
select * from user where name='zekai2239999';
/*
+---------+--------------+---------------------+
| id | name | email |
+---------+--------------+---------------------+
| 2240000 | zekai2239999 | zekai2239999@qq.com |
+---------+--------------+---------------------+
1 row in set (0.00 sec)
索引
定义
在关系数据库中,索引是一种单独的物理的对数据表中的一列或多列的值进行排序的一种存储结构;也称之为key
作用
对数据库操作最频繁的是查询操作,索引是提升查询效率最有效的手段,通过缩小查询范围来提高查询效率.
缩小查询范围 => 降低查询次数 => 降低IO操作(耗时) => 提高运行速度(效率)
实现原理
底层原理-- B+树 与 二分查找法
数据库中的索引,实现思路与字典是一致的,先定位到章,然后定位到章下一小结.需要一个独立的存储结构,专门存储索引数据
本质上索引是通过不断的缩小查询范围来提高查询效率
索引是独立于真实数据的一个存储结构,这个结构到底是什么样的?
索引最终的目的是要尽可能降低io次数,减少查找的次数,以最少的io找到需要的数据,此时B+树闪亮登场
光有数据结构还不行,还需要有对应的算法做支持,就是二分查找法
有了B+数据结构后查找数据的方式就不再是逐个的对比了,而是通过二分查找法来查找(**流程演示**)
另外,其实大多数文件系统都是使用B+是来完成的!
操作
B+树的性质
应该尽可能的将数据量小的字段作为索引
通过分析可以发现在上面的树中,查找一个任何一个数据都是3次IO操作, 但是这个3次并不是固定的,它取决于数结构的高度,目前是三层,如果要存储新的数据比99还大的数据时,发现叶子节点已经不够了必须在上面加一个子节点,由于树根只能有一个,所以整个数的高度会增加,一旦高度增加则 查找是IO次数也会增加,所以:
应该尽可能的将数据量小的字段作为索引,这样一个叶子节点能存储的数据就更多,从而降低树的高度;
例如:name
和id
,应当将id设置为索引而不是name
最左匹配原则*
当b+树的数据项是复合的数据结构,比如(name,age,sex)的时候(多字段联合索引),b+树会按照从左到右的顺序来建立搜索树,比如当(张三,20,F)这样的数据来检索的时候,b+树会优先比较name来确定下一步的所搜方向,如果name相同再依次比较age和sex,最后得到检索的数据;但当(20,F)这样的没有name的数据来的时候,b+树就不知道下一步该查哪个节点,因为建立搜索树的时候name就是第一个比较因子,必须要先根据name来搜索才能知道下一步去哪里查询。比如当(张三,F)这样的数据来检索时,b+树可以用name来指定搜索方向,但下一个字段age的缺失,所以只能把名字等于张三的数据都找到,然后再匹配性别是F的数据了, 这个是非常重要的性质,即索引的最左匹配特性。
B+树的分类
数据库中的B+树索引可以分为聚集索引和辅助索引
- 相同点: 内部形式都是B+树的形式,即高度是平衡的,叶子节点存放着所有的数据.
- 不同点: 叶子节点存放的是否是 一整行的信息
聚集索引*
InnoDB存储引擎表示索引组织表,即表中数据按照主键顺序存放。而聚集索引(clustered index)就是按照每张表的主键构造一棵B+树,同时叶子结点存放的即为整张表的行记录数据,也将聚集索引的叶子结点称为数据页。聚集索引的这个特性决定了索引组织表中数据也是索引的一部分。同B+树数据结构一样,每个数据页都通过一个双向链表来进行链接。
如果未定义主键,MySQL取第一个唯一索引(unique)而且只含非空列(NOT NULL)作为主键,InnoDB使用它作为聚簇索引。
如果没有这样的列,InnoDB就自己产生一个这样的ID值,它有六个字节,而且是隐藏的,使其作为聚簇索引。
由于实际的数据页只能按照一棵B+树进行排序,因此每张表只能拥有一个聚集索引。在多少情况下,查询优化器倾向于采用聚集索引。因为聚集索引能够在B+树索引的叶子节点上直接找到数据。此外由于定义了数据的逻辑顺序,聚集索引能够特别快地访问针对范围值得查询。
简单总结:
聚焦索引的特点:
叶子节点保存的就是完整的一行记录,如果设置了主键,主键就作为聚集索引,
如果没有主键,则找第一个NOT NULL 且QUNIQUE的列作为聚集索引,
如果也没有这样的列,innoDB会在表内自动产生一个聚集索引,它是自增的
聚集索引的好处:
1. 对主键的排序查找和范围查找速度非常快,叶子节点的数据就是用户所要查询的数据.
2. 范围查找,如果要查找主键某一范围内的数据,通过叶子节点的上层中间节点就可以得到页的范围,之后直接读取数据页.
辅助索引*
除了聚集索引之外的索引都称之为辅助索引或第二索引,包括 foreign key
与 unique
辅助索引的特点:
其叶子节点不包含行记录的全部数据,保存的是索引数据与所在行的主键值,InnoDB用这个 主键值来从聚集索引中搜查找数据
覆盖索引
覆盖索引指的是需要的数据仅在辅助索引中就能找到:
#假设stu表的name字段是一个辅助索引
select name from stu where name = "jack";
这样的话则不需要在查找聚集索引数据已经找到
回表
如果要查找的数据在辅助索引中不存在,则需要回到聚集索引中查找,这种现象称之为回表
# name字段是一个辅助索引 而sex字段不是索引
select sex from stu where name = "jack";
需要从辅助索引中获取主键的值,在拿着主键值到聚集索引中找到sex的值
查询速度对比:
聚集索引 > 覆盖索引 > 非覆盖索引
索引的种类
主键索引 primary key
加速查找 + 不能重复 + 不能为空 primary key
添加主键索引
// 1.新建主键索引
create table xxx(
id int auto_increment,
primary key(id)
)charset utf8;
// 2. 添加主键索引
// 创建t1表
create table t1(id int);
//法1:
alter table xxx change id id int auto_increment primary key;
//法2:
alter table t1 add primary key(id);
//修改表字段添加主键索引
alter table t1 modify id int primary key;
删除主键
// 删除主键索引 必须使自增长不存在
删除主键索引
alter table t1 drop primary key;
修改表字段使自增长去除
alter table t1 modify id int primary key;
唯一索引 unique(name)
加速查找 + 不能重复 unique(name)
新增唯一索引
//1.创建时添加
create table t2(
id int auto_increment primary key,
name varchar(32) not null default '',
unique u_name(name) //唯一索引赋值名称 u_name
)charset utf8;
// 2.在已存在的表创建
create unique index 索引名 on 表名(字段名);
create unique index ix_name on t2(name);
/*Query OK, 0 rows affected (0.01 sec)
Records: 0 Duplicates: 0 Warnings: 0*/
//3.添加索引
alter table 表名 add unique index 索引名(字段名);
alter table t2 add unique index ix_name(name);
删除唯一索引
alter table t2 drop index u_name;
/*
Query OK, 0 rows affected (0.01 sec)
Records: 0 Duplicates: 0 Warnings: 0 */
唯一联合索引 uniqe (name, email)
uniqe (name, email)
不能重复
类似于ti(a,b) 值 1,2
下一条数据不能与a,b相等
可以a,c
普通索引 index(name)
加速查找 index(name)
新增普通索引
//1.创建时添加
create table t3(
id int auto_increment primary key,
name varchar(32) not null default '',
index u_name(name) //唯一索引赋值名称 u_name
)charset utf8;
// 2.在已存在的表创建
create index 索引名 on 表名(字段名);
create index ix_name on t3(name);
/*Query OK, 0 rows affected (0.01 sec)
Records: 0 Duplicates: 0 Warnings: 0*/
//3.添加索引
alter table 表名 add index 索引名(字段名);
alter table t3 add index ix_name(name);
删除普通索引
alter table t3 drop index ix_name;
/*
Query OK, 0 rows affected (0.01 sec)
Records: 0 Duplicates: 0 Warnings: 0 */
联合索引 index(name,email)
可以重复
什么时候会创建联合索引?
根据公司的业务场景, 在最常用的几列上添加索引
如果公司代码:select * from user where name='zekai' and email='zekai@qq.com'; 语句多,需要对name与email进行查找
如果遇到上述业务情况, 错误的做法:
index ix_name (name),
index ix_email(email)
正确的做法: 创建联合索引
index ix_name_email(name, email)
索引的优缺点
.frm是表的结构 .ibd是表数据
通过观察*.ibd文件可知:
- 索引加快了查询速度
- 但加了索引之后,会占用大量的磁盘空间
- 索引并不是加的越多越好
索引不会命中
aaa文件 == user表中 == 300w数据
范围问题, > <
select count(id) from user where id > 1000 and id<2000000;
/* +-----------+
| count(id) |
+-----------+
| 1998999 |
+-----------+
1 row in set (1.15 sec) */
不会命中的情况
//a. 不能在SQl语句中,进行四则运算, 会降低SQL的查询效率
//b. 使用函数
select * from user where reverse(name) = 'zekai999';
// Empty set (2.23 sec)
//c. 类型不一致
如果列是字符串类型,传入条件是必须用引号引起来,不然速度很慢
select * from user where name = 999;
// 列的设置是字符串,传入整型就会拖慢速度
// Empty set, 65535 warnings (3.14 sec)
//d. order by
// 排序条件为索引,则select字段必须也是索引字段,否则无法命中
select name from user order by email desc;
1.当根据索引排序时候,select查询的字段如果不是索引,则速度仍然很慢
2.对主键索引进行查询,速度提升,因为索引就是已经排好序的
select name from user order by name desc;
特别的:如果对主键排序,则还是速度很快:
//e. count(1)或count(列)代替count(*) 在mysql中没有差别了
count(列)比count(*)要快
// f. 组合索引 最左前缀匹配
删除索引: alter table user drop index ix_name;
// Query OK, 0 rows affected (0.04 sec)
创建组合索引:
alter table user add index ix_name_email(name,email);
// Query OK, 0 rows affected (25.41 sec)
// Records: 0 Duplicates: 0 Warnings: 0
where name='zekai' and email='xxxx' -- 命中索引
where name='zekai' -- 命中索引
where email='zekai@qq.com' -- 未命中索引
例子:
index (a,b,c,d)
/* 创建了四种索引 a ,ab ,abc,abcd
where a=2 and b=3 and c=4 and d=5 --->命中索引
where a=2 and c=3 and d=4 ----> 命中了a索引
如果是单独的就不会命中,如b/c/d
*/
explain
查询索引是否命中的情况,explain \G
格式化输出查询信息
explain select * from user where name='zekai' and email='zekai@qq.com'\G;
*************************** 1. row ***************************
id: 1
select_type: SIMPLE -索引的类型
table: user
partitions: NULL
type: ref -索引指向 all 全表扫描
possible_keys: ix_name_email -可能用到的索引
key: ix_name_email -确实用到的索引
key_len: 214 -索引的长度
ref: const,const
rows: 1 -扫描的长度
filtered: 100.00
Extra: Using index -使用到了索引
1 row in set, 1 warning (0.01 sec)
// where name='zekai' and email='xxxx' -- 命中索引
mysql> explain select * from user where name='zekai'\G;
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: user
partitions: NULL
type: ref
possible_keys: ix_name_email
key: ix_name_email
key_len: 62
ref: const
rows: 1
filtered: 100.00
Extra: Using index
1 row in set, 1 warning (0.00 sec)
//where name='zekai' -- 命中索引
mysql> explain select * from user where email = 'zekai@qq.com' \G;
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: user
partitions: NULL
type: index
possible_keys: NULL
key: ix_name_email
key_len: 214
ref: NULL
rows: 2995160 -扫描长度
filtered: 10.00
Extra: Using where; Using index
1 row in set, 1 warning (0.00 sec)
//where email='zekai@qq.com' -- 未命中索引
索引覆盖
如果一个索引包含(或覆盖)所有需要查询的字段的值,称为‘覆盖索引’。即只需扫描索引而无须回表。
elect id from user where id=2000;
只扫描索引而无需回表的优点:
1.索引条目通常远小于数据行大小,只需要读取索引,则mysql会极大地减少数据访问量。
2.因为索引是按照列值顺序存储的,所以对于IO密集的范围查找会比随机从磁盘读取每一行数据的IO少很多。
3.一些存储引擎如myisam在内存中只缓存索引,数据则依赖于操作系统来缓存,因此要访问数据需要一次系统调用
4.innodb的聚簇索引,覆盖索引对innodb表特别有用。(innodb的二级索引在叶子节点中保存了行的主键值,所以如果二级主键能够覆盖查询,则可以避免对主键索引的二次查询)
覆盖索引必须要存储索引列的值,而哈希索引、空间索引和全文索引不存储索引列的值,所以mysql只能用B-tree索引做覆盖索引。
慢查询日志
MySQL记录下查询超过指定时间的语句,我们将超过指定时间的SQL语句查询称为“慢查询”。
查看慢sql的相关变量
show variables like '%slow%';
show variables like '%slow%';
/*
+---------------------------+-----------------------------------------------+
| Variable_name | Value |
+---------------------------+-----------------------------------------------+
| log_slow_admin_statements | OFF |
| log_slow_slave_statements | OFF |
| slow_launch_time | 2 |
| slow_query_log | OFF | //默认关闭慢sql查询日志
| slow_query_log_file | D:\mysql-5.7.28\data\DESKTOP-45N3IG2-slow.log | //慢sql记录的位置
+---------------------------+-----------------------------------------------+
5 rows in set, 1 warning (0.05 sec)
查看慢sql的时间设置
show variables like '%long%';
mysql> show variables like '%long%';
+----------------------------------------------------------+-----------+
| Variable_name | Value |
+----------------------------------------------------------+-----------+
| long_query_time | 10.000000 | //时间是10s
| performance_schema_events_stages_history_long_size | 10000 |
| performance_schema_events_statements_history_long_size | 10000 |
| performance_schema_events_transactions_history_long_size | 10000 |
| performance_schema_events_waits_history_long_size | 10000 |
+----------------------------------------------------------+-----------+
5 rows in set, 1 warning (0.00 sec)
配置慢sql的变量
set global 变量名=值
set global slow_query_log=on; // 打开慢查询日志
set global slow_query_log_file = "D:/myslow.log"; // 设置日志保存地址
set global long_query_time = 1 ; // 更改慢sql的时间限制