智牛股_第5章_Netty+PowerDesigner

智牛股_第5章_Netty+PowerDesigner

学习目标

目标1:掌握Netty的使用

目标2:数据库设计规范

目标3:数据库建模理论

目标4:PowerDesigner使用

目标5:数据库调优

目标6:数据库设计之交易模块

第1章 Netty使用

1. 目标

  • 掌握Netty在实际项目当中的集成使用

2. 步骤

  • 工程设计
  • 工程结构
  • 服务端工程实现
  • 客户端工程实现
  • 功能演示与验证

3. 实现

3.1 工程设计

在这里插入图片描述

建立两个工程, 通讯服务端与通讯客户端, 可以相互发送消息, 做个简易聊天演示。

3.2 工程结构

在这里插入图片描述

工程依赖:

   <modules>
        <module>netty-server</module>
        <module>netty-client</module>
    </modules>

    <dependencies>
        <!-- Netty 核心组件依赖 -->
        <dependency>
            <groupId>io.netty</groupId>
            <artifactId>netty-all</artifactId>
            <version>4.1.16.Final</version>
        </dependency>

    </dependencies>
3.3 服务端工程
  1. 服务端工程NettyServer
  • 接收客户请求, 并打印客户端发送的消息
  • 消息采用内置String作为编码与解码器
  • 开启信息输入监听线程, 发送消息至客户端
  1. NettyProviderServer类:

    public class NettyProviderServer {
    
        private int port;
        public NettyProviderServer (int port){
            this.port = port;
        }
    
        // netty 服务端启动
        public void runServer() throws Exception{
    
            // 用来接收进来的连接
            EventLoopGroup bossGroup = new NioEventLoopGroup();
            // 用来处理已经被接收的连接,一旦bossGroup接收到连接,就会把连接信息注册到workerGroup上
            EventLoopGroup workerGroup = new NioEventLoopGroup();
    
            try {
                // nio服务的启动类
                ServerBootstrap sbs = new ServerBootstrap();
                // 配置nio服务参数
                sbs.group(bossGroup, workerGroup)
                        .channel(NioServerSocketChannel.class) // 说明一个新的Channel如何接收进来的连接
                        .option(ChannelOption.SO_BACKLOG, 128) // tcp最大缓存链接个数,它是tcp的参数, tcp_max_syn_backlog(半连接上限数量, CENTOS6.5默认是128)
                        .childOption(ChannelOption.SO_KEEPALIVE, true) //保持连接
                        .handler(new LoggingHandler(LogLevel.INFO)) // 打印日志级别
                        .childHandler(new ChannelInitializer<SocketChannel>() {
                            @Override
                            protected void initChannel(SocketChannel socketChannel) throws Exception {
    
                                //管道注册handler
                                ChannelPipeline pipeline = socketChannel.pipeline();
                                //编码通道处理
                                pipeline.addLast("decode", new StringDecoder());
                                //转码通道处理
                                pipeline.addLast("encode", new StringEncoder());
    
                                // 处理接收到的请求
                                pipeline.addLast(new NettyServerHandler()); // 这里相当于过滤器,可以配置多个
                            }
                        });
    
                System.err.println("-------server 启动------");
    
                // 监听输入端消息并发送给所有客户端
                new Thread(new Runnable() {
                    @Override
                    public void run() {
                        try {
                            while (true) {
                                BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
                                String str = in.readLine();
                                if (NettyServerHandler.channelList.size() > 0) {
                                    for (Channel channel : NettyServerHandler.channelList) {
                                        channel.writeAndFlush(str);
                                    }
                                }
                            }
                        }catch(Exception e) {
                            e.printStackTrace();
                        }
                    }
                }).start();
    
                // 绑定端口,开始接受链接
                ChannelFuture cf = sbs.bind(port).sync();
                cf.channel().closeFuture().sync();
    
            } finally{
                bossGroup.shutdownGracefully();
                workerGroup.shutdownGracefully();
            }
        }
    
        // 开启netty服务线程
        public static void main(String[] args) throws Exception {
            new NettyProviderServer(9911).runServer();
        }
    
    }
    

    服务端监听9911端口, 客户端连接时需指定此端口。

    public class NettyServerHandler extends ChannelInboundHandlerAdapter {
    
        public static List<Channel> channelList = new ArrayList<Channel>();
    
        @Override
        public void channelActive(ChannelHandlerContext ctx) throws Exception {
            super.channelActive(ctx);
            channelList.add(ctx.channel());
        }
    
        // 读取数据
        @Override
        public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
            System.out.println("server--收到消息: " + msg);
        }
    
    
        // 出现异常的处理
        @Override
        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
            System.err.println("server--读取数据出现异常:");
            cause.printStackTrace();
            ctx.close();
        }
    
    }
    

    处理channel所接收的消息。

