上篇文章提到了,最近在用 Python 做一个网站。除了 Tornado ,主要还用到了 SQLAlchemy。这篇就是介绍我在使用 SQLAlchemy 的过程中,学到的一些知识。

首先说下,由于最新的 0.8 版还是开发版本,因此我使用的是 0.79 版,API 也许会有些不同。
因为我是搭配 MySQL InnoDB 使用,所以使用其他数据库的也不能完全照搬本文。

接着就从安装开始介绍吧,以 Debian/Ubuntu 为例(请确保有管理员权限):

  1. MySQL
    apt-get install mysql-server
    apt-get install mysql-client
    apt-get install libmysqlclient15-dev
  2. python-mysqldb
    apt-get install python-mysqldb
  3. easy_install
    wget http://peak.telecommunity.com/dist/ez_setup.py
    python ez_setup.py
  4. MySQL-Python
    easy_install MySQL-Python
  5. SQLAlchemy
    easy_install SQLAlchemy

如果是用其他操作系统,遇到问题就 Google 一下吧。我是在 Mac OS X 上开发的,途中也遇到些问题,不过当时没记下来……
值得一提的是我用了 MySQL-Python 来连 MySQL,因为不支持异步调用,所以和 Tornado 不是很搭。不过性能其实很好,因此以后再去研究下其他方案吧……

装好后就可以开始使用了:

from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker DB_CONNECT_STRING = 'mysql+mysqldb://root:123@localhost/ooxx?charset=utf8'
engine = create_engine(DB_CONNECT_STRING, echo=True)
DB_Session = sessionmaker(bind=engine)
session = DB_Session()

这里的 DB_CONNECT_STRING 就是连接数据库的路径。“mysql+mysqldb”指定了使用 MySQL-Python 来连接,“root”和“123”分别是用户名和密码,“localhost”是数据库的域名,“ooxx”是使用的数据库名(可省略),“charset”指定了连接时使用的字符集(可省略)。
create_engine() 会返回一个数据库引擎,echo 参数为 True 时,会显示每条执行的 SQL 语句,生产环境下可关闭。
sessionmaker() 会生成一个数据库会话类。这个类的实例可以当成一个数据库连接,它同时还记录了一些查询的数据,并决定什么时候执行 SQL 语句。由于 SQLAlchemy 自己维护了一个数据库连接池(默认 5 个连接),因此初始化一个会话的开销并不大。对 Tornado 而言,可以在 BaseHandler 的 initialize() 里初始化:

class BaseHandler(tornado.web.RequestHandler):
def initialize(self):
self.session = models.DB_Session() def on_finish(self):
self.session.close()

对其他 Web 服务器来说,可以使用 sqlalchemy.orm.scoped_session,它能保证每个线程获得的 session 对象都是唯一的。不过 Tornado 本身就是单线程的,如果使用了异步方式,就可能会出现问题,因此我并没使用它。

拿到 session 后,就可以执行 SQL 了:

session.execute('create database abc')
print session.execute('show databases').fetchall()
session.execute('use abc')
# 建 user 表的过程略
print session.execute('select * from user where id = 1').first()
print session.execute('select * from user where id = :id', {'id': 1}).first()

不过这和直接使用 MySQL-Python 没啥区别,所以就不介绍了;我还是喜欢 ORM 的方式,这也是我采用 SQLAlchemy 的唯一原因。

于是来定义一个表:

from sqlalchemy import Column
from sqlalchemy.types import CHAR, Integer, String
from sqlalchemy.ext.declarative import declarative_base BaseModel = declarative_base() def init_db():
BaseModel.metadata.create_all(engine) def drop_db():
BaseModel.metadata.drop_all(engine) class User(BaseModel):
__tablename__ = 'user' id = Column(Integer, primary_key=True)
name = Column(CHAR(30)) # or Column(String(30)) init_db()

declarative_base() 创建了一个 BaseModel 类,这个类的子类可以自动与一个表关联。
以 User 类为例,它的 __tablename__ 属性就是数据库中该表的名称,它有 id 和 name 这两个字段,分别为整型和 30 个定长字符。Column 还有一些其他的参数,我就不解释了。
最后,BaseModel.metadata.create_all(engine) 会找到 BaseModel 的所有子类,并在数据库中建立这些表;drop_all() 则是删除这些表。

接着就开始使用这个表吧:

from sqlalchemy import func, or_, not_

user = User(name='a')
session.add(user)
user = User(name='b')
session.add(user)
user = User(name='a')
session.add(user)
user = User()
session.add(user)
session.commit() query = session.query(User)
print query # 显示SQL 语句
print query.statement # 同上
for user in query: # 遍历时查询
print user.name
print query.all() # 返回的是一个类似列表的对象
print query.first().name # 记录不存在时,first() 会返回 None
# print query.one().name # 不存在,或有多行记录时会抛出异常
print query.filter(User.id == 2).first().name
print query.get(2).name # 以主键获取,等效于上句
print query.filter('id = 2').first().name # 支持字符串 query2 = session.query(User.name)
print query2.all() # 每行是个元组
print query2.limit(1).all() # 最多返回 1 条记录
print query2.offset(1).all() # 从第 2 条记录开始返回
print query2.order_by(User.name).all()
print query2.order_by('name').all()
print query2.order_by(User.name.desc()).all()
print query2.order_by('name desc').all()
print session.query(User.id).order_by(User.name.desc(), User.id).all() print query2.filter(User.id == 1).scalar() # 如果有记录,返回第一条记录的第一个元素
print session.query('id').select_from(User).filter('id = 1').scalar()
print query2.filter(User.id > 1, User.name != 'a').scalar() # and
query3 = query2.filter(User.id > 1) # 多次拼接的 filter 也是 and
query3 = query3.filter(User.name != 'a')
print query3.scalar()
print query2.filter(or_(User.id == 1, User.id == 2)).all() # or
print query2.filter(User.id.in_((1, 2))).all() # in query4 = session.query(User.id)
print query4.filter(User.name == None).scalar()
print query4.filter('name is null').scalar()
print query4.filter(not_(User.name == None)).all() # not
print query4.filter(User.name != None).all() print query4.count()
print session.query(func.count('*')).select_from(User).scalar()
print session.query(func.count('1')).select_from(User).scalar()
print session.query(func.count(User.id)).scalar()
print session.query(func.count('*')).filter(User.id > 0).scalar() # filter() 中包含 User,因此不需要指定表
print session.query(func.count('*')).filter(User.name == 'a').limit(1).scalar() == 1 # 可以用 limit() 限制 count() 的返回数
print session.query(func.sum(User.id)).scalar()
print session.query(func.now()).scalar() # func 后可以跟任意函数名,只要该数据库支持
print session.query(func.current_timestamp()).scalar()
print session.query(func.md5(User.name)).filter(User.id == 1).scalar() query.filter(User.id == 1).update({User.name: 'c'})
user = query.get(1)
print user.name user.name = 'd'
session.flush() # 写数据库,但并不提交
print query.get(1).name session.delete(user)
session.flush()
print query.get(1) session.rollback()
print query.get(1).name
query.filter(User.id == 1).delete()
session.commit()
print query.get(1)

增删改查都涉及到了,自己看看输出的 SQL 语句就知道了,于是基础知识就介绍到此了。

下面开始介绍一些进阶的知识。

如何批量插入大批数据?
可以使用非 ORM 的方式:

session.execute(
User.__table__.insert(),
[{'name': `randint(1, 100)`,'age': randint(1, 100)} for i in xrange(10000)]
)
session.commit()

上面我批量插入了 10000 条记录,半秒内就执行完了;而 ORM 方式会花掉很长时间。

如何让执行的 SQL 语句增加前缀?
使用 query 对象的 prefix_with() 方法:

session.query(User.name).prefix_with('HIGH_PRIORITY').all()
session.execute(User.__table__.insert().prefix_with('IGNORE'), {'id': 1, 'name': '1'})

如何替换一个已有主键的记录?
使用 session.merge() 方法替代 session.add(),其实就是 SELECT + UPDATE:

user = User(id=1, name='ooxx')
session.merge(user)
session.commit()

或者使用 MySQL 的 INSERT … ON DUPLICATE KEY UPDATE,需要用到 @compiles 装饰器,有点难懂,自己看吧:《SQLAlchemy ON DUPLICATE KEY UPDATE》 和 sqlalchemy_mysql_ext

如何使用无符号整数?
可以使用 MySQL 的方言:

from sqlalchemy.dialects.mysql import INTEGER

id = Column(INTEGER(unsigned=True), primary_key=True)

模型的属性名需要和表的字段名不一样怎么办?
开发时遇到过一个奇怪的需求,有个其他系统的表里包含了一个“from”字段,这在 Python 里是关键字,于是只能这样处理了:

from_ = Column('from', CHAR(10))

如何获取字段的长度?
Column 会生成一个很复杂的对象,想获取长度比较麻烦,这里以 User.name 为例:

User.name.property.columns[0].type.length

如何指定使用 InnoDB,以及使用 UTF-8 编码?
最简单的方式就是修改数据库的默认配置。如果非要在代码里指定的话,可以这样:

class User(BaseModel):
__table_args__ = {
'mysql_engine': 'InnoDB',
'mysql_charset': 'utf8'
}

