百万年薪python之路 -- MySQL数据库之 Navicat工具和pymysql模块

时间:2021-09-24 15:16:41

一. IDE工具介绍(Navicat)

生产环境还是推荐使用mysql命令行,但为了方便我们测试,可以使用IDE工具,我们使用Navicat工具,这个工具本质上就是一个socket客户端,可视化的连接mysql服务端的一个工具,并且他是图形界面版的。我们使用它和直接使用命令行的区别就类似linux和windows系统操作起来的一个区别。

下载链接:https://pan.baidu.com/s/1bpo5mqj

Navicat的安装教程看这篇博客:https://www.cnblogs.com/clschao/articles/10022040.html

掌握:
    # 1. 测试+链接数据库
    # 2. 新建库
    # 3. 新建表,新建字段+类型+约束
    # 4. 设计表: 外键
    # 5. 新建查询
    # 6. 备份库/表

# 注意:
批量加注释: ctrl + ? 键
批量去注释: ctrl + shift + ? 键

二. pymysql模块

1. pymysql安装

# 安装
一:
    pip3 install pymysql
二:
    PyCharm里settings-----> Project Interpreter-----> 右上角的加号+  ------> 搜索框里搜索

2. python连接数据库

import pymysql

# 打开数据库连接
conn = pymysql.connect('数据库ip','用户账号','密码','数据库名')  

# 使用 cursor() 方法创建一个游标对象 cursor
cursor = conn.cursor()

# 使用 execute() 方法执行 SQL语句
cursor.execute("SQL语句")

# 查询
data = cursor.fetchone()    # 查询单行数据
data = cursor.fetchmany(条数) # 查询多行数据
data = cursor.fetchall()    # 查询所有的数据
print(data)

# 关闭游标
cursor.close()

# 关闭数据库连接
conn.close()

更多参数

import pymysql

conn = pymysql.connect(
        host='localhost', user='root', password="123",
        database='db', port=3306, charset='utf8',   # charset时utf8,不能是utf-8
)

cur = conn.cursor(cursor=pymysql.cursors.DictCursor)    # 可以把cursor得到的结果集变成[{},{},......]这种列表里套字典形式.fetchall或者fetchmany取出的结果是列表套字典的数据形式,如果fetchmany(1)则取到的结果是一个字典,而不是列表套字典

3. 查询表操作

Python查询Mysql使用 fetchone() 方法获取单条数据, 使用fetchall() 方法获取多条数据。

  • fetchone(): 该方法获取下一个查询结果集。结果集是一个对象
  • fetchall(): 接收全部的返回结果行.
  • rowcount: 这是一个只读属性,并返回执行execute()方法后影响的行数。
import pymysql

# 建立数据库连接,第一个参数:ip地址,第二个:用户名,第三个:密码,第四个:数据库名
conn = pymysql.connect('127.0.0.1','root','123456','pysql')

# 使用cursor()方法创建一个游标对象 cursor
# cursor = conn.cursor()
cur = conn.cursor(cursor=pymysql.cursors.DictCursor)

# sql代码
sql = 'select * from info;'

# 执行sql语句,返回sql查询成功的记录数目,是个数字,是受sql语句影响到的记录行数,其实除了受影响的记录的条数之外,这些记录的数据也都返回了给游标
row = cursor.execute(sql)

print(cursor.rowcount)  # 这是一个只读属性,并返回执行execute()方法后影响的行数
print(row)  # 一个数字

# one_data = cursor.fetchone()
# many_data = cursor.fetchmany(3)   # 当括号里写1是,得到的是字典
# all_data = cursor.fetchall()

# print(one_data)
# print(many_data)
# print(all_data)

cursor.close()  # 关闭游标
conn.close()    # 关闭连接
光标的移动
我们可以移动游标的位置,继续取我们前面的数据,通过cursor.scroll(数字,模式),第一个参数就是一个int类型的数字,表示往后移动的记录条数,第二个参数为移动的模式,有两个值:absolute:绝对移动,relative:相对移动
#绝对移动:它是相对于所有数据的起始位置开始往后面移动的
#相对移动:他是相对于游标的当前位置开始往后移动的

#绝对移动的演示
#print(cursor.fetchall())
#cursor.scroll(3,'absolute') #从初始位置往后移动三条,那么下次取出的数据为第四条数据
#print(cursor.fetchone())

#相对移动的演示
#print(cursor.fetchone())
#cursor.scroll(1,'relative') #通过上面取了一次数据,游标的位置在第二条的开头,我现在相对移动了1个记录,那么下次再取,取出的是第三条,我相对于上一条,往下移动了一条
#print(cursor.fetchone())