3.4 客户端工程
  1. 客户端工程NettyClient
  • 发起请求, 与服务端建立连接。
  • 监听服务端下发消息, 并将信息打印出来。
  • 开启信息输入监听线程, 将消息发送至服务端。
  1. NettyClientServer类

    public class NettyClientServer {
    
        // 要请求的服务器的ip地址
        private String ip;
        // 服务器的端口
        private int port;
    
        public NettyClientServer(String ip, int port){
            this.ip = ip;
            this.port = port;
        }
    
        // 启动服务
        private void runServer() throws Exception {
    
            EventLoopGroup bossGroup = new NioEventLoopGroup();
    
            Bootstrap bs = new Bootstrap();
    
            bs.group(bossGroup)
                    .channel(NioSocketChannel.class)
                    .option(ChannelOption.SO_KEEPALIVE, true)
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel socketChannel) throws Exception {
                            //管道注册handler
                            ChannelPipeline pipeline = socketChannel.pipeline();
                            //编码通道处理
                            pipeline.addLast("decode", new StringDecoder());
                            //转码通道处理
                            pipeline.addLast("encode", new StringEncoder());
    
                            // 处理来自服务端的响应信息
                            socketChannel.pipeline().addLast(new NettyClientHandler());
                        }
                    });
            System.out.println("-------client 启动------");
            // 客户端开启
            ChannelFuture cf = bs.connect(ip, port).sync();
    
            String reqStr = "客户端发起连接请求";
    
            Channel channel = cf.channel();
    
            // 发送客户端的请求
            channel.writeAndFlush(reqStr);
    
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        while(true) {
                            BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
                            String msg = in.readLine();
                            channel.writeAndFlush(msg);
                        }
                    }catch(Exception e) {
                        e.printStackTrace();
                    }
                }
            }).start();
    
    
        }
    
        public static void main(String[] args) throws Exception {
            new NettyClientServer("127.0.0.1", 9911).runServer();
        }
    }
    
    
  2. NettyClientHandler类

    public class NettyClientHandler extends ChannelInboundHandlerAdapter {
    
        public static Channel serverChannel = null;
    
        @Override
        public void channelActive(ChannelHandlerContext ctx) throws Exception {
            super.channelActive(ctx);
            serverChannel = ctx.channel();
        }
    
        @Override
        public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
            System.out.println("client--收到消息:" + msg);
        }
    
        // 出现异常的处理
        @Override
        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
            System.err.println("client 读取数据出现异常");
            cause.printStackTrace();
            ctx.close();
        }
    
    }
    
    
3.5 工程演示
  1. 启动服务

    服务端:
    在这里插入图片描述

    客户端:
    在这里插入图片描述

  2. 发送验证

    客户端发送消息: 我是客户端client

    服务端发送消息: 我是服务端server

    查看结果:

在这里插入图片描述

在这里插入图片描述

从结果可以看到, 服务端和客户端均能正常收发相互之间消息。

4. 总结

  • 理解服务端与客户端的工程配置, 关键实现代码, 编码与解码器的使用, 客户端发送消息与服务端接收消息的处理; 这里采用的String类型的内置编码器, 在实际工作中, 我们还可以自定义编码器,比如二进制数据结构, 头部(数据包长度,请求类型, CRC32校验等)与包体, 能够更灵活高效的封装处理数据。

第2章 数据库设计规范与模型

1. 目标

  • 了解数据库的设计规范
  • 如何去设计数据库, 遵循的原则
  • 掌握数据库的具体规范与设计原则
  • 了解数据库的模型分类,与各模型的概念和作用

2. 分析

  • 为什么需要规范
  • 如何去设计数据库
  • 数据库设计的具体规范
  • 数据库设计所遵循的原则
  • 数据库模型概念
  • 概念模型
  • 逻辑模型
  • 物理模型

3. 讲解

3.1 为什么需要规范

在互联网项目,分工明确,拆分细致, 但一个项目往往会涉及众多参与人员,包括架构师、高级工程师、研发工程师、测试工程师、DBA等不同角色。 数据库是一个项目的最底层设计, 如果设计不清晰,结构模糊, 规范混乱,是很难做成一个成功的项目,而这些需要我们从最基本的开始,对数据库有一套明确的设计规范。

3.2 如何设计
  1. 传统数据库设计往往会遵循设计范式(三范式), 随着不断发展, 范式设计往往难以满足现在项目的要求, 出现了反范式设计。

    • 范式设计:

      第一范式:数据库表中的每一列都是不可分割的基本数据项, 比如股票名称、股票代码。

      第二范式:数据库表所有属性都必须和主键有完全依赖关系, 比如用户编号为主键,那么与之依赖的有用户名、用户地址、用户性别和身份证等, 这些属性完全与具体用户产生依赖。

      第三范式:数据库表中属性不能存在传递关系, 比如A->B->C, 属性之间存在这样关系则不符合第三范式。再具体点, 比如股票表(股票编号, 股票名称,股票市场,股票交易手续费),这个表就不符合第三范式,因为股票交易手续费是依赖股票市场来确定, 需要再拆分为(股票编号, 股票名称)和(股票市场,股票交易手续费)多张表。

    • 反范式设计:

      范式设计虽然关系层次明确, 但是给业务查询带来了相当的复杂度,中间关系表膨胀, 查询需要连接更多的表, 导致性能严重下降。

      反范式并不是完全打破范式设计, 而是在范式设计与查询性能上找到平衡点,适合业务的设计才是好的设计。

  2. 在实际业务中, 如何去设计呢? 做好需求分析, 理解业务流程, 弄清业务概念, 技术是生产工具, 服务于业务才能体现其价值,所以明白业务需求才知道怎么去设计。 业务设计三步法则:

    • 先拆解: 哪些是业务系统的核心模块, 核心模块具体包含哪些对象, 哪些又是非核心模块, 必须先列清楚。
    • 画关系: 将拆解的模块串接起来,形成一个整体, 哪些是依赖关系, 哪些是包含关系,哪些是聚合, 哪些又是组合。
    • 再细化:有上面的清晰脉络, 再去填充对象的属性, 仔细考量, 适当加入反范式设计, 冗余一些常用信息,这些就需再仔细去细化, 斟酌与权衡。
3.3 命名与表设计规范

