python操作数据库

本文涉及的产品
云数据库 RDS MySQL Serverless,0.5-2RCU 50GB
简介:

游标

  在stored Routines调用中开的一个缓冲区,用于存放SQL调用的结果集。(结果集是只读的)

   意味着我们的查询可以返回一个文档也可以返回一个游标去指向一个结果集,而后通过游标的切换而获取每个结果

 


 

Python连接数据库

涉及模块

mysqldb  py3不再更新

pymysql  py3用的模块

 

安装pymysql 模块

pip install pymysql

导入模块

import pymysql

 

查看pymysql源码

  默认自动提交是False的

 

找到 Connection 类中,看如下代码:

def __init__(self, host=None, user=None, password="",

    database=None, port=0, unix_socket=None,

    charset='', sql_mode=None,

    read_default_file=None, conv=None, use_unicode=None,

    client_flag=0, cursorclass=Cursor, init_command=None,

    connect_timeout=10, ssl=None, read_default_group=None,

    compress=None, named_pipe=None, no_delay=None,

    autocommit=False, db=None, passwd=None, local_infile=False,

    max_allowed_packet=16*1024*1024, defer_connect=False,

    auth_plugin_map={}, read_timeout=None, write_timeout=None,

    bind_address=None, binary_prefix=False

):

 

连接数据库,判断数据库正常与否

使用try或者使用ping方法都可以

 

通过ping测试连通性

通过判断是否是None或者异常,则认为服务是否是存活的

import pymysql

conn = pymysql.connect('ip','root','123456')

print(conn.ping)

print(conn.ping(False))

 

如果连接失败我们要对其进行关闭,所以最好加入到try中

import pymysql

 

try:

    conn = pymysql.connect('x.x5.x','root','123456','test110')

    print(conn.ping(False))

finally:

    if conn:

        conn.close()

 

 


游标 Curosr

   在stored Routines调用中开的一个缓冲区,用于存放SQL调用的结果集。(结果集是只读的)

   意味着我们的查询可以返回一个文档也可以返回一个游标去指向一个结果集,而后通过游标的切换而获取每个结果

 

  在操作数据库的时候必须使用cursor类的实例,提供execute()方法,执行sql语句返回成功的行数

 

执行sql语句

import pymysql

 

try:

    conn = pymysql.connect('.x4.1.1','root','123456','test110')

    print(conn.ping(False))

    cursor = conn.cursor()               # 获取游标

    insert_sql = "insert into student(name,age) values('jerry',20)"

    line = cursor.execute(insert_sql)     # 执行

    print(line)

 

finally:

    if conn:

        conn.close()

 

返回如下:

None

1

 

但是数据库中并无数据,因为没有commit

在Connection类中,默认设置的是

 

一般不需要开启自动提交,需要手动管理事务并统一提交

 


事物的管理

有任何异常conn都要回滚确保数据无误,如果没有异常则commit()

close和commit 没有先后关系顺序

 

一个标准的数据库连接和操作关闭流程

import pymysql

 

conn = None

 

try:

    conn = pymysql.connect('47.xxx','root','123456','test110')

    print(conn.ping(False))

 

    # 获取游标

    cursor = conn.cursor()

    insert_sql = "insert into student(name,age) values('jerry',20)"

    line = cursor.execute(insert_sql)

 

    cursor.close()

    conn.commit()

 

except:

    conn.rollback()

 

finally:

    if conn:

        conn.close()

 

批量插入

try:

    conn = pymysql.connect('111.11.110','root','123456','test110')

    print(conn.ping(False))

 

    # 获取游标

    cursor = conn.cursor()

 

    #插入数据

    for i in range(10):

        insert_sql = "insert into student(name,age) values('jerry',{})".format(i)

        line = cursor.execute(insert_sql)

        print('line:',line)

    cursor.close()

    conn.commit()

往往会将批量的修改全部写前面,最后统一执行commit,游标关闭和链接提交是可以不分先后的

 

 