4. 对数据的增删改操作

一定要 数据库连接对象.commit() 提交,不然不会对数据库进行改变.

插入数据
import pymysql

# 打开数据库连接
conn = pymysql.connect('127.0.0.1','root','123456','pysql')

# 使用cursor()方法获取操作游标
cursor = conn.cursor()

# SQL 插入语句
sql = """ insert into info(username,password) values('zfy','123456'),('lfz','123');"""

try:
   cursor.execute(sql) # 执行sql语句
   conn.commit()         # 提交到数据库执行
except:
   conn.rollback()       # 如果发生错误则回滚

# 关闭数据库连接
conn.close()
删除数据
import pymysql

# 打开数据库连接
conn = pymysql.connect('127.0.0.1','root','123456','pysql')

# 使用cursor()方法获取操作游标
cursor = conn.cursor()

# SQL 删除语句
sql = """ delete from info where id = %s""" # 不要直接字符串拼接,防止sql注入

try:
   cursor.execute(sql,[2]) # 执行sql语句,第二个参数:列表里写sql的拼接字符.
   conn.commit()         # 提交到数据库执行
except:
   conn.rollback()       # 如果发生错误则回滚

# 关闭数据库连接
conn.close()
更新数据
import pymysql

# 打开数据库连接
conn = pymysql.connect('127.0.0.1','root','123456','pysql')

# 使用cursor()方法获取操作游标
cursor = conn.cursor()

# SQL 更新语句
sql = """ update info set password = 'hahaha' where id = %s """ # 不要直接字符串拼接,防止sql注入

try:
   cursor.execute(sql,[3]) # 执行sql语句,第二个参数:列表里写sql的拼接字符.
   conn.commit()         # 提交到数据库执行
except:
   conn.rollback()       # 如果发生错误则回滚

# 关闭数据库连接
conn.close()

4. 获取插入的最后一条数据的自增ID

import pymysql
conn=pymysql.connect(host='localhost',user='root',password='123',database='egon')
cursor=conn.cursor()

sql='insert into userinfo(name,password) values("xxx","123");'
rows=cursor.execute(sql)
print(cursor.lastrowid) #在插入语句后查看

conn.commit()

cursor.close()
conn.close()

5. execute()之sql注入

​ 之前我们进行用户名密码认证是先将用户名和密码保存到一个文件中,然后通过读文件里面的内容,来和客户端发送过来的用户名密码进行匹配,现在我们学了数据库,我们可以将这些用户数据保存到数据库中,然后通过数据库里面的数据来对客户端进行用户名和密码的认证。

​ 自行创建一个用户信息表userinfo,里面包含两个字段,username和password,然后里面写两条记录

    百万年薪python之路  --  MySQL数据库之  Navicat工具和pymysql模块

#我们来使用数据来进行一下用户名和密码的认证操作
import pymysql

conn = pymysql.connect(
    host='127.0.0.1',
    port=3306,
    user='root',
    password='666',
    database='crm',
    charset='utf8'
)

cursor = conn.cursor(pymysql.cursors.DictCursor)
uname = input('请输入用户名:')
pword = input('请输入密码:')

sql = "select * from userinfo where username='%s' and password='%s';"%(uname,pword)

res = cursor.execute(sql) #res我们说是得到的行数,如果这个行数不为零,说明用户输入的用户名和密码存在,如果为0说名存在,你想想对不

print(res) #如果输入的用户名和密码错误,这个结果为0,如果正确,这个结果为1
if res:
    print('登陆成功')
else:
    print('用户名和密码错误!')

#通过上面的验证方式,比我们使用文件来保存用户名和密码信息的来进行验证操作要方便很多。

​ 但是我们来看下面的操作,如果将在输入用户名的地方输入一个 chao'空格然后--空格然后加上任意的字符串,就能够登陆成功,也就是只知道用户名的情况下,他就能登陆成功的情况:

uname = input('请输入用户名:')
pword = input('请输入密码:')

sql = "select * from userinfo where username='%s' and password='%s';"%(uname,pword)
print(sql)
res = cursor.execute(sql) #res我们说是得到的行数,如果这个行数不为零,说明用户输入的用户名和密码存在,如果为0说名存在,你想想对不

print(res) #如果输入的用户名和密码错误,这个结果为0,如果正确,这个结果为1
if res:
    print('登陆成功')
else:
    print('用户名和密码错误!')
#运行看结果:居然登陆成功
请输入用户名:chao' -- xxx
请输入密码:
select * from userinfo where username='chao' -- xxx' and password='';
1
登陆成功