​ 规范并不是千遍一律, 但要在自己的项目体系内形成一套有效规范, 以下规范可作参考:

  1. 命名规范

    1.命名有意义,一眼知道这张表是干什么用的
    2.数据库,表都用小写
    数据库形如:backend
    数据表形如:client_device_info(客户端设备信息),不要缩写,字母全小写
    3.索引命名以idx_为前缀
    4.命名不要过长(应尽量少于25字符)
    5.不要使用保留字
    6.同一字段在不同的表中也应是相同的类型和长度
    7.同一数据库下有不同的模块,可以考虑对表名用不同的前缀标识
    8.备份表时加上时间标识
    
  2. 表设计规范

    1.如果没有特殊情况,建议选择InnoDB引擎
    2.每个表都应该有主键,可选择自增字段,或长整型字段。例外情况,如果有多个字段可以组成唯一, 并且需要频繁查询的, 那么可以采用复合主键,比如用户编号+交易商编号做为复合主键,因为可以确保不产生重复数据, 同时节省空间。 
    3.(不做强制要求)尽量将字段设置为NOT NULL。因为NULL值的存储需要额外的空间,且会导致比较运算更为复杂,会使得优化器更难以优化sql。空值是不占用空间, 但NULL列需要占用一个额外字节。
    4.使用更短小的列,比如整型列。整型列的执行速度往往更快。
    5.存储精确浮点数必须使用DECIMAL代替float和double。
    6.建议使用unsigned类型存储非负值
    7.建议使用 int unsigned存储ipv4
    8.整型定义中不添加显示长度的值,使用int,而不是int(4)
    9.尽可能不要使用text,blob类型
    10.varchar(n) n表示字符数而不是字节数,比如varchar(255)最大可存储255个汉字,需根据实际字符长度选择n的值。
    11.字符集建议选择utf-8
    12.存储年时使用year类型
    13.存储日期时使用date类型
    14.存储时间时,建议使用timestamp类型,因为timestamp使用的是4字节,datetime使用的是8字节。
    15.不要在数据库中使用varbinary或blob存储图片及文件,mysql 并不适合大量存储这类型文件
    16.join 操作的字段,在不同表中的类型及命名要一致
    17.如果更改表结构会影响性能,需要找DBA、表设计人员进行联合评审。
    
3.4 数据库设计原则
  1. 核心原则

    不在数据库做运算; cpu计算务必移至业务层;

    控制每个表的列数量(字段少而精,字段数建议在20以内);

    平衡范式与冗余(效率优先;往往牺牲范式)

    拒绝3B(拒绝大sql语句:big sql、拒绝大事物:big transaction、拒绝大批量:big batch);

  2. 字段类原则

    用好数值类型(用合适的字段类型节约空间);

    字符转化为数字(能转化的最好转化,同样节约空间、提高查询性能);

    避免使用NULL字段(NULL字段很难查询优化、NULL字段的索引需要额外空间、NULL字段的复合索引无效);

    少用text类型(尽量使用varchar代替text字段);

  3. 索引类原则

    合理使用索引(改善查询,减慢更新,索引一定不是越多越好);
    字符字段大量重复属性时, 尽量建前缀索引(比如地区chinaXXX, alter table x_test add index(x_city(6)) ); 不在索引做列运算;
    innodb主键推荐使用自增列(主键建立聚簇索引,主键不应该被修改,字符串不应该做主键)
    不用外键(由程序保证约束);

  4. SQL类原则

    sql语句尽可能简单(一条sql只能在一个cpu运算,大语句拆小语句,减少锁时间,一条大sql可以堵死整个库);
    避免使用trig/func(触发器、函数, 采用程序取而代之);
    大批量SQL的更新操作要开启事务,越多效果性能越明显。

3.5 什么是数据库模型

数据模型是数据库的基础,数据是对客观事物的符号表示,模型是现实世界的抽象。数据模型是对数据特征的抽象。数据库模型主要包含概念数据模型、逻辑数据模型与物理数据模型。

数据库的类型是根据数据模型来划分的,而任何一个DBMS也是根据数据模型有针对性地设计出来的,这就意味着必须把数据库组织成符合DBMS规定的数据模型。目前成熟地应用在数据库系统中的逻辑数据模型有:层次模型(树状结构表示的数据模型)、网状模型(有向图表示)和关系模型(表格表示)。

3.6 数据库建模三步曲

概念模型->逻辑模型->物理模型

3.7 概念模型

概念模型是面向用户、面向现实世界的数据模型, 与DBMS无关,与业务相关,按用户的观点来对数据和信息建模。

了解用户的需求,用户的业务领域工作情况, 经过分析和总结,提炼出来的用以描述用户业务需求的一些概念的东西。例如金融交易市场中存在众多概念, 一个完整的交易,能够抽取出的概念有, 客户、 股票、订单、持仓等。

简单概括为 是什么

3.8 逻辑模型

逻辑模型就是要将概念模型具体化,就是要实现概念模型所描述的东西,需要哪些具体的功能,处理哪些具体的信息,是对概念模型的进一步细化。例如金融交易市场中的客户,信息包含: 客户名称, 客户账号,客户电话、客户地址等属性; 股票包含:股票代码, 股票名称, 股票市场,股票单位等; 订单包含客户账户, 股票代码,交易手数, 交易金额等。

这个阶段需要清楚系统要划分多少个模块, 按计算机系统的观点对数据建模,要建立多少个数据表, 每张表具体包含的属性特征, 主要用于DBMS的实现。

简单概括为 做什么

3.9 物理模型

物理模型是对真实数据库的描述, 对数据最低层的抽象, 描述数据在系统内部的表示方式和存取方法,是面向计算机系统的。如关系数据库中的一些对象: 表、视图、字段、数据类型、长度、主键、外键、索引、约束、是否可为空、默认值。