操作流程

建立连接--> 获取游标 --> 执行SQL --> 提交事物 --> 释放资源

如果出现异常需要回滚再释放资源

 

 


查询

不需要事物的地方一定不要使用事物,影响效率

查看分别有什么区别

 

conn = pymysql.connect('47.4.xx.xx','root','123456','test110')

print(conn.ping(False))

 

获取游标

cursor = conn.cursor()

 

sql = 'select * from student'

line = cursor.execute(sql)

#

获取前两个

# print(cursor.fetchaone())

# print(cursor.fetchall())

获取5个并以元组方式返回

print(cursor.fetchmany(5))

print(cursor.fetchmany(5))

获取剩下的全部

print(cursor.fetchall())

 

查看返回结果fethchall之所以获取很少的数据,因为都使用的是游标,类似于一个指针

 

重置游标

print(cursor.fetchmany(5))

print(cursor.fetchall())

cursor.rownumber = 0

print(cursor.fetchall())

这样指针又指向了0,其实是指向的索引

 

查看fethchall源码

实际上就是做了一个切片,以切片方式并记录当前位置返回我们想要的结果

 

def fetchall(self):

    """Fetch all the rows"""

    self._check_executed()

    if self._rows is None:

        return ()

    if self.rownumber:

        result = self._rows[self.rownumber:]

    else:

        result = self._rows

    self.rownumber = len(self._rows)

    return result

 

DictCursor 字典游标,带一些字段名方式进行返回

Cursor 类有一个Mixin的子类 DictCursor

 

导入模块

from pymysql.cursors import DictCursor

 

cursor = conn.cursor(cursor=DictCursor)

以上是元组和字典的返回差异

 

返回如下:

[{'name': 'jerry', 'en': None, 'age': 1}, {'name': 'jerry', 'en': None, 'age': 2}]

 

 

SQL注入攻击

在登陆的时候做了一些匹配或者明文匹配所导致

一般情况都需要进行加密

 

比如这样的语句:

select * from t where name='ben' and password='ben';

 

在登陆时要做唯一约束的,用户在写提交程序的时候,用户名需要异步去验证

这个过程已经是查过数据库了

 

在登陆时要做唯一约束的,用户在写提交程序的时候,用户名需要异步去验证

这个过程已经是查过数据库了

 

但是用户在执行的时候加了这么一句

select * from t where name='ben' and password='ben' or 1 = 1 ;

or 1=1 是真值,相当于 select * 

 

正常字符串拼接所造成的

正常的拼接:

name = 'jerry'

age = '3 or 1=1'

sql = 'select * from student where name={} and age={}'.format(name,age)

 

这样是最原始的拼接字符串的方式,太危险

这样通过id 或者其他来获取你的数据,总有一个参数能返回数据,每个参数都会遭到攻击

SELECT * FROM t where a = 1 and b = 1 or 1 = 1 and id=5 or 1=1;

如果密码失败,那么还可以通过查询来进行拖库,这样也会返回数据

 

所以,不能使用字符串拼接的方式来拼写sql

凡是用户提交的数据都不可信,要做严格的检查,哪怕是调用函数

 

解决sql注入

参数化查询,可以有效防止注入攻击,并提高查询效率

通过cursor.execute参数进行防注入

cursor.execute(query,args=None)

 

查看args源码

 

def execute(self, query, args=None):

    """Execute a query

 

    :param str query: Query to execute.

 

    :param args: parameters used with query. (optional)

    :type args: tuple, list or dict        明确写明args必须是一个元组列表或者字典

 

    :return: Number of affected rows

    :rtype: int

 

    If args is a list or tuple, %s can be used as a placeholder in the query.

    If args is a dict, %(name)s can be used as a placeholder in the query.

    """

    while self.nextset():

        pass

 

    query = self.mogrify(query, args)

 

    result = self._query(query)

    self._executed = query

    return result

