基础内容存储
4.1 数据库存储的种类
数据库的基本结构分为三层:
-
物理数据层:它属于数据库的最内层,直接存储在物理设备上的数据内容,是原始数据,是用户加工的对象,由数据库指令来操作处理的字符串、字符,二进制等原始字节码组成。
-
概念数据层:它属于数据库的中间层,使用逻辑来对具体物理存储的数据进行抽象操作,对存储的物理数据进行集合,它涉及物理数据的逻辑关系,是数据库操作员或者程序开发者所对应看到的情况。
-
用户数据层:它属于最上层,也就是用户看到的这一层,这些是属于在概念数据层上进行逻辑计算和记录的集合或结果。
4.2 SQL与NoSQL
4.2.1 SQL
SQL是结构化查询语言(Structrued Query Language)的简称,是一种数据库查询和程序设计语言,其目的是用于存取数据以及查询、更新、删除、合并、计算等进行关系数据库系统的管理。
4.2.2 SQL语句语法
SQL的语句语法包含六个大的部分:
- 数据查询语言(DQL) 用于从表中获得数据,确定数据怎样在应用程序给出。包括:SELECT、WHERE、ORDER BY、GROUP BY和HAVING。
- 数据操作语言(DML) 包括:INSERT、UPDATE和DELETE。
- 事务处理语言(TPL) 确保被DML语句影响的表的所有行及时得以更新。包括:BEGIN TRANSACTION、COMMIT和ROLLBACK。
- 数据控制语言(DCL) 通过GRANT或REVOKE获得许可,确定单个用户和用户组对数据库对象的访问。
- 数据定义语言(DDL) 包括:CREATE和DROP。在数据库中创建新表或删除表(CREATE TABLE或DROP TABLE),为表加入索引等。
- 指针控制语言(CCL) 像DECLARE CURSOR、FETCH INTO和UPDATE WHERE CURRENT一样,用于对一个或多个表单独行的操作。
SQL中的五种数据类型:
- 字符型(VARCHAR VS CHAR)
varchar和char都是用来储存字符串长度小于255的字符。
- 向长度为40个字符的varchar中输入10个字节的数据,取出的数据长度还是10个字符。
- 向长度为40个字符的char中输入10个字节的数据,取出的数据长度是40个字符,字符串后面会附加多余的空格。
- 文本型(TEXT) 文本型数据没有长度,但要避免使用,因为其一般不做压缩。 一旦向文本型字段中输入任何数据(甚至是空值),就会有2K的空间被自动分配给该数据。除非删除该记录,否则无法收回这部分存储空间。
- 数值型(INT、NUMERIC、MONEY)
- 逻辑型(BIT) BIT型字段只能取两个值:0或1。 当创建好一个表后,不能向表中添加BIT型字段。
- 日期型(DATETIME VS SMALLDATETIME)
- DATETIME存储的日期范围:1753年1月1日第1毫秒到9999年12月31日最后1毫秒;
- SMALLDATETIME存储的日期范围:1900年1月1日第1秒到2079年6月6日最后1秒;
4.2.3 NoSQL
NoSQL是非关系型数据库,特点是去掉关系数据库的关系型特性,易于扩展。
NoSQL的开源软件:
- Membase
- Mongo DB
- Hypertable
- Apache Cassandra
- CouchDB
4.2.4 NoSQL语句语法
NoSQL没有规范,故选择MongoDB来介绍。
MongoDB属于Key-Value型的数据库,其存储的内容看起来像:
{"Key": "Value"}
{"Array": ["a", 1, 2]}
{"Mark": false}
{"include": {"a": 1, "b": "xyz"}}
增加或修改用户密码:
db.addUser("UserName", "pwd123", true) #添加用户、设置密码、是否只读
查看用户列表:
db.system.users.find()
删除用户:
db.removeUser('name')
存储一些数组对象:
db.user_addr.save({'id': 'xxx@163.com', 'groups': ['t1@msn.com', 't2@hotmail.com']})
根据query条件修改,若不存在则插入,允许修改多条记录:
db.my.update({'yy': 5}, {'$set': {'xx': 2}, upsert=true, multi=true})
删除x=5的记录:
db.my.remove({'x': 5})
删除所有记录:
db.my.remove()
查询所有记录:
db.userInfo.find()
等同于
select * from userInfo
查询去掉后的当前集合中的某列的重复数据:
db.userInfo.distinct("name")
等同于
select distinct name from userInfo
按年龄升序排列:
db.userInfo.find().sort({age: 1})
降序:
db.userInfo.find().sort({age: -1})
查询10条以后的数据:
db.userInfo.find().skip(10)
等同于
select * from userInfo where id not in (select top 10 * from userInfo)
4.3 内存与IO读写速度
4.4 同步内存数据
Redis数据库是内存式数据库,是Key-Value型的存储系统,它支持存储的Value类型包括string、list、set、zset(有序集合)和hash。
Python支持Redis编程:
import redis
启动Redis服务后,尝试连接数据库:
r = redis.Redis(host='127.0.0.1', port=6379, db=0) #db=0代表使用redis的0号数据库
r.set('foo', 'my_redis')
print r.get('foo')
r.delete('foo')
print r.dbsize()
可以使用save函数强制Redis往硬盘里写入数据,防止数据丢失:
r.save()
4.4.1 Redis数据库的持久性
Redis支持两种持久化方式,一种是快照(Snapshot),一种是追加文件(Append Only File,AOF),一般使用AOF进行持久化操作。
使用快照的方式进行dump操作的话,Redis配置里会这样写:
save 500 1 # 900秒内如果超过1个key被修改的话,则发起快照保存
save 30 5 # 300秒内如果超过5个key被修改的话,则发起快照保存
保存函数:
save()
:将阻塞当前进程,知道内容dump到磁盘上;
bgsave()
:立即返回,然后在后台进行保存操作;
AOF比快照方式有更好的持久化性,是由于在使用AOF方式时,redis会将每一个收到的写命令都通过write函数追加到文件中。
可以通过配置文件告诉Redis,我们想要通过fsync函数强制操作系统写入到磁盘的时机。有三种方式(默认每秒fsync一次):
appendonly yes # 启用aof持久化方式
# appendfsync always # 每次收到写命令就立即强制写入磁盘(慢,但保证完全的持久化)
appendfsync everysec # 每秒钟强制写入磁盘一次(在性能和持久化之间的折中方案)
# appendfsync no # 完全依赖操作系统(性能最好,但持久化没保证)
4.4.2 Redis主从数据库复制
Redis中,通过主从复制可以允许多个从服务器拥有和主服务器相同的数据库副本。
配置从服务器:
slaveof 192.168.x.x 6379 # 指定主服务器的ip和端口
4.5 数据备份与恢复
4.5.1 备份的类型
- 传统备份
- 数据归档
- 在线备份
- 离线备份
- 全备份
- 增量备份
- 并行技术
- 数据克隆
4.5.2 使用Python编写备份代码
使用Python来编写数据库的备份代码:
import os
import time
import string
source = '/db_source'
target_dir = '/my_backup/'
target = target_dir + time.strftime('%Y%m%d')
rar = 'rar a -r -m5 ' + target + '_back.rar' + source
databases = ['DB1', 'DB2']
sql_user = 'root'
sql_pwd = 'xxx'
if os.system(rar) == 0:
print 'backup ok', target
else:
print 'run rar fail'
for database in databases:
sql = target_dir + database + time.strftime('%Y%m%d') + '.sql'
sql_run = '/_my_path/mysqldump -u %s -p%s %s > %s' % (sql_user, sql_pwd, database_name, target_sql)
if os.system(sql_run) == 0:
print database, 'ok'
else:
print database, 'fail'
使用RAR来进行文件的压缩,并且使用SQL语句将存储过程备份下来,这样就是比较完整的数据库备份了。
使用FTP上传到指定服务器:
import os
from ftplib import FTP
tdir = '/my_backup/'
ftp = FTP()
ftp.connect('xxx.xxx.xxx.xx', '21')
ftp.login('user', 'pass')
ftp.cwd('/dbbackup')
li = os.listdir(tdir)
for l in li:
try:
ftp.nlst().index(1)
except:
tp = os.path.join(tdir, 1)
f = open(tp, 'rb')
ftp.storbinary('STOR%s'%os.path.basename(tp), f)
f.close()
ftp.quit()
载入FTP库即可轻松实现FTP的传输过程。
4.6 不可或缺的SQLite
SQLite数据库是一款非常小巧的嵌入式开源数据库。
导入包使用:
import sqlite3
在调用connect函数的时候,指定库名称,如果指定的数据库存在就直接打开这个数据库,如果不存在就新创建一个再打开。
c1 = sqlite3.connect("C:/test.db")
c2 = sqlite3.connect(":memory:") # 创建在内存中
连接对象,连接数据库后返回的对象c1,它有以下操作:
commit() # 事务提交
rollback() # 事务回滚
close() # 关闭一个数据库连接
cursor() # 创建一个游标
我们需要使用游标对象SQL语句查询数据库,获得查询对象。通过以下方法来定义一个游标:
ccu = c1.cursor()
游标对象有以下几个操作:
execute() # 执行sql语句
executemany() # 执行多条sql语句
close() # 关闭游标
fetchone() # 从结果中取一条记录,并将游标指向下一条记录
fetchmany() # 从结果中取多条记录
fetchall() # 从结果中取出所有记录
scroll() # 游标滚动
创建操作:
ccu.execute("create table abcd (a integer primary key, b integer, c varchar(10) UNIQUE, d text NULL)")
上面语句创建了一个叫abcd的表,它有一个主键a,一个b和一个c(c是不可以重复的),以及一个d(默认为NULL)。
插入操作:
for t in [(0, 10, 'abc', 'yao'), (1, 20, 'xyz', 'wo')]:
c2.execute("insert into abcd values (?,?,?,?)", t)
如果t只是单个数据,也要采用t=(n,)的形式。 插入数据后,只有提交了之后才能生效。
提交操作:
c2.commit()
查询操作:
ccu.execute("select * from catalog")
要提取查询到的数据,使用游标的fetch函数:
ccu.fetchall() # 输出:[(0, 10, u'abc', u'yao'), (1, 20, u'xyz', u'wo')]
若使用fetchone函数,则首先返回列表中的第一项,再次使用,则返回第二项,以此类推。
修改操作:
ccu.execute("update abcd set name='xyz' where a = 0")
c2.commit() # 修改数据后要提交
删除操作:
ccu.execute("delete from abcd where a = 1")
c2.commit() # 删除数据后要提交
小结: SQLite提供了非常简便的、单一的数据库服务,可以作为游戏中客户端的独立数据库,也可以在Python所编写的服务器中充当配置服务和单一的信息保存。