• Python中操作MySQL


    pymysql(第三方库):

     首先,必须先和数据库建立一个传输数据的连接通道,需要用到pymysql下的connect()方法

    ​ pymysql.connect() 方法返回的是Connections类下的Connection 实例,connect() 方法传参就是在给Connection类的 _init_ 初始化不定长参数,也可以理解为 connect() 方法就是在创建新的 Connetion 对象

    connect() / Connection初始化 常用参数说明
    host 主机ip
    user 用户名
    password 密码
    database 数据库
    port 端口号
    charset 字符集
    import pymysql
    # 建立传输数据的链接通道
    conn = pymysql.connect(host="localhost", port=3306, user="root", passwd="root", charset="utf8")
    print(conn)     # <pymysql.connections.Connection object at 0x000001ADB3CD4E50>

    在使用pymysql.connect() 方法与数据库建立连接后,想要操作数据库时,就需要使用游标 Cursor

    ​通过连接好的数据库(此处为conn)调用 cursor() 方法即可返回一个新的游标对象,在连接没有关闭之前,游标对象可以反复使用

    # 获取游标,用于操作数据库
    cursor = conn.cursor()
    print(cursor)    # <pymysql.cursors.Cursor object at 0x000001ADB3ED4FD0>

     常用基础方法:

    execute():发送指令

    fetchone():取出下一条记录

    fetchchall():取出所有数据

    commit():提交变化到稳定存储(在对数据库进行增删改的时候需要做的操作)

    import pymysql
    # 建立传输数据的链接通道
    conn = pymysql.connect(host="localhost", port=3306, user="root", passwd="root", charset="utf8")
    print(conn)     # <pymysql.connections.Connection object at 0x000001C3C6687FD0>
    # 获取游标,用于操作数据库
    cursor = conn.cursor()
    print(cursor)   # <pymysql.cursors.Cursor object at 0x000001C3C6687F70>
    
    # 执行一个查询(即:发送指令)
    cursor.execute("show databases")
    # 取出下一条记录(有点像可迭代对象(iterable),具有next方法,可以逐一获取每行数据,但不是一个可迭代对象)
    result = cursor.fetchone()
    print(result)   # ('db1',)
    # 取出所有行,由于上面代码去了一条记录,所以fetchall获取的数据没有上面的那条数据
    result = cursor.fetchall()
    print(result)   # (('information_schema',), ('mysql',), ('performance_schema',), ('sys',))
    
    # 创建数据库
    cursor.execute("create database db3 default charset utf8 collate utf8_general_ci")
    # 增、删、改  都需要使用commit (提交变化到稳定存储)。
    conn.commit()
    cursor.execute("show databases")
    print(cursor.fetchall())     # (('db1',), ('db3',), ('information_schema',), ('mysql',), ('performance_schema',), ('sys',))
    
    # 删除数据库
    cursor.execute("drop database db3")
    conn.commit()
    cursor.execute("show databases")
    print(cursor.fetchall())    # (('db1',), ('information_schema',), ('mysql',), ('performance_schema',), ('sys',))
    
    cursor.execute("use db1")
    print(cursor.fetchall())    # ()
    cursor.execute("show tables")
    result = cursor.fetchall()
    print(result)       # (('123',), ('emp',), ('employee',), ('student',), ('student_1',))

    SQL注入

    假如,你开发了一个用户认证的系统,应该用户登录成功后才能正确的返回相应的用户结果

    import pymysql
    
    # 输入用户名和密码
    user = input("请输入用户名:") # ' or 1=1 -- 
    pwd = input("请输入密码:") # 123
    
    
    conn = pymysql.connect(host='127.0.0.1', port=3306, user='root', passwd='root123', charset="utf8",db='usersdb')
    cursor = conn.cursor()
    
    # 基于字符串格式化来 拼接SQL语句
    # sql = "select * from users where name='root' and password='123'"
    # sql = "select * from users where name='' or 1=1 -- ' and password='123'"
    sql = "select * from users where name='{}' and password='{}'".format(user, pwd)
    cursor.execute(sql)
    
    result = cursor.fetchone()
    print(result) # None,不是None
    
    cursor.close()
    conn.close()

    如果用户在输入user时,输入了: ' or 1=1 -- ,这样即使用户输入的密码不存在,也会可以通过验证。

    为什么呢?

    因为在SQL拼接时,拼接后的结果是:

    select * from users where name='' or 1=1 -- ' and password='123'

    注意:在MySQL中 -- 表示注释。

     

    那么,在Python开发中 如何来避免SQL注入呢?

    切记,SQL语句不要在使用python的字符串格式化,而是使用pymysql的execute方法。

    # SQL语句不在用python的字符串格式化,而是使用pymsql的execute方法
    import pymysql
    
    # 输入账号密码
    user = input("输入账号")
    pw = input("输入密码")
    
    # 创建数据库链接
    conn = pymysql.connect(host="localhost", port=3306, user="root", passwd="root", charset="utf8")
    # 创建游标
    cursor = conn.cursor()
    
    # 方式一:发送指令,execute会自动帮助我们检测,user、pw中是否存在特殊字符并帮助处理。
    cursor.execute("select * from user where name=%s and password=%s", [user, pw])
    # 方式二:
    # cursor.execute("select * from user where name=%(name)s and password=%(passwd)s", {"name": user, "passwd": pw})
    
    result = cursor.fetchall()
    print(result)
    
    # 关闭游标、关闭链接
    cursor.close()
    conn.close()

    Python代码:事务

    什么是数据库事务

    数据库事务是访问并可能操作各种数据项的一个数据库操作(包括增删改,不包括查询)序列,这些操作要么全部执行,要么全部不执行,是一个不可分割的工作单位。事务由事务开始与事务结束之间执行的全部数据库操作组成。

    import pymysql
    
    # 创建链接
    conn = pymysql.connect(host="localhost", port=3306, user="root", passwd="root", charset="utf8")
    # 这样写可以直接进入到数据库shool中
    # conn = pymysql.connect(host="localhost", port=3306, user="root", passwd="root", charset="utf8", db="shool")
    # 创建游标
    cursor = conn.cursor()
    
    # 进入数据库
    cursor.execute("use shool")
    
    
    # 开启事务
    conn.begin()
    
    # 尝试执行事务
    try:
        cursor.execute("update student set sname=%s where sid=8", ["某人飞"])
    except Exception as e:
        print("发生异常,进行回滚")
        conn.rollback()
    else:
        print("提交事务")
        conn.commit()
    
    # 关闭游标、关闭链接
    cursor.close()
    conn.close()

    在用MySQL时,不知你是否会疑问:同时有很多做更新、插入、删除动作,MySQL如何保证数据不出错呢?

     

    MySQL中自带了锁的功能,可以帮助我们实现开发过程中遇到的同时处理数据的情况。对于数据库中的锁,从锁的范围来讲有:

    • 表级锁,即A操作表时,其他人对整个表都不能操作,等待A操作完之后,才能继续。

    • 行级锁,即A操作表时,其他人对指定的行数据不能操作,其他行可以操作,等待A操作完之后,才能继续。

    MYISAM支持表锁,不支持行锁;
    InnoDB引擎支持行锁和表锁。
    
    即:在MYISAM下如果要加锁,无论怎么加都会是表锁。
        在InnoDB引擎支持下如果是基于索引查询的数据则是行级锁,否则就是表锁。

    在innodb引擎中,update、insert、delete的行为内部都会先申请锁(排它锁),申请到之后才执行相关操作,最后再释放锁。

    所以,当多个人同时像数据库执行:insert、update、delete等操作时,内部加锁后会排队逐一执行。
    而select则默认不会申请锁
    select * from xxx;

    如果,你想要让select去申请锁,则需要配合 事务 + 特殊语法来实现。

    • for update,排它锁,加锁之后,其他不可以读写。
    begin; 
        select * from L1 where name="飞某人" for update;    -- name列不是索引(表锁)
    commit;
    begin; -- 或者 start transaction;
        select * from L1 where id=1 for update;              -- id列是索引(行锁)
    commit;

    当行锁是id(索引)时,其他事务也需要有单独的索引才不会被阻塞,否则数据会通过id列就导致了阻塞

    • lock in share mode,共享锁,加锁之后,其他可读但不可写。
    begin; 
        select * from L1 where name="飞某人" lock in share mode;    -- 假设name列不是索引(表锁)
    commit;
    begin; -- 或者 start transaction;
        select * from L1 where id=1 lock in share mode;           -- id列是索引(行锁)
    commit;

    排它锁

    排它锁( for update),加锁之后,其他事务不可以读写。

    应用场景:总共100件商品,每次购买一件需要让商品个数减1 。

    A: 访问页面查看商品剩余 100
    B: 访问页面查看商品剩余 100
    
    此时 A、B 同时下单,那么他们同时执行SQL:
        update goods set count=count-1 where id=3
    由于Innodb引擎内部会加锁,所以他们两个即使同一时刻执行,内部也会排序逐步执行。
    
    
    但是,当商品剩余 1个时,就需要注意了。
    A: 访问页面查看商品剩余 1
    B: 访问页面查看商品剩余 1
    
    此时 A、B 同时下单,那么他们同时执行SQL:
        update goods set count=count-1 where id=3
    这样剩余数量就会出现 -1,很显然这是不正确的,所以应该怎么办呢?
    
    
    这种情况下,可以利用 排它锁,在更新之前先查询剩余数量,只有数量 >0 才可以购买,所以,下单时应该执行:
        begin; -- start transaction;
        select count from goods where id=3 for update;
        -- 获取个数进行判断
        if 个数>0:
            update goods set count=count-1 where id=3;
        else:
            -- 已售罄
        commit;

    基于Python代码示例:

    import pymysql
    import threading
    
    
    def task():
        # 创建链接
        conn = pymysql.connect(host="localhost", port=3306, user="root", passwd="root", charset="utf8", db="db1")
    
        # 创建游标
        # 参数的作用是将游标接收到的数据整理成字典的形式
        cursor = conn.cursor(pymysql.cursors.DictCursor)
    
        # 开启事务
        conn.begin()
    
        # 尝试执行事务
        try:
            cursor.execute("select id,age from tran where id=1 for update")
            # fetchall    有参:( {"id":1,"age":10},{"id":2,"age":10}, )  无参获取的结果:((1,10),(2,10))
            # fetchone    有参{"id":1,"age":10}    无参获取的结果:(1,10)
            result = cursor.fetchone()
            print(result)
            current_age = result['age']
            if current_age > 0:
                cursor.execute("update tran set age=age-1 where id=1")
            else:
                print("已售罄")
        except Exception as e:
            print("异常,进行回滚")
            conn.rollback()
        else:
            print("提交事务")
            conn.commit()
        cursor.close()
        conn.close()
    
    
    def fun():
        thread = threading.Thread(target=task)
        thread.start()
    
    
    if __name__ == '__main__':
        for x in range(11):
            fun()

     

    数据库连接池

    假设在用户使用时,每个用户都会获取一个数据库连接,如果有很多用户同时申请获取连接,然后使用后连接也都断开了,如果又要连接......这样的过程很明显用上面的代码是行不通的,数据库顶不住啊!

    这时候就需要一个数据库连接池,来管理用户对数据库连接的操作。

    在操作数据库时需要使用数据库连接池

    pip3.9 install pymysql
    pip3.9 install dbutils
    # 导入连接池工具包
    import threading
    import pymysql
    from dbutils.pooled_db import PooledDB
    
    MYSQL_DB_POOL = PooledDB(
        creator=pymysql,  # 使用链接数据库的模块,因为dbutils并不能链接数据库,真正连接数据库还是需要pymsql
        maxconnections=5,  # 连接池允许的最大连接数,0和None表示不限制连接数
        mincached=2,  # 初始化时,链接池中至少创建的空闲的链接,0表示不创建
        maxcached=3,  # 链接池中最多闲置的链接,0和None不限制
        blocking=True,  # 连接池中如果没有可用连接后,是否阻塞等待。True,等待;False,不等待然后报错
        setsession=[],  # 开始会话前执行的命令列表。如:["set datestyle to ...", "set time zone ..."]
        ping=0,
        # ping MySQL服务端,检查是否服务可用。
        # 如:0 = None = never, 1 = default = whenever it is requested,
        # 2 = when a cursor is created, 4 = when a query is executed, 7 = always
        host='127.0.0.1',
        port=3306,
        user='root',
        password='root',
        database='shool',
        charset='utf8'
    )
    
    
    def task():
        # 获取连接池中的连接
        conn = MYSQL_DB_POOL.connection()
        # 获取连接中的游标
        cursor = conn.cursor(pymysql.cursors.DictCursor)
        # cursor = conn.cursor()
    
        # 执行命令
        cursor.execute("show tables")
        # 接受数据
        result = cursor.fetchall()
        print(result)
        cursor.close()
    
        # 把链接交还给数据库连接池
        conn.close()
    
    
    def run():
        for x in range(10):
            thread = threading.Thread(target=task)
            thread.start()
    
    
    if __name__ == '__main__':
        run()

    SQL工具类

    因为数据库连接池只有一个,所以我们需要基于数据库连接池开发一个公共的SQL操作,方便以后操作数据库。

    使用模块的方法实现单例

    Python 的模块就是天然的单例模式,因为模块在第一次导入时,会生成 .pyc 文件,当第二次导入时,就会直接加载 .pyc 文件,而不会再次执行模块代码。因此,我们只需把相关的函数和数据定义在一个模块中,就可以获得一个单例对象了。如果我们真的想要一个单例类,可以考虑这样做

    # 数据库连接池_单例.py
    import pymysql
    from dbutils.pooled_db import PooledDB
    
    
    # 模块的方式实现单例
    class DBHelper(object):
        # 初始化数据库连接池
        def __init__(self):
            # TODO 此处配置,可以去配置文件中读取。
            self.pool = PooledDB(
                creator=pymysql,  # 使用链接数据库的模块
                maxconnections=5,  # 连接池允许的最大连接数,0和None表示不限制连接数
                mincached=2,  # 初始化时,链接池中至少创建的空闲的链接,0表示不创建
                maxcached=3,  # 链接池中最多闲置的链接,0和None不限制
                blocking=True,  # 连接池中如果没有可用连接后,是否阻塞等待。True,等待;False,不等待然后报错
                setsession=[],  # 开始会话前执行的命令列表。如:["set datestyle to ...", "set time zone ..."]
                ping=0,
                # ping MySQL服务端,检查是否服务可用。# 如:0 = None = never, 1 = default = whenever it is requested, 2 = when a cursor is created, 4 = when a query is executed, 7 = always
                host='127.0.0.1',
                port=3306,
                user='root',
                password='root',
                database='shool',
                charset='utf8'
            )
    
        # 获取连接和游标
        def get_conn_cursor(self):
            conn = self.pool.connection()
            cursor = conn.cursor(pymysql.cursors.DictCursor)
            return conn, cursor
    
        # 关闭游标,把链接交还给连接池
        def close_conn_cursor(self, *args):
            for item in args:
                item.close()
    
        # 发送指令
        def exec(self, sql, **kwargs):
            conn, cursor = self.get_conn_cursor()
            cursor.execute(sql, kwargs)
            conn.commit()
            self.close_conn_cursor(conn, cursor)
    
        # 获取一条数据
        def fetch_one(self, sql, **kwargs):
            conn, cursor = self.get_conn_cursor()
            cursor.execute(sql, kwargs)
            result = cursor.fetchone()
            self.close_conn_cursor(conn, cursor)
            return result
    
        # 获取所有数据
        def fetch_all(self, sql, **kwargs):
            conn, cursor = self.get_conn_cursor()
            cursor.execute(sql, kwargs)
            result = cursor.fetchall()
            self.close_conn_cursor(conn, cursor)
    
            return result
    
    
    db = DBHelper()
    # 数据库连接池测试.py
    # 测试模块的方式实现的单例
    from 数据库连接池_单例 import db
    
    db.exec("insert into student(sname) values(%(name)s)", name="飞某人")
    
    ret = db.fetch_one("select * from student")
    print(ret)
    
    ret = db.fetch_one("select * from student where sid=%(nid)s", nid=3)
    print(ret)
    
    ret = db.fetch_all("select * from student")
    print(ret)
    
    ret = db.fetch_all("select * from student where sid>%(nid)s", nid=2)
    print(ret)

    上下文管理的方式实现单例

    with 获取连接:
        执行SQL(执行完毕后,自动将连接交还给连接池)
    # 数据库连接池_单例.py
    import pymysql
    from dbutils.pooled_db import PooledDB
    # 上下文管理方式实现单例
    # 创建一个连接池
    POOL = PooledDB(
        creator=pymysql,  # 使用链接数据库的模块,因为dbutils并不能链接数据库,真正连接数据库还是需要pymsql
        maxconnections=5,  # 连接池允许的最大连接数,0和None表示不限制连接数
        mincached=2,  # 初始化时,链接池中至少创建的空闲的链接,0表示不创建
        maxcached=3,  # 链接池中最多闲置的链接,0和None不限制
        blocking=True,  # 连接池中如果没有可用连接后,是否阻塞等待。True,等待;False,不等待然后报错
        setsession=[],  # 开始会话前执行的命令列表。如:["set datestyle to ...", "set time zone ..."]
        ping=0,
        # ping MySQL服务端,检查是否服务可用。
        # 如:0 = None = never, 1 = default = whenever it is requested,
        # 2 = when a cursor is created, 4 = when a query is executed, 7 = always
        host='127.0.0.1',
        port=3306,
        user='root',
        password='root',
        database='shool',
        charset='utf8'
    )
    
    class Connect(object):
        # 初始化数据库连接与游标
        def __init__(self):
            self.conn = POOL.connection()
            self.cursor = self.conn.cursor(pymysql.cursors.DictCursor)
    
        # 上文管理
        def __enter__(self):
            return self
    
        # 下文管理
        def __exit__(self, exc_type, exc_val, exc_tb):
            self.cursor.close()
            self.conn.close()
    
        # 发送指令
        def exec(self, sql, **kwargs):
            self.cursor.execute(sql, kwargs)
            self.conn.commit()
    
        # 获取一条数据
        def fetch_one(self):
            result = self.cursor.fetchone()
            return result
    
        # 获取所有数据
        def fetch_all(self):
            result = self.cursor.fetchall()
            return result
    # 数据库连接池测试.py
    # 上下文管理测试
    
    from 数据库连接池_单例 import Connect
    
    with Connect() as obj:
        obj.exec("select * from student")
        result = obj.fetch_one()
        print(result)
        result = obj.fetch_all()
        print(result)
    
        obj.exec("select * from student where sname=%(name)s or sid=%(tid)s", name="某人飞", tid=4)
        result = obj.fetch_all()
        print(result)
  • 相关阅读:
    基础数据类型
    python2x与python3x区别(30个)更新中。。。
    注释
    常量
    变量
    十、SpringCloud config分布式配置中心
    九、Gateway新一代网关
    八、Hystrix断路器(下)
    八、Hystrix断路器(上)
    七、OpenFeign服务接口调用
  • 原文地址:https://www.cnblogs.com/fjfsu/p/15888590.html
Copyright © 2020-2023  润新知