通过拼接字符串的方式是能看到别人所有数据,那么改进如下:

 

try:

    conn = pymysql.connect('4x.x.x.x','root','123456','test110')

    cursor = conn.cursor(DictCursor)

 

    age = '20 or 1=1'

    sql = 'SELECT * FROM student where age=%s'

    # sql =' SELECT * FROM student where age={}'.format(age)

    cursor.execute(sql,(age,))    #将规则写入到execute中,判断是否有其他敏感字符

    print(cursor.fetchall())

 

Warning: (1292, "Truncated incorrect DOUBLE value: '3 or 1=1'")

[{'name': 'jerry', 'age': 3, 'en': None}]

  self._do_get_result()

 

尽可能转为目标的数据,友好的做了转换,但是发现有一些非法的字段被拦截掉了

提示非法DOUBLE的类型

 

更加复杂的例子

spacer.gif

 

sql = 'SELECT * FROM student where age > %(age)s'

# sql =' SELECT * FROM student where age={}'.format(age)

cursor.execute(sql,{'age':10})        #参数化查询

print(cursor.fetchall())

 

 

参数化为啥可以提高效率?

因为sql语句缓存

 

客户端每发一次sql语句到mysql中都有一个sql编译过程,只是对sql语句进行编译

如果不用参数化查询,那么id=1 id=2 id=3 来了三条语句,需要重新编译

 

mysql服务端会对sql语句编译和缓存,编译只对sql部分,只是sql部分并不是结果

编译过程,需要语法分析,生成AST并优化生成执行计划等过程 这个过程比较耗费资源

可认为本身sql语句字符串就是一个key,找到key则直接找到结果

那么如果使用拼接的方案,每次发过去的sql都不一样,都需要编译并缓存

开发时,应该使用参数化查询

这里只是查询的字符串的缓存,并不是查询结果

 

 


游标的上下文

查看游标的源码 __enter__ 和 __exit__

 

游标类:

def __enter__(self):

    return self        #返回自己

 

def __exit__(self, *exc_info):

    del exc_info

        self.close()    #调用close()关闭自己

 

连接类进入上下文的时候会返回一个游标对象,就是游标自己

游标类也使用上下文,用完了之后还会调用enter 和 exit

在退出时关闭游标对象,执行 self.close()

 

查看close源码:

 

def close(self):

    """

    Closing a cursor just exhausts all remaining data.

    """

    conn = self.connection

    if conn is None:

        return

    try:

        while self.nextset():

            pass

    finally:

        self.connection = None

 

    def __enter__(self):

        return self

 

    def __exit__(self, *exc_info):

        del exc_info

        self.close()

 

创建的时候用的是cursor,连接的时候也可以关闭

最后将连接 = None,说明没有连接,无法使用

 

 

 

连接类的上下文

有没有with as xxx ,是定义的问题,在退出with的时候,查看有否异常,如果存在异常则回滚

进入连接类的时候会返回一个游标

 

连接类:

def __enter__(self):

    """Context manager that returns a Cursor"""

    return self.cursor()

 

def __exit__(self, exc, value, traceback):

    """On successful exit, commit. On exception, rollback"""

    if exc:

        self.rollback()

    else:

        self.commit()        #如果没有异常则提交

 

创建的时候用的是self.cursor(),通过enter 进来的时候调用self.cursor(),直接调用了游标

游标通过调用本地方法获取

def cursor(self, cursor=None):

    """Create a new cursor to execute queries with"""

    if cursor:

        return cursor(self)

    return self.cursorclass(self)

如果没有存在,那么直接调用cursorclass ,那么cursorclass直接调用游标类

而cursorclass 就是在Connection 初始化中去获取

self.cursorclass = cursorclass

cursorclass直接指向了游标类,通过调用游标类返回一个自己的实例提供调用

 

总结

连接:

游标的上下文是返回自己提供使用的,在close()将游标关闭,关闭的是自己将其标记为None

