1、背景

许多年以前,在我刚接触数据科学和数据库的时候,经常需要从MySQL中获取数据进行计算。一开始采用的方法是使用pymysql执行SQL语句,然后将返回的结果处理成pandas的DataFrame以便后续计算;随后知道了pandas.read_sql函数,便如获至宝。

当时使用read_sql函数的主要方式如下:

conn = pymysql.connect(...)
df = pd.read_sql(sql, conn)

那个时候,我被告知,程序只要可以正常结束即可,警告信息可以直接忽略。但实际上这不是一个好习惯。如果某段带有警告的程序在将来的业务中被复用,就会有变成bug的可能。

如果按照我最初的方式使用read_sql,那么将会得到如下一个警告:

 UserWarning: pandas only support SQLAlchemy connectable(engine/connection) or database string URI or sqlite3 DBAPI2 connection. Other DBAPI2 objects are not tested, please consider using SQLAlchemy.

这个警告的意思是说,向read_sql传入的第二个参数不符合它的预期:pandas希望用户传入一个由SQLAlchemy创建的数据库连接对象或者字符串;也可以传入符合DBAPI2的数据库连接对象,但数据库必须是sqlite3

至此,暂时无需深入理解SQLAlchemy,将其视为一个可以创建数据库连接的工具即可。实际上,如果传入字符串,pandas会将其转化为SQLAlchemy连接对象。

而上述由pymysql创建的conn是一个符合DBAPI2、用于连接MySQL的对象,所以pandas给出了警告。

众所周知,关系型数据库的种类很多,它们之间的语法差异也不可谓不大,处理这些差异是一项繁杂的工作——而SQLAlchemy对这些关系型数据库都进行了完善的测试,因此,pandas直接使用SQLAlchemy来避免可能出现的各种问题。

2、使用SQLAlchemy

很多文章都说,SQLAlchemy的架构是如何好,功能是如何丰富。实际上,即使它们说的是真的,对于我这种不熟悉数据库架构、只会进行增删改查操作的数据库小白来说,也没办法理解并感同身受。我所关心的是,如何使用SQLAlchemy来支撑我的日常工作。因此,我将从使用情景出发,结合SQLAlchemy的使用文档,来总结它的用法。

在运行代码前,注意一下版本:

import sqlalchemy
print(sqlalchemy.__version__)
# 1.4.41

另外,请读者注意,下文的每一个代码块都需要继承其上所有代码块生成的变量才能顺利执行。

2.1 连接池

用户和数据库进行交互的流程可以简化为:建立连接-进行操作-关闭连接。大多数现代应用程序和数据库的交互是非常频繁的,这就要求数据库连接保持长时间的通畅;此外,多个程序或进程往往需要同时访问数据库,因此需要多个连接。注意到,连接的建立是需要消耗资源的,反映到用户体验上,就是可能会产生卡顿。为了保证应用程序的流畅性,人们发明了连接池技术。

通俗来讲,连接池就是预先创建一定数量的连接放在一起,像一个“池子”一样,需要用的时候,从池子中取出一个连接使用,用完后再放回去。因为连接是事先创建好的,而「取一个连接使用,完成后放回」所消耗的资源相较于每次都从头创建连接而言,是可以忽略不计的。于是,使用连接池可以很好地改善应用程序的运行效率。

2.2 创建连接池

通过create_engine可以创建一个Engine对象。Engine的原义为“引擎”,在这里可以引申为连接池:

from sqlalchemy import create_engineengine = create_engine('mysql+pymysql://root:123456@127.0.0.1:3306/platform', echo=True)

不难看出,create_engine的第一个参数是数据库的连接信息,它告诉了SQLAlchemy如下信息:数据库类型是MySQL,连接数据库用的第三方驱动是PyMySQL,后面则是数据库的常规配置信息。

没错,SQLAlchemy本身并不提供连接驱动,而是通过第三方的DBAPI驱动来连接数据库。指定echo=True是为了在接下来的操作中将engine执行的所有SQL打印到控制台。

create_engine使用了一种「延迟初始化」的机制,这意味着,直到用户执行了第一条确切的SQL命令,SQLAlchemy才会创建连接池。关于这一点,可以在create_engine执行前后通过执行SQL:show processlist进行验证。

2.3 执行原生查询SQL

应用程序中最常用的一个场景是,将编写好的SQL发送至数据库服务器执行,并获取返回结果。利用SQLAlchemy,可以这样操作:

from sqlalchemy import textwith engine.connect() as conn:result = conn.execute(text("select 'hello world!'"))print(result.all())

在控制台,会有类似如下的输出:

