Python 数据库的Connection、Cursor两大对象
pymysql是Python中操作MySQL的模块,其使用方法和py2的MySQLdb几乎相同。
Python 数据库图解流程
Connection、Cursor比喻
Connection()的参数列表
host,连接的数据库服务器主机名,默认为本地主机(localhost)。
user,连接数据库的用户名,默认为当前用户。
passwd,连接密码,没有默认值。
db,连接的数据库名,没有默认值。
conv,将文字映射到Python类型的字典。
MySQLdb.converters.conversions
cursorclass,cursor()使用的种类,默认值为MySQLdb.cursors.Cursor。
compress,启用协议压缩功能。
named_pipe,在windows中,与一个命名管道相连接。
init_command,一旦连接建立,就为数据库服务器指定一条语句来运行。
read_default_file,使用指定的MySQL配置文件。
read_default_group,读取的默认组。
unix_socket,在unix中,连接使用的套接字,默认使用TCP。
port,指定数据库服务器的连接端口,默认是3306。
连接对象的db.close()方法可关闭数据库连接,并释放相关资源。
连接对象的db.cursor([cursorClass])方法返回一个指针对象,用于访问和操作数据库中的数据。
连接对象的db.begin()方法用于开始一个事务,如果数据库的AUTOCOMMIT已经开启就关闭它,直到事务调用commit()和rollback()结束。
连接对象的db.commit()和db.rollback()方法分别表示事务提交和回退。
指针对象的cursor.close()方法关闭指针并释放相关资源。
指针对象的cursor.execute(query[,parameters])方法执行数据库查询。
指针对象的cursor.fetchall()可取出指针结果集中的所有行,返回的结果集一个元组(tuples)。
指针对象的cursor.fetchmany([size=cursor.arraysize])从查询结果集中取出多行,我们可利用可选的参数指定取出的行数。
指针对象的cursor.fetchone()从查询结果集中返回下一行。
指针对象的cursor.arraysize属性指定由cursor.fetchmany()方法返回行的数目,影响fetchall()的性能,默认值为1。
指针对象的cursor.rowcount属性指出上次查询或更新所发生行数。-1表示还没开始查询或没有查询到数据。
Cursor
常用方法:
close():关闭此游标对象
fetchone():得到结果集的下一行
fetchmany([size = cursor.arraysize]):得到结果集的下几行
fetchall():得到结果集中剩下的所有行
excute(sql[, args]):执行一个数据库查询或命令
excutemany(sql, args):执行多个数据库查询或命令
常用属性:
connection:创建此游标对象的数据库连接
arraysize:使用fetchmany()方法一次取出多少条记录,默认为1
事务
事务命令
事务指逻辑上的一组操作,组成这组操作的各个单元,要不全部成功,要不全部不成功。
数据库开启事务命令
start transaction 开启事务
Rollback 回滚事务,即撤销指定的sql语句(只能回退insert delete update语句),回滚到上一次commit的位置
Commit 提交事务,提交未存储的事务
savepoint 保留点 ,事务处理中设置的临时占位符 你可以对它发布回退(与整个事务回退不同)
转账实例:
UPDATE account set balance=balance-5000 WHERE name=”yuan”;
UPDATE account set balance=balance+5000 WHERE name=”xialv”;
-- 创建表
create table test2(id int PRIMARY KEY auto_increment,name VARCHAR(20)) engine=innodb; -- 插入数据
INSERT INTO test2(name) VALUE ("alvin"),
("yuan"),
("xialv"); start transaction; -- 开启事务
insert into test2 (name)values('silv');
select * from test2;
commit; -- 提交事务 -- 保留点 start transaction;
insert into test2 (name)values('wu');
savepoint insert_wu; -- 给上面刚才insert的命令起了个名字叫insert_wu,并设置一个保留点,对重要的sql,要紧挨着设置保留点
select * from test2; delete from test2 where id=4;
savepoint delete1;
select * from test2; delete from test2 where id=1;
savepoint delete2;
select * from test2; rollback to delete1; -- 回滚到某个设置的节点 select * from test2; savepoint
事务特性
<1> 原子性(Atomicity):原子性是指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。
<2> 一致性(Consistency):事务前后数据的完整性必须保持一致。在事务执行之前数据库是符合数据完整性约束的,无论事务是否执行成功,事务结束后的数据库中的数据也应该是符合完整性约束的。在某一时间点,如果数据库中的所有记录都能保证满足当前数据库中的所有约束,则可以说当前的数据库是符合数据完整性约束的。
比如删部门表前应该删掉关联员工(已经建立外键),如果数据库服务器发生错误,有一个员工没删掉,那么此时员工的部门表已经删除,那么就不符合完整性约束了,所以这样的数据库也就性能太差啦!
<3>隔离性(Isolation):事务的隔离性是指多个用户并发访问数据库时,一个用户的事务不能被其它用户的事务所干扰,多个并发事务之间数据要相互隔离。
<4>持久性(Durability):持久性是指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,接下来即使数据库发生故障也不应该对其有任何影响。
三、隔离性:
将数据库设计为串行化程的数据库,让一张表在同一时间内只能有一个线程来操作。如果将数据库设计为这样,那数据库的效率太低了。所以数据库的设计这没有直接将数据库设计为串行化,而是为数据库提供多个隔离级别选项,使数据库的使用者可以根据使用情况自己定义到底需要什么样的隔离级别。
不考虑隔离性可能出现的问题:
脏读
--一个事务读取到了另一个事务未提交的数据,这是特别危险的,要尽力防止。
a 1000
b 1000
a:
start transaction;
update set money=money+100 where name=b;
b:
start transaction;
select * from account where name=b;--1100
commit;
a:
rollback;
b: start transaction;
select * from account where name=b;--1000
不可重复读
--在一个事务内读取表中的某一行数据,多次读取结果不同。(一个事务读取到了另一个事务已经提交
-- 的数据--增加记录、删除记录、修改记录),在某写情况下并不是问题,在另一些情况下就是问题。 a:
start transaction;
select 活期账户 from account where name=b;--1000 活期账户:1000
select 定期账户 from account where name=b;--1000 定期账户:1000
select 固定资产 from account where name=b;--1000 固定资产:1000
------------------------------
b:
start transaction;
update set money=0 where name=b;(把某一个账户的资金置为0)
commit;
------------------------------
select 活期+定期+固定 from account where name=b; --2000 总资产: 2000
虚读
是指在一个事务内读取到了别的事务插入的数据,导致前后读取不一致。
(一个事务读取到了另一个事务已经提交的数据---增加记录、删除记录),在某写情况下并不是问题,在另一些情况下就是问题。 b 1000
c 2000
d 3000
a:
start transaction
select sum(money) from account;---3000 3000(b+c)
-------------------
d:start transaction;
insert into account values(d,3000);
commit;
-------------------
select count(*)from account;---3 3(算上插入的d总共三个人)
3000/3 = 1000 1000 (但是总金额还是按照b和c两个人算的,所以平均金额不对)
四个隔离级别:
Serializable:可避免脏读、不可重复读、虚读情况的发生。(串行化)
Repeatable read:可避免脏读、不可重复读情况的发生。(可重复读)不可以避免虚读
Read committed:可避免脏读情况发生(读已提交)
Read uncommitted:最低级别,以上情况均无法保证。(读未提交)
安全性考虑:Serializable>Repeatable read>Read committed>Read uncommitted
数据库效率:Read uncommitted>Read committed>Repeatable read>Serializable
一般情况下,我们会使用Repeatable read、Read committed mysql数据库默认的数据库隔离级别Repeatable read
mysql中设置数据库的隔离级别语句:
set [global/session] transaction isolation level xxxx;
如果使用global则修改的是数据库的默认隔离级别,所有新开的窗口的隔离级别继承自这个默认隔离级别如果使用session修改,则修改的是当前客户端的隔离级别,和数据库默认隔离级别无关。当前的客户端是什么隔离级别,就能防止什么隔离级别问题,和其他客户端是什么隔离级别无关。
mysql中设置数据库的隔离级别语句:
select @@tx_isolation;
事务
事务指逻辑上的一组操作,组成这组操作的各个单元,要不全部成功,要不全部不成功。
数据库开启事务命令
-- start transaction 开启事务
-- Rollback 回滚事务,即撤销指定的sql语句(只能回退insert delete update语句),回滚到上一次commit的位置
-- Commit 提交事务,提交未存储的事务
--
-- savepoint 保留点 ,事务处理中设置的临时占位符 你可以对它发布回退(与整个事务回退不同)
实例
--创建表
create table account(
id int primary key auto_increment,
name varchar (25),
balance double
);
insert into account values (1,'alex',8000),(2,'ego',8000);
-- +----+------+---------+
-- | id | name | balance |
-- +----+------+---------+
-- | 1 | alex | 8000 |
-- | 2 | ego | 8000 |
-- +----+------+---------+
start transaction ;--开始事务
update account set balance=balance-5000 where name='alex';
select * from account;
-- +----+------+---------+ --此时数据并没有写入数据库,只是显示命令的结果,除非在操作下面写commit
-- | id | name | balance |
-- +----+------+---------+
-- | 1 | alex | 3000 |
-- | 2 | ego | 8000 |
-- +----+------+---------+
savepoint update1; --设置保留点
update account set balance=balance+5000 where name='ego';
select * from account;
-- +----+------+---------+ --一样数据没有写入数据库
-- | id | name | balance |
-- +----+------+---------+
-- | 1 | alex | 3000 |
-- | 2 | ego | 13000 |
-- +----+------+---------+
savepoint update2;
rollback to update1; --回滚至操作update1处,update1以上的操作任然存在,update1下的操作将全被取消
select * from account;
-- +----+------+---------+
-- | id | name | balance |
-- +----+------+---------+
-- | 1 | alex | 3000 |
-- | 2 | ego | 8000 |
-- +----+------+---------+
rollback ; --直接回滚,则会回滚自前面的commit处,如果没有commit就一直回滚至开头
-- +----+------+---------+
-- | id | name | balance |
-- +----+------+---------+
-- | 1 | alex | 8000 |
-- | 2 | ego | 8000 |
-- +----+------+---------+
commit ; --提交数据,此时数据才真正写入数据库
select * from account;
-- +----+------+---------+
-- | id | name | balance |
-- +----+------+---------+
-- | 1 | alex | 8000 |
-- | 2 | ego | 8000 |
-- +----+------+---------+
Python3 pymysql事务处理,执行多条sql语句后提交
# -*- coding: utf-8 -*- import pymysql.cursors
# 连接数据库
connect = pymysql.Connect(
host='localhost',
port=3310,
user='user',
passwd='',
db='test',
charset='utf8'
)
# 事务处理
sql_1 = "UPDATE staff SET saving = saving + 1000 WHERE user_id = '1001' "
sql_2 = "UPDATE staff SET expend = expend + 1000 WHERE user_id = '1001' "
sql_3 = "UPDATE staff SET income = income + 2000 WHERE user_id = '1001' " try:
cursor.execute(sql_1) # 储蓄增加1000
cursor.execute(sql_2) # 支出增加1000
cursor.execute(sql_3) # 收入增加2000
except Exception as e:
connect.rollback() # 事务回滚
print('事务处理失败', e)
else:
connect.commit() # 事务提交
print('事务处理成功', cursor.rowcount) # 关闭连接
cursor.close()
connect.close()
python pymysql cursor的问题
使用python操作mysql的时候一般都会使用如下语句:
官方示例
import pymysql.cursors
# 连接到数据库后实际上TCP的连接状态是ESTABLISHED
connection = pymysql.connect(host='localhost',
user='user',
password='passwd',
db='db',
charset='utf8mb4',
cursorclass=pymysql.cursors.DictCursor)
try:
with connection.cursor() as cursor:
sql = "INSERT INTO `users` (`email`, `password`) VALUES (%s, %s)"
cursor.execute(sql, ('webmaster@python.org', 'very-secret'))
#默认不自动提交事务,所以需要手动提交
connection.commit()
with connection.cursor() as cursor:
sql = "SELECT `id`, `password` FROM `users` WHERE `email`=%s"
cursor.execute(sql, ('webmaster@python.org',))
result = cursor.fetchone()
print(result)
finally:
connection.close()
在这段代码里,有一个疑惑的点是,我们现在创建了一个连接,但是实例化了多个cursor,我们可不可以使用同一个连接的同一个cursor来重复使用,代码如下
with connect.cursor() as cursor:
cursor.execute("select * from person limit 1")
print(id(cursor))
data = cursor.fetchone()
print(data)
print("=============")
cursor.execute("select * from person limit 1")
print(id(cursor))
data = cursor.fetchone()
print(data)
上面的代码,我们执行完了之后发现查询操作是可以直接使用的,并且不会产生冲突,我们通过打印cursor的地址发现是同一个cursor。
插入操作
with connect.cursor() as cursor:
for id in range(1, 100, 2):
cursor.execute("insert into test(id)values(%d)"%id)
print(id)
id += 1
cursor.execute("insert into test(id)values(%d)"%id)
time.sleep(2)
print(id)
在单进程单线程环境下依然没有问题
删除
with connect.cursor() as cursor:
for id in range(100):
# cursor.execute("insert into test(id)values(%d)" % id)
cursor.execute("delete from test where id=%s"%id)
time.sleep(5)
time.sleep(10)
也没有问题
但是有博客说多进程环境下会出现问题,我一直想重现,但是没有成功,等以后重现了再来更新。
但是
- 创建了一个 cursor 以后,建议完成一个事务就 commit 一下,不要一直用它,这样一直使用,并不会和数据库完成数据同步,如果操作太多,提交的时候会超时,造成部分数据更新,部分数据丢失,数据不一致且效率低。
并且握着 cursor 期间,数据库端发生改变,也没法和客户端同步。
参考链接:
Python 数据库的Connection、Cursor两大对象
PyMySQL 0.9.2
python - 在 python MySQLdb模块中,是否应该重用游标
你看一下MySQLdb下面的cursors模块,DictCursor
的这个功能是继承于CursorDictRowsMixIn
,这个MixIn提供了3个额外的方法: fetchoneDict、fetchmanyDict、fetchallDict。
>>> import MySQLdb
>>> c = MySQLdb.connect('127.0.0.1', 'root', 'password', 'test')
>>> x = c.cursor(MySQLdb.cursors.DictCursor)
>>> x.execute('select * from user')
2L
>>> x.fetchoneDict()
{'age': 26L, 'id': 1L, 'name': 'test'}
二、execute()之sql注入
这里只截图演示三种SQL注入示例截图
1
2
3
解决方法:
# 原来是我们对sql进行字符串拼接
# sql="select * from userinfo where name='%s' and password='%s'" %(username,pwd)
# print(sql)
# result=cursor.execute(sql)
#改写为(execute帮我们做字符串拼接,我们无需且一定不能再为%s加引号了)
sql="select * from userinfo where name=%s and password=%s" #!!!注意%s需要去掉引号,因为pymysql会自动为我们加上
result=cursor.execute(sql,[user,pwd]) #pymysql模块自动帮我们解决sql注入的问题,只要我们按照pymysql的规矩来。
pymysql .cursor属性方法(tcy)
cursor.excutemany(query, args)#执行多个数据库查询或命令
参数:
query - 要在服务器上执行的查询
args - 序列或映射的序列。它用作参数。
返回:
受影响的行数(如果有)
说明:
提高多行INSERT和REPLACE的性能。否则它等同于使用execute()循环遍历args
executemany()生成的最大语句大小为max_allowed_packet - packet_header_size
cursor.max_stmt_length #1024000
con.max_allowed_packet#16777216
实例:
str_insert = "INSERT INTO Cu_table (id) VALUES (%s)"
cursor.executemany(str_insert,['A','B','C','D']) #一次插入A B C D 四个值 方法:调用函数
cursor.callproc(procname, args=()) # 调用数据库中的存储过程
参数:
procname表示数据库中存储过程的名字,args表示为存储过程传入的参数。
返回:
返回原始args
调用的结果作为输入序列的修改副本返回。
该过程还可以提供结果集作为输出。然后必须通过标准.fetch * ()
方法使其可用 调用无参数存储过程:cursor.callproc('p2') # 等价cursor.execute("call p2()")
调用有参数存储过程:cursor.callproc('p1', args=(1, 22, 3, 4)) class pymysql.cursors.Cursor(connection) # 游标结果作为元祖的元祖返回
说明:
这是您用于与数据库交互的对象。
不要自己创建Cursor实例。调用connections.Connection.cursor()
class pymysql.cursors.SSCursor(connection) # 无缓冲游标结果作为元祖的元祖返回,
用途:
用于返回大量数据查询,或慢速网络连接到远程服务器 不将每行数据复制到缓冲区,根据需要获取行。客户端内存使用少
在慢速网络上或结果集非常大时行返回速度快 限制:
MySQL协议不支持返回总行数,判断有多少行唯一方法是迭代返回的每一行。
目前无法向后滚动,因为只有当前行保存在内存中。
class pymysql.cursors.DictCursor(connection) # 将结果作为字典返回游标
class pymysql.cursors.SSDictCursor(connection) # 无缓冲游标结果作为字典返回 游标属性:
cursor.max_stmt_length # 1024000
cursor.rownumber # 5 #当前结果集中游标所在行的索引(起始行号为 0)
cursor.arraysize # 1 #此读/写属性指定用.fetchmany()一次获取的行数。
# 默认1表示一次获取一行;也可以用于执行.executemany()
cursor.lastrowid # None #只读属性提供上次修改行的rowid
# DB仅在执行单个INSERT 操作时返回rowid 。
# 如未设rowid或DB不支持rowid应将此属性设置为None
# 如最后执行语句修改了多行,例如用INSERT和.executemany()时lastrowid语义是未定义 cursor.rowcount # 5 #最近一次 execute() 创建或影响的行数
# 如无cursor.execute()或接口无法确定最后一个操作的rowcount则该属性为-1
# 该行数属性可以在动态更新其值的方式来编码。
# 这对于仅在第一次调用.fetch()方法后返回可用rowcount值的 数据库非常有用。 commit()方法:在数据库里增,删,改的时候。必须要进行提交,否则插入的时候数据不生效