对于连接来讲,在with进入之后返回的是cursor()游标自己的对象

 

连接类如下,在调用它的时候,上下文先执行,并将游标类调用,所以调用的是cursor()

def __enter__(self):

    """Context manager that returns a Cursor"""

    return self.cursor()

 

  关键是关闭的时候并没有自行关闭连接,因为连接是共用连接(长连接),所以不会关闭连接的,但是游标需要关闭,完全由用户控制

 

退出:

  但是如果退出with语句块,肯定会检查是否有异常,提交或者回滚

  当离开语句块的时候会提交或回滚

 

所以,代码需要如下改进:

 

import pymysql

from pymysql.cursors import DictCursor

 

conn = None

 

try:

    # 建立连接

    conn = pymysql.connect('7.94.xx','root','123456','test110')

    # cursor = conn.cursor(DictCursor)    #注释游标获取,在with中已经获取了游标

 

    with conn as cursor:        #代替了上一上,在进入上下文的时候,conn已经获取了游标

        d = {'age':'5'}

        sql = 'select * from student where age>%(age)s'

        print(sql)

        # 执行

        line = cursor.execute(sql,d)

        print(line)

        print(cursor.fetchall())

 

except:

    print('errrrrr')

finally:

    if conn:

        conn.close()

 

这样进入到with 连接对象 as cursor ,的时候直接调用了conn连接对象的上下文,并调用了游标类

返回了游标的self,这样就可以直接在with中调用,免去了开销

 

 

如果使用游标的上下文则可以:

conn = None

 

try:

    # 建立连接

    conn = pymysql.connect('ip','root','123456','test110')

    # cursor = conn.cursor(DictCursor)

 

    with conn as cursor:

        with cursor:

            d = {'age':10}

            sql = 'select * from student where age<%(age)s'

            cursor.execute(sql,d)

            print(cursor.fetchall())

 

except:

    print('errrrrr')

finally:

    if conn:

        conn.close()

 

当with conn as的时候, 返回一个新的cursor对象,当退出时,只要提交或者回滚了事物,并没有关闭

不关闭游标表示可以继续反复使用它,节省了开销

但是在最后finally中定义了关闭

finally:

    if conn:

        conn.close()

 


 

连接池

数据库最大的开销其实是连接,所以引入了连接池的概念

设置一个数据库连接池,使用者如果需要则get一个

 

分析

一个连接池,应该是一个可以设置大小容器,存放数据库的连接,使用者需要连接从池中获取一个连接,用完归还

 

启动的时候开启连接到数据库中,连接数据库的时候避免了频繁连接数据库,也限制了主动连接

连接池中只是存放的连接,具体如何使用是用户的事情,只需要存放一个正常的连接即可

 

选型

对于线程、所机制、信号量等无非使用Queue比较合适当前情景

Queue本身就保证了线程的绝对安全性

 

# coding:utf-8

import pymysql

from pymysql.cursors import DictCursor

from queue import Queue

conn = None

 

class ConnPool:

    def __init__(self,size,*args,**kwargs):

        self.size = size

        self._pool = Queue

 

    for i in range(size):

        conn = pymysql.connect(*args,**kwargs)      传入用户名密码

        self._pool.put(conn)                 传入到队列,生产者

 

    def get_conn(self):

        return self._pool.get()

 

    def return_conn(self,conn:pymysql.connections.Connection):

        self._pool.get(conn)        # 消费连接

 

线程池引入:初始化线程池、初始化之后上面代码就等于有了连接了

接下来就是如何使用的问题

 

那么可否将id放入到集合里,集合中是不允许重复的,id也是

 

判断只要地址不同就可以,那么也有风险,如果内存被回收,或者在这时间又赋予这个id内存地址

所以是有风险的,也不排除用户的操作问题;

所以需要做类型的判断;

 

 

判断类型

import pymysql

from pymysql.cursors import DictCursor

from queue import Queue

conn = None

 