MySQL 5.5 开始支持存储 4 字节的 UTF-8 编码的字符了,iOS 里自带的 emoji(如

Tornado sqlalchemy的更多相关文章

  1. tornado+sqlalchemy+celery,数据库连接消耗在哪里

    随着公司业务的发展,网站的日活数也逐渐增多,以前只需要考虑将所需要的功能实现就行了,当日活越来越大的时候,就需要考虑对服务器的资源使用消耗情况有一个清楚的认知.     最近老是发现数据库的连接数如果 ...

  2. Tornado + Bootstrap 快速搭建自己的web应用

    前言 最近用 python tordado 框架, 整了一个模板页面, 用于接入与发布数据的展示, tornado 简单易用, bootstrap 比较流行, 用起来也省事, 配合起来做些小案例非常迅 ...

  3. python学习路线--从入门到入土

    入门技术博客 进阶自己挑选 入门基础 Python入门相对容易又可以干很多事(网站,运维,数据,爬虫等),是一门方便的工具语言.2016年TIOBE排名显示Python已经名列第四,成为脚本语言之首. ...

  4. PEP 442 -- Safe object finalization

    https://www.python.org/dev/peps/pep-0442/ PEP 442 -- Safe object finalization PEP: 442 Title: Safe o ...

  5. 记一个python+sqlalchemy+tornado的一个高并发下,产生重复记录的bug

    场景:在用户通过支付通道支付完成返回时,发现我收到的处理数据记录中有两条同样的数据记录, 也就是同一笔钱,我数据库中记为了两条一样的记录. tornado端代码 from tornado import ...

  6. tornado with MySQL, torndb, django model, SQLAlchemy ==> JSON dumped

    现在,我们用torndo做web开发框架,用他内部机制来处理HTTP请求.传说中的非阻塞式服务. 整来整去,可谓之一波三折.可是,无论怎么样,算是被我做成功了. 在tornado服务上,采用三种数据库 ...

  7. 对Tornado异步操作Sqlalchemy方法的选定 不错

    使用原因 在一个实时通讯的项目中,由于需要使用Websocket这一协议,便在Python框架中选定了Tornado,也同时使用了Sqlalchemy这一ORM框架. 大家都知道Tornado有异步非 ...

  8. sqlalchemy在pythonweb中开发的使用(基于tornado的基础上)

    一.关于SQLAlchemy的安装pip install SQLAlchemy安装如果上面的方式安装不成功的情况可以使用下面的方法 百度下载window或者linux下面对应的sqlalchemy的版 ...

  9. tornado 06 数据库—ORM—SQLAlchemy——基本内容及操作

    tornado 06 数据库—ORM—SQLAlchemy——基本内容及操作 一. ORM #在服务器后台,数据是要储存在数据库的,但是如果项目在开发和部署的时候,是使用的不同的数据库,该怎么办?是不 ...

随机推荐

  1. input file上传文件扩展名限制

    方法一(不推荐使用):用jS获获取扩展名进行验证: <script type="text/javascript" charset="utf-8"> ...

  2. yii2 使用twig 模板引擎

    yii2 默认使用PHP 和html 混合的方式来写视图层,但我个人还是喜欢纯模板语言的方式.而且已经非常习惯使用twig的语法,最近想使用yii2进行开发,所以还是选择使用twig视图引擎. git ...

  3. Hbase预分区种子生成

    提前生成Hbase预分区种子,在创建Hbase表时也进行相应的预分区,同时设置预分区的个数,预分区的范围对应Hbase监控页面的Region Server的start key与End key,从而使数 ...

  4. JavaScript 数据验证类

    JavaScript 数据验证类 /* JavaScript:验证类 author:杨波 date:20160323 1.用户名验证 2.密码验证 3.重复密码验证 4.邮箱验证 5.手机号验证 6. ...

  5. MySQL绿色版安装(mysql-5.7.12-win32)

    1. 从官网下载安装包:mysql-5.7.12-win32.zip 2. 解压到C盘(任意盘符都行) C:\mysql-5.7.12-win32 3. 修改配置文件:C:\mysql-5.7.12- ...

  6. c语言实现二维数组排序,一个4*5的数组,要求每行都进行升序排列,并求出每行的平均值。

    #include<stdio.h>#define N 5#define M 4void main(){ int x,i,j,k,t,a[M][N]; float sum=0.0; floa ...

  7. Python类属性,实例属性

    1.Python类数据属性:定义在类里面但在函数外面的变量,它们都是静态的. #一段很简单的代码,但反应了很多 >>> class A(): a=1 #一个类里面有个属性a > ...

  8. CENTOS 6.4 安装oracle 10g,手工建库及升级到10.2.0.5

    一. 数据库软件安装 参照官方手册 1.安装rpm包 注这里的yum直接用163的yum yum -y install binutils compat-libstdc++-33 compat-libs ...

  9. LoadRunner使用技巧之添加事务

    事务(Transaction)用于模拟用户的一个相对完整的.有意义的业务操作过程,例如登录.查询.交易.转账,这些都可以作为事务,而一般不会把每次HTTP请求作为一个事务. 以刚刚过去的双11淘宝为例 ...

  10. VBS基本知识

    由于一些需要,开始学习VBS了.此篇文章一直将处于编辑添加状态. 1.VBS简介 VBS 即VBScript(Microsoft Visual Basic Script Editon),是微软开发的一 ...