2023-01-13 15:14:03,249 INFO sqlalchemy.engine.Engine SELECT DATABASE()
2023-01-13 15:14:03,249 INFO sqlalchemy.engine.Engine [raw sql] {}
2023-01-13 15:14:03,252 INFO sqlalchemy.engine.Engine SELECT @@sql_mode
2023-01-13 15:14:03,252 INFO sqlalchemy.engine.Engine [raw sql] {}
2023-01-13 15:14:03,253 INFO sqlalchemy.engine.Engine SELECT @@lower_case_table_names
2023-01-13 15:14:03,253 INFO sqlalchemy.engine.Engine [raw sql] {}
2023-01-13 15:14:03,255 INFO sqlalchemy.engine.Engine select 'hello world!'
2023-01-13 15:14:03,255 INFO sqlalchemy.engine.Engine [generated in 0.00024s] {}
[('hello world!',)]

除了最后一行是print打印的输出,其余均是engine在执行过程中产生的日志。

2.3.1 使用read_sql

engine对象作为read_sql的第二个参数传入,则不会产生第一章中说的警告:

import pandas as pddf = pd.read_sql("select * from test", engine)  # 数据表自行创建
print(df)

print打印的内容如下:

   id name  age
0   1   张三   12
1   2   李四   14
2   3   王五   13

实际上,将engine作为参数传入read_sql后读取数据,已经能够解决一开始提到的问题了。然而,这其实并未真正发挥SQLAlchemy的作用。前文提到,不同的数据库类型具有不同的语法,这在应用升级或代码迁移时常常会产生问题。当然可以为了适配各种数据库而编写多套代码——但这未免太不优雅了。

这个问题的产生是基于以下两个事实:

  • 不同的数据库类型具有不同的语法;
  • 程序中需要编写原生的SQL。

问题的解决需要从源头入手:语法不同这件事没法改变,那只好从第二点入手,即,避免编写原生SQL。仔细想想,其实这一点是可以做到的。

以select操作为例,从某个表中查询数据是有一个标准范式的:从哪里(from)查,限定条件(where)是什么,是否联合查询(join),排序规则是什么(order by),是否限定数量(limit)等

以此为启发,读者大概能想到,SQLAlchemy将这些范式进行了梳理与总结,将原生SQL中的固定部分(范式内容)和自定义部分(条件内容)进行分离,从而使用户只需专注业务的逻辑限制,而不必费心于繁杂的SQL语法。

2.4 获取元数据

这里可以把元数据简单理解为数据库中的表及其结构信息。

要想从表中查询数据,首先需要知道表是什么样子的,即首先要获取表的元数据。在SQLAlchemy中,可以通过如下方式获取已有表的元数据:

metadata = sqlalchemy.MetaData()
test = sqlalchemy.Table('test', metadata, autoload=True, autoload_with=engine)
print(repr(test))

打印的内容如下:

2023-01-13 17:18:46,502 INFO sqlalchemy.engine.Engine SHOW CREATE TABLE `test`
2023-01-13 17:18:46,503 INFO sqlalchemy.engine.Engine [raw sql] {}
Table('test', MetaData(), Column('id', INTEGER(), table=<test>, primary_key=True, nullable=False, comment='id'), Column('name', VARCHAR(charset='utf8', collation='utf8_general_ci', length=10), table=<test>, nullable=False, comment='名称'), Column('age', SMALLINT(), table=<test>, comment='年龄'), schema=None)

print打印的内容是test的表结构信息。通过日志信息还可以发现,表结构信息实际上就是通过执行show create table得到的。

2.5 获取数据

已经拿到了表的信息,就可以执行select操作了:

from sqlalchemy import selectstmt = select(test).where(test.c.name == "张三")
print(stmt)

stmt的创建过程就是2.3节提到的,对经过梳理的范式进行填充得到的:select表明是进行读操作;传入的test对象声明了数据来源表;.where对数据进行了筛选。

test.cSQLAlchemy用于记录表字段的对象,可以通过print(test.c.keys())来查看该表的所有字段名称。

上述代码块输入如下内容:

SELECT test.id, test.name, test.age
FROM test
WHERE test.name = :name_1

可以看出,这就是一个结构化的查询语句了。

获取查询结果,执行如下代码:

with engine.connect() as conn:row = conn.execute(stmt)print(row.all())

控制台打印:

2023-01-13 17:33:29,172 INFO sqlalchemy.engine.Engine SELECT test.id, test.name, test.age
FROM test
WHERE test.name = %(name_1)s
2023-01-13 17:33:29,173 INFO sqlalchemy.engine.Engine [cached since 601.2s ago] {'name_1': '张三'}
[(1, '张三', 12)]

3、总结

作为入门,本文简单地总结了SQLAlchemy的基本用法,也只是触及这个享誉颇多的第三方库的皮毛。实际上,要深入研究SQLAlchemy的运行机制,需要了解更多有关关系型数据库设计原则与应用程序开发原则的内容,如对象关系映射(ORM)、数据库抽象面临的问题等。这些内容就显得不是那么容易理解。