class ConnPool:

    def __init__(self,size,*args,**kwargs):

        self.size = size

        self._pool = Queue(size)

 

        for i in range(size):

            conn = pymysql.connect(*args,**kwargs)  # 传入用户名密码

            self._pool.put(conn)                    # 传入到队列,生产者

 

    def get_conn(self):

        return self._pool.get()

 

    # 判断类型是否是连接

    def return_conn(self,conn:pymysql.connections.Connection):

        if isinstance(conn,pymysql.connections.Connection):

            self._pool.put(conn)

 

初始化类

pool = ConnPool(5,'4ip','root','123456','test')

#

conn = pool.get_conn()

print(conn)

消费连接

pool.return_conn(conn)

 

那么对于没有关闭的连接是需要手动的,所以能否在类中实现关闭,后期可以在当前类的上下文

 

 

引入Thread.Local ,前提是只有一个在用它,而且Thread.Local是顺序执行

在每个线程中用,限定在多线程的场景下使用,起码保证线程内,Thread.Local是安全的

 

import pymysql

from pymysql.cursors import DictCursor

from queue import Queue

import threading

 

class ConnPool:

    def __init__(self,size,*args,**kwargs):

        self.size = size

        self._pool = Queue(size)

        self.local = threading.local()

 

    for i in range(size):

        conn = pymysql.connect(*args,**kwargs) 传入用户名密码

        self._pool.put(conn) 传入到队列,生产者

 

    # 标记self.local.conn 获取的时候则赋值并返回get到的连接

    def get_conn(self):

    # return self._pool.get()

        conn = self._pool.get()

        self.local.conn = conn    # 标记当前连接,用于获取put之后标记None,如果get到了则标记当前连接

        return conn

 

    # 线程是顺序的,就是说一直用完到还回线程

    def return_conn(self,conn:pymysql.connections.Connection):

        if isinstance(conn,pymysql.connections.Connection):

            self._pool.put(conn)

            self.local.conn = None 用完之后标记为None,仿照连接类去写

 

初始化类

pool = ConnPool(5,'47.9.x.x.','root','123456','test')

#

conn = pool.get_conn()

print(conn)

消费连接

pool.return_conn(conn)

 

 

上下文改进

游标的上下文带来了一些列问题,那么可否自行增加上下文,如果是None则返回一个游标

如果不是None,那么就是连接了

 

enter   用于是否是None,是的话则赋予一个连接

exit    只要有一个退出,那么就标记当前为None

 

所以thread.local 还是可以的,因为都是在本地线程中使用,内存地址没有改动,直到关闭

 

    # 线程是顺序的,就是说一直用完到还回线程

    def return_conn(self,conn:pymysql.connections.Connection):

        if isinstance(conn,pymysql.connections.Connection):

            self._pool.put(conn)

            self.local.conn = None      # 用完之后标记为None,仿照连接类去写

 

    def __enter__(self):

        #刚进来的时候线程不存在则抛异常,肯定是None,所以给一个连接

        if getattr(self.local,'conn',None) is None:

            self.local.conn = self.get_conn()

        return self.local.conn.cursor() #返回一个游标

 

    def __exit__(self, exc_type, exc_val, exc_tb):

        if exc_type:

            self.local.conn.rollback()

        else:

            self.local.conn.commit()

        self.return_conn(self.local.conn)

        self.local.conn = None

 

 

初始化类

pool = ConnPool(5,'.x.x.x.x','root','123456','test')

 

with pool as cursor:

    with cursor:

        sql = 'select * from student'

        cursor.execute(sql)

        # print(cursor.fetchall())

        for x in cursor:

            print(x)

 

 


本文转自zuzhou 51CTO博客,原文链接:http://blog.51cto.com/yijiu/2058432