这个阶段需要采用数据库语言来描述真实世界的模型, 形成具体的SQL语句。

简单概括为 怎么做

4. 总结

  • 明白数据库设计为什么需要规范, 具体有哪些规范, 该如何设计,具体把握的要点, 理解掌握数据库设计的基本原则, 奠定良好的基础。
  • 数据库模型作为数据库设计的理论基础, 我们需要知道有哪些主要模型, 各模型的主要作用和应用场景, 在实际工作中能更好的去运用。

第3章 PowerDesigner使用

1. 目标

  • 掌握PowerDesigner的使用, 学会通过建模工具来设计数据库表。

2. 步骤

  • PowerDesigner简介
  • PD建模类型介绍
  • PD工具如何去创建物理模型, 并生成SQL脚本

3. 实现

3.1 简介

PowerDesigner是一款功能非常强大的建模工具软件,也是最流行的建模软件之一。它的发展历程是怎样? 相比其他主流工具有什么区别? 比如Rational Rose, 它是专攻UML对象模型的建模工具,之后才向数据库建模发展;而PowerDesigner正好相反,它是以数据库建模起家,后来才发展为一款综合全面的Case工具。

3.2 PD建模类型
  1. 概念数据模型 (CDM)
    对数据和信息进行建模,利用实体-关系图(E-R图)的形式组织数据,检验数据设计的有效性和合理性。

  2. 逻辑数据模型 (LDM)
    逻辑模型中一方面显示了实体、实体的属性和实体之间的关系;另一方面又将继承、实体关系中的引用等在实体的属性中进行展示。逻辑模型介于概念模型和物理模型之间,具有物理模型方面的特性,逻辑模型主要是使得整个概念模型更易于理解,同时又不依赖于具体的数据库实现,使用逻辑模型可以生成针对具体数据库管理系统的物理模型。逻辑模型并不是在整个步骤中必须的,可以直接从概念模型来生成物理模型。

  3. 物理数据模型 (PDM)
    基于特定DBMS,在概念数据模型、逻辑数据模型的基础上进行设计。由物理数据模型生成数据库,或对数据库进行逆向工程得到物理数据模型。

  4. 面向对象模型 (OOM)
    描述业务和操作员之间的关系, 通过UML图进行表示, 比如常见的图形:类图、对象图、包图、用例图、时序图、协作图、交互图、活动图、状态图、组件图、复合结构图、部署图(配置图)。OOM 本质上是软件系统的一个静态的概念模型。

  5. 业务程序模型 (BPM)
    BPM 描述业务的不同内在任务与流程,帮助大家识别、描述和分解业务流程。PM 是从业务合伙人的观点来看业务逻辑和规则的概念模型,使用一个图表描述程序,流程,信息和合作协议之间的交互作用。

  6. 信息流模型(ILM)
    ILM是一个高层的信息流模型,主要用于分布式数据库之间的数据复制。

  7. 企业架构模型(EAM)
    从业务层、应用层以及技术层的对企业的体系架构进行全方面的描述。包括:组织结构图、业务通信图、进程图、城市规划图、应用架构图、面向服务图、技术基础框架图。

3.3 PD如何物理建模
  1. 选择物理模型

在这里插入图片描述

  1. 选择数据库类型为MySQL
    在这里插入图片描述

  2. 新建表
    在这里插入图片描述

  3. 创建表信息
    在这里插入图片描述

    name可以填写为中文,code 为表名称, comment为表注释

  4. 创建表属性
    在这里插入图片描述

    也可双击进行编辑:
    在这里插入图片描述

    填写完成,可以看到会生成对应的SQL信息:
    在这里插入图片描述

  5. 创建索引
    要形成良好习惯, 表建完之后, 考虑哪些是常用的字段, 建立好索引,一般一个表的索引不超过4个。
    在这里插入图片描述

    可以看到SQL的索引语句已经生成:
    在这里插入图片描述

  6. 去除drop index脚本

    drop index 不支持 if exists, 直接运行生成的脚本会出现错误, 这里我们把它删掉。
    在这里插入图片描述

    选择“Edit Current DBMS"

在这里插入图片描述

清空此处脚本语句, 点击确定。
  1. 生成SQL
    在这里插入图片描述

    选择“Generate Database” 或者直接快捷键“Ctrl + G”
    在这里插入图片描述

    如果出现错误, 请将“Check model”的勾选去除。

    出现此提示为成功生成

在这里插入图片描述

  1. 执行脚本

在这里插入图片描述

脚本执行成功, 没有出现错误。

4. 总结

  • PowerDesigner是一款多年来,广泛应用于数据库建模的工具, 学习了解其所能支持的建模类型,掌握基本的建模使用, 帮助将来在实际工作中, 做出合理规范的数据库设计。

第4章 数据库调优

1. 目标

  • 掌握更为深入的数据库调优方法与分析思路。

2. 分析

  • 调优简介
  • 如何调优
  • 数据库执行计划分析

3. 讲解

3.1 调优简介

MySQL是目前使用最多的开源数据库,但是MySQL数据库的默认设置性能并非最优,特别在生产环境当中, 如果不去合理优化,不能发挥最佳性能, 甚至会产生各种各样的问题。 因此数据库必须进行必要的调整与优化。

那么如何进行调优? 有哪些方法? 每个系统环境配置不一样, 如何去设置? 线上出现的慢查询, 又如何去定位分析? 带着这些问题, 我们逐一去分析解决。

3.2 如何调优
  1. 影响数据库性能原因:

在这里插入图片描述

联机分析处理(OLAP,On-line Analytical Processing),数据量大,DML少。

联机事务处理(OLTP,On-line Transaction Processing),数据量少,DML频繁。

在这里插入图片描述

  1. 调优方法

    • 业务设计的合理性

      业务设计并不是满足一切业务需求, 在碰到性能瓶颈之处, 要学会权衡取舍, 比如一些业务数据报表, 不需要很强的实时性, 那么就不用直接查询核心数据表增加压力, 采用预生成方式, 这样既满足业务要求, 又能保持核心数据表的操作性能。

    • 增加索引

      索引可以极大优化查询性能, 索引包含主键索引(PRIMAY KEY)、唯一索引(UNIQUE)、常规索引(INDEX)和全文索引(FULLTEXT)。

      索引底层可以采用Btree索引和Hash索引:

      B-TREE, B树是一种自平衡的树,能够保持数据有序。这种数据结构能够让查找数据、顺序访问、插入数据及删除的动作,都在对数时间内完成。B树为系统大块数据的读写操作做了优化。B树可以减少定位记录时所经历的中间过程,从而加快存取速度。

      B-TREE特征

      B-TREE通常意味着所有的值都是按顺序存储的,并且每一个叶子页到根的距离都相同。

      B-TREE索引能够加快访问数据的速度,是因为存储引擎不再需要进行全表扫描来获取需要的数据,取而代之的是从索引的根节点开始搜索。

      B-TREE对索引列是顺序组织存储的,所以很适合查找范围数据。

      Hash索引

      Hash是采用哈希算法计算,得到哈希值,排序在哈希数组上。所以Hash索引可以一次定位,其效率很高,而Btree索引需要经过多次的磁盘IO,但是Hash索引也存在缺点:

      • 因为Hash索引比较的是经过Hash计算的值,所以只能进行等式比较,不能用于范围查询

      • 由于哈希值是按照顺序排列的,但是哈希值映射的真正数据在哈希表中就不一定按照顺序排列,所以无法利用Hash索引来加速任何排序操作

      • 当哈希值大量重复且数据量非常大时,其检索效率并没有Btree索引高的。

      注意: 索引并不是一味的增加, 一般一张表索引的数量控制4-5个之内。

    • 分库分表

      分库可以分解数据压力, 采用集群方式,或业务拆分, 从根本上提升整个系统的I/O吞吐能力。

      分表在于单表集中压力的化解, 一般可通过垂直/水平方式进行拆分。分表一般都是单维度拆分, 利用某个字段取模或者根据时间字段进行拆分; 对于多维度的拆分,可采用基因法, 基因法是根据某个字段的特征, 再融入到其他维度字段中,利用基因进行数据定位。 例如, 通过uid分库,分为8个库,采用uid%8的方式进行路由,此时是由uid的最后3bit来决定这行User数据具体落到哪个库上,那么这3bit可以看为分库基因。

      分表也并不是万能, 比如关联查询、分页查询、以及数据路由带来的开销。

    • 读写分离

      读写分离,基本的原理是让主数据库处理事务性的增、改、删操作(INSERT、UPDATE、DELETE),而从数据库处理SELECT查询操作。

      在实际的生产环境中,对数据库的读和写都在同一个数据库服务器中,是不能满足实际需求的。无论是在安全性、高可用性还是高并发等各个方面都是完全不能满足实际需求的。
      因此,通过主从复制的方式来同步数据,再通过读写分离来提升数据库的并发负载能力。

    • SQL语句调优

      通过SQL语句的调整来优化响应时间。常见的SQL语句优化点包:

      • 调整Where字句中的连接顺序,一般采用自下而上的顺序解析where字句。
      • 尽量将多条SQL语句压缩到一句, 减少网络开销, IO执行开销。
      • 用where语句替换HAVING语句,因为HAVING只会在检索出所有记录之后才对结果集进行过滤。
      • 使用表的别名,减少属性解析的时间。
      • 在in和exists中通常情况下使用EXISTS,因为in不走索引。
      • 用union all替换union,union会尝试进行合并,然后在输出最终结果前进行排序。
    • mysql配置优化

      • 连接数
      • 缓存
      • 吞吐量
      • IO线程
      • 闲置连接回收
    • 其他优化

      • 索引重建优化
      • 磁盘碎片优化
      • 数据库新特性评估使用,版本升级。
3.3 执行计划分析
  1. 什么是执行计划

    执行计划就是分析执行Sql语句的过程,包括Sql查询的顺序、是否使用索引、以及使用的索引信息等内容。

    一个执行计划由若干基本操作组成. 例如, 遍历整张表, 利用索引, 执行一个嵌套循环或Hash连接等等。

  2. explain关键字与输出项说明

    要生成执行计划, 在相应的语句之前加上explain关键字:

    explain select ...
    

    explain只是预估, 并非实际执行。

    EXPLAIN输出项

    ColumnJSON NameMeaning
    idselect_idThe SELECT identifier(选择标识符)
    select_typeNoneThe SELECT type (查询类型)
    tabletable_nameThe table for the output row (输出结果的表名)
    partitionspartitionsThe matching partitions(匹配的分区信息)
    typeaccess_typeThe join type(表示的连接类型)
    possible_keyspossible_keysThe possible indexes to choose (可能使用到的索引)
    keykeyThe index actually chosen(实际使用的索引)
    key_lenkey_lengthThe length of the chosen key(索引字段的长度)
    refrefThe columns compared to the index(表的连接匹配条件, 哪些列或常量被用于查找索引列上的值)
    rowsrowsEstimate of rows to be examined(扫描估算的行数)
    filteredfilteredPercentage of rows filtered by table condition(按条件过滤的行百分比)
    ExtraNoneAdditional information(执行计划的情况的描述和说明)

    注意:在5.7以前的版本中,想要显示partitions需要使用explain partitions命令;想要显示filtered需要使用explain extended命令。在5.7版本后,默认explain直接显示partitions和filtered中的信息。

  3. 通过实际SQL来解读执行计划信息

    通过脚本,创建好用于SQL执行计划的数据。

    下面分析两条SQL语句的执行计划:

    第一条语句是要查询用户所关联的角色信息, USER_ACCOUNT为唯一索引。

     EXPLAIN SELECT
    	u.id,
    

u.USER_ACCOUNT,
r.ROLE_NAME
FROM
s_authority_user u
LEFT JOIN s_authority_user_role ur ON u.id = ur.USER_ID
LEFT JOIN s_authority_role r ON ur.role_id = r.id
WHERE
u.USER_ACCOUNT = ‘admin’
在这里插入图片描述







第二条语句是一个多SQL查询:

```sql
  EXPLAIN SELECT
  	u1.* 
  FROM
  	s_authority_user u1 UNION
  SELECT
  	u2.* 
  FROM
 	s_authority_user u2

在这里插入图片描述

下面, 详细分析执行计划各字段含义:

  • id属性

    id为SELECT的标识符, 如果这一行表示其他行的union结果,这个值可以为空。在这种情况下,table列会显示为形如<union M,N>,表示它是id为M和N的查询行的联合结果。例如第二条SQL的查询。

    • id相同执行顺序由上至下。
    • id不同,id值越大优先级越高,越先被执行。
    • id为null时表示一个结果集,不需要使用它查询,常出现在包含union等查询语句中。
  • select_type属性

    常见的属性值

    idselect_typedescription
    1SIMPLE不包含任何子查询或union等查询
    2PRIMARY包含子查询最外层查询就显示为 PRIMARY
    3SUBQUERYselectwhere字句中包含的查询
    (in匹配不会生效, 要采用等号)
    4DERIVEDfrom字句中包含的查询
    5UNION出现在union后的查询语句中
    6UNION RESULT从UNION中获取结果集,例如上面的例子

    除此之外还有, derived:from字句中出现的子查询; materialized:被物化的子查询等, 这些用得相对较少。

  • table属性

    显示的查询表名,如果查询使用了别名,那么这里显示的是别名,如果不涉及对数据表的操作,那么这显示为null,如果显示为尖括号括起来的就表示这个是临时表,后边的N就是执行计划中的id,表示结果来自于这个查询产生。如果是尖括号括起来的<union M,N>,与类似,也是一个临时表,表示这个结果来自于union查询的id为M,N的结果集。

    例如:

    EXPLAIN SELECT
    	u.id,
    u.USER_ACCOUNT,
    	r.ROLE_NAME 
    FROM
    	s_authority_user u
    LEFT JOIN (select USER_ID, role_id from s_authority_user_role group by user_id) ur ON u.id = ur.USER_ID
    	LEFT JOIN s_authority_role r ON ur.role_id = r.id 
    WHERE
    	u.USER_ACCOUNT = 'admin'
    	
    
  • partitions属性

    该列显示的为分区表命中的分区情况,非分区表该字段为空(null)。

    例如, 创建一个支持分区的表:

    drop table if exists tmp_partitions;
    create table tmp_partitions (
        id int unsigned not null AUTO_INCREMENT,
        name varchar(255),
        PRIMARY KEY (id)
    ) engine = innodb
    partition by key (id) partitions 3;
    

    查看其执行计划:

    在这里插入图片描述

  • type属性

    表示表查询的连接类型

    idtypedescription
    1ALL扫描全表数据
    2index遍历索引
    3range索引范围查找
    4index_subquery在子查询中使用 ref
    5unique_subquery在子查询中使用 eq_ref
    6UNION ref_or_null对Null进行索引的优化的 ref
    7fulltext使用全文索引
    8ref使用非唯一索引查找数据
    9eq_ref在join查询中使用PRIMARY KEY或UNIQUE NOT NULL索引关联
    10const使用主键或者唯一索引,且匹配的结果只有一条记录
    11system const连接类型的特例,查询的表为系统表
    • 性能依次从好到差:system,const,eq_ref,ref,fulltext,ref_or_null,unique_subquery,index_subquery,range,index_merge,index,ALL。
    • index:索引全表扫描,把索引从头到尾扫一遍,常见于使用索引列就可以处理不需要读取数据文件的查询、可以使用索引排序或者分组的查询。按照官方文档的说法。
    • ref:不像eq_ref那样要求连接顺序,也没有主键和唯一索引的要求,只要使用相等条件检索时就可能出现,常见于辅助索引的等值查找; 或者多列主键、唯一索引中,除第一个列之外的列, 作为等值查找也会出现,总之,返回数据不唯一的等值查找就可能出现。
    • range:索引范围扫描,常见于使用 =, <>, >, >=, <, <=, IS NULL, <=>, BETWEEN, IN()或者like等运算符的查询中。
  • possible_keys属性

    可能使用的索引,注意不一定会使用。查询涉及到的字段上若存在索引,则该索引将被列出来。当该列为 null时就要考虑当前的SQL是否需要优化了。

  • key属性

    显示查询使用到的索引,select_type为index_merge时,这里可能出现两个以上的索引,其他的select_type这里只会出现一个。

  • key_len属性

    索引长度,字符索引长度的计算公式:

    (Character Set:utf8mb4=4,utf8=3,gbk=2,latin1=1) * 列长度 + 1(允许null) + 2(变长列)
    

    key_len只计算where条件用到的索引长度,而排序和分组就算用到了索引,也不会计算到key_len中。

  • ref属性

    如果是使用的常数等值查询,这里会显示const,如果是连接查询,被驱动表的执行计划这里会显示驱动表的关联字段,如果是条件使用了表达式或者函数,或者条件列发生了内部隐式转换,这里可能显示为func。

  • rows属性

    返回估算的结果集数目, 因为explain并非真正执行语句, 所以并非精确值。

  • extra属性
    常见的有:

    • Using index: 使用覆盖索引
    • Using where: 使用了where子句来过滤结果集, 表示存储引擎返回的记录并不是所有的都满足查询条件,需要在server层进行过滤。
    • Using filesort: 使用文件排序,使用非索引列进行排序时出现,非常消耗性能,尽量优化。
    • Using temporary: 表示使用了临时表存储中间结果

    注意: extra辅助信息中的using filesort和using temporary,这两项非常消耗性能,需要谨慎。

4. 总结

  • 除了基本的SQL调优之外, 我们应该还要学会掌握更深入的数据库调优方法, 从数据库配置, 设计层面着手, 发挥更大的数据库性能。
  • 掌握执行计划分析, 能够帮助我们在实际生产环境中, 分析解决各种复杂查询问题。

第5章 数据库设计之交易模块

1. 目标

  • 了解数据库交易模块的设计, 掌握核心表结构。

2. 分析

  • 交易平台设计
  • 交易模块物理模型设计
  • 核心表结构设计

3. 讲解

3.1 交易平台设计

金融交易整个平台主要分为交易模块和非交易(业务为主)模块, 交易模块是整个系统的核心,也是整个数据层的脊骨,那么交易平台如何去设计? 整个设计遵循FIX国际金融标准协议, 借鉴行业内的标杆, 恒生和金证柜台交易系统, 并作扩充改进, 支撑更广泛的金融产品, 如外汇、期货、CFD等交易。

可参考附件《FixApi文档》。

3.2 交易模块物理模型

在这里插入图片描述

订单表、成交记录表和仓位表组成整个交易模块的核心,此设计可以支撑多次成交,仓位合并,订单合并等复杂业务场景。

3.3 主要表设计
  • 用户表:
    属性名称数据类型是否为空注释
    id主键标识bigint(20)NO主键标识
    userNo用户编号varchar(16)NO用户编号
    name用户名称varchar(16)YES用户名称
    userPwd用户密码varchar(16)NO用户密码
    phone电话号码varchar(16)YES电话号码
    companyId公司IDbigint(20)YES公司ID
    email邮箱varchar(32)YES邮箱
    address地址varchar(255)YES地址
    lastLoginIp最近一次用户登陆IPvarchar(32)YES最近一次用户登陆IP
    lastLoginTime最近一次登陆时间datetimeYES最近一次登陆时间
    status状态(0:有效, 1:锁定, 2:禁用)tinyint(3)NO状态(0:有效, 1:锁定, 2:禁用)
    craeteTime创建时间datetimeYES创建时间
    institutionTypeId机构类型idvarchar(32)YES机构类型id
    institutionId机构idbigint(19)YES机构ID

|companyName| 公司名称| varchar(64)| YES| 公司名称|

  • 用户账号表

    属性名称数据类型是否为空注释
    id主键标识bigint(20)NO主键标识

|userId| 用户表ID | bigint(20)| NO| 用户表的ID |
|accountNo| 交易账号| varchar(16)| NO| 交易账号|
|balance| 余额| bigint(21)| YES| 余额|
|tradeGroupId| 账户组ID| bigint(20)| NO| 账户组ID|
|activeTime| 开户时间| datetime| YES| 开户时间|
|status| 状态(0:有效, 1:锁定, 2:禁用)| tinyint(3)| NO| 状态(0:有效, 1:锁定, 2:禁用)|
|companyId| 公司ID| bigint(20)| YES| 公司ID|
|userName| 用户名称| varchar(32)| YES| 用户名称|
|institutionId| 对应机构类型下的机构id| bigint(19)| YES| 对应机构类型下的机构id|
|institutionTypeId| 机构类型id| varchar(32)| YES| 机构类型id|
|tradeGroupName| 账户组名称| varchar(32)| YES| 账户组名称|

  • 用户账户组表

    属性名称数据类型是否为空注释

|id| 主键标识| bigint(20)| NO| 主键标识|
|groupNo| 账户组编号| varchar(16)| NO| 账户组编号|
|groupName| 账户组名称| varchar(16)| NO| 账户组名称|
|currency| 交易币种(CNY:人民币, HKD:港币,USD:美元)| varchar(12)| YES| 交易币种(CNY:人民币, HKD:港币,USD:美元)|
|companyId| 公司ID| bigint(20)| NO| 公司ID|
|commissionRate| 佣金比例, 单位:%| double(4,2)| YES| 佣金比例, 单位:%|
|taxesRate| 税费比例, 单位:%| double(4,2)| YES| 税费比例, 单位:%|
|level| 账户等级(0:普通,1:VIP)| tinyint(3)| YES| 账户等级(0:普通,1:VIP)|
|status| 状态(0:启用, 1:禁用)| tinyint(3)| NO| 状态(0:启用, 1:禁用)|
|craeteTime| 创建时间| datetime| YES| 创建时间|
|companyName| 公司名称| varchar(64)| YES| 公司名称|

  • 订单表

    属性名称数据类型是否为空注释
    id主键标识bigint(20)NO主键标识
    accountId交易账号IDbigint(20)NO交易账号ID
    stockId股票IDbigint(20)NO股票ID
    status状态(0:待成交, 1:部分成交, 2:完全成交, 3:手工撤单, 4:系统撤单)tinyint(3)NO状态(0:待成交, 1:部分成交, 2:完全成交, 3:手工撤单, 4:系统撤单)
    type挂单类型(0:市价单, 1:限价单)tinyint(3)NO挂单类型(0:市价单, 1:限价单)
    direction买卖方向tinyint(2)NO买卖方向
    initVolume挂单的数量int(11)NO挂单的数量
    execVolume成交数量int(11)YES成交数量
    requestPrice请求价格double(8,2)YES请求价格
    execPrice成交价格double(8,2)YES成交价格
    execTime成交时间datetimeYES成交时间
    accountno账户编号varchar(32)YES账户编号
    stockcode股票编号varchar(32)YES股票编号
    stockname股票名称varchar(32)YES股票名称
  • 成交记录表

    属性名称数据类型是否为空注释
    id主键, 唯一标识bigint(20)NO主键, 唯一标识
    tradeOrderId交易订单号bigint(20)NO交易订单号
    positionId持仓记录IDbigint(20)NO持仓记录ID
    accountId交易账号IDbigint(20)NO交易账号ID
    stockId股票IDbigint(20)NO股票ID
    stockMarket股票市场(0: 上交所, 1:深交所,2:港股, 3:美股)tinyint(3)YES股票市场(0: 上交所, 1:深交所,2:港股, 3:美股)
    execVolume成交数量int(11)YES成交数量
    execPrice成交价格double(8,2)YES成交价格
    execTime成交时间datetimeYES成交时间
    direction买卖方向tinyint(2)NO买卖方向
    profit盈亏double(8,2)YES盈亏
    commission佣金double(6,2)YES佣金
    taxes税费double(6,2)YES税费
    status状态(0:有效,1:无效)tinyint(2)NO状态(0:有效,1:无效)
  • 仓位表

    属性名称数据类型是否为空注释
    id主键, 唯一标识bigint(20)NO主键, 唯一标识
    accountId交易账号IDbigint(20)NO交易账号ID
    stockId股票IDbigint(20)NO股票ID
    costPrice成本价double(8,2)YES成本价
    volume持仓数量int(11)NO持仓数量
    stockMarket股票市场(0: 上交所, 1:深交所,2:港股, 3:美股)tinyint(3)YES股票市场(0: 上交所, 1:深交所,2:港股, 3:美股)
    avgPrice成交均价double(6,2)NO成交均价
    validSellVolume可卖数量(T+1制度使用)int(11)YES可卖数量(T+1制度使用)
    profit盈亏double(8,2)YES盈亏
    execTime成交时间datetimeYES成交时间
    status状态(0:有效,1:无效)tinyint(2)NO状态(0:有效,1:无效)
    interest持仓累计利息double(8,2)YES持仓累计利息
  • 股票信息表

    属性名称数据类型是否为空注释
    id主键标识bigint(20)NO主键标识
    name名称varchar(16)YES名称
    code股票编号varchar(12)YES股票编号
    categoryId股票分类IDbigint(20)NO股票分类ID
    marketNo股票市场(0: 上交所, 1:深交所,2:港股, 3:美股)tinyint(3)YES股票市场(0: 上交所, 1:深交所,2:港股, 3:美股)
    currency交易币种(CNY:人民币, HKD:港币,USD:美元)double(12,2)YES交易币种(CNY:人民币, HKD:港币,USD:美元)
    unit股票单位(默认为:100)int(11)NO股票单位(默认为:100)
    tag标签(0:普通, 1:热门)tinyint(3)YES标签(0:普通, 1:热门)
    status状态(0:启用, 1:禁用)tinyint(2)NO状态(0:启用, 1:禁用)
    marketSource行情来源varchar(32)YES行情来源
    display_order显示顺序, 升序排列int(11)YES显示顺序, 升序排列
    updateTime修改时间datetimeYES修改时间
    craeteTime创建时间datetimeYES创建时间
  • 股票分类表

    属性名称数据类型是否为空注释
    id主键标识bigint(20)NO主键标识
    name名称varchar(16)YES名称
    code分类编号varchar(12)YES分类编号
    status状态(0:启用, 1:禁用)tinyint(2)NO状态(0:启用, 1:禁用)
    startMins交易开始时间(分钟)intNO交易开始时间(分钟)
    endMins交易结束时间(分钟)intNO交易结束时间(分钟)
    commissionRate佣金比例(%)intNO佣金比例(%)

4. 总结

                                      | datetime     | YES      | 修改时间                                            |

| craeteTime | 创建时间 | datetime | YES | 创建时间 |

  • 股票分类表

    属性名称数据类型是否为空注释
    id主键标识bigint(20)NO主键标识
    name名称varchar(16)YES名称
    code分类编号varchar(12)YES分类编号
    status状态(0:启用, 1:禁用)tinyint(2)NO状态(0:启用, 1:禁用)
    startMins交易开始时间(分钟)intNO交易开始时间(分钟)
    endMins交易结束时间(分钟)intNO交易结束时间(分钟)
    commissionRate佣金比例(%)intNO佣金比例(%)

4. 总结

  • 交易系统的设计是整个设计的核心部分, 如何拆分, 建立表关联, 既不能太复杂, 也不能过于简单, 可以参考行业的规范, 这是经过多年的累积, 且经历过长期的验证, 能够满足业务功能要求, 同时又具备较强的扩展性, 不仅适用于股票, 还可适用于外汇, 贵金属, 期货等产品, 在报表加工统计方面, 结构层次清晰, 利于呈现。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

管程序猿

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值