总之,无论在多大规模的应用程序中,使用SQLAlchemy来和关系型数据库进行交互,都是一件近似最优解的方案。

【SQLAlchemy】第一篇——入门相关推荐

  1. 第一篇 入门必备 (Android学习笔记)

    第一篇 入门必备 第1章 初识Android 第2章 搭建你的开发环境 第3章 创建第一个程序--HelloWorld 第4章 使用Android工具 ●Android之父 Android安迪·罗宾( ...

  2. SpringBoot 第一篇入门

    一,第一步创建Maven工程,步骤如下 二,pom文件添加SpringBoot 依赖,我们第一次实例演示web 所以一并添加web starter: <project xmlns="h ...

  3. 语音合成第一篇-入门

    定义 文本转语音,又称语音合成(Speech Sysnthesis),指的是将一段文本按照一定需求转化成对应的音频,这种特性决定了的输出数据比输入长得多.文本转语音是一项包含了语义学.声学.数字信号处 ...

  4. elasticsearch 第一篇(入门篇)

    介绍 elasticsearch是一个高效的.可扩展的全文搜索引擎 基本概念 Near Realtime(NRT): es是一个接近实时查询平台,意味从存储一条数据到可以索引到数据时差很小,通常在1s ...

  5. kaggle新手入门第一篇——Titanic

    Titanic作为Kaggle官方网站的第一篇入门比赛,如果你想学习kaggle,那么从它开始无疑是比较好的一个选择. 首先贴一下网址:https://www.kaggle.com/c/titanic ...

  6. Electron系列教程——第一篇:入门

    Electron系列教程--第一篇:入门 一.楔子 想要学习Electron,跟着官网或者中文网,仔细阅读,并实践,其实是够了,不必要重复.那为什么还要写这个系列呢?大概有两方面原因,其一:我使用el ...

  7. 2021-05-08 小华子第一篇

    ***小华子入门第一篇*** 开门第一篇 入门欢迎仪式 赶紧来一个掌声啥的 自我介绍 大家好,我先自我介绍一下,我叫顾小华(不要寻思,肯定不是真名),现在在某一所本科院校读大三,从今天开始我要写一些e ...

  8. 《Ansible权威指南 》一 第一篇 Part 1 基础入门篇

    本节书摘来自华章出版社<Ansible权威指南 >一书中的第1章,第1.1节,李松涛 魏 巍 甘 捷 著更多章节内容可以访问云栖社区"华章计算机"公众号查看. 第一篇 ...

  9. Linxu内核模块开发入门(金荣的第一篇个人技术博客)

    前言 第一次使用Markdown语法编辑的第一篇CSDN技术文章,内容为本人第一个内核模块的入门教程,如有不完善的地方,请大家多多批评指正,支持开放.自由.分享,谢谢大家. Linxu内核模块开发入门 ...

最新文章

  1. Educational Round 66 题解
  2. python语言函数库_Python 的标准库,从0到1学Python
  3. 将动画装入MicroPython I2C OLED
  4. DeepMind让AI首次在量子水平描述物质!Nature:化学领域最有价值技术之一
  5. 趣链 BitXHub跨链平台 (8)交易验证
  6. NLB网路负载均衡管理器详解
  7. 列表,元组,字典类的常见简单方法
  8. wince java_Wince之旅——设备控制(重启网卡为例)
  9. the catalina_home environment variable
  10. Sentinel服务熔断配置fallback和blockHandler_削峰填谷_流量控制_速率控制_服务熔断_服务降级---微服务升级_SpringCloud Alibaba工作笔记0052
  11. ubuntu遇到的 the system is runing low-graphics mode 问题
  12. 181031每日一句
  13. 【离散数学】第二章 命题逻辑的推理理论
  14. Doris0.13.15升级至0.14.12.4故障[Bug] NPE when replaying CheckConsistencyJob
  15. 范美忠妻子:美忠是个好男人
  16. linux 防火墙 端口号命令
  17. fuchsia代码管理
  18. Cause: com.microsoft.sqlserver.jdbc.SQLServerException: 关键字 'user' 附近有语法错误
  19. Python 实现 GIF 动态图片分解 , 多帧动态图分解成多张静态图片
  20. 用ps扣出透明背景图片,做图标的方法

热门文章

  1. 使用腾讯位置服务制作个性化地图(视频教学)
  2. Q4业绩超预期股价却反跌,英伟达财报留不住市场信心?
  3. mysql查询姓李的老师的个数_MySQL 面试题
  4. 我在哔哩哔哩上上传了什么?
  5. 倾向得分加权匹配分析方法的R实现
  6. log-pilot 多行日志收集
  7. pssh Oracle,需要了解的pssh(r11笔记第28天)
  8. 复习队列的入队和出队操作
  9. iOS——指纹的应用
  10. linux下虚拟lcd屏幕总线错误,LCD显示屏出现闪烁的原因与解决