为什么要使用SQLAlchemy?
将你的代码抽象出来不依赖与数据库的类型和某种数据库自身的的独特性,SQLAlchemy提供了强大又通用的语句和类型,你不在需要考虑你所选用数据库的实现及其厂商。SQLAlchemy同样使得将数据逻辑从Oracle或者PostgreSQL到你的应用程序的数据库或者其他数据仓库。它将将对数据库的操作在提交给数据库之前进行了统一的规范和转义。这样就避免了一些常见的问题例如数据库注入攻击。
SQLAlchemy通过两种主要的数据库访问方式提供了强大的灵活性:SQL表达式和ORM。这些方式可以单独使用也可以结合使用,完全取决于你的喜好和应用程序的需要。
SQLAlchemy Core和 SQL Expression Language
SQL表达式是一种比较Pythonic的方式取代原始的SQL语句,它是一个对原始SQL语言的初级抽象,虽然聚焦于具体的数据库,然而,通过对多数的后端数据库提供了统一的语言实现了访问方式的统一。
ORM
SQLAlchemy ORM类似于你在其他语言中可能遇到的许多其他对象关系映射器(ORM)。它关注于应用程序的领域模型,并利用工作模式单元来维护对象状态。它还在SQL表达式语言之上提供了高级抽象,使用户能够以更习惯的方式工作。您可以混合使用ORM和SQL表达式语言来创建非常强大的应用程序。ORM利用了一个声明式系统,该系统与许多其他ORM(如Ruby on Rails中的ORM)使用的活动记录系统类似。
虽然ORM非常有用,但您必须记住,在关联类的方式和底层数据库关系的工作方式之间是有区别的。在第6章中,我们将更全面地探讨这种方法如何影响您的实现。
在SQLAlchemy Core和ORM之间如何选择?
在开始使用SQLAlchemy构建应用程序之前,您需要决定是主要使用ORM还是Core。选择SQLAlchemy Core或ORM作为应用程序的主要数据访问层通常取决于几个因素和个人偏好。
这两种模式使用的语法略有不同,但是Core和ORM之间最大的区别是将数据视为模式或业务对象。SQLAlchemy Core有一个以模式为中心的视图,它与传统SQL一样,主要关注表、键和索引结构。SQLAlchemy Core在数据仓库、报告、分析和其他场景中非常出色,在这些场景中,能够严格控制查询或对未建模的数据进行操作非常有用。强大的数据库连接池和结果集优化非常适合处理大量数据,甚至在多个数据库中也是如此。
但是,如果您希望更多地关注领域驱动的设计,ORM将在元数据和业务对象中封装大部分底层模式和结构。这种封装可以使数据库交互更像普通的Python代码。大多数常见的应用程序都适合以这种方式建模。它也可以是引入领域驱动设计的一种非常有效的方法,将SQLAlchemy引入到遗留应用程序中,或者在整个应用程序中散布原始SQL语句。微服务还受益于底层数据库的抽象,允许开发人员只关注正在实现的流程。
但是,由于ORM是在SQLAlchemy Core之上构建的,所以您可以使用它处理Oracle数据仓库和Amazon Redshift等服务的能力,就像它与MySQL交互一样。当您需要组合业务对象和存储的数据时,这对于ORM来说是一个极好的补充。
这里有一个快速清单,可以帮助你决定哪种选择最适合你:
-
如果您使用的框架已经内建了ORM,但希望添加更强大的报表,请使用Core。
-
如果您希望以更以模式为中心的视图(如SQL中使用的)查看数据,请使用Core。
-
如果您有不需要业务对象的数据,请使用Core。
-
如果您将数据视为业务对象,请使用ORM。
-
如果您正在构建一个快速原型,请使用ORM。
-
如果您的需求组合确实可以利用业务对象和其他与问题领域无关的数据,请同时使用它们!
现在,您已经了解了SQLAlchemy的结构以及Core和ORM之间的区别,我们已经准备好安装并开始使用SQLAlchemy来连接数据库。
SQLAlchemy的安装
默认情况下,SQLAlchemy将支持SQLite3,不需要额外的驱动程序;但是,需要一个使用标准Python DBAPI (PEP-249)规范的附加数据库驱动程序来连接到其他数据库。这些DBAPI为每个数据库服务器使用的dialect提供了基础,并且通常支持在不同数据库服务器和版本中看到的独特特性。虽然许多数据库可以使用多个dbapi,但是下面的说明主要针对最常见的数据库:
PostgreSQL
Psycopg2提供了对PostgreSQL版本和特性的广泛支持,可以与pip install Psycopg2一起安装。
MySQL
PyMySQL是我用来连接MySQL数据库服务器的首选Python库。它可以与pip安装pymysql一起安装。SQLAlchemy中的MySQL支持要求MySQL版本4.1或更高,这是由于该版本之前密码的工作方式。此外,如果特定的语句类型仅在MySQL的某个版本中可用,SQLAlchemy不提供在语句不可用的MySQL版本上使用这些语句的方法。如果SQLAlchemy中的某个组件或函数在您的环境中不起作用,那么检查MySQL文档是很重要的。
其他类型
SQLAlchemy还可以与Drizzle、Firebird、Oracle、Sybase和Microsoft SQL Server一起使用。该社区还为许多其他数据库提供了外部dialect,如IBM DB2、Informix、Amazon Redshift、EXASolution、SAP SQL Anywhere、Monet等。
现在可以安装SQLAlchemy
pip install -U sqlalchemy
检查安装结果:目前最新的发行版本是1.2.15,
>>> import sqlalchemy
>>> sqlalchemy.__version__
'1.2.15'
>>>
连接一个数据库
要连接到数据库,我们需要创建一个SQLAlchemy引擎。SQLAlchemy引擎创建到数据库的公共接口来执行SQL语句。它通过包装数据库连接池和dialect来实现这一点,这些连接池和dialect可以协同工作,提供对后端数据库的统一访问。这使我们的Python代码不必担心数据库或dbapi之间的差异。
SQLAlchemy提供了一个函数来为我们创建一个引擎,该引擎提供了一个连接字符串和一些额外的关键字参数。连接字符串是一种特殊格式的字符串,它提供:
-
数据库类型(Postgres, MySQL等)
-
除非数据库类型是默认的(Psycopg2、PyMySQL等),否则使用dialect。
-
可选身份验证细节(用户名和密码)
-
数据库的位置(数据库服务器的文件或主机名)
-
可选的数据库服务器端口,不指定则根据所选数据库类型连接其常规的默认端口
-
可选的数据库名称
SQLite数据库连接字符串让我们表示特定的文件或存储位置。示例P-1定义了一个名为cookies的SQLite数据库文件。db通过第二行中的相对路径存储在当前目录中,第三行是内存中的数据库,第四行(Unix)和第五行(Windows)是文件的完整路径。在Windows上,连接字符串看起来像engine4;除非您使用原始字符串(r”),否则需要使用\进行适当的字符串转义。
示例P-1:创建一个连接SQLite的engine
from sqlalchemy import create_engine
engine = create_engine('sqlite:///cookies.db')
engine2 = create_engine('sqlite:///:memory:')
engine3 = create_engine('sqlite:home/cookiemonster/cookies.db')
engine4 = create_engine('sqlite:///c:\\Users\\cookiemonster\\cookies.db')
提示:create_engine只是创建了一个engine实例,但是它并不会立即去连接数据库,只有在需要连接数据库的操作触发后,engine才会去连接数据库,譬如一个查询操作。
让我们为名为mydb的本地PostgreSQL数据库创建一个引擎。我们首先从基本sqlalchemy包导入create_engine函数。接下来,我们将使用该函数构造一个引擎实例。在示例P-2中,您会注意到我使用postgresql+psycopg2作为连接字符串的引擎和dialect组件,即使只使用postgres也可以工作。这是因为我更喜欢显式而不是隐式,就像Python之禅推荐的那样。
示例P-2:创建一个连接本地PostgreSQL的engine
from sqlalchemy import create_engine
engine = create_engine('postgresql+psycopg2://username:password@localhost:5432/mydb')
现在让我们看一下远程服务器上的MySQL数据库。您将注意到,在示例P-3中,在连接字符串之后,我们有一个关键字参数pool_recycle,用于定义循环使用连接的频率。
示例P-3:创建一个连接远程MySQL数据库的engine
from sqlalchemy import create_engine
engine = create_engine('mysql+pymysql://cookiemonster:chocolatechip@mysql01.monster.'
'internal/cookies', pool_recycle=3600)
注意:默认情况下,MySQL会关闭空闲时间超过8小时的连接。要解决这个问题,在创建引擎时使用pool_recycle=3600,如示例P-3所示。
一些在创建engine时可选的参数如下:
-
echo
这将记录引擎处理的操作,如SQL语句及其参数。它默认为false。
-
encoding
这定义了SQLAlchemy使用的字符串编码。它默认为utf-8,大多数DBAPI默认支持这种编码。这并不定义后端数据库本身使用的编码类型。
-
isolation_level
这指示SQLAlchemy使用特定的隔离级别。例如,带有Psycopg2的PostgreSQL有READ COMMITTED、READ UNCOMMITTED、REPEATABLE READ、SERIALIZABLE和AUTOCOMMIT,默认情况下是READ COMMITTED。PyMySQL具有相同的选项,默认为InnoDB数据库的可重复读取。
注意:使用isolation_level关键字参数将为任何给定的DBAPI设置隔离级别。这与通过连接字符串中的键-值对(如支持该方法的Psycopg2)来实现相同。
-
pool_recyle
这将定期回收或超时数据库连接。由于前面提到的连接超时,这对于MySQL非常重要。它的默认值是-1,这意味着没有超时。
一旦初始化了引擎,就可以实际打开到数据库的连接。这是通过调用引擎上的connect()方法实现的,如下所示:
from sqlalchemy import create_engine
engine = create_engine('mysql+pymysql://cookiemonster:chocolatechip'
'@mysql01.monster.internal/cookies', pool_recycle=3600)
connection = engine.connect()
现在我们已经有了数据库连接,可以开始使用SQLAlchemy Core或ORM。
MySQL数据库事务隔离级别(Transaction Isolation Level)
今天在学习JDBC的时候看到了关于MySQL的事务的隔离级别的问题,感觉内容挺高级的,所以记录一篇文章,以备后面使用。
数据库隔离级别有四种,应用《高性能mysql》一书中的说明:
然后说说修改事务隔离级别的方法:
1.全局修改,修改mysql.ini配置文件,在最后加上
1 #可选参数有:READ-UNCOMMITTED, READ-COMMITTED, REPEATABLE-READ, SERIALIZABLE. 2 [mysqld] 3 transaction-isolation = REPEATABLE-READ
这里全局默认是REPEATABLE-READ,其实MySQL本来默认也是这个级别
2.对当前session修改,在登录mysql客户端后,执行命令:
set session transaction isolation level read uncommitted;
要记住mysql有一个autocommit参数,默认是on,他的作用是每一条单独的查询都是一个事务,并且自动开始,自动提交(执行完以后就自动结束了,如果你要适用select for update,而不手动调用 start transaction,这个for update的行锁机制等于没用,因为行锁在自动提交后就释放了),所以事务隔离级别和锁机制即使你不显式调用start transaction,这种机制在单独的一条查询语句中也是适用的,分析锁的运作的时候一定要注意这一点
再来说说锁机制:
共享锁:由读表操作加上的锁,加锁后其他用户只能获取该表或行的共享锁,不能获取排它锁,也就是说只能读不能写
排它锁:由写表操作加上的锁,加锁后其他用户不能获取该表或行的任何锁,典型是mysql事务中
start transaction;
select * from user where userId = 1 for update;
执行完这句以后
1)当其他事务想要获取共享锁,比如事务隔离级别为SERIALIZABLE的事务,执行
select * from user;
将会被挂起,因为SERIALIZABLE的select语句需要获取共享锁
2)当其他事务执行
select * from user where userId = 1 for update;
update user set userAge = 100 where userId = 1;
也会被挂起,因为for update会获取这一行数据的排它锁,需要等到前一个事务释放该排它锁才可以继续进行
锁的范围:
行锁: 对某行记录加上锁
表锁: 对整个表加上锁
这样组合起来就有,行级共享锁,表级共享锁,行级排他锁,表级排他锁
下面来说说不同的事务隔离级别的实例效果,例子使用InnoDB,开启两个客户端A,B,在A中修改事务隔离级别,在B中开启事务并修改数据,然后在A中的事务查看B的事务修改效果(两个客户端相当于是两个连接,在一个客户端中的修改参数变量的值是不会影响到另外的一个客户端的):
1.READ-UNCOMMITTED(读取未提交内容)级别
1)A修改事务级别并开始事务,对user表做一次查询
2)B更新一条记录
3)此时B事务还未提交,A在事务内做一次查询,发现查询结果已经改变
4)B进行事务回滚
5)A再做一次查询,查询结果又变回去了
6)A表对user表数据进行修改
7)B表重新开始事务后,对user表记录进行修改,修改被挂起,直至超时,但是对另一条数据的修改成功,说明A的修改对user表的数据行加行共享锁(因为可以使用select)
可以看出READ-UNCOMMITTED隔离级别,当两个事务同时进行时,即使事务没有提交,所做的修改也会对事务内的查询做出影响,这种级别显然很不安全。但是在表对某行进行修改时,会对该行加上行共享锁
2. READ-COMMITTED(读取提交内容)
1)设置A的事务隔离级别,并进入事务做一次查询
2)B开始事务,并对记录进行修改
3)A再对user表进行查询,发现记录没有受到影响
4)B提交事务
5)A再对user表查询,发现记录被修改
6)A对user表进行修改
7)B重新开始事务,并对user表同一条进行修改,发现修改被挂起,直到超时,但对另一条记录修改,却是成功,说明A的修改对user表加上了行共享锁(因为可以select)
READ-COMMITTED事务隔离级别,只有在事务提交后,才会对另一个事务产生影响,并且在对表进行修改时,会对表数据行加上行共享锁
3. REPEATABLE-READ(可重读)
1)A设置事务隔离级别,进入事务后查询一次
2)B开始事务,并对user表进行修改
3)A查看user表数据,数据未发生改变
4)B提交事务
5)A再进行一次查询,结果还是没有变化
6)A提交事务后,再查看结果,结果已经更新
7)A重新开始事务,并对user表进行修改
8)B表重新开始事务,并对user表进行修改,修改被挂起,直到超时,对另一条记录修改却成功,说明A对表进行修改时加了行共享锁(可以select)
REPEATABLE-READ事务隔离级别,当两个事务同时进行时,其中一个事务修改数据对另一个事务不会造成影响,即使修改的事务已经提交也不会对另一个事务造成影响。
在事务中对某条记录修改,会对记录加上行共享锁,直到事务结束才会释放。
4.SERIERLIZED(可串行化)
1)修改A的事务隔离级别,并作一次查询
2)B对表进行查询,正常得出结果,可知对user表的查询是可以进行的
3)B开始事务,并对记录做修改,因为A事务未提交,所以B的修改处于等待状态,等待A事务结束,最后超时,说明A在对user表做查询操作后,对表加上了共享锁
SERIALIZABLE事务隔离级别最严厉,在进行查询时就会对表或行加上共享锁,其他事务对该表将只能进行读操作,而不能进行写操作。