目录
connect,连接:... 1
cursor: 3
Connection和Cursor,上下文支持:... 5
SQL注入***:... 7
数据库开发:
驱动:
MySQL基于TCP,在TCP协议之上开发,在网络连接后,传输的数据必须遵循MySQL协议,封装好MySQL协议的包就是驱动程序;
MySQL的驱动:
MySQLdb,最有名的库,对MySQL的C client封装实现,支持py2,不更新了,不支持py3;
MySQL官方Connector;
pymysql,语法兼容MySQLdb,py写的,支持py3;
pymysql:
>pip install pymysql
connect,连接:
pymysql.connect() #先要建立一个传输数据通道,pymysql.connect()方法返回的是Connections模块下的Connection类的实例,传参就是给Connection类的__init__()提供参数
Connection类初始化参数:
host
port
user
password
database
autocommit=False
事务管理:
Connections类有3个方法:
begin() #开始事务
commit() #将变更提交
rollback() #回滚事务
总结,一般流程:
建立连接;
获取游标;
执行sql;
提交事务(出现异常需回滚);
释放资源;
注:
connections模块下的Connection类:
class Connection(object):
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,
autocommit=False, db=None, passwd=None, local_infile=False,
max_allowed_packet=16*1024*1024, defer_connect=False,
auth_plugin_map=None, read_timeout=None, write_timeout=None,
bind_address=None, binary_prefix=False, program_name=None,
server_public_key=None):
def ping(self, reconnect=True):
def cursor(self, cursor=None):
def begin(self):
def commit(self):
def rollback(self):
Connection.ping() #测试db server是否活着,reconnect表示断开与服务器连接是否重连
例:
conn = None
try:
conn = pymysql.connect('10.113.129.2', 'root', 'rootqazwsx', 'test1') #port不写默认3306
print(conn.ping(False))
finally:
if conn:
conn.close()
输出:
None
cursor:
操作DB,必须使用游标,需要先获取一个游标对象;
Connection.cursor(cursor=None),返回一个新的游标对象(cursors模块的Cursor类),cursor参数可指定一个Cursor类,若为None,则使用默认Cursor类;
连接没有关闭前,游标对象可重复使用;
查询,Cursor类的获取查询结果集的方法有:
fetchone(),获取结果集的下一行;返回元组;
fetchmany(size=None),size指定返回行数的行,size不给则返回空元组;查询一条记录是一个元组,指定size条记录再组成元组,元组套元组;
fetchall(),获取所有行;返回元组套元组;
fetch操作的是结果集,结果集是保存是client的,即fetch的时候,查询已经结束了;
cursor.rownumber,返回当前行号,可修改;
cursor.rownumber = 0,可调整游标位置,用索引实现的支持负数;
cursor.rowcount,返回总行数;
带列名查询:
DictCursor类,是Mixin的子类;
返回一行是一个字典;
返回多行,放在列表中,元素是字典,代表行;
cursor的迭代:
看源码可知,获取的是查询数据库的每一行记录;
for x in cursor:
print(x)
注:
cursors模块中Cursor类:
class Cursor(object):
def execute(self, query, args=None): #执行sql语句用此方法,执行成功则返回影响的行数(而不是查询结果)
def fetchone(self):
def fetchmany(self, size=None):
def fetchall(self):
def __iter__(self):
return iter(self.fetchone, None)
例:
conn = None
cursor = None
try:
conn = pymysql.connect('10.113.129.2', 'root', 'rootqazwsx', 'test1')
print(conn.ping())
cursor = conn.cursor()
sql = "insert into t values(7, 'wang', 18)"
line = cursor.execute(sql) #用游标来操作数据
print(line)
# cursor.close() #游标也可放到finally处关闭
conn.commit() #默认Connections类的初始化参数autocommit=False,此处若没有conn.commit()则数据不会持久化到disk,生产代码不建议开启autocommit=True,要用手动管理事务
except:
conn.rollback()
finally:
if cursor:
cursor.close()
if conn:
conn.close()
输出:
None
1
例:批量新增
conn = None
cursor = None
try:
conn = pymysql.connect('10.113.129.2', 'root', 'rootqazwsx', 'test1')
print(conn.ping())
cursor = conn.cursor()
for i in range(8,19):
sql = "insert into t values({0}, 'test{0}', 18)".format(i)
line = cursor.execute(sql)
print(line)
# cursor.close()
conn.commit()
except:
conn.rollback()
finally:
if cursor:
cursor.close()
if conn:
conn.close()
例,查询:
conn = None
cursor = None
try:
conn = pymysql.connect('10.113.129.2', 'root', 'rootqazwsx', 'test1')
print(conn.ping())
cursor = conn.cursor() #取值时,是根据select中指定的字段顺序有序的显示在元组中
# cursor = conn.cursor(cursor=pymysql.cursors.DictCursor) #返回字典,取值时用key,字典比元组费空间
sql = "select * from t"
line = cursor.execute(sql)
print(line)
print(cursor.fetchone())
print(cursor.fetchone())
print(cursor.fetchmany(5)) #返回tuple且是tuple套tuple(不可变),另有些库是返回列表,可改后再update回去
print(cursor.fetchmany(5))
print(cursor.fetchall())
cursor.rownumber = 0 #可动游标,用索引实现的,看源码得知同序列中的索引,可超界
print(cursor.fetchall())
conn.commit()
except:
conn.rollback()
finally:
if cursor:
cursor.close()
if conn:
conn.close()
输出:
None
18
(1, 'jowin', 30)
(4, 'chai', 28)
((3, 'chaijowin', 18), (0, None, None), (4, 'www', 30), (6, 'ftp', 28), (7, 'wang', 18))
((8, 'test8', 18), (9, 'test9', 18), (10, 'test10', 18), (11, 'test11', 18), (12, 'test12', 18))
((13, 'test13', 18), (14, 'test14', 18), (15, 'test15', 18), (16, 'test16', 18), (17, 'test17', 18), (18, 'test18', 18))
((1, 'jowin', 30), (4, 'chai', 28), (3, 'chaijowin', 18), (0, None, None), (4, 'www', 30), (6, 'ftp', 28), (7, 'wang', 18), (8, 'test8', 18), (9, 'test9', 18), (10, 'test10', 18), (11, 'test11', 18), (12, 'test12', 18), (13, 'test13', 18), (14, 'test14', 18), (15, 'test15', 18), (16, 'test16', 18), (17, 'test17', 18), (18, 'test18', 18))
Connection和Cursor,上下文支持:
cursor频繁打开并不耗资源,耗资源的是connection;
查看Connection的源码可知,__enter__()用于获取游标,__exit__()用于异常回滚和无异常提交,并不关闭cursor也不关闭conn连接:
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() #无异常提交
查看Cursor的源码:
def __enter__(self):
return self #返回自己
def __exit__(self, *exc_info):
del exc_info
self.close() #关闭游标
例:
conn = None
try:
conn = pymysql.connect('10.113.129.2', 'root', 'rootqazwsx', 'test1')
with conn.cursor() as cursor:
id = '2 or 1=1'
sql = 'select * from login where id=%s'
line = cursor.execute(sql, (id,))
print(line)
print(cursor.fetchall())
conn.commit()
except Exception as e:
print(e)
conn.rollback()
finally:
if conn:
conn.close()
例:
conn = pymysql.connect('10.113.129.2', 'root', 'rootqazwsx', 'test1')
try:
cursor = conn.cursor()
with cursor:
id = '1 or 1=1'
sql = 'select * from login where id=%s'
line = cursor.execute(sql, (id,))
print(line)
print(cursor.fetchall())
conn.commit()
except Exception as e:
print(e)
conn.rollback()
finally:
if conn:
conn.close()
例,常用此种,conn和cursor都用上下文:
conn = None
try:
conn = pymysql.connect('10.113.129.2', 'root', 'rootqazwsx', 'test1')
with conn as cursor:
with cursor:
id = '1 or 1=1'
sql = "select * from login where id=%s"
line = cursor.execute(sql, (id,))
print(line)
print(cursor.fetchall())
except Exception as e:
print(e)
finally:
if conn:
conn.close()
SQL注入***:
登录的用户名上作unique key约束,才能保证登录名不会出问题;
在web页上注册用户时提示“该用户已存在”,用到的技术异步提交,用ajax实现,用户每改一个字母都会提交到后台查询;
永远不要相信client传来的数据是规范及安全的;
select * from login where name='jowin' and password='jowin123' or 1=1;
同
select * from login where True;
name = 'jowin'
password = 'jowin123'
sql = "select * from login where name='{}' and password='{}'".format(name,password) #字符串拼接,生产中要禁用,这种方式防不住sql***,解决:参数化查询
参数化查询:
可有效防止注入***;
可提高查询效率,原因:
SQL语句缓存;
数据库服务器一般会对sql语句编译和缓存,编译只对sql语句部分,所以参数中就算有sql指令也不会被执行;
编译过程,需要词法分析、语法分析、生成AST、优化、生成执行计划等过程,比较耗费资源;
服务端会先查找是否对同一条查询语句进行了缓存,如果缓存未失效,则不需要再次编译,从而降低了编译成本,降低了内存消耗;
可以认为sql语句字符串就是一个key,如果使用拼接方案,每次发过去的sql语句都不一样,都需要编译并缓存;
大量查询时,首选使用参数化查询,以节省资源;
Cursor.execute(query,args=None)
args必须是tuple、list、dict
query,查询字符串如果使用%(name)s,args就必须使用dict
方式一:
id = '2 or 1=1'
sql = "select * from login where id=%s"
line = cursor.execute(sql, (id,)) #方式一,tuple
方式二:
d = {'id': '2 or 1=1'}
sql = "select * from login where id=%(id)s" #方式二,使用dict
line = cursor.execute(sql, d)
例:
mysql> select * from login;
+----+-------+----------+
| id | name | password |
+----+-------+----------+
| 1 | jowin | jowin123 |
| 2 | chai | chai123 |
+----+-------+----------+
2 rows in set (0.01 sec)
mysql> select * from login where name='jowin' and password='jowin123';
+----+-------+----------+
| id | name | password |
+----+-------+----------+
| 1 | jowin | jowin123 |
+----+-------+----------+
1 row in set (0.00 sec)
mysql> select * from login where name='jowin' and password='jowin123' or 1=1;
+----+-------+----------+
| id | name | password |
+----+-------+----------+
| 1 | jowin | jowin123 |
| 2 | chai | chai123 |
+----+-------+----------+
2 rows in set (0.00 sec)
mysql> select * from login where name='jowin' and password='jowin123 or 1=1';
Empty set (0.00 sec)
mysql> select * from login where name='jowin' and password='jowin123' and id=5 or 1=1;
+----+-------+----------+
| id | name | password |
+----+-------+----------+
| 1 | jowin | jowin123 |
| 2 | chai | chai123 |
+----+-------+----------+
2 rows in set (0.01 sec)
mysql> select * from login where True;
+----+-------+----------+
| id | name | password |
+----+-------+----------+
| 1 | jowin | jowin123 |
| 2 | chai | chai123 |
+----+-------+----------+
2 rows in set (0.00 sec)
例:
conn = None
cursor = None
try:
conn = pymysql.connect('10.113.129.2', 'root', 'rootqazwsx', 'test1')
cursor = conn.cursor()
id = '2 or 1=1'
# sql = "select * from login where id={}".format(id) #生产要禁用这种方式
# line = cursor.execute(sql)
sql = "select * from login where id=%s"
line = cursor.execute(sql, (id,)) #方式一,tuple
print(line)
print(cursor.fetchall())
conn.commit()
except:
conn.rollback()
finally:
if cursor:
cursor.close()
if conn:
conn.close()
输出:
1
((2, 'chai', 'chai123'),)
D:\Python\Python35\lib\site-packages\pymysql\cursors.py:170: Warning: (1292, "Truncated incorrect DOUBLE value: '2 or 1=1'")
result = self._query(query)
例:
conn = None
cursor = None
try:
conn = pymysql.connect('10.113.129.2', 'root', 'rootqazwsx', 'test1')
cursor = conn.cursor()
# id = '2 or 1=1'
# sql = "select * from login where id=%s"
# line = cursor.execute(sql, (id,))
# print(line)
# print(cursor.fetchall())
d = {'id': '2 or 1=1'}
sql = "select * from login where id=%(id)s" #方式二,使用dict
line = cursor.execute(sql, d)
print(line)
print(cursor.fetchall())
conn.commit()
except:
conn.rollback()
finally:
if cursor:
cursor.close()
if conn:
conn.close()
输出:
D:\Python\Python35\lib\site-packages\pymysql\cursors.py:170: Warning: (1292, "Truncated incorrect DOUBLE value: '2 or 1=1'")
result = self._query(query)
1
((2, 'chai', 'chai123'),)
数据库连接池的实现: