Python SQL和NoSQL数据库操作实战

一、Python访问与操作关系型数据库实战

1、关系型数据库

长期以来,关系数据库一直是存储和操纵数据的标准。这种技术十分成熟,无处不在。

Python可以连接多种关系数据库,但Python处理所有数据库的方式都大致相同,所以这里将通过其中一种数据库sqlite3来演示基本原理,然后讨论在选择和使用关系数据库做数据存储时的一些差别和注意事项。

2、sqlite3数据库的用法

Python为各种数据库提供了很多模块,不过下面的例子只会介绍sqlite3。虽然不适合大型、高流量的应用程序,但sqlite3具备两个优点。

因为sqlite3是标准库的一部分,所以任何需要数据库的地方都可使用,不必操心还要添加依赖项。
sqlite3把所有记录都存储在本地文件中,因此不需要客户端和服务器端,这正是PostgreSQL、MySQL和其他大型数据库需要的。

以上特性使得sqlite3成为小型应用程序和快速原型系统的便捷选择。

为了使用sqlite3数据库,首先得有一个Connection对象。只需调用connect函数即可得到一个Connection对象,参数是准备用来存储数据的文件名:

>>> import sqlite3
>>> conn = sqlite3.connect("datafile.db")

也可以用":memory:"作为文件名,这样数据就会保存在内存中。如果存储的是Python整数、字符串和浮点数,那就不需要其他参数了。

如果想让sqlite3把某些列的查询结果自动转换为其他类型,则带上detect_types参数会比较有用,将其设为sqlite3.PARSE_DECLTYPES|sqlite3.PARSE_COLNAMES就能指导Connection对象对查询语句中的列名和类型进行解析,并尝试将它们与自定义的转换器进行匹配。

第二步要由Connection创建一个Cursor对象:

>>> cursor = conn.cursor()
>>> cursor
<sqlite3.Cursor object at 0xb7a12980>

到了这一步,就能对数据库进行查询了。在本例中,因为数据库中还没有表或记录,所以需要首先创建一个表并插入几条记录:

>>> cursor.execute("create table people (id integer primary key, name text,
     count integer)")
>>> cursor.execute("insert into people (name, count) values ('Bob', 1)")
>>> cursor.execute("insert into people (name, count) values (?, ?)", 
...               ("Jill", 15))
>>> conn.commit()

最后一条insert语句演示了用变量查询的推荐写法。这里没有构建查询字符串,而是用“?”表示每个变量,这样更为安全,然后将多个变量组成一个元组,作为参数传给execute方法。这样的优点是不必担心会转义出错,sqlite3会处理好的。

在查询中还可以采用带有“:”前缀的变量名,传入的将会是一个字典,其中包含相应要插入的值:

>>> cursor.execute("insert into people (name, count) values (:username, \
                   :usercount)", {"username": "Joe", "usercount": 10})

表里填入数据后,就可以用SQL命令查询数据了,还是可以用“?”代表变量绑定关系,或者用变量名和字典也行:

>>> result = cursor.execute("select * from people")
>>> print(result.fetchall())
[('Bob', 1), ('Jill', 15), ('Joe', 10)]
>>> result = cursor.execute("select * from people where name like :name", 
...                         {"name": "bob"})
>>> print(result.fetchall())
[('Bob', 1)]
>>> cursor.execute("update people set count=? where name=?", (20, "Jill"))
>>> result = cursor.execute("select * from people")
>>> print(result.fetchall())
[('Bob', 1), ('Jill', 20), ('Joe', 10)]

除可用fetchall方法之外,fetchone方法能从查询结果中获取一行数据,fetchmany则能返回任意数量的数据行。为方便起见,也可以迭代遍历游标(cursor)对象中的数据行,类似于对文件进行迭代遍历:

>>> result = cursor.execute("select * from people") 
>>> for row in result:
...     print(row) 
...
('Bob', 1)
('Jill', 20)
('Joe', 10)

默认情况下,sqlite3不会立即提交事务。这意味着可以选择在事务失败时回滚事务,但这也意味着需要动用Connection对象的commit方法才能保证所有修改都得以保存。因为close方法不会自动提交任何活跃事务,所以在关闭数据库连接之前进行提交就是特别好的做法:

>>> cursor.execute("update people set count=? where name=?", (20, "Jill"))
>>> conn.commit()
>>> conn.close()

常见sqlite3数据库操作:

操作 sqlite3命令
创建数据库连接 conn = sqlite3.connect(filename)
在数据库连接中创建游标 Cursor = conn.cursor()
通过游标执行查询 cursor.execute(query)
返回查询结果

cursor.fetchall()、cursor.fetchmany(num_rows)、cursor.fetchone()

for row in cursor:....

向数据库提交事务 conn.commit()
关闭数据库连接 conn.close()

通常,操作sqlite3数据库有以上这些操作就足矣了。

3、MySQL、PostgreSQL和其他关系数据库的使用

正如之前所述,其他几个SQL数据库都提供了遵循DB-API规范的客户端库。因此,用Python访问这些数据库的方式非常相似,但需要注意以下几点区别。

  • 与SQLite不同,这些数据库需要有数据库服务器端,以供客户端连接。客户端和服务器端也许位于同一台机器上,也许位于不同的机器上,因此数据库连接需要携带更多的参数,通常包括主机、账户名和密码。
  • "select * from test where name like:name"之类在查询中插入参数的方式,可能会采用不同的格式,类似于?、%s5(name)s等。

上述变化并不很大,但往往会妨碍代码在不同数据库之间实现完全的可移植。

4、利用ORM简化数据库操作

之前提到的DB-API数据库客户端库存在一些问题,并且要求编写原始的SQL语句也会存在问题。

  • 不同的SQL数据库对SQL的实现略有不同,因此如果从一种数据库迁移到另一种数据库,同一条SQL语句不一定会始终有效。假如本地开发是对sqlite3进行的,之后在生产环境中要使用MySQL或PostgreSQL,这时就可能会产生这种迁移需求。此外,如前所述,不同的数据库产品有不同的实现方式,如将参数传给查询语句的方式。
  • 第二个缺点是需要用到原始SQL语句。在代码中包含SQL语句会更加难以维护,特别是在代码量很大时。这时其中一些语句将成为模板和例行过程,其他语句则会十分复杂和棘手。并且所有语句都需要进行测试,这可能会变得很麻烦。
  • 编写SQL的要求意味着至少需要考虑两种语言:Python和某种SQL。很多情况下用原始SQL是值得这么麻烦的,但在其他很多时候却并不划算。

鉴于上述问题,人们需要Python有一种更易于管理的数据库处理方式,并且只需要编写普通的Python代码即可。解决方案就是对象关系映射器(Object Relational Mapper,ORM),它能将关系数据库的数据类型和数据结构转换或映射为Python对象。

在Python的世界中,最常见的两个ORM就是Django ORM和SQLAlchemy,当然其他还有很多。Django ORM与Django Web框架紧密集成,通常不在外面单独使用。这里仅需注意一点,Django ORM是Django应用程序的默认选项,也是一个好选择,具备开发完善的工具和丰富的社区支持。

1. SQLAlchemy

SQLAlchemy是Python世界中的另一种大牌ORM。SQLAlchemy的目标是自动执行冗长的数据库任务,并为数据提供基于Python对象的接口,同时仍允许开发人员控制数据库并访问底层SQL。这里介绍一些基本示例,先将数据存储到关系数据库,然后用SQLAlchemy检索数据。

用pip即可在Python环境中安装SQLAlchemy:

> pip install sqlalchemy

注意:从便于使用SQLAlchemy及相关工具的角度出发,比较方便的做法是在同一个虚拟环境中打开两个shell窗口,一个用于Python,另一个用于系统的命令行。

SQLAlchemy提供了几种与数据库和表进行交互的途径。虽然必要时ORM也允许编写SQL语句,但ORM的强大之处正如其名:将关系数据库表和列映射为Python对象。

