基于flask—数据库设计

需求分析

根据产品经理给的原型图,搞他就是。

该类产品一般都有三个端:

  • 用户
  • 自媒体
  • MIS管理后台

开发

团队(7-8类至少10人)

  • 产品经理:1
  • UI:1
  • 前端:2-3(3)App端混合式开发,一种语言编译两种平台
  • 后端:2-3(3)
  • 推荐系统:2-3(2)
  • 聊天机器人:1-2(1)
  • 测试:1-2(2)
  • 运维:无专门的运维人员,是由运维团队进行对各项目的运维工作。
    开发平台

从开始提出第一个原型图到第一版本雏形用了三个月。
在公司中要么以要么以内网对远端的服务器进行局部访问。不会提供公网地址————>
避免被黑。


github/码云-------------->开源,即便是收费的私有仓库也不太安全。
自己搭私有服务器。

数据库设计

方式:根据产品原型图一页一页的往下捋,找出需要存储的数据(持久数据),先不分表。
即时通讯:不需要做历史记录。
活体识别:需要截图到后台(后台是人工设别吗?)

notes:

1. 主键:
一条数据的唯一标识。

MySQL中若开发人员不定义主键,innoDB会自动创建主键。但是建议自己去申明主键------->不用区分不同数据库引擎。
当使用业务数据为主键,在业务上没问题,需要考虑后期维护。如果涉及到表的关联,需作为外键的字段应该不能被经常修改。(eg:手机号虽然唯一,但是又可能修改)

so: 一般不以业务数据为主键。

2. 一对多,多对多的关系,往往需要新建数据表。
用户<----->频道 ---->多对多
通过第三张表关联

3. 范式与反范式设计
范式:不能存在冗余字段
反范式设计:用空间换时间。
eg:
评论数据:怎么存?------->
文章------->评论(一对多)

select count(*) from comment where article_id=1
有些数据可以从其它表中统计出来----->遵循范式设计。(不存在冗余字段)
首页只需要展示评论数量而不是评论的具体内容,首页有很多文章,每篇文章都要查询评论表再聚合这样聚合设计统计会非常慢。
反范式设计——>提高速率
eg:评论数量
从评论表中执行selecet 语句,效率太低。

4.搜索
时间与状态设计:
今日头条,推文下的时间是推荐时间。---->不是文章发布时间
文章创建时间、修改时间、审核时间、
文章状态------->待审核、审核中、审核通过、草稿(互斥status(1234))

新闻置顶:后台统一控制。
封面图片:0-3张
封面若是放在文章表中——1.浪费资源 2. 业务改变时需要修改表(产品经理调整需要扩容)。
------>新建一张表。
mysql5.7之后新增了json类型数据。本质上仍是长字符串。

cover = '{'type':3,'pics':['url1','url2','url3']}'

修改机会特别少。

表的复用与自关联


字段类型的选择

1.主键:bigint,unsigned
is_delete(底层一般没有bool真假类型):tinyint(-128-127/0-255)
对于整型:
unsigned int(3) /unsigned int(6): 存储范围一样大,3/6控制的是显示长度。不足位的时候补0。
2.手机号:字符串

  • 0101…------->固话---->int类型前面的0会被省掉。
  • char(11): where mobile=’’ 此时查询效率更高---->可以直接进行减操作==0则找到。
  • varchar(11):节省空间,比较效率更低。------>只能一个个按位比较。
    是否可以为空,是否有默认值。

show create table news_article_basic; #查看创表语句


3.索引:

  • 主键 Primary Key
  • 外键Foregin Key : 不申明外键,表之间的关系通过字段就已经表现出来了。

4. 外键:

  • 保持数据的完整性
    user(user_id)主---->article(user_id)外 ---->删除user表中主键为1的用户,对应的文章表中的数据还有意义吗?——无。—>接下来该怎么处理?
  • 外键可以实现一些约束

申明外键:

ALTER TABLE 'user_resource' CONSTRAINT 'FKEEAF1E02D82D57F9' FOREIGN KEY('user_id')REFERENCE 'sys_user' ('id')  
# 没有选项,默认是 no action
 [ON DELETE reference_option]
 [ON UPDATE reference_option]
 

1. CASCADE(cascade)——表
在父表上update/delete记录时,同步update/delete子表的匹配记录。

  • ON DELETE:删除时自动删除从表。删除从表,主表不变
  • ON UPDATE:更新主表时自动更新从表。更新从表,主表不变

2. SET NULL(set null)——记录
在父表上update/delete记录时,将子表上匹配记录的列设为null (子表的外键列不能为not null)

  • ON DELETE:删除主表时自动更新从表值为NULL。删除从表,主表不变
  • ON UPDATE:更新主表时自动更新从表值为NULL。更新从表,主表不变

3. NO ACTION——记录
如果子表中有匹配的记录,则不允许对父表对应候选键进行update/delete操作

  • ON DELETE:从表记录不存在时,主表才可以删除。删除从表,主表不变
  • ON UPDATE:从表记录不存在时,主表才可以更新。更新从表,主表不变

4. RESTRICT
同no action, 都是立即检查外键约束

5. SET DEFAULT
父表有变更时,子表将外键列设置成一个默认的值 但Innodb目前不支持

  • 索引 Key / Index
    • 提升查询效率,减慢增删改速度
  • 唯一约束 Unique

notes:

  • 当数据量大的时候,将外键全部删除!———>用代码实现表之间的逻辑。
  • 查询效率:1. 数据计算----->直接定位 。2.另外的数据结构(tree)–>缩小查询范围。
  • 数据重复值多的字段不适合建索引。

mysql数据库引擎

show ENGINES     # 查看所有引擎
show  VARIALES LIKE 'storage_engine';   # 查看默认引擎
  1. 底层软件组织
  2. DBMS使用数据库引擎进行数据的增删改查。
  3. MySQL的核心

1. InnoDB

  • 事务型数据库首选
  • 支持事务安全表(ACID)
  • 支持行锁定and外键
  • MySQL默认引擎

特点:

1、InnoDB给MySQL提供了具有提交回滚崩溃恢复能力的事物安全(ACID兼容)存储引擎。

InnoDB锁定在行级并且也在SELECT语句中提供一个类似Oracle的非锁定读。这些功能增加了多用户部署和性能。在SQL查询中,可以自由地将InnoDB类型的表和其他MySQL的表类型混合起来,甚至在同一个查询中也可以混合。

2、InnoDB是为处理巨大数据量最大性能设计。它的CPU效率可能是任何其他基于磁盘的关系型数据库引擎锁不能匹敌的

3、InnoDB存储引擎完全与MySQL服务器整合,InnoDB存储引擎为在主内存中缓存数据和索引而维持它自己的缓冲池。InnoDB将它的表和索引在一个逻辑表空间中,表空间可以包含数个文件(或原始磁盘文件)。这与MyISAM表不同,比如在MyISAM表中每个表被存放在分离的文件中。InnoDB表可以是任何尺寸,即使在文件尺寸被限制为2GB的操作系统上

4、InnoDB支持外键完整性约束

5、存储表中的数据时,每张表的存储都按主键顺序存放,如果没有显示在表定义时指定主键,InnoDB会为每一行生成一个6字节的ROWID,并以此作为主键

6、InnoDB被用在众多需要高性能大型数据库站点上

InnoDB不创建目录,使用InnoDB时,MySQL将在MySQL数据目录下创建一个名为ibdata1的10MB大小的自动扩展数据文件,以及两个名为ib_logfile0和ib_logfile1的5MB大小的日志文件

MyISAM存储引擎

MyISAM基于ISAM存储引擎,并对其进行扩展。它是在Web、数据仓储和其他应用环境下最常使用的存储引擎之一。MyISAM拥有较高的插入、查询速度,但不支持事物。MyISAM主要特性有:

1、大文件(达到63位文件长度)在支持大文件的文件系统和操作系统上被支持

2、当把删除和更新及插入操作混合使用的时候,动态尺寸的行产生更少碎片。这要通过合并相邻被删除的块,以及若下一个块被删除,就扩展到下一块自动完成

3、每个MyISAM表最大索引数是64,这可以通过重新编译来改变。每个索引最大的列数是16

4、最大的键长度是1000字节,这也可以通过编译来改变,对于键长度超过250字节的情况,一个超过1024字节的键将被用上

5、BLOB和TEXT列可以被索引

6、NULL被允许在索引的列中,这个值占每个键的0~1个字节

7、所有数字键值以高字节优先被存储以允许一个更高的索引压缩

8、每个MyISAM类型的表都有一个AUTO_INCREMENT的内部列,当INSERT和UPDATE操作的时候该列被更新,同时AUTO_INCREMENT列将被刷新。所以说,MyISAM类型表的AUTO_INCREMENT列更新比InnoDB类型的AUTO_INCREMENT更快

9、可以把数据文件和索引文件放在不同目录

10、每个字符列可以有不同的字符集

11、有VARCHAR的表可以固定或动态记录长度

12、VARCHAR和CHAR列可以多达64KB

使用MyISAM引擎创建数据库,将产生3个文件。文件的名字以表名字开始,扩展名之处文件类型:frm文件存储表定义、数据文件的扩展名为.MYD(MYData)、索引文件的扩展名时.MYI(MYIndex)

MEMORY存储引擎

MEMORY存储引擎将表中的数据存储到内存中,未查询和引用其他表数据提供快速访问。MEMORY主要特性有:

1、MEMORY表的每个表可以有多达32个索引,每个索引16列,以及500字节的最大键长度

2、MEMORY存储引擎执行HASH和BTREE缩影

3、可以在一个MEMORY表中有非唯一键值

4、MEMORY表使用一个固定的记录长度格式

5、MEMORY不支持BLOB或TEXT列

6、MEMORY支持AUTO_INCREMENT列和对可包含NULL值的列的索引

7、MEMORY表在所由客户端之间共享(就像其他任何非TEMPORARY表)

8、MEMORY表内存被存储在内存中,内存是MEMORY表和服务器在查询处理时的空闲中,创建的内部表共享

9、当不再需要MEMORY表的内容时,要释放被MEMORY表使用的内存,应该执行DELETE FROM或TRUNCATE TABLE,或者删除整个表(使用DROP TABLE)

存储引擎的选择

提供提交回滚崩溃恢复能力的事物安全(ACID兼容)能力,并要求实现并发控制,InnoDB是一个好的选择

数据表主要用来插入和查询记录,则MyISAM引擎能提供较高的处理效率

临时存放数据,数据量小不需要较高的数据安全性,可以选择将数据保存在内存中的Memory引擎,MySQL中使用该引擎作为临时表,存放查询的中间结果

只有INSERT和SELECT操作,可以选择Archive,Archive支持高并发的插入操作,但是本身不是事务安全的。Archive非常适合存储归档数据,如记录日志信息可以使用Archive

一个数据库中多个表可以使用不同引擎以满足各种性能和实际需求,使用合适的存储引擎,将会提高整个数据库的性能


ORM

  • python代码----->ORM-------->sql语句处理数据库中数据
  • python代码<-----ORM<--------sql语句处理数据库中数据
    本质都是用原生框架进行操作数据。

作用

  1. 省去自己拼写SQL,保证SQL语法的正确性
  2. 一次编写可以适配多个数据库
  3. 防止注入攻击(and 1=1 …)
  4. 在数据库表名字段名发生变化时,只需修改模型类的映射,无需修改数据库操作的代码
    (相比SQL的话,可能需要同步修改涉及到的每一个SQL语句)

使用ORM的方式选择

1. 先创建模型类,再迁移到数据库中
优点:

  • 简单快捷,定义一次模型类即可,不用写sql

缺点:

  • 不能尽善尽美的控制创建表的所有细节问题,表结构发生变化的时候,也会难免发生迁移错误

2. 先用原生SQL创建数据库表,再编写模型类作映射

优点:

  • 可以很好的控制表结构的细节,避免发生迁移错误

缺点:

  • 可能编写工作多(编写sql与模型类,似乎有些牵强)

表的分割——将一张表的数据一分为二。

因为,一张表中某些数据经常被查询,一些数据不常被查询。
用户基本表和用户信息表

SQLAlchemy

SQLAlchemy—> python 、开源、提供SQL工具包+对象关系映射,使用MIT许可证发行。

  • 采用简单的python语言,为高效和高性能的数据库访问设计,实现完整的企业级持久模型。
  • Flask-SQLAlchemy是在Flask框架的一个扩展,封装SQLAlchemy,目的在于简化Flask中SQLAlchemy的使用,提供有用的默认值和额外的助手来更简单的完成日常任务。
    安装
    • pip install flask-sqlalchemy
    • pip install mysqlcilent # 使用MySQL时,需要安装MySQL的python客户端库。

连接设置

1.SQLALCHEMY_DATABASE_URI 数据库的连接信息

  • Postgres: postgresql://user:password@localhost/mydatabase
  • Mysql: mysql://user:password@localhost/mydatabase
  • Oracle:oracle://user:[email protected]:1521/sidname
  • SQLite: sqlite:////absolute/path/to/foo.db
  • 格式:数据库类型://用户名:密码@localhost/数据库名

2.SQLALCHEMY_TRACK_MODIFICATIONS(sqlalchemy_track_modifications) 在Flask中是否追踪数据修改。

3.SQLALCHEMY_ECHO (sqlalchemy_echo)显示生成的SQL语句,可用于调试。
这些配置参数需要放在Flask的应用配置(app.config)中

from flask import Flask

app = Flask(__name__)


# 配置对象
class Config(object):
	SQLALCHEMY_DATABASE_URL = 'mysql://root:[email protected]:3306/toutiao'
SALALCHEMY_TRACK_MODIFICATIONS = False # 一般都为False
SALALCHEMY_ECHO = True   # 调式模式下为True,开发模式下:False

class config.from_object(config)

模型类的映射:

from flask_sqlalchemy import SQLAlchemy
# 1. 创建SQLAlchemy对象
db=SQLAlchemy(app) # sqlalchemy对象接收app——因为数据库的连接信息在app中

# 2.定义模型类
class User(db.Model):
	"""
	用户基本信息
	"""
	__tablename__ = 'user_basic'
	
	class STATUS:
		ENABLE = 1
		DISABLE = 0

	id = db.Colunm('empliyee_id',)

创建SQLAlchemy对象有两种方式:
方式一:
db = SQLAlchemy(app)
方式二:
db = SQLAlchemy()
db.init_app(app)
该方式需要在上下文(cotext)中使用。

environ = {'wsgi.version':(1,0), 'wsgi.input': '', 'REQUEST_METHOD': 'GET', 'PATH_INFO': '/', 'SERVER_NAME': 'itcast server', 'wsgi.url_scheme': 'http', 'SERVER_PORT': '80'}

with app.request_context(environ):
    try:
        user = User(mobile='18911111111', name='itheima')
        db.session.add(user)
        db.session.flush() # 将db.session记录的sql传到数据库中执行
        profile = UserProfile(id=user.id)
        db.session.add(profile)
        db.session.commit()
    except:
        db.session.rollback()

原生SQL创表,映射模型类:

from flask import Flask

app = Flask(__name__)


class config(object):
	SQLALCHEMY_DATABASES_URL='mysql://root:password@localhost/employees'
	SQLALCHEMY_TRACK_MODFICAYIONS=False
	SQLALCHEMY_ECHO=True
	
app.config.from_object(config)

# 数据库中已有原生数据表
CREATE TABLE `user_basic` (
  `user_id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '用户ID',
  `email` varchar(20) COMMENT '邮箱',
  `status` tinyint(1) NOT NULL DEFAULT '1' COMMENT '状态,是否可用,0-不可用,1-可用',
 ....
  `read_count` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '累计阅读人数',
  PRIMARY KEY (`user_id`),
  UNIQUE KEY `mobile` (`mobile`),
  UNIQUE KEY `user_name` (`user_name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='用户基本信息表';

CREATE TABLE `user_profile` (
  `user_id` bigint(20) unsigned NOT NULL COMMENT '用户ID',
...
  `career` varchar(20) COMMENT '职业',
  PRIMARY KEY (`user_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='用户资料表';

模型类:

class User(db.Model):
    """
    用户基本信息
    """
    __tablename__ = 'user_basic'
	
	# 该类用于枚举常量起别名
    class STATUS:
        ENABLE = 1
        DISABLE = 0

    id = db.Column('user_id', db.Integer, primary_key=True, doc='用户ID')
    mobile = db.Column(db.String, doc='手机号')
    password = db.Column(db.String, doc='密码')
 ... 
    email = db.Column(db.String, doc='邮箱')
    status = db.Column(db.Integer, default=1, doc='状态,是否可用')

class UserProfile(db.Model):
    """
    用户资料表
    """
    __tablename__ = 'user_profile'

    class GENDER:
        MALE = 0
        FEMALE = 1

    id = db.Column('user_id', db.Integer, primary_key=True, doc='用户ID')
    gender = db.Column(db.Integer, default=0, doc='性别')
    ....
    career = db.Column(db.String, doc='职业')

模型类语法:( 区别django)

  1. 模型类的父类不是直接导包获得,而是借助于自己创建的SQLAlchemy对象里的一个model模块。
  2. 表名和模型类不一致时,_tablename_ = ‘表名’
  3. 使用:**db.Colunm()**映射字段。
  4. doc 和数据库没有映射关系,仅仅是给字段一个说明,更方便的看代码。表名字段名字段类型能对应就行了,约束索引就不用写了。默认值:表中有建议模型类也要写上。
  5. 模型类的创建、修改时间------>给字段默认值:default = datetime.now(datetime中的datetime)更新时间:onupdate
 ctime = db.Column('create_time', db.DateTime, default=datetime.now, doc='创建时间')
 utime = db.Column('update_time', db.DateTime, default=datetime.now, onupdate=datetime.now, doc='更新时间')
  • id :模型类字段
  • db :SQLAlchemy对象
  • user_id:表中关联字段名
  • db.Integer :映射字段类型
  • primary_key = True
  • doc = ‘用户ID’ # 字段描述信息
  • 对于文章状态这种字段:
 if user.satus == 0:
 	pass
 if arti....# 问题在于用户并不知道你1.2是什么。

解决办法---->在模型类中再定义一个类------>(打包一组数据)枚举可能情况(常量)给其取别名

 class STATUS:
        ENABLE = 1
        DISABLE = 0
 ...
 if user1.status == User.STATUS.DISABLE:  # 等价于:user.satus == 0
 ...

用字典打包数据:

STATUS = {
	 ENABLE = 1,
      DISABLE = 0
}
...
if user1.status == User.STATUS.['DISABLE']

创建SQLAlchemy对象方式二:
db=SQLAlchemy()
db.init_app(app)

tips:传app的原因是因为,数据库的连接信息在app中。
第二种方法,在创建SQLAlchemy创建时,又可能flask对象又可能还没创建出来,所以需要调用init_app(app)传入flask对象。

区别:
因为第一种方法,在SQLAlchemy对象创建时就从flask对象中把配置文件拿到手了。
第二种方法
此方法在单独运行调试时,对数据库的操作需要在Flask的应用上下文中进行:

with app.app_context():
	User.query.all()

SQLAlchemy操作

django提供了数据库的操作,所以可直接是由ORM而flask需要SQLAlchemy软件。

1. 新增 (db.session.add())

概念:

  • db.session是SQLAlchemy在每个事务中用来记录数据库操作的对象。—>SQLAlchemy所转化的所有SQL语句都是通过session与数据库进行沟通。
  • 有一个session代表了一个事务。

单个对象添加:

  1. 创建模型类对象:User(字段=值,…)
  2. 添加对象:调用SQLAlchemy的session.add()
  3. 提交事务:session.commit()

多个对象批量添加:

  1. 创建对象
  2. session.add_all(对象1,对象2…)
  3. 提交事务
user =  User(mobile='123xxxxxxxx',...)
db.session.add(user)
db.session.commit()
# //
db.session.add(user1,user2,...)
db.session.commit()

2. 查询 (模型类.query. /db.session.query(模型类). )


不带过滤条件

all()

  1. User.query.all()
  2. db.session.query(User).all()
    返回:[ (),(),() ]

first()

  1. User.query.first()
  2. db.session.query(User).first()
    返回:第一个 对象

get()

  1. User.query.get(‘主键’)
  2. db.session.query(User).get(‘主键’)
    接收主键,根据主键返回对象,主键不存在返回None

带过滤条件

select (这里写所有字段名而不是*)from User where mobile=‘123456789’ and user_id=3;
1. filter_by
User.query.filter_by(mobile=‘123456789’,user_id=3).all()

  • filter_by只能按照等值add查值(不等于,大于…只能用filter)

2. filter
User.query.filter(User.mobile==‘123456789’,User.user_id==3).all()
3. or_
from sqlachemy import or_
User.query.filter(or_(User.mobile==‘123456789’,User.user_id==3)).all()
4.and_
from sqlalchemy import and_
User.query.filter(and_(User.name != ‘13911111111’, User.mobile.startswith(‘185’))).all()
5. not_
from sqlachemy import not_
User.query.filter(not_(User.mobile == ‘13911111111’)).all()
6.offset

  • 偏移、起始位置
    User.query.offset(2).all()
    7. limit
    User.query.limit(3).all()
  • select-----limit(2,3) # 从第二行开始,往后取三条。(3,4,5)
  • offset和limit组合使用。
  • User.query.offset(2).limit(3).all()
    8. order_by
    order_by应位于filter之后。
    User.query.order_by(User.id).all() # 正序
    User.query.order_by(User.id.desc()).all() # 倒序

复合查询(多个查询条件)

User.query.filter(User.name.startswith(‘13’)).order_by(User.id.asc()).offset(2).limit(5).all()

  • 结尾必须要有
    • all()------->满足条件的所有
    • first()------->满足条件的第一个
    • 因为:User.query.order_by(User.id.desc()) ----->一个SQLAlchemy的查询对象,这个对象缓存了将要转化的SQL语句。.all()or.first()才执行sql语句查询符合条件的对象。
  • 要结果必须要all()或者filter()
  • start_with:以什么开头
  • end_with:以什么结尾

优化查询 load_only

user = User.query.filter_by(id=1).first()  # 查询所有字段
select user_id, mobile......

select * from   # 程序不要使用
select user_id, mobile,.... # 查询指定字段

from sqlalchemy.orm import load_only
User.query.options(load_only(User.name, User.mobile)).filter_by(id=1).first() # 查询特定字段
  • 若有些数据库版本比较老,并不能知道*(数据库表)包含哪些字段。
    1. 查询数据库得到所有的字段,替换掉*
    2. 执行sql语句。
  • 一个表中的字段实际是很多的,并不是所有字段都需要查询。若一次查询所有字段,会增大网络传输的开销。
  • 所以在程序开发过程中,需要什么字段查什么字段。
  • 在SQLAlchemy中似乎每次返回都是返回对象所有字段。
    - 优化:load_only指定返回字段。

聚合查询(只能用db.session.query(查询字段)…)

查询结果中有不存在于模型类中的字段------->无法指定模型类对象。
查询每个用户关注数:

from sqlalchemy import func

db.session.query(Relation.user_id, func.count(Relation.target_user_id)).filter(Relation.relation == Relation.RELATION.FOLLOW).group_by(Relation.user_id).all()

结果:[(),(),()…]

关联查询——1.2是两次查询的惰性查询。

主模型类:User
子模型类:UserProfile
1. 使用ForeignKey(前提是数据库表中指明了外键)
主键表&外键表
外–(访问)—>主:外键唯一确定–>从ForeingKey(‘数据库表.主键字段’)
主---------------->外:额外字段
字段名 = db.relationship(‘类’,uselist=Flase)
一对一:uselist=False---->返回一个对象
默认返回一个列表。


  1. 为外键表添加外键:ForeignKey(‘主键表名.user_id’)。因为foreignkey是底层生成表的内容,所以接收的参数是表名。
  2. 在主键表中添加字段:字段名 = db.relationship(‘外键模型类’,uselist=?)
    此处需要操作的是一对象。so…
    一对一:False
    一对多:True

若不做额外申明,relationship为惰性查询,需要获取外键表的字段时,才查询外键表。

# User
 profile = db.relationship(' UserProfile', uselist=False)
 
 #查询
user.profile.query.get(3)
...
FROM user_profile 
WHERE user_profile.user_id = 3

user.profile------->得到的是relationship的UserProfile对象
2. 使用primaryjoin(若数据库中根本就没有指定外键这个字段)
在主键表中,指定主键字段与另一张表某字段相同。
profile = db.relationship(‘Userprofile’,primaryjion=‘User.id==foreign(UserProfile.id)’, uselist=False)

3. 指定字段关联查询
需求:查询所关注的用户的名字。

select from user_basic as a inner jion user_relation as on a.user_id=b.target_user_id where b.target_user_id=2

from sqlalchemy.orm import load_only
from sqlalchemy.orm import contains_eager

# Relation
user = db.relationship('User',rimaryjoin=='Relation.target_user_id = foreign(User.id)')

# 查询语句
Relation.query.join(Relation.user).options(load_only(Relation.target_user_id), contains_eager(Relation.user).load_only(User.name)).all()

结果:[, , , , , , , , , ]
列表
取值:list[0].属性
list[0].user------>得到的是relationship的User对象。

3. 更新

  • 一 获取对象赋新值,保存到数据库,提交事务
user = User.query.get(1)
user.name = 'Python'
db.session.add(user)
db.session.commit()
  • 二 查询更新:update,提交事务
 User.query.filter_by(id=1).update({'name':'python'})
  db.session.commit()

4. 删除delete()

  • 方式一:delete(对象名)
user = User.query.order_by(User.id.desc()).first()
  db.session.delete(user)
  db.session.commit()
  • 方式二:delete
User.query.filter(User.mobile='18512345678').delete()
db.session.commit()

5. 事务

flask:事务默认开启
在事务开启到事务提交之间的内容------>一个事务
db.session.commit():提交事务

environ = {'wsgi.version':(1,0), 'wsgi.input': '', 'REQUEST_METHOD': 'GET', 'PATH_INFO': '/', 'SERVER_NAME': 'itcast server', 'wsgi.url_scheme': 'http', 'SERVER_PORT': '80'}

with app.request_context(environ):
    try:
        user = User(mobile='18911111111', name='itheima')
        db.session.add(user)
        db.session.flush() # 将db.session记录的sql传到数据库中执行
        profile = UserProfile(id=user.id)
        db.session.add(profile)
        db.session.commit()
    except:
        db.session.rollback()

数据库理论

1. 复制集与分布式

数据库服务器集群:
主:写操作(产生有效数据的操作)
从:读操作(不产生有效数据)
复制集:一主多从。舵主多从(多主之间互为复制关系)
分布式集群:所有主构成完整数据,为了提高“可用性”一般会给每个分片配一个从服务器。


  • 复制集
    • 数据库中数据相同,起备份作用
    • 高可用 High Avaliable HA
  • 分布式
    • 数据库中数据不同,共同组成完整数据
    • 通常每一个节点被称为一个分片(shard)
    • 高吞吐 High Throughput
    • 复制集和分布式可以单独使用,也可组合使用(每个分片都租建一个复制集)
  • 主从:
    • 该概念是从使用的角度阐述问题
    • 主节点 -> 程序在该节点最先更新数据。
    • 从节点->这个节点的数据时从主节点复制来的
    • 复制集 可选 主从 主主 主主从从
    • 分布式 每个分片都是主。组合使用复制集时,复制集市从。

2. MySQL

(1)主从复制

MySQL主从依赖MySQL自己实现

  1. master改变---->binary log(二进制日志)
  2. 在slave中有一个I/O thread 和master(主机)建立一个TCP长连接进行通讯,若binary log发生改变,直接将数据拷贝到slave中的Relay log(中继,用于积攒指令) 中
  3. SQL thread 从Relay log获取指令到slave中执行。

主从同步的作用:

  1. 数据备份(免得master死掉之后数据找不回来)
  2. django的读写分离,利用了主从同步——为了更好的利用资源,让从端有事可做。——提高了吞吐量。
  3. 高可用——master挂掉之后,修改配置使slave马上顶上,不至于无机可用。
  4. 主从同步之后不一定要读写分离

思考——读写分离对事务是否有影响???
答:可能会有影响。

对于写操作包括开启事务和提交或回滚要在一台机器上执行,分散到多台master执行后数据库原生的单机事务就失效了。
——对于写操作,最好在一台机器上执行。

对于事务中同时包含写操作,与事务隔离级别(在一个事务中做了修改对其它事务是否可见)设置有关,如果事务隔离级别为read-uncommitted() 或者 read-committed,读写分离没影响,如果隔离级别为repeatable-read、serializable,读写分离就有影响,因为在slave上会看到新数据,而正在事务中的master看不到新数据。

事务隔离级别 必背!
事务A,事务B

  • read uncommited 读未提交
    不保密,一点保密性都没有。B一遍A就知道
  • read commited 读已提交
    对一个事务中的操作,必须commit之后才能被其它事务看见。B的改变必须提交,A 才能知道。
  • repeatable read 可重复读
    不管,在一个事务中不管你读几次,读到的数据都是最初的数据,即便别的事务已经提交了对数据的修改。
    只有在同一台机器上,隔离级别才不会被破坏???
  • serialize 串行
(1)分库分表(sharding)
  1. 用户请求量太大——将请求分散到多个服务器
  2. 一个数据库中的数据量太多——将表分散到别的库
  3. 表数据太多——将一张表分程多张表。

方法:

  1. 垂直拆分
    库:
    针对系统中不同业务进行拆分,用户一个库,商品一个库。切分之后放在多个服务器上。1.不切分,所有的业务数据在一个库中——数据库的单库处理成为瓶颈。2. 垂直分库,放在一个数据库服务器中——随着用户量的增大,单个数据库的处理能力成为瓶颈,同时单个服务器的磁盘空间,内存,tps等非常吃紧。
    表:(一般是表中字段较多)
    从上到下垂直切,左右各一张表,存统一数据库
  2. 水平拆分(几千万条)
    库:
    单张表数据-------->多个服务器,每个服务器具有相应的库与表,只是表中数据集合不同。
    水平分库分表能够有效的缓解单机和单库的性能瓶颈和压力,突破IO、连接数、硬件资源等的瓶颈。
    表:(据量巨大——几千万条。比如订单表)
    按照某种规则(RANGE,HASH取模等),切分到多张表里面去。 但是这些表还是在同一个库中,所以库级别的数据库操作还是有IO瓶颈。不建议采用。
    水平分库分表的分散方式:
  • RANGE
    按主键范围进行划分
    比如:从0到10000一个表,10001到20000一个表;

  • HASH取模 离散化

一个商场系统,一般都是将用户,订单作为主表,然后将和它们相关的作为附表,这样不会造成跨库事务之类的问题。 取用户id,然后hash取模,分配到不同的数据库上。
比如x%3 =1 在第一台。=2…

  • 地理区域

比如按照华东,华南,华北这样来区分业务,七牛云应该就是如此。

  • 时间

按照时间切分,就是将6个月前,甚至一年前的数据切出去放到另外的一张表,因为随着时间流逝,这些表的数据 被查询的概率变小,所以没必要和“热数据”放在一起,这个也是“冷热数据分离”。

分库分表后面临的问题

事务支持

分库分表后,就成了分布式事务了。如果依赖数据库本身的分布式事务管理功能去执行事务,将付出高昂的性能代价; 如果由应用程序去协助控制,形成程序逻辑上的事务,又会造成编程方面的负担。

多库结果集合并(group by,order by)

跨库join

分库分表后表之间的关联操作将受到限制,我们无法join位于不同分库的表,也无法join分表粒度不同的表, 结果原本一次查询能够完成的业务,可能需要多次查询才能完成。 粗略的解决方法: 全局表:基础数据,所有库都拷贝一份。 字段冗余:这样有些字段就不用join去查询了。 系统层组装:分别查询出所有,然后组装起来,较复杂。

分布式ID

问题:水平分库分表——表中的字段都是一样的,知识值放在了不同表、库、服务器中。主键是否会受到影响???——有可能会(比如主键采用自增的方式,当把所有表何在一起时,主键会重复)
解决——分布ID
保证进行分库分表之后,从整体考虑数据库主键仍是唯一。

方案选择

1. UUID
Universally Unique Identifier
128位二进制组成,一般转换成十六进制,然后用String表示。
优点:

  • 本地生成,没有经过网络I/O,性能较快。
  • 无序,无法预测其生成顺序(缺点也是)

缺点:

  • 128位二进制一般只能转化位36位16进制,太长了只能用string存储,空间占用较多。
    如:6832548fjeghdj9t 字符串类型:6832占4个字节。整型就没这么多。
  • 不能生成递增有序的数字。(不能趋势递增——趋势递增能提高数据库性能)

2.数据库主键自增

  • 单独数据库 记录主键值:数据库就一张表,一张表里就一个字段:主键ID
    任何业务数据需要id先到该数据库中生成——插入记录,产生Id,再把Id存入业务数据库。

缺点:过于依赖中心数据库,中心数据库宕掉,整个业务就瘫掉了。

  • 业务数据库分别设置不同的自增起始值和固定步长,如:

第一台 start 1 step 9
第二台 start 2 step 9
第三台 start 3 step 9

优点:
简单方便,有序递增,方便排序和分页

缺点:
没办法保证扩容。
3.Redis
redis 中的字符串——string set id 100---->再redis中如果字符串中的数据是个数字的话,redis会自动把它当作一个整型进行对待。
Incr id :自动对id进行累加。调一次Incr id增加一次
IncrBy:
redis是单线程,能保证原子性。

缺:
中心库,redis挂就挂
内存型数据库,关机数据可能丢失。
4.雪花算法-Snowflake(现成的轮子)——强依赖时间
推特公司开源——雪花算法。
64位的整数,代表ID
1bit:第一位符号位
41bit:时间戳(可记录69年)
10bit:记录机器id,总共记录1024台机器,一般前五位代表数据中心,后5位某数据中心机器id
12bit:循环位,用来对同一个毫秒之内产生不同的ID,12位可以最多记录4095个,也就是在同一个机器同一毫秒最多记录4095个,多余的需要进行等待下毫秒。
上面只是一个将64bit划分的标准,当然也不一定这么做,可以根据不同业务的具体场景来划分,比如下面给出一个业务场景:

服务目前QPS10万,预计几年之内会发展到百万。
当前机器三地部署,上海,北京,深圳都有。
当前机器10台左右,预计未来会增加至百台。
这个时候我们根据上面的场景可以再次合理的划分62bit,QPS几年之内会发展到百万,那么每毫秒就是千级的请求,目前10台机器那么每台机器承担百级的请求,为了保证扩展,后面的循环位可以限制到1024,也就是2^10,那么循环位10位就足够了。
“1符号 41时间戳 (前2不变) 3机房7机器ID 10循环位2扩展”

5.时钟回拨——隐含问题
因为机器的原因会发生时间回拨,我们的雪花算法是强依赖我们的时间的,如果时间发生回拨,有可能会生成重复的ID。
解决——记录上次时间戳,将本次时间戳与上次记录对比,若本次小于上次(发生时间回拨)——抛出异常。

数据库优化

索引

SQL查询优化

  • 避免全表扫描,应考虑在 where 及 order by 涉及的列上建立索引;

  • 查询时使用select明确指明所要查询的字段,避免使用select *的操作;

  • SQL语句尽量大写,如

    SELECT name FROM t WHERE id=1
    对于小写的sql语句,通常数据库在解析sql语句时,通常会先转换成大写再执行。

  • 尽量避免在 where 子句中使用!=或<>操作符, MySQL只有对以下操作符才使用索引:<,<=,=,>,>=,BETWEEN,IN,以及某些时候的LIKE;

    SELECT id FROM t WHERE name LIKE ‘abc%’

  • 对于模糊查询,如:

    SELECT id FROM t WHERE name LIKE ‘%abc%’
    或者

    SELECT id FROM t WHERE name LIKE ‘%abc’

  • 将导致全表扫描,应避免使用,若要提高效率,可以考虑全文检索;

  • 遵循最左原则,在where子句中写查询条件时把索引字段放在前面,如

    mobile为索引字段,name为非索引字段
    推荐
    SELECT … FROM t WHERE mobile=‘13911111111’ AND name=‘python’
    不推荐
    SELECT … FROM t WHERE name=‘python’ AND mobile=‘13911111111’

    建立了复合索引 key(a, b, c)
    推荐
    SELECT … FROM t WHERE a=… AND b=… AND c= …
    SELECT … FROM t WHERE a=… AND b=…
    SELECT … FROM t WHERE a=…
    不推荐 (字段出现顺序不符合索引建立的顺序)
    SELECT … FROM t WHERE b=… AND c=…
    SELECT … FROM t WHERE b=… AND a=… AND c=…

  • 能使用关联查询解决的尽量不要使用子查询,如

    子查询
    SELECT article_id, title FROM t_article WHERE user_id IN (SELECT user_id FROM t_user WHERE user_name IN (‘itcast’, ‘itheima’, ‘python’))

    关联查询(推荐)
    SELECT b.article_id, b.title From t_user AS a INNER JOIN t_article AS b ON a.user_id=b.user_id WHERE a.user_name IN (‘itcast’, ‘itheima’, ‘python’);

  • 能不使用关联查询的尽量不要使用关联查询;

  • 不需要获取全表数据的时候,不要查询全表数据,使用LIMIT来限制数据。

数据库优化

  • 在进行表设计时,可适度增加冗余字段(反范式设计),减少JOIN操作;
  • 多字段表可以进行垂直分表优化,多数据表可以进行水平分表优化;
  • 选择恰当的数据类型,如整型的选择;
  • 对于强调快速读取的操作,可以考虑使用MyISAM数据库引擎;
  • 对较频繁的作为查询条件的字段创建索引;唯一性太差的字段不适合单独创建索引,即使频繁作为查询条件;更新非常频繁的字段不适合创建索引;
  • 编写SQL时使用上面的方式对SQL语句进行优化;
  • 使用慢查询工具找出效率低下的SQL语句进行优化;
  • 构建缓存,减少数据库磁盘操作;
  • 可以考虑结合使用内在型数据库,如Redis,进行混合存储。

你可能感兴趣的:(python,后端)