Python操作MySQL以及数据库索引
python操作MySQL
安装
pip3 install pymsql
使用
方法:
-
conn = pymysql.conn():
实例化对象,建立mysql连接--- host:连接的mysql主机ip
--- user:连接mysql的用户
--- password:连接mysql的密码
--- database:连接mysql的数据库
--- charset:连接mysql的字符编码
-
cursor = conn.cursor():
建立游标 -
cursor.execute(sql):
执行sql语句 -
conn.commit():
提交,如果要增删改的话需要提交才能执行sql -
cursor.fetchall():
取出sql执行完毕的所有数据,默认以元组结果展示 -
cursor.fetchone():
取出sql执行完毕的一条数据 -
cursor.fetchmany(size):取出sql执行完毕的size条数据
-
conn.commit():
增删改数据库数据的时候要写上这个代码 -
cursor.lastrowid:
获取最后一行记录 -
cursor.close():
关闭连接 -
conn.close():
关闭连接
import pymysql
user = input("请输入用户名").strip()
pwd = input("请输入密码").strip()
# 建立连接
conn = pymysql.connect(
host="192.168.32.130",
user="root",
password="123",
database="work",
charset="utf8"
)
# cursor = conn.cursor() # 执行完毕之后的结果默认以元组形式输出
cursor = conn.cursor(cursor=pymysql.cursors.DictCursor) # 指定输出类型为字典
# 定义sql语句
# sql = "insert into pymysql_test (user ,pwd) values (%s,%s)" # 增
# sql = "delete from pymysql_test where id=2;" # 删
# sql = "update pymysql_test set pwd="123" where id=3;" # 改
sql = "select * from pymysql_test where user=%s and pwd=%s" # 查
sql = "select * from pymysql_test where user='%s' and pwd='%s'" % (user,pwd) # 查 演示sql注入,注意,占位符会把字符串类型的引号去掉,让它不再是字符串类型
# sql = "select * from pymysql_test" # 查
# 执行sql语句
# cursor.execute(sql)
cursor.execute(sql,(user,pwd))
# 如果要 增删改数据的话 需要commit,查不需要
conn.commit()
# 获取最后一行的ID值
print(cursor.lastrowid)
# 接收执行结果
res = cursor.fetchall() # 取出所有数据
# res = cursor.fetchone() # 取出一条数据
# res = cursor.fetchmany(2) # 取出指定条数数据,如果指定条数超过数据库里已有的条目则全部展示,不会报错
print(res)
# 关闭连接
cursor.close()
conn.close()
if res:
print("登录成功")
else:
print("登录失败")
SQL注入问题
那么以上我们对pymysql进行了简单的使用,那么接下来有一个安全上的问题,就是sql注入问题
之所以产生sql注入问题的原因是 我们没有对用户的数据进行约束,用户在输入的时候可能会随便输入,就会引发sql注入问题:
我们正常登陆是这样的:
请输入用户名qinyj
请输入密码123
sql语句是:select * from pymysql_test where user='qinyj' and pwd='123'
登陆成功
那么有些用户可能随便输入用户名也能登陆成功,比如以下这种:
输入用户名:qinyj' or 1=1 #
输入密码:dsadsa
请输入用户名qinyj' #
请输入密码123
sql语句是: select * from pymysql_test where user='qinyj' #' and pwd='123'
登陆成功
以上用户名输入错误还能登陆成功的情况,是因为有 #
号,mysql在碰到 这个符号之后不会执行后面的sql语句,所以相当于select * from pymysql_test
查所有的数据
这种情况我们就称之为sql注入问题,这是一个非常严重的问题,在公司中开发是绝对不允许的,我们要解决这个问题可以有两种方法:
--- 1. 自己编写对用户输入的规则,通过后才执行sql
--- 2. 使用pymysql自带的检验工具 cursor.execute(sql, (user, pwd))
,将用户名、密码传入执行sql的参数中。
MySQL的索引
在介绍 MySQL索引之前,我们先创建一个表,表结构如下
mysql> desc pymysql_test;
+-------+-------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------+-------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| user | varchar(32) | NO | | | |
| email | varchar(32) | NO | | | |
+-------+-------------+------+-----+---------+----------------+
3 rows in set (0.00 sec)
来对这个表插入500W条数据,方便下面的讲解。
import pymysql
conn = pymysql.connect(
host="192.168.32.130",
user="root",
password="123",
database="work",
charset="utf8"
)
cursor = conn.cursor(cursor=pymysql.cursors.DictCursor)
sql = "insert into pymysql_test(user,email) values (%s,%s)"
data = []
for i in range(5000000):
info = ("qinyj" + str(i),"qinyj" + str(i) + "@qq.com")
data.append(info)
# print(data)
cursor.executemany(sql,data) # 新增多条数据
conn.commit()
cursor.close()
conn.close()
select * from pymysql_test;
...
5000000 rows in set (20.45 sec)
为什么使用索引
使用索引就是为了提高查询的效率,类似于新华字典中的目录
索引的本质就是一个特殊的文件,记录着索引信息
索引的底层原理:B + 树
索引的种类
- 主键索引
- 唯一索引
- 普通索引
mysql> desc test;
+-------+-------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+-------------+------+-----+---------+-------+
| id | int(11) | YES | | NULL | |
| name | varchar(32) | YES | | NULL | |
+-------+-------------+------+-----+---------+-------+
2 rows in set (0.00 sec)
主键索引
加速查找、不能重复、不能为空
创建主键索引:primary key
1、create table test(id int auto_increment primary key)charset=utf8;
2、alter table test modify id int auto_increment primary key;
3、alter table test add primary key(id);
删除主键索引:
注意:如果主键加上了自增的约束就不能被删除
alter table test drop primary key;
唯一索引
加速查找、不能重复 unique(xxx)
唯一联合索引:unique(xxx,xxx)
创建唯一索引:unique
1、create table test(id int auto_increment primary key,name varchar(32) not null default '',unique u_name(name))charset=utf8;
2、create unique index u_name on test(name);
3、alter table test add unique index u_name (name);
创建联合索引:
1、create table test(id int auto_increment primary key,name varchar(32) not null default '',address varchar(32) not null default '',age int not null default 0, unique u_name_age(name,age))charset=utf8;
2、alter table pymysql_test add unique index pt_name (user,email);
删除唯一索引:
alter table test drop index u_name;
普通索引
加速查找 index(xxx)
普通联合索引:index(xxx,xxx)
创建普通索引:index
1、create table test(id int auto_increment primary key,name varchar(32) not null default '',index ix_name(name))charset=utf8;
2、create index ix_name on test(name);
3、alter table test add index ix_name (name);
创建联合普通索引:
create table test(id int auto_increment primary key,name varchar(32) not null default '',age int not null default 0,index ix_name_age (name,age))charset=utf8;
删除普通索引
alter table test drop index ix_name;
索引优缺点
通过观察 *.ibd 文件可知:
- 索引加快了查询速度
- 加了索引之后,会占用大量的磁盘空间
所以索引不是加的越多越好
不会命中索引的情况
mysql> desc pymysql_test;
+-------+-------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------+-------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| user | varchar(32) | NO | | | |
| email | varchar(32) | NO | | | |
+-------+-------------+------+-----+---------+----------------+
3 rows in set (0.00 sec)
会降低sql的查询效率的情况:
-
在sql语句中进行四则运算
select * from pymysql_test where id*2=5127;
Empty set (6.55 sec) -
使用聚合函数
select count(id) from pymysql_test;
1 row in set (4.06 sec)
-
查询类型不一致的时候,如果列是字符串类型,条件也必须用引号引起来表示字符串
select * from pymysql_test where email=999;
Empty set, 65535 warnings (9.20 sec)
-
排序 order by
排序条件为索引,则查询字段必须也是索引字段,否则无法命中
如果查询的字段与排序字段的不是主键的字段,那么速度依然很慢
select email from pymysql_test order by email desc;
如果查询的字段与排序的字段是主键的字段,那么速度还是很快
select id from pymysql_test order by id desc;
5000004 rows in set (9.48 sec)
-
count(1) 或 count(列) 代替 count(*) 在mysql中没有差别
select count(*) from pymysql_test;
select count(1) from pymysql_test;
select count(id) from pymysql_test;
1 row in set (4.29 sec)
-
组合索引最左前缀
什么时候会创建联合索引?
根据公司业务场景,在最常用的几列上添加索引
mysql> desc pymysql_test; +-------+-------------+------+-----+---------+----------------+ | Field | Type | Null | Key | Default | Extra | +-------+-------------+------+-----+---------+----------------+ | id | int(11) | NO | PRI | NULL | auto_increment | | user | varchar(32) | NO | | | | | email | varchar(32) | NO | | | | +-------+-------------+------+-----+---------+----------------+ 3 rows in set (0.00 sec) 创建联合索引 alter table pymysql_test add unique index pt_name (user,email); Query OK, 0 rows affected (1 min 4.77 sec) Records: 0 Duplicates: 0 Warnings: 0
创建完联合索引之后呢,我们使用组合查询效率会非常高,
联合查询:效率非常高 select * from pymysql_test where user="qinyj1324" and email="qinyj1324@qq.com"; 1 row in set (0.00 sec) 查询最左前前缀:效率也是非常高 select * from pymysql_test where user="qinyj122432" ; 1 row in set (0.00 sec) 查询后面的:效率就低了 select * from pymysql_test where email="qinyj1324@qq.com"; 1 row in set (5.96 sec)
例子:
组合索引:index (a,b,c,d)
where a=2 and b=2 and c=2 and d=5 --> 命中索引
where a=2 and c=2 and d=5 -->部分命中索引
where d =5 -->没有命中
explain
explain关键字可以模拟优化器执行sql语句,从而知道MySQL是如何处理sql语句的,分析查询语句或是结构的性能瓶颈
我们只需要在查询sql语句前面加上explain就行了
explain select * from pymysql_test where email="qinyj1324@qq.com"\G;;
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: pymysql_test
type: index
possible_keys: NULL
key: pt_name
key_len: 196
ref: NULL
rows: 4988562
Extra: Using where; Using index
1 row in set (0.00 sec)
参数解释:
- type:索引指向,如果是all的话那就是使用索引
- possible_keys:可能用到的索引
- key:确实用到的索引
- key_len:索引长度
- rows:扫描的行数
- Extra:描述信息,使用到了索引
索引覆盖
select id from pymysql_test where id=20000;
MySQL慢查询日志
查看慢sql的相关变量
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 |
| slow_query_log_file | /data/app/mysql/localhost-slow.log |
+---------------------------+------------------------------------+
show variables like '%long%';
+--------------------------------------------------------+-----------+
| Variable_name | Value |
+--------------------------------------------------------+-----------+
| long_query_time | 10.000000 |
参数解释:
- slow_query_log:慢查询日志开关,默认关闭
- slow_query_log_file:慢查询存放位置
- long_query_time:慢查询时间限制
配置慢查询的变量:
set global slow_query_log = on;
set global slow_query_log_file = '/data/app/mysql/myslow.log';
set global long_query_time = 1;
配置完毕之后退出重连一下就会有生成的慢查询日志了
ll /data/app/mysql/myslow.log
-rw-rw---- 1 mysql mysql 180 Oct 31 05:37 /data/app/mysql/myslow.log