Mysql 基于TCP 协议之上开发,但是网络连接后,传输的数据必须遵循Mysql 协议的包,就是驱动程序
Mysql驱动
Mysqldb
最有名的库,对Mysql的client封装实现,支持python2 不在更新,不支持python3
Mysql官方的Connector
pymysql 语法兼容Mysqldb ,使用python写的库,支持oython
pip install pymysql
创建数据库和表
CREATE DATABASE IF NOT EXISTS school;
SHOW DATABASES;
USE school;
CREATE TABLE `student` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(30) NOT NULL,
`age` int(11) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
首先,必须建立一个传输数据通道——连接。
pymysql.connect()方法返回的是Connections模块下的Connection类实例。connect方法传参就是给Connection
类的__init__
提供参数
Connection初始化常用参数 | 说明 |
---|---|
host | 主机 |
user | 用户名 |
password | 密码 |
database | 数据库 |
port | 端口 |
对数据库进行操作:
import pymysql
HOST= '172.22.141.122'
USERNAME = 'eric01'
PASSWORD= 'eric01'
DBNAME = 'test'
PORT = 3306
conn = pymysql.connect(HOST,USERNAME,PASSWORD,DBNAME,PORT)
# conn.ping(False) # 如果ping 失败将会从连接一遍,还是失败将报错
# 可以不使用,因为在使用中有异常都会出异常
# print(conn) # 结果为:
sql = 'select * from reg
# sql = "insert into reg(loginname,name,password) values ('wangwu','wangwu','wangwu')"
# # 第一步 拿游标 拿到游标后才能继续往下操作
r = conn.query(sql) # 这一步其=操作,数据库中的ID,已经发生变化,但是没有commit
print(r,type(r))
上面的代码并没有对数据库值进行更改:现在将代码进行更改:
import pymysql
conn = None
cursor = None
# Autocommit == False 默认的自动提交为False
try:
for i in range(5):
conn = pymysql.connect(HOST,USERNAME,PASSWORD,DBNAME,PORT)
cursor = conn.cursor()
# sql = 'select * from reg'
sql = "insert into reg(loginname,name,password) values ('wangwu','wangwu','wangwu')"
r =cursor.execute(sql)
print(r,type(r))
conn.commit()
except: # 事物操作,执行多个sql 操作,上面若出现任何错误就要回滚,不对数据库有任何操作
conn.rollback()
print('~~~~~~~~~~')
finally :
if cursor:
cursor.close()
if conn:
conn.close()
查询:
try:
conn = pymysql.connect(HOST,USERNAME,PASSWORD,DBNAME,PORT)
cursor = conn.cursor()
sql = 'select * from reg'
# sql = "insert into reg(loginname,name,password) values ('wangwu','wangwu','wangwu');"
r =cursor.execute(sql) # 这里返回的是查到的数据条数.
print(r,type(r))
print(cursor.fetchone()) # 读取一行
print(cursor.rownumber,cursor.rowcount)# 打印游标当前行位置和总行数
print(cursor.fetchone())# 在读取一行,返回的是一个元组
print('-'*30)
print(cursor.fetchmany(2)) # 读取多行(2行)
print('-'*30)
print(cursor.fetchall())# 读取当前游标到数据结束的所有数据
print('-'*30)
print(cursor.fetchall())# 不能在读取了,游标回到末尾了
cursor.rownumber =0 # 回到起点,可以设置游标位置
# conn.commit()
# if r==1 : 查找只有一个条件符合的情况
# userid = cursor.fetchone()[0] # 元组取第一个数
# print(userid)
# sql = "update reg set name = 'eric02' where id = '{}'".format(uesrid) # 更新数据
# cursor.exectute(sql) # 执行
except Exception as e:
print(e)
conn.rollback()
print('~~~~~~~~~~')
一般流程:
带列名字查询
Cursor 类有一个Mixin的子类DictCursor
只需要cursor = conn.cursor(DictCursor)
就可以了
# 返回结果
{'name': 'tom', 'age': 20, 'id': 4}
{'name': 'tom0', 'age': 20, 'id': 5}
SQL注入攻击
sql = 'SELECT * from student WHERE id = {}'.format('5 or 1=1')
# 运行的结果竟然是返回了全部数据。
SQL 注入攻击
猜测后台数据库的查询语句使用拼接字符串等方式,从而经过设计为服务端传参,令其拼接出特殊字符串的SQL语
句,返回攻击者想要的结果。
如何解决呢?
参数化查询,可以有效防止注入攻击,并提高查询的效率。
def execute(self, query, args=None):
pass
#分析源码,后面可以传入args参数,可迭代对象,元组或者字典
id = '1 or 1=1'
sql = "SELECT * from student WHERE id = %s" # 参数化查询
r = cursor.execute(sql,(id,))#
# 参数查询,仅对sql语句进行编译,后面的参数将不再进行编译,直接传入
或者用字典方式传入
sql = "select * from reg where id = %(n)s and loginname = %(p)s"
r = cursor.execute(sql,{'n':'1','p':'eric101'}# #一一对应传参
参数化查询为什么提高效率?
原因就是——SQL语句缓存。
数据库服务器一般会对SQL语句编译和缓存,编译只对SQL语句部分,所以参数中就算有SQL指令也不会被当做指
令执行。
编译过程,需要词法分析、语法分析、生成AST、优化、生成执行计划等过程,比较耗费资源。
服务端会先查找是否对同一条查询语句进行了缓存,如果缓存未失效,则不需要再次编译,从而降低了编译的成
本,降低了内存消耗。
可以认为SQL语句字符串就是一个key,如果使用拼接方案,每次发过去的SQL语句都不一样,都需要编译并缓存。
大量查询的时候,首选使用参数化查询,以节省资源。
开发时,应该使用参数化查询。
注意:这里说的是查询字符串的缓存,不是查询结果的缓存。
批量执行executemany()
sql = "insert into student (name, age) values(%s, %s)"
cursor.executemany(sql, (('jerry{}'.format(i), 30 + i) for i in range(5)))
上下文管理
# 连接类
# 源码分析
class Connection(object):
def __enter__(self):
"""Context manager that returns a Cursor"""
return self.cursor() # 创建一个cursor类
def __exit__(self, exc, value, traceback):
"""On successful exit, commit. On exception, rollback"""
if exc:
self.rollback() # 退出回滚数据
else:
self.commit() # 没有错,提交数据
# 退出关闭
上下文管理
conn = None
try:
conn = pymysql.connect(HOST,USERNAME,PASSWORD,DBNAME,PORT)
cursor = None
try:
with conn:
cursor = conn.cursor()
sql = "select * from reg where id = '123"
r =cursor.execute(sql,{'n':'1'})
print(r)
for x in cursor.fetchall():
print(x)
except Exception as e:
print(e)
print('~~~~~~')
finally:
if cursor:
cursor.close()
finally:
if conn:
conn.close()
上下文管理方法二: with conn.cursor() as cursor 退出时会关闭cursor
try:
conn = pymysql.connect(HOST,USERNAME,PASSWORD,DBNAME,PORT)
cursor = None
try:
with conn:
with conn.cursor() as cursor: #
sql = "select * from reg where id = '123"
r =cursor.execute(sql,{'n':'1'})
print(r)
for x in cursor.fetchall():
print(x)
#########################
with conn as cursor: # 这里的上下文同样会创建一个cursor对象,这才是我们应该使用的方法
pass
except Exception as e:
print(e)
print('~~~~~~')
finally:
if conn:
conn.close()
最精简写法:
with pymysql.connect(HOST,USERNAME,PASSWORD,DBNAME,PORT,cursor = DictCursor) as sursor:
with cursor:
sql = "select * from reg where id = '123"
r =cursor.execute(sql,{'n':'1'})
if r:
pass
for x in cursor.fetchall():
print(x)
conn的with进入是返回一个新的cursor对象,退出时,只是提交或者回滚了事务。并没有关闭cursor和conn。
不关闭cursor就可以接着用,省的反复创建它。
import pymysql
HOST= '172.22.141.122'
USERNAME = 'eric01'
PASSWORD= 'eric01'
DBNAME = 'test'
PORT = 3306
conn = pymysql.connect(HOST,USERNAME,PASSWORD,DBNAME,PORT)
print(conn)
# conn.ping(False) # 如果ping不通就报错 # 为True时可以重新连接
# sql = 'select * from reg'
# sql = "insert into reg(loginname,name,password) values ('wangwu','wangwu','wangwu')"
# # 拿游标
# r =conn.query(sql)
# print(r)
# print()
# # 上面为直接操作数据库,不好用,还是拿到游标在操作比较好
cursor = conn.cursor()
sql = "insert into reg(loginname,name,password) values ('wangwu','wangwu','wangwu')"
cursor.execute(sql) # 用游标执行
# 关闭
cursor.close()
conn.close()
import pymysql
HOST= '172.22.141.122'
USERNAME = 'eric01'
PASSWORD= 'eric01'
DBNAME = 'test'
PORT = 3306
conn = None
cursor = None # 对结果集的操作.
try:
conn = pymysql.connect(HOST,USERNAME,PASSWORD,DBNAME,PORT)
print(conn)
# conn.ping(False) # 如果ping不通就报错 # 为True时可以重新连接
# sql = 'select * from reg'
# sql = "insert into reg(loginname,name,password) values ('wangwu','wangwu','wangwu')"
# # 拿游标
# r =conn.query(sql)
# print(r)
# print()
# # 上面为直接操作数据库,不好用,还是拿到游标在操作比较好
cursor = conn.cursor()
for i in range(5): # 5 个提交,为一个事物,要么一起提交,要么全部不提交
sql = "insert into reg(loginname,name,password) values ('wangwu','wangwu','wangwu')"
r = cursor.execute(sql)
print(r)
# 用游标执行
conn.commit()
except:
conn.rollback() # 若提交失败则调用回滚.相当于什么操作都没发生.
print('~~~~~~~~~~~~')
# 关闭
finally:# 如果上面的try没有执行,就没有conn和cursor ,因此要特别考虑
if cursor:
cursor.close()
if conn:
conn.close()
fetch
import pymysql
HOST= '172.22.141.122'
USERNAME = 'eric01'
PASSWORD= 'eric01'
DBNAME = 'test'
PORT = 3306
conn = None
cursor = None # 对结果集的操作.
try:
conn = pymysql.connect(HOST,USERNAME,PASSWORD,DBNAME,PORT)
print(conn)
# conn.ping(False) # 如果ping不通就报错 # 为True时可以重新连接
# sql = 'select * from reg'
# sql = "insert into reg(loginname,name,password) values ('wangwu','wangwu','wangwu')"
# # 拿游标
# r =conn.query(sql)
# print(r)
# print()
# # 上面为直接操作数据库,不好用,还是拿到游标在操作比较好
cursor = conn.cursor()
# for i in range(5): # 5 个提交,为一个事物,要么一起提交,要么全部不提交
#
# sql = "insert into reg(loginname,name,password) values ('wangwu','wangwu','wangwu')"
# r = cursor.execute(sql)
# print(r)
# 用游标执行
sql = 'select * from reg'
print(sql)
r = cursor.execute(sql) # r 返回的查询结果的条数.
print(cursor.rownumber,cursor.rowcount)
print(cursor.fetchone())
print(cursor.rownumber, cursor.rowcount)
print(cursor.fetchone())
print(cursor.fetchmany(2))
print(cursor.rownumber, cursor.rowcount)
print(cursor.fetchmany(2))
print(cursor.fetchall()) # 读取剩余的部分
print(cursor.rownumber, cursor.rowcount)
cursor.rownumber = 0 # 将游标 调整到0
cursor.rownumber = -4 # 负数表示倒着移动几行
print(cursor.fetchall())
conn.commit()
except:
conn.rollback() # 若提交失败则调用回滚.相当于什么操作都没发生.
print('~~~~~~~~~~~~')
# 关闭
finally:# 如果上面的try没有执行,就没有conn和cursor ,因此要特别考虑
if cursor:
cursor.close()
if conn:
conn.close()
查询更新
# 字典
import pymysql
HOST= '172.22.141.122'
USERNAME = 'eric01'
PASSWORD= 'eric01'
DBNAME = 'test'
PORT = 3306
conn = None
cursor = None # 对结果集的操作.
try:
conn = pymysql.connect(HOST,USERNAME,PASSWORD,DBNAME,PORT)
print(conn)
# conn.ping(False) # 如果ping不通就报错 # 为True时可以重新连接
# sql = 'select * from reg'
# sql = "insert into reg(loginname,name,password) values ('wangwu','wangwu','wangwu')"
# # 拿游标
# r =conn.query(sql)
# print(r)
# print()
# # 上面为直接操作数据库,不好用,还是拿到游标在操作比较好
cursor = conn.cursor()
# for i in range(5): # 5 个提交,为一个事物,要么一起提交,要么全部不提交
#
# sql = "insert into reg(loginname,name,password) values ('wangwu','wangwu','wangwu')"
# r = cursor.execute(sql)
# print(r)
# 用游标执行
sql = 'select * from reg'
print(sql)
r = cursor.execute(sql) # r 返回的查询结果的条数.
if r ==1: # 查到结果个数为1 时,接着操作
user = cursor.fetchone() # 读取一条信息
print(user)
sql = "update reg set loginname = 'lisi' where id ='{}'".format(user[0])
# 数据修改为先查后改,先查到数据后才能进行修改,如果查询不存在就不用更改
cursor.execute(sql) # 游标法执行
conn.commit()
except:
conn.rollback() # 若提交失败则调用回滚.相当于什么操作都没发生.
print('~~~~~~~~~~~~')
# 关闭
finally:# 如果上面的try没有执行,就没有conn和cursor ,因此要特别考虑
if cursor:
cursor.close()
if conn:
conn.close()
import pymysql
from pymysql.cursors import DictCursor
HOST= '172.22.141.122'
USERNAME = 'eric01'
PASSWORD= 'eric01'
DBNAME = 'test'
PORT = 3306
conn = pymysql.connect(HOST, USERNAME, PASSWORD, DBNAME, PORT)
cursor = conn.cursor(DictCursor)
sql = 'select * from reg'
cursor.execute(sql)
print(cursor.fetchall())
查询
import sqlalchemy
from sqlalchemy import create_engine, Column, String, Integer
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
# ORM Maping
# print(Student.__table__)
HOST = '172.22.141.122'
USERNAME = 'eric01'
PASSWORD = 'eric01'
DBNAME = 'test'
PORT = 3306
# engine = create_engine("mysql://scott:tiger@hostname/dbname", echo=True) # echo =True ,后期会将语句报保存到日志
engine = create_engine("mysql+pymysql://{}:{}@{}:{}/{}".format(USERNAME, PASSWORD, HOST, PORT, DBNAME),
echo=True) # echo =True ,后期会将语句报保存到日志
# print(engine)
# 创建后将不需要在执行下面的语句
# Base.metadata.drop_all(engine) # 删除base 所管理的所有mapping类
# Base.metadata.create_all(engine)
Base = declarative_base()
class Student(Base):
__tablename__ = 'student' # 指定数据库表对应
id = Column(Integer, primary_key=True, autoincrement=True) # 字段名一致 不重复写
name = Column(String(64), nullable=False)
age = Column(Integer)
def __repr__(self):
return "<{} id = {}, name = {},age = {}>" .format(
__class__.__name__, self.id, self.name,self.age)
print(Student.__dict__)
#########################
from sqlalchemy.orm.session import Session
session:Session = sessionmaker(bind = engine)()
print(session,type(session))
# 上面的语句等同于下面的代码
# Session = sessionmaker(bind=engine)
# session = Session()
# 线程不安全,所以不能在多线程中使用.
# 增加和修改数据
# student = Student(name= 'tom' )
# student.name= 'jerry'
# student.age = 20
#
# print( student)
#
# session.add(student) # 加入刚创建的session 对象
# session.commit()
# try:
# student.name = 'ben'
# session.add_all([student])
# session.commit()
# except:
# session.rollback()
# print('~~~~~~~~~')
student = session.query(Student) # 用session 对象查这个表( Student)
print(1,student) # 并未真正执行,
print(student.count()) # 使用了子查询,效率不高
from sqlalchemy.orm.state import InstanceState
def getstate(instance,i):
state :InstanceState = sqlalchemy.inspect(instance)
output = "{}:{}".format(i,state.key)
print(output)
student = Student(id = 2,name = 'tom',age = 20)
getstate(student,0)
student = session.query(Student).get(1)
getstate(student,1)