下面利用SQLAlchemy重复的操作:创建表、添加3条数据行、查询表并更新1行。使用ORM时的配置工作会多一些,但在大型项目中这非常值得。

首先,需要导入几个组件,用于连接数据库并将表映射成Python对象。在基础sqlalchemy包中,需要用到方法create_engine和select,还有类MetaData和Table。但因为在创建Table对象时需要指定模式(schema)信息,所以还要导入Column类和各列数据类型的对应类,本例中为Integer和String。

还需要从sqlalchemy.orm子包中导入sessionmaker函数:

>>> from sqlalchemy import create_engine, select, MetaData, Table, Column, 
     Integer, String
>>> from sqlalchemy.orm import sessionmaker

现在就可以考虑连接数据库了:

>>> dbPath = 'datafile2.db'
>>> engine = create_engine('sqlite:///%s' % dbPath)
>>> metadata = MetaData(engine)
>>> people  = Table('people', metadata, 
...                 Column('id', Integer, primary_key=True),
...                 Column('name', String),
...                 Column('count', Integer),
...                )
>>> Session = sessionmaker(bind=engine)
>>> session = Session()
>>> metadata.create_all(engine)

为了创建并连接数据库,需要创建对应数据库的引擎。然后需要有一个MetaData对象,它是用于管理表及其结构的容器。然后创建一个名为people的Table对象,给出的参数为数据库中的表名、刚刚创建的MetaData对象、要创建的列及其数据类型。最后,用sessionmaker函数为引擎创建Session类,并使用该类实例化session对象。这时数据库已连接完成,最后一步是用create_all方法建表。

数据库表建完后,下一步是插入一些记录。在SQLAlchemy中同样有多种插入方式,但在本例中会比较明确。创建一个insert对象,然后执行:

>>> people_ins = people.insert().values(name='Bob', count=1)
>>> str(people_ins)
'INSERT INTO people (name, count) VALUES (?, ?)'
>>> session.execute(people_ins)
<sqlalchemy.engine.result.ResultProxy object at 0x7f126c6dd438>
>>> session.commit()

这里用到insert()方法创建了一个insert对象,同时指定了要插入的字段和值。people_ins是insert对象,用str()函数就可以显示出,其实在幕后创建了正确的SQL命令。

然后用session对象的execute()方法执行插入操作,并用commit()方法提交给数据库:

>>> session.execute(people_ins, [
...     {'name': 'Jill', 'count':15},
...     {'name': 'Joe', 'count':10}
... ])
<sqlalchemy.engine.result.ResultProxy object at 0x7f126c6dd908>
>>> session.commit()
>>> result = session.execute(select([people])) 
>>> for row in result:
...     print(row) 
...
(1, 'Bob', 1)
(2, 'Jill', 15)
(3, 'Joe', 10)

通过传入一个字典列表可以简化操作并执行多条记录的插入,每条插入记录为一个字典,其中包含了字段名和字段值:

>>> result = session.execute(select([people]).where(people.c.name == 'Jill')) 
>>> for row in result:
...     print(row)
...
(2, 'Jill', 15)

select()方法还可以与where()方法一起使用,用于查找指定的记录。以上示例查找的是name列等于'Jill'的所有记录。注意,where表达式采用了people.c.name,c表示name是people表中的列:

>>> result = session.execute(people.update().values(count=20).where
      (people.c.name == 'Jill'))
>>> session.commit()
>>> result = session.execute(select([people]).where(people.c.name == 'Jill'))
>>> for row in result:
...     print(row)
...
(2, 'Jill', 20)
>>>

update()方法也可以和where()方法组合使用,实现对单条记录的更新。

到目前为止,都是直接使用表对象,其实还可以用SQLAlchemy将表直接映射为类。这种映射技术的优点是,数据列会直接映射成类的属性。

下面创建一个类People以作演示:

>>> from sqlalchemy.ext.declarative import declarative_base
>>> Base = declarative_base()
>>> class People(Base):
...     __tablename__ = "people"
...     id = Column(Integer, primary_key=True)
...     name = Column(String)
...     count = Column(Integer) 
...
>>> results = session.query(People).filter_by(name='Jill') 
>>> for person in results:
...     print(person.id, person.name, person.count) 
...
2 Jill 20

只要新建一个映射类的实例,并将其加入session中,就可以完成插入操作了:

>>> new_person = People(name='Jane', count=5)
>>> session.add(new_person)
>>> session.commit()
>>> 
>>> results = session.query(People).all() 
>>> for person in results:
...     print(person.id, person.name, person.count)
...
1 Bob 1
2 Jill 20
3 Joe 10
4 Jane 5

更新操作也相当简单。只要检索需要更新的记录,修改映射实例中的值,然后将更新过的记录加入回写数据库的session中:

>>> jill = session.query(People).filter_by(name='Jill').first()
>>> jill.name
'Jill'
>>> jill.count = 22
>>> session.add(jill)
>>> session.commit()
>>> results = session.query(People).all()
>>> for person in results:
...     print(person.id, person.name, person.count) 
...
1 Bob 1
2 Jill 22
3 Joe 10
4 Jane 5

删除操作和修改很类似,先获取要删除的记录,然后用session对象的delete()方法删除即可:

>>> jane = session.query(People).filter_by(name='Jane').first()
>>> session.delete(jane)
>>> session.commit()
>>> jane = session.query(People).filter_by(name='Jane').first()
>>> print(jane)
None

用SQLAlchemy时确实要比仅用原始SQL增加一点配置工作,但也带来一些实际的好处。首先,采用ORM就意味着不必操心不同数据库支持的SQL语句的细微差别。上述示例面对sqlite3、MySQL和PostgreSQL都能正常工作,除了创建引擎时提供的字符串不同,并要确保有正确的数据库驱动程序可用,就不用对代码进行任何修改了。

另一个优点就是数据交互可以通过Python对象来完成,对于缺乏SQL编程经验的程序员来说,可能会更容易一些。他们可以用Python对象及其方法,而不用去构造SQL语句了。

2. 用Alembic修改数据库结构

在用到关系数据库的开发过程中,常常不得不在开始工作之后对数据库结构进行修改。如果这种情况算不上普遍,那也至少是很常见。要添加字段,要修改字段类型,凡此种种。当然,可以手动修改数据库表和访问它们的ORM代码,但会有一些缺点。首先,这种修改很难在必要时进行回滚。其次,某个版本的代码所用的数据库配置也很难被跟踪记录下来。

解决办法就是用数据库迁移(migration)工具协助进行修改,并把改动记录下来。迁移是用代码操纵的,代码中应该包含执行所需的修改和逆操作两部分。这样修改就可以被记录下来,并能按正确的顺序执行或回退。这样一来,数据库就能可靠地升级或降级到开发过程中的任一状态了。

作为示例,这里简要介绍一下Alembic,这是一种流行的轻量级迁移工具,适用于SQLAlchemy。为了启动Alembic,请切换到系统命令行窗口,进入项目所在目录,安装Alembic,并用alemic init创建通用的运行环境:

> pip install alembic
> alembic init alembic

上述代码创建了用Alembic进行数据迁移所需的文件结构。这里有一个alembic.ini文件,里面至少有一处需要编辑一下。

squalchemy.url这一行需要根据当前情况进行修改:

sqlalchemy.url = driver://user:pass@localhost/dbname

把这行改为:

sqlalchemy.url = sqlite:///datafile.db

因为用的是本地sqlite文件,所以不需要用户名或密码。

下一步用Alembic的revision命令创建一个修订:

> alembic revision -m "create an address table"
Generating /home/naomi/qpb_testing/alembic/versions/ 
     384ead9efdfd_create_a_test_address_table.py ... done

上述代码在alembic/versions目录中创建了一个修订脚本384ead9efdfd_create_a_test_address_table.py,该脚本文件如下所示:

"""create an address table

Revision ID: 384ead9efdfd 
Revises: 
Create Date: 2017-07-26 21:03:29.042762

""" 
from alembic import op 
import sqlalchemy as sa

# revision identifiers, used by Alembic. 
revision = '384ead9efdfd' 
down_revision = None 
branch_labels = None 
depends_on = None

def upgrade():
    pass

def downgrade():
    pass

在文件头部信息中,包含了修订ID和日期。文件还包含了一个down_revision变量,用于引导各个版本的回滚。如果进行第二次修订,则其down_revision变量就应该包含该修订ID。

为了执行修订,要更新修订脚本,在upgrade()方法中给出执行修订的代码,并在downgrade()方法中给出回退代码:

def upgrade():
    op.create_table(
        'address',
        sa.Column('id', sa.Integer, primary_key=True),
        sa.Column('address', sa.String(50), nullable=False),
        sa.Column('city', sa.String(50), nullable=False),
        sa.Column('state', sa.String(20), nullable=False),
    )

def downgrade():
    op.drop_table('address')

以上代码创建完毕后,就可以执行升级了。但首先请切换回Python shell窗口,查看一下数据库中有哪些表存在:

>>> print(engine.table_names())
['people']

正如所料,这里只有之前创建的一张表。现在,可以运行Alembic的upgrade命令执行升级并添加一张新表。请切换到系统命令行窗口,然后运行:

> alembic upgrade head
INFO  [alembic.runtime.migration] Context impl SQLiteImpl.
INFO  [alembic.runtime.migration] Will assume non-transactional DDL.
INFO  [alembic.runtime.migration] Running upgrade  -> 384ead9efdfd, create an
     address table

如果回到Python shell窗口查看一下,就会发现数据库中多了两张表:

>>> engine.table_names()
['alembic_version', 'people', 'address'

第一张新表'alembic version'由Alembic创建,用于记录数据库当前所处的版本(供将来升级和降级参考)。第二张新表'address'是通过升级操作加入的,并且已经就绪。

如果想把数据库的状态回滚到之前的状态,只需要在系统命令窗口中运行Alembic的downgrade命令即可。请给downgrade命令带上“-1”参数,告知Alembic要降一个版本:

> alembic downgrade -1
INFO  [alembic.runtime.migration] Context impl SQLiteImpl.
INFO  [alembic.runtime.migration] Will assume non-transactional DDL.
INFO  [alembic.runtime.migration] Running downgrade 384ead9efdfd -> , create
    an address table

现在如果登入Python会话,将会回到起始状态,只是版本记录表仍然会存在:

>>> engine.table_names()
['alembic_version', 'people']

当然,只要愿意就可以再次运行upgrade,将表再升级回去,添加新的修订,进行升级,如此等等。

二、Python访问与操作非关系型数据库实战

1、NoSQL数据库

尽管流行时日已久,但关系数据库并不是存储数据的唯一选择。关系数据库所做的就是在关联表中完成数据的规格化,而其他方案则以不同方式看待数据。通常,这些类型的数据库称为NoSQL数据库,因为它们通常并不遵循行、列、表的结构,而行、列、表则正是创造出SQL用以描述的结构。

NoSQL数据库不是将数据视作行、列和表的集合进行处理的,而是能够将存储的数据视为键/值对、索引文档,甚至是图形。可用的NoSQL数据库有很多,它们的数据处理方式都多少有点不同。总体而言,这些数据都不大可能被严格地规格化,而规格化可以让信息检索更加简单快速。

这里介绍Python如何访问两种常见NoSQL数据库:Redis和MongoDB。仅仅介绍NoSQL数据库和Python能力的皮毛而已,但应该能对可完成的工作给出大致的概念。

对于比较熟悉Redis或MongoDB的人,可以学习到一点儿Python客户端库的使用方式。对于NoSQL数据库的新手,至少可以了解一下这些数据库的工作方式。

2、用Redis实现键/值存储

Redis是基于内存的网络化键/值存储系统。因为值存放在内存中,所以查找速度可以非常快,并且通过网络访问的设计使其能适用于多种场合。Redis通常用作缓存、消息代理(message broker)和信息快速检索系统。Redis的名字源于远程字典服务器(remote dictionary server),其实这就是对它的最佳理解方式,它的行为很像是变成了网络服务的Python字典。

以下示例演示了在Python中使用Redis的方式。如果对Redis命令行界面比较熟悉,或者在其他编程语言中用过Redis客户端,那么这些小例程应该能对Python中使用Redis提供指导。

虽然可用的Python的Redis客户端有好几种,根据Redis官方网站的推荐方案是redis-py,用pip install redis即可进行安装。

运行Redis服务器,进行代码测试需要有正常运行的Redis服务器。虽然可以选用基于云的Redis服务,但对代码测试而言,最好还是选用Docker实例或在计算机上自行安装一套服务。

如果已安装过Docker,使用Docker实例可能是启动和运行Redis服务器最快捷简单的方案。使用类似> docker run -p 6379:6379 redis的命令,应该就能在命令行上启动Redis实例了。

在Linux系统中,用系统软件包管理器安装Redis应该相当容易。而在Mac系统中,只要用brew install redis应该就可以正常安装了。

有了正常运行的Redis服务器,下面就介绍通过Python与Redis交互的简单示例。首先需要导入Redis库,并创建Redis连接对象:

>>> import redis
>>> r = redis.Redis(host='localhost', port=6379)

创建Redis连接时可以采用多个可选的连接参数,包括主机、端口、密码或SSH证书。假如Redis服务器运行于localhost的默认端口6379上,那就不需要带可选参数了。有了连接对象,就可以用它访问键/值存储了。

第一件要做的事情,可能就是用keys()方法获取数据库中的键列表,返回当前存储的键列表。然后可以设置一些各种类型的键,并尝试通过各种方法来检索对应的值:

>>> r.keys()
[]
>>> r.set('a_key', 'my value')
True
>>> r.keys()
[b'a_key']
>>> v = r.get('a_key')
>>> v
b'my value'
>>> r.incr('counter')
1
>>> r.get('counter')
b'1'
>>> r.incr('counter')
2
>>> r.get('counter')
b'2'

上述示例演示了如何获取Redis数据库中的键列表、用值设置键、用变量counter设置键并递增变量。

以下示例将处理数组或列表的存储:

>>> r.rpush("words", "one")
1
>>> r.rpush("words", "two")
2
>>> r.lrange("words", 0, -1)
[b'one', b'two']
>>> r.rpush("words", "three")
3
>>> r.lrange("words", 0, -1)
[b'one', b'two', b'three']
>>> r.llen("words")
3
>>> r.lpush("words", "zero")
4
>>> r.lrange("words", 0, -1)
[b'zero', b'one', b'two', b'three']
>>> r.lrange("words", 2, 2)
[b'two']
>>> r.lindex("words", 1)
b'one'
>>> r.lindex("words", 2)
b'two'

一开始设置键时,列表"words"还不在数据库中,但向列表末尾追加或加入(push)值的行为将会创建该键,并创建一个空列表作为值,然后把值'one'追加进去。这里rpush中的r表示从右侧插入。然后又用rpush在末尾继续加入一个单词。用lrange()函数可以检索列表中的值,参数是键、起始索引和结束索引,索引-1表示列表的末尾。

另外还请注意,用lpush()可以在列表的开头或左侧添加值。用lindex()检索单个值的方式与lranger()相同,但是给出的是值的索引。

值的到期时间,有一个Redis特性对缓存特别有用,能够为键/值对设置到期时间。超时后键和值都将被删除。如果要将Redis用作缓存,该技术就特别有用。设置键对应的值时,可以同时设置以秒为单位的超时值:

>>> r.setex("timed", "10 seconds", 10)
True
>>> r.pttl("timed")
7165
>>> r.pttl("timed")
5208
>>> r.pttl("timed")
1542
>>> r.pttl("timed")
>>>

上面把"timed"的到期时间设为10秒。然后用pttl()方法可以查看到期前的剩余时间,单位为毫秒。当值到期时,键和值都将自动从数据库中删除。这一特性和Redis提供的对其细粒度的控制,着实非常有用。对于简单的缓存应用,可能无须再编写更多代码就能解决问题了。

值得注意的是,Redis将数据保存在内存中,因此请记住数据是非持久性的。如果服务器崩溃,某些数据就可能会丢失。为了减少数据丢失的可能,Redis有一些用于管理持久性的参数可选,可以选择将每次修改都写入磁盘,可以在预定时间定期做快照(snapshot),也可以根本不存入磁盘。还可以用Python客户端的save()和bgsave()方法以编程方式强制执行一次快照保存,用save()会阻塞当前操作直至保存完成,而用bgsave()则会在后台执行保存操作。

3、MongoDB中的文档

另一种比较流行的NoSQL数据库就是MongoDB,有时也被称为基于文档的数据库。因为MongoDB不按行和列来编排,而只是存储文档。MongoDB被设计成可在跨越多集群的多个节点间自由伸缩,同时具备数十亿个文档的处理能力。在MongoDB中,文档存储的格式叫作BSON(二进制JSON,Binary JSON),因此文档是由键/值对组成的,貌似JSON对象或Python字典。

以下示例展示了如何用Python与MongoDB的集合(collection)和文档(document)进行交互,也给出了适当的使用提醒。在数据有伸缩和分布式需求、插入率较高、表结构复杂不定等场合,MongoDB就是个出色的选择。但在许多情况下,MongoDB都不是最佳选择。因此在做出选择之前,一定要对需求和可选对象进行彻底的调查。

运行MongoDB服务器与Redis一样,如果要测试MongoDB,就需要访问MongoDB服务器。有很多云托管的Mongo服务可供选用,但如果只是进行测试,可能最好还是运行Docker实例或在自己的服务器上安装。

与Redis的情况一样,最简单的解决方案就是运行Docker实例。如果已有Docker,仅需在命令行输入> docker run -p 27017:27017 mongo即可。

在Linux系统中,应该由软件包管理器进行安装,而在Mac系统中用brew install mongodb即可。在Windows系统中,请访问MongoDB官方网站获取Windows版本和安装说明。与Redis一样,有关如何配置和启动服务器的说明,请在线搜索。

与Redis的情况一样,连接MongoDB数据库的Python客户端库有好几种。为了演示它们的工作原理,不妨介绍一下pymongo。使用pymongo的第一步就是安装,可以用pip完成:

> pip install pymongo

pymongo安装完成后,就可以创建MongoClient实例,指定常规的连接信息,然后就可以连接到MongoDB服务器了:

>>> from pymongo import MongoClient
>>> mongo = MongoClient(host='localhost', port=27017)     ⇽---  host='localhost'和port=27017是默认值,不需要指定

MongoDB的组织架构包括一个数据库,数据库内包含多个集合,每个集合都可以包含多个文档。但在访问数据库和集合之前,不需要先创建。如果数据库和集合不存在,那么插入时会自动创建,且检索记录时只是没有返回结果而已。

为了能测试客户端,要创建一个示例文档,例如,一个Python字典:

>>> import datetime
>>> a_document = {'name': 'Jane',
...               'age': 34,
...               'interests': ['Python', 'databases', 'statistics'],
...               'date_added': datetime.datetime.now()
... }
>>> db = mongo.my_data     ⇽---  选中一个尚未创建的数据库
>>> collection = db.docs   ⇽---  选中数据库中的一个集合,也尚未创建
>>> collection.find_one()  ⇽---  查询第一条记录,即使集合或数据库不存在也不会引发异常  
>>> db.collection_names()
[]

以上就连接到数据库和文档集合了。这时它们不存在,但会在被访问时创建。注意,即使数据库和集合不存在,也不会引发异常。当请求获取集合列表时,会得到一个空列表,因为集合中还没有内容。

如果要存入文档,请用集合的insert()方法,操作成功会返回该文档的唯一ObjectId:

>>> collection.insert(a_document)
ObjectId('59701cc4f5ef0516e1da0dec')     ⇽---  唯一的ObjectId
>>> db.collection_names()
['docs']

现在文档已存入docs集合中,当请求数据库中的集合名称时该集合就会显示出来。文档保存到集合中之后,就可以对其查询、更新、替换和删除了:

>>> collection.find_one()     ⇽---  获取第一条记录
{'_id': ObjectId('59701cc4f5ef0516e1da0dec'), 'name': 'Jane', 'age': 34, 
     'interests': ['Python', 'databases', 'statistics'], 'date_added': 
     datetime.datetime(2017, 7, 19, 21, 59, 32, 752000)}
>>> from bson.objectid import ObjectId
>>> collection.find_one({"_id":ObjectId('59701cc4f5ef0516e1da0dec')})     ⇽---  获取符合指定条件的记录,这里是用了ObjectId
{'_id': ObjectId('59701cc4f5ef0516e1da0dec'), 'name': 'Jane', 
     'age': 34, 'interests': ['Python', 'databases', 
     'statistics'], 'date_added': datetime.datetime(2017, 
     7, 19, 21, 59, 32, 752000)}
>>> collection.update_one({"_id":ObjectId('59701cc4f5ef0516e1da0dec')}, 
     {"$set": {"name":"Ann"}})     ⇽---  按照$set对象的内容对记录做出更新
<pymongo.results.UpdateResult object at 0x7f4ebd601d38>
>>> collection.find_one({"_id":ObjectId('59701cc4f5ef0516e1da0dec')})
{'_id': ObjectId('59701cc4f5ef0516e1da0dec'), 'name': 'Ann', 'age': 34, 
     'interests': ['Python', 'databases', 'statistics'], 'date_added': 
     datetime.datetime(2017, 7, 19, 21, 59, 32, 752000)}
>>> collection.replace_one({"_id":ObjectId('59701cc4f5ef0516e1da0dec')}, 
     {"name":"Ann"})     ⇽---  用新的对象将记录替换
<pymongo.results.UpdateResult object at 0x7f4ebd601750>
>>> collection.find_one({"_id":ObjectId('59701cc4f5ef0516e1da0dec')})
{'_id': ObjectId('59701cc4f5ef0516e1da0dec'), 'name': 'Ann'}
>>> collection.delete_one({"_id":ObjectId('59701cc4f5ef0516e1da0dec')})     ⇽---  删除符合条件的记录
<pymongo.results.DeleteResult object at 0x7f4ebd601d80>
>>> collection.find_one()

首先注意,MongoDB会按照字段(field)的字典及其值进行匹配。字典也用于表示操作符,例如$lt(小于)和$gt(大于),以及更新记录时用到的$set之类的命令。

另一件需要注意的事情是,即便记录已被删除且集合为空,集合仍然是存在的,除非指定要删除集合:

>>> db.collection_names()
['docs']
>>> collection.drop()
>>> db.collection_names()
[]

当然,MongoDB还可以做很多事情。除对一条记录进行操作之外,同一命令还有操作多条记录的版本,如find_many和update_many。MongoDB还支持索引以提高性能,并提供了多个用于数据分组、计数和聚合的方法,以及内置的MapReduce方法。

总结:

  • Python自带一套数据库API(DB-API),为几种关系数据库的客户端提供大体一致的接口。
  • 采用对象关系映射器(ORM)可以让横跨多种数据库的代码更加标准化。
  • 采用ORM还可以通过Python代码和对象访问关系数据库,而不用通过SQL查询了。
  • Alembic之类的工具结合ORM,可以用代码对关系数据库的表结构进行可逆更改。
  • Redis之类的键/值存储系统,提供了快速的基于内存的数据存取手段。
  • MongoDB提供了可伸缩的、结构不像关系数据库那么严格的数据存储方案。

猜你喜欢

转载自blog.csdn.net/qq_35029061/article/details/130138083