目录
1.自我介绍
2.项目介绍
3.购物车模块介绍
*3.1*
*3.2*
*3.3*
*3.4*
*3.5*
*3.6*
*3.7*
*3.8*
*3.9*
4.订单模块介绍
5.秒杀模块介绍
1.秒杀实现
cdn
2.超售怎么解决
SQL优化
字段优化
建立索引优化
SQL查询优化
调优参数
SQL三范式
面试官你好,我叫xx,毕业于xxxxx,我从事java程序开发已经四年了,下面我具体说一下我的项目经验.
从2017年至今,我从事java编程工作已经四年了,前面两年我在长沙微猴科技,2019年来到深圳,在深圳和合联众在微猴做过两个项目,一个汽车站管理系统,一个金力飞人力资源管理系统,在和合联众做了一个课程学习管理平台,和优乐淘购物平台
我从去年10月开始,一直参与这个商城系统的开发,我们这个系统是b2c模式的电商系统,我简单介绍一下我们这个系统的架构,项目是一个分布式架构项目,后台部分用的是springboot,springsvc,mybatis,权限验证是c罗,前端用的是vue+element ui,总共是7个人做这个项目.
购物车与登录模块/订单模块/用户模块/等模块交互
购物车考虑的点很多,比如
我们规定购物车必须要先登录才能使用
经过一个拦截器,根据token判断用户是否登录,如果登录判断Token是否过期,未登录以及过期的提示登陆
账户退出之后,购物车内容必须保存起来(mongoDB)---(直接使用mongo命令连接数据库---创建时,直接使用use [database]命令即可--查看当前数据库:最简单的命令就是db.stats()--直接通过db.xxx.insert(),db.xxx.save()命令来保存数据)
mongodb简介: MongoDB是一个介于关系数据库和非关系数据库之间的产品,是非关系数据库当中功能最丰富,最像关系数据库的。它支持的数据结构非常松散,是类似json的bson格式,因此可以存储比较复杂的数据类型。Mongo最大的特点是它支持的查询语言非常强大,其语法有点类似于面向对象的查询语言,几乎可以实现类似关系数据库单表查询的绝大部分功能,而且还支持对数据建立索引。
购物车页面要能够显示商品的详细信息(商品名称、链接、数量、单价、总价)
商品分spu表和sku表,分别代表公共信息和规格信息,详细信息数据可以从sku里面获取
多条商品能够以列表显示;
用hash数据结构保存,购物车 用每个用户一个大key,小key1保存商品名字+数量,value1保存数值,小key2保存商品名+info,vlue2保存json
然后,抽出各个用户中的小key2,存在一个公共的大key里面,保存的时候用hsetnx指令(不存在就从数据库中拿过去保存,有值就什么都不做)
修改删除功能 : 能够调整商品的数量//移除购物车中的商品;
购物车能够添加50件不同的商品;
如果使用优惠券,结算价格相应调整;
购物车中的商品结算后,从购物车中清除;
限购商品数量不能超过限购数量,购买完成后不能再次添加购物车,不限购的不能超过库存;
交互的模块:登录模块/商品模块/用户模块(加减积分)/支付模块
订单模块维度: 状态机维度(订单状态/支付状态/商品状态/商品支付状态/)----用户信息-----下单时间---支付时间---支付细节
主订单(OrderHeader)的状态枚举有:
处理中/ 成功/ 失败/ 订单取消/ 订单部分取消/ 订单完成/ 订单关闭
1.秒杀时间非常短,并发访问很高,如果和原网站部署在一起可能使原网站瞬间瘫痪,所以我们单独部署秒杀程序,给他分配独立域名.
2.秒杀之前用户为了不错过活动会频繁刷新画面,这对后台系统来说会造成很大的负载,所以数据库集群,redis集群,以及负载均衡之外,秒杀页面要实现静态化,避免后台系统渲染动态页面消耗过多的资源
3.秒杀需要瞬间增加网络带宽,一方面可以在云端租用临时的带宽资源,另一方面可以把秒杀页面放到CDN节点上面,这样就可以节省,秒杀服务器的带宽
4.为了避免秒杀之前用户提前获取秒杀下单前的URL路径,所以需要对URL路径实行动态化,例如在秒杀开始之后,服务端生成特殊规则的随机数,然后把这些随机数保存在redis上,那么秒杀的时候url地址必须带上这样的随机数才能进入到下单画面,避免秒杀还没开始,就提前下单
5.秒杀倒计时也需要特殊的设计,秒杀开始之前CDN节点上面的js文件的功能是先获取北京时间,然后在HTML上生成倒计时时钟,秒杀开始时刷新页面,重新加载js文件.也就是秒杀开始那一刻,后台程序在CDN节点上更新了js文件内容,
秒杀开始的时候,用户浏览器会自动刷新,就能加载到这个最新的js文件,于是js文件生成秒杀按钮,用户点击按钮之后js会向后台发起秒杀请求,秒杀之前任何顾客都没办法发起秒杀请求
6.扣减库存不能采用,付款之后扣减库存,因为这会让用户体验糟糕(秒杀到了,付钱的时候才告诉他没有库存) ,我们会使用,下单就立即扣减库存,这意味着用户下单成功,支付的时候一定有货,不会出现提示没库存的现象....还有就是,如果用户下单之后如果超过10分钟没有支付,自动释放库存
7.除此之外还要防止超售/重复购买,超售用redis分布式锁来解决(下面有)
重复购买可以设定一个分布式计数器.实现如下:
判断key为空设置为1,判断不为空,计数器加一
redisTemplate.boundValueOps(id).increment();定义一个拦截器(实现HandlerInterceptor)
8.(秒杀并发问题怎么解决)秒杀我们除了需要用上了数据库集群/redis集群,还有负载均衡,阿里云CDN加速技术...面对海量的请求还是需要做好限流,以及在后台系统引入消息队列,拦截大量的请求
在CDN网站管理后台,增添域名,等待审核。
审核通过以后,会给你一个CNAME,在阿里云域名管理后台的解析页面,进行解析
用redis分布式锁来解决
加锁实际上就是在redis中,给Key键设置一个值,为避免死锁,并给定一个过期时间。
SET lock_key random_value NX PX 5000
值得注意的是:random_value
是客户端生成的唯一的字符串。NX
代表只在键不存在时,才对键进行设置操作。PX 5000
设置键的过期时间为5000毫秒。
这样,如果上面的命令执行成功,则证明客户端获取到了锁。
1、String
字符串是最常用的数据类型,他能够存储任何类型的字符串,当然也包括二进制、JSON化的对象、甚至是base64编码之后 的图片。在Redis中一个字符串最大的容量为512MB,可以说是无所不能了。
2、Hash
常用作存储结构化数据、比如论坛系统中可以用来存储用户的Id、昵称、头像、积分等信息。如果需要修改其中的信息,只需要通过Key取出Value进行反序列化修改某一项的值,再序列化存储到Redis中,Hash结构存储,由于Hash结构会在单个Hash元素在不足一定数量时进行压缩存储,所以可以大量节约内存。这一点在String结构里是不存在的。
3、List
List的实现为一个双向链表,即可以支持反向查找和遍历,更方便操作,不过带来了部分额外的内存开销,Redis 内部的很多实现,包括发送缓冲队列等也都是用的这个数据结构。另外,可以利用 lrange 命令,做基于 Redis 的分页功能,性能极佳,用户体验好。
4、Set
set 对外提供的功能与 list 类似是一个列表的功能,特殊之处在于 set 是可以自动排重的,当你需要存储一个列表数据,又不希望出现重复数据时,这个时候就可以选择使用set。
5、Sort Set
2、VARCHAR的长度只分配真正需要的空间
3、使用枚举或整数代替字符串类型
4、尽量使用TIMESTAMP而非DATETIME
5、单表不要有太多字段,建议在20以内
6、避免使用NULL字段,很难查询优化且占用额外索引空间
1 根据业务针对性的创建索引
2 值分布很稀少的字段不适合建索引,例如”性别”这种只有两三个值的字段
3 字符字段只建前缀索引
4 字符字段最好不要做主键
5 不用外键,由程序保证约束
6 尽量不用UNIQUE,由程序保证约束
7 使用多列索引时主意顺序和查询条件保持一致,同时删除不必要的单列索引
1、可通过开启慢查询日志来找出较慢的SQL
2、不做列运算:SELECT id WHERE age + 1 = 10,任何对列的操作都将导致表扫描,它包括数据库教程函数、计算表达式等等,查询时要尽可能将操作移至等号右边
3、sql语句尽可能简单点 ,大语句拆小语句,减少锁时间;一条大sql可以堵死整个库
4、不用SELECT *
5、OR改写成IN:OR的效率是n级别,IN的效率是log(n)级别,in的个数建议控制在200以内
6、不用函数和触发器,在应用程序实现
7、避免%xxx式查询
8、少用JOIN
9、使用同类型进行比较,比如用’123’和’123’比,123和123比
10、尽量避免在WHERE子句中使用 != 或 <> 操作符,否则将引擎放弃使用索引而进行全表扫描
11、对于连续数值,使用BETWEEN不用IN:SELECT id FROM t WHERE num BETWEEN 1 AND 5
12、列表数据不要拿全表,要使用LIMIT来分页,每页数量也不要太大
调优工具
sysbench:一个模块化,跨平台以及多线程的性能测试工具
iibench-mysql:基于 Java 的 MySQL/Percona/MariaDB 索引进行插入性能测试工具
tpcc-mysql:Percona开发的TPC-C测试工具
1 max user connection(设置最大连接数)
最大连接数,默认为0无上限,最好设一个合理上限thread_concurrency:并发线程数,设为CPU核数的两倍
2 back_log (MySql的连接数据达到maxconnections,多少个请求可以被存在堆栈中)
backlog值指出在MySQL暂时停止回答新请求之前的短时间内多少个请求可以被存在堆栈中。也就是说,如果MySql的连接数据达到maxconnections时,
新来的请求将会被存在堆栈中,以等待某一连接释放资源,该堆栈的数量即backlog,如果等待连接的数量超过backlog,将不被授予连接资源。可以从默认的50升至500
3.wait_timeout (数据库连接闲置时间)
数据库连接闲置时间,闲置连接会占用内存资源。可以从默认的8小时减到半小时
skipnameresolve
禁止对外部连接进行DNS解析,消除DNS解析时间,但需要所有远程主机用IP访问
keybuffersize
索引块的缓存大小,增加会提升索引处理速度,对MyISAM表性能影响最大。对于内存4G左右,可设为256M或384M,
通过查询show status like’keyread%’,保证keyreads / keyreadrequests在0.1%以下最好
innodbbufferpool_size
缓存数据块和索引块,对InnoDB表性能影响最大。通过查询show status like ‘Innodbbufferpoolread%’,
保证 (Innodbbufferpoolreadrequests – Innodbbufferpoolreads)/ Innodbbufferpoolreadrequests 越高越好
innodbadditionalmempoolsize
InnoDB存储引擎用来存放数据字典信息以及一些内部数据结构的内存空间大小,当数据库对象非常多的时候,适当调整该参数的大小以确保所有数据都能存放在内存中提高访问效率,
当过小的时候,MySQL会记录Warning信息到数据库的错误日志中,这时就需要该调整这个参数大小
innodblogbuffer_size
InnoDB存储引擎的事务日志所使用的缓冲区,一般来说不建议超过32MB
querycachesize
缓存MySQL中的ResultSet,也就是一条SQL语句执行的结果集,所以仅仅只能针对select语句。当某个表的数据有任何任何变化,
都会导致所有引用了该表的select语句在Query Cache中的缓存数据失效。所以,当我们的数据变化非常频繁的情况下,使用Query Cache可能会得不偿失。
根据命中率(Qcachehits/(Qcachehits+Qcache_inserts)*100))进行调整,一般不建议太大,256MB可能已经差不多了,大型的配置型静态数据可适当调大.
可以通过命令show status like 'Qcache_%'查看目前系统Query catch使用大小
readbuffersize
MySql读入缓冲区大小。对表进行顺序扫描的请求将分配一个读入缓冲区,MySql会为它分配一段内存缓冲区。
如果对表的顺序扫描请求非常频繁,可以通过增加该变量值以及内存缓冲区大小提高其性能
sortbuffersize
MySql执行排序使用的缓冲大小。如果想要增加ORDER BY的速度,首先看是否可以让MySQL使用索引而不是额外的排序阶段。
如果不能,可以尝试增加sortbuffersize变量的大小
readrndbuffer_size
MySql的随机读缓冲区大小。当按任意顺序读取行时(例如,按照排序顺序),将分配一个随机读缓存区。进行排序查询时,
MySql会首先扫描一遍该缓冲,以避免磁盘搜索,提高查询速度,如果需要排序大量数据,可适当调高该值。
但MySql会为每个客户连接发放该缓冲空间,所以应尽量适当设置该值,以避免内存开销过大。
record_buffer
每个进行一个顺序扫描的线程为其扫描的每张表分配这个大小的一个缓冲区。如果你做很多顺序扫描,可能想要增加该值
threadcachesize
保存当前没有与连接关联但是准备为后面新的连接服务的线程,可以快速响应连接的线程请求而无需创建新的
table_cache
类似于threadcachesize,但用来缓存表文件,对InnoDB效果不大,主要用于MyISAM
第一范式(1NF)
在任何一个关系数据库中,第一范式(1NF)是对关系模式的基本要求,不满足第一范式(1NF)的数据库就不是关系数据库。
所谓第一范式(1NF)是指数据库表的每一列都是不可分割的基本数据项,同一列中不能有多个值,即实体中的某个属性不能有多个值或者不能有重复的属性。(出现重复的属性,重新定义一个实体,建立一对多的关系)如果出现重复的属性,就可能需要定义一个新的实体,新的实体由重复的属性构成,新实体与原实体之间为一对多关系。在第一范式(1NF)中表的每一行只包含一个实例的信息。
2 第二范式(2NF)
第二范式(2NF)是在第一范式(1NF)的基础上建立起来的,即满足第二范式(2NF)必须先满足第一范式(1NF)。第二范式(2NF)要求数据库表中的每个实例或行必须可以被惟一区分。为实现区分通常需要为表加上一个列,以存储各个实例的惟一标识。
第二范式(2NF)要求实体的属性完全依赖于主关键字。所谓完全依赖是指不能存在仅依赖主关键字一部分的属性,如果存在,那么这个属性和主关键字的这一部分应该分离出来形成一个新的实体,新实体与原实体之间是一对多的关系。为实现区分通常需要为表加上一个列,以存储各个实例的惟一标识。简而言之,第二范式就是非主属性非部分依赖于主关键字。
3 第三范式(3NF)
满足第三范式(3NF)必须先满足第二范式(2NF)。简而言之,第三范式(3NF)要求(在一对多关系里面,除了主键不要再另外存别的信息)一个数据库表中不包含已在其它表中已包含的非主关键字信息。例如,存在一个部门信息表,其中每个部门有部门编号(dept_id)、部门名称、部门简介等信息。那么员工信息表中列出部门编号后就不能再将部门名称、部门简介等与部门有关的信息再加入员工信息表中。如果不存在部门信息表,则根据第三范式(3NF)也应该构建它,否则就会有大量的数据冗余。简而言之,第三范式就是属性不依赖于其它非主属性。