当前位置:首页 > 问答 > 正文

Python老版本怎么跟数据库打交道,连接中那些容易忽略的小技巧分享

说到用老版本Python,比如Python 2.7或者早期的Python 3.x(像3.4、3.5这些)跟数据库打交道,现在很多人可能已经不太熟悉了,那时候没有现在这么统一的DB-API规范,各家有各家的玩法,但核心思路还是相通的,这里主要聊聊用像MySQLdbpymysql或者sqlite3这类库时,一些容易被忽略的小细节,这些经验很多是老程序员在项目实战里一点点踩坑总结出来的,网上零散的讨论也有,但不成系统。

第一个容易忽略的点,就是连接字符串的编码问题。 尤其是在Python 2时代,字符串有strunicode两种类型,简直是混乱之源,很多新手配置连接时,只写个主机、端口、用户名密码就以为万事大吉了,结果一执行包含中文的SQL语句就报错,提示什么UnicodeDecodeError或者Incorrect string value,这里的窍门是,一定要在建立连接的时候,就通过参数明确指定编码,比如用MySQLdb连接MySQL,你得加上charset='utf8'这个参数,像这样:conn = MySQLdb.connect(host='localhost', user='root', passwd='password', db='test', charset='utf8'),这个charset参数是告诉数据库驱动,后续所有的通信,包括我们发过去的SQL语句和从数据库拿回来的数据,都按照UTF-8编码来处理,这一步做对了,能避免八九成的中文乱码问题,有些更老的教程可能还会让你用set_character_set方法,但在连接时直接指定是更推荐的做法。

第二个小技巧,是关于游标(cursor)的。 大家都知道执行SQL要用游标,但游标用完了要不要关?连接用完了要不要关?很多人图省事,就不关,觉得程序结束自然就释放了,这在写个小脚本时可能没问题,但如果是个需要长时间运行的程序,比如一个Web应用的后台任务,这么干迟早会出问题,数据库连接是宝贵的资源,有上限的,不关闭的话,连接会一直占着,直到达到数据库的最大连接数,到时候整个应用都无法连接数据库了,一个好习惯是使用try...except...finally语句块来确保资源被释放,结构大概是这样的:

try:
    conn = MySQLdb.connect(...)
    cursor = conn.cursor()
    cursor.execute("SELECT ...")
    # ... 处理结果
except Exception as e:
    print("出错了:", e)
finally:
    if cursor:
        cursor.close()  # 先关游标
    if conn:
        conn.close()   # 再关连接

这样无论中途是否发生异常,最后的finally块里的代码都会执行,保证连接被还回连接池,这就是所谓的“防御性编程”。

第三点,事务的提交和回滚很容易被忘。 像MySQL的InnoDB引擎是支持事务的,当你执行了INSERT、UPDATE、DELETE这些会修改数据的操作后,数据并没有真正写到磁盘上的数据库里,而是放在一个临时区域,你必须显式地执行conn.commit(),这个改动才会永久生效,如果你没提交就把连接关了,那么所有的修改都会丢失,反过来,如果一组操作中途出错了,为了保持数据的一致性,你需要执行conn.rollback()来回滚所有操作,让数据恢复到这组操作之前的状态,很多人写代码只写了execute,忘了commit,然后纠结为什么数据没存进去,完整的流程应该是:try块里执行SQL,没问题就commitexcept块里捕捉到异常就rollback;最后在finally块里关闭连接,这个小细节能保证数据的安全和准确。

第四,获取数据的方式也有讲究。 游标对象有几种不同的方法取数据,比如fetchone()(取一条)、fetchall()(取全部)、fetchmany(size)(取指定条数)。fetchall()看起来最直接,但如果你查询的结果集非常大,比如有几十万行,一次性全部加载到程序内存里,很可能直接把内存撑爆,导致程序崩溃,这时候fetchmany就派上用场了,你可以用一个循环,一次只取1000条或5000条出来处理,处理完了再取下一批,这样内存占用就很平稳,这在老版本Python里尤其要注意,因为内存管理可能没新版本那么高效。

cursor.execute("SELECT * FROM huge_table")
while True:
    rows = cursor.fetchmany(1000)  # 一次取1000行
    if not rows:
        break
    for row in rows:
        # ... 处理每一行数据

第五个小技巧,是关于参数化查询的。 拼接SQL字符串是万恶之源,比如"SELECT * FROM users WHERE name = '" + name + "'",这种方式极其危险,是SQL注入攻击的典型漏洞,正确的做法是使用问号或者%s作为占位符,然后把参数作为一个元组传给execute方法,像这样:cursor.execute("SELECT * FROM users WHERE name = %s", (name,)),注意,第二个参数必须是一个元组,如果只有一个参数,后面要加个逗号,写成(name,),不然Python会认为它是普通的括号表达式,而不是元组,数据库驱动会帮你安全地处理参数转义,既能防注入,有时候还能利用查询缓存提升效率,这个好习惯无论版本新旧都必须养成。

还有一个非常琐碎但关键时刻能救命的点:关注连接的超时和重连。 数据库连接不是铁打的,它可能因为网络波动、数据库服务器重启、或者连接空闲时间过长而被服务器端主动关闭,如果你的程序空闲了一段时间后再次使用这个旧连接,就会报“连接已关闭”之类的错误,在老的持久化应用(比如守护进程)里,比较稳妥的做法是:要么在每次使用连接前检查一下连接是否还活着(有些驱动提供ping()方法),要么设置一个合理的连接超时时间,要么干脆实现一个重连机制,一旦发现连接失效,就重新建立一个,这在当时没有成熟连接池库的情况下,需要自己手动处理一下。

在老版本Python里和数据库打交道,就像开一辆需要手动换挡的老爷车,虽然没现在自动挡那么方便,但只要你注意了这些细节——配好编码、管好连接和游标、记得提交事务、分批取大数据、绝对要用参数化查询,再加上对网络异常的防范——就能开得又稳又好,这些经验都是当年一个个坑踩过来的,希望对还有需要的人有点帮助。

Python老版本怎么跟数据库打交道,连接中那些容易忽略的小技巧分享