Python学习之 --- 数据库编程之pymysql 编程

数据库编程

驱动


Mysql 基于TCP 协议之上开发,但是网络连接后,传输的数据必须遵循Mysql 协议的包,就是驱动程序

Mysql驱动

  • Mysqldb

    最有名的库,对Mysql的client封装实现,支持python2 不在更新,不支持python3

  • Mysql官方的Connector

  • pymysql 语法兼容Mysqldb ,使用python写的库,支持oython

pymysql的使用

安装:
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;

连接 Connect

首先,必须建立一个传输数据通道——连接。
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('~~~~~~~~~~')

一般流程:

  • 建立连接
  • 获取游标
  • 执行sql 语句
  • 提交事务
  • 释放资源

带列名字查询

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)

你可能感兴趣的:(数据库入门)