我们来分析一下:
此时uname这个变量等于什么,等于chao' -- xxx,然后我们来看我们的sql语句被这个字符串替换之后是个什么样子:
select * from userinfo where username='chao' -- xxx' and password=''; 其中chao后面的这个',在进行字符串替换的时候,我们输入的是chao',这个引号和前面的引号组成了一对,然后后面--在sql语句里面是注释的意思,也就是说--后面的sql语句被注释掉了。也就是说,拿到的sql语句是select * from userinfo where username='chao';然后就去自己的数据库里面去执行了,发现能够找到对应的记录,因为有用户名为chao的记录,然后他就登陆成功了,但是其实他连密码都不知道,只知道个用户名。。。,他完美的跳过了你的认证环节。

​ 然后我们再来看一个例子,直接连用户名和密码都不知道,但是依然能够登陆成功的情况:

请输入用户名:xxx' or 1=1 -- xxxxxx
请输入密码:
select * from userinfo where username='xxx' or 1=1 -- xxxxxx' and password='';
3
登陆成功

我们只输入了一个xxx' 加or 加 1=1 加 -- 加任意字符串
看上面被执行的sql语句你就发现了,or 后面跟了一个永远为真的条件,那么即便是username对不上,但是or后面的条件是成立的,也能够登陆成功。

​ 上面两个例子就是两个sql注入的问题,看完上面这两个例子,有没有感觉后背发凉啊同志们,别急,我们来解决一下这个问题,怎么解决呢?

​ 有些网站直接在你输入内容的时候,是不是就给你限定了,你不能输入一些特殊的符号,因为有些特殊符号可以改变sql的执行逻辑,其实不光是--,还有一些其他的符号也能改变sql语句的执行逻辑,这个方案我们是在客户端给用户输入的地方进行限制,但是别人可不可以模拟你的客户端来发送请求,是可以的,他模拟一个客户端,不按照你的客户端的要求来,就发一些特殊字符,你的客户端是限制不了的。所以单纯的在客户端进行这个特殊字符的过滤是不能解决根本问题的,那怎么办?我们服务端也需要进行验证,可以通过正则来将客户端发送过来的内容进行特殊字符的匹配,如果有这些特殊字符,我们就让它登陆失败。

    在服务端来解决sql注入的问题:不要自己来进行sql字符串的拼接了,pymysql能帮我们拼接,他能够防止sql注入,所以以后我们再写sql语句的时候按下面的方式写:

之前我们的sql语句是这样写的:
sql = "select * from userinfo where username='%s' and password='%s';"%(uname,pword)

以后再写的时候,sql语句里面的%s左右的引号去掉,并且语句后面的%(uname,pword)这些内容也不要自己写了,按照下面的方式写
sql = "select * from userinfo where username=%s and password=%s;"
难道我们不传值了吗,不是的,我们通过下面的形式,在excute里面写参数:
#cursor.execute(sql,[uname,pword]) ,其实它本质也是帮你进行了字符串的替换,只不过它会将uname和pword里面的特殊字符给过滤掉。

看下面的例子:
uname = input('请输入用户名:') #输入的内容是:chao' -- xxx或者xxx' or 1=1 -- xxxxx
pword = input('请输入密码:')

sql = "select * from userinfo where username=%s and password=%s;"
print(sql)
res = cursor.execute(sql,[uname,pword]) #res我们说是得到的行数,如果这个行数不为零,说明用户输入的用户名和密码存在,如果为0说名存在,你想想对不

print(res) #如果输入的用户名和密码错误,这个结果为0,如果正确,这个结果为1
if res:
    print('登陆成功')
else:
    print('用户名和密码错误!')
#看结果:
请输入用户名:xxx' or 1=1 -- xxxxx
请输入密码:
select * from userinfo where username=%s and password=%s;
0
用户名和密码错误!

​ 通过pymysql提供的excute完美的解决了问题。

总结咱们刚才说的两种sql注入的语句
#1、sql注入之:用户存在,绕过密码
chao' -- 任意字符

#2、sql注入之:用户不存在,绕过用户与密码
xxx' or 1=1 -- 任意字符

​ 解决方法总结:

# 原来是我们对sql进行字符串拼接
# sql="select * from userinfo where name='%s' and password='%s'" %(user,pwd)
# print(sql)
# res=cursor.execute(sql)

#改写为(execute帮我们做字符串拼接,我们无需且一定不能再为%s加引号了)
sql="select * from userinfo where name=%s and password=%s" #!!!注意%s需要去掉引号,因为pymysql会自动为我们加上
res=cursor.execute(sql,[user,pwd]) #pymysql模块自动帮我们解决sql注入的问题,只要我们按照pymysql的规矩来。