相关实践学习
基于CentOS快速搭建LAMP环境
本教程介绍如何搭建LAMP环境,其中LAMP分别代表Linux、Apache、MySQL和PHP。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
相关文章
|
21天前
|
缓存 NoSQL 关系型数据库
在Python Web开发过程中:数据库与缓存,MySQL和NoSQL数据库的主要差异是什么?
MySQL是关系型DB,依赖预定义的表格结构,适合结构化数据和复杂查询,但扩展性有限。NoSQL提供灵活的非结构化数据存储(如JSON),无统一查询语言,但能横向扩展,适用于大规模、高并发场景。选择取决于应用需求和扩展策略。
112 1
|
28天前
|
数据格式 Python
如何使用Python的Pandas库进行数据透视图(melt/cast)操作?
Pandas的`melt()`和`pivot()`函数用于数据透视。基本步骤:导入pandas,创建DataFrame,然后使用这两个函数转换数据格式。示例代码展示了如何通过`melt()`转为长格式,再用`pivot()`恢复为宽格式。输入数据是包含&#39;Name&#39;和&#39;Age&#39;列的DataFrame,最终结果经过转换后呈现出不同的布局。
39 6
|
29天前
|
SQL 数据库连接 数据库
你不知道ADo.Net中操作数据库的步骤【超详细整理】
你不知道ADo.Net中操作数据库的步骤【超详细整理】
16 0
|
30天前
|
XML 关系型数据库 MySQL
python将word(doc或docx)的内容导入mysql数据库
用python先把doc文件转换成docx文件(这一步也可以不要后续会说明),然后读取docx的文件并另存为htm格式的文件(上一步可以直接把doc文件另存为htm),python根据bs4获取p标签里的内容,如果段落中有图片则保存图片。(图片在word文档中的位置可以很好的还原到生成的数据库内容) 我见网上有把docx压缩后解压获取图片的,然后根据在根据xml来读取图片的位置,我觉得比较繁琐。用docx模块读取段落的时候还需要是不是判断段落中有分页等,然而转成htm之后就不用判断那么多直接判断段落里的样式或者图片等就可以了。
21 1
|
1月前
|
索引 Python
如何使用Python的Pandas库进行数据透视表(pivot table)操作?
如何使用Python的Pandas库进行数据透视表(pivot table)操作?
16 0
|
1月前
|
Unix Shell Linux
赞!优雅的Python多环境管理神器!易上手易操作!
赞!优雅的Python多环境管理神器!易上手易操作!
|
11天前
|
人工智能 机器人 C++
【C++/Python】Windows用Swig实现C++调用Python(史上最简单详细,80岁看了都会操作)
【C++/Python】Windows用Swig实现C++调用Python(史上最简单详细,80岁看了都会操作)
|
2天前
|
SQL JavaScript API
❤Nodejs 第四章(操作本地数据库实现删除-源码地址已开放)
【4月更文挑战第4天】❤Nodejs 第四章(操作本地数据库实现删除-源码地址已开放)在Node.js中实现删除本地数据库记录的操作。首先尝试通过SQL删除ID为8的用户,然后编写`app.delete`路由处理程序,从请求体获取ID并执行删除。。最终成功删除用户并展示了数据库的更新结果。下一节将优化增删改查功能。
15 1
|
5天前
|
SQL 关系型数据库 数据库
Python中SQLite数据库操作详解:利用sqlite3模块
【4月更文挑战第13天】在Python编程中,SQLite数据库是一个轻量级的关系型数据库管理系统,它包含在一个单一的文件内,不需要一个单独的服务器进程或操作系统级别的配置。由于其简单易用和高效性,SQLite经常作为应用程序的本地数据库解决方案。Python的内置sqlite3模块提供了与SQLite数据库交互的接口,使得在Python中操作SQLite数据库变得非常容易。
|
28天前
|
机器学习/深度学习 数据采集 C++
【Python 基础教程 07】全面掌握Python3数字操作:入门到精通的实用指南
【Python 基础教程 07】全面掌握Python3数字操作:入门到精通的实用指南
84 2

热门文章

最新文章