卷王必备学习的MyBatis-Plus用法,不来瞧瞧吗~~
一、快速开始本文基于springboot、maven、jdk1.8、mysql开发,所以开始前我们需要准备好这套环境。新建如下数据库:建议大家选择utf8mb4这种字符集,做过微信的同学应该会知道,微信用户名称的表情,是需要这种字符集才能存储的。我就默认其他环境已经准备好了,咱们直接从mybatis-plus开始。1.1 依赖准备想要什么依赖版本的去maven仓库查看:https://mvnrepository.com/引入mybatis-plus依赖:<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.5.0</version>
</dependency>引入mysql依赖:<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.27</version>
</dependency>目前,多数项目会有多数据源的要求,或者是主从部署的要求,所以我们还需要引入mybatis-plus关于多数据源的依赖:<!-- mybatis-plus 多数据源 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot-starter</artifactId>
<version>3.5.0</version>
</dependency>1.2 配置准备springboot启动类。配置@MapperScan注解,用于扫描Mapper文件位置:import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
@EnableDiscoveryClient
@MapperScan("com.wjbgn.user.mapper")
@SpringBootApplication
public class RobNecessitiesUserApplication {
public static void main(String[] args) {
SpringApplication.run(RobNecessitiesUserApplication.class, args);
}
}数据源配置,此处配置一主一从的环境,当前我只有一台,所以此处配置一样的:spring:
datasource:
dynamic:
primary: master #设置默认的数据源或者数据源组,默认值即为master
strict: false #严格匹配数据源,默认false. true未匹配到指定数据源时抛异常,false使用默认数据源
datasource:
master:
url: jdbc:mysql://127.0.0.1:3306/rob_necessities?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone =Asia/Shanghai
username: root
password: 123456
slave_1:
url: jdbc:mysql://127.0.0.1:3306/rob_necessities?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone =Asia/Shanghai
username: root
password: 123456补充:这里面因为默认使用的是HikariCP数据源,目前也推荐使用这个,相比于druid有更高的性能,但是不能忽略下面的配置,否则服务会不断抛出异常,原因是数据库的连接时常和连接池的配置没有做好。spring:
datasource:
dynamic:
hikari:
max-lifetime: 1800000
connection-timeout: 5000
idle-timeout: 3600000
max-pool-size: 12
min-idle: 4
connection-test-query: /**ping*/1.3 启动服务下面直接启动服务:得到如上结果表示启动成功了。二、使用前面我们成功的集成进来了mybatis-plus,配合springboot使用不要太方便。下面我们看看如何使用它来操作我们的数据库。介绍一下常规的用法。2.1 实体类注解mybatis-plus为使用者封装了很多的注解,方便我们使用,我们首先看下实体类中有哪些注解。有如下的实体类:@TableName(value = "user")
public class UserDO {
/**
* 主键
*/
@TableId(value = "id", type = IdType.AUTO)
private Long id;
/**
* 昵称
*/
@TableField("nickname")
private String nickname;
/**
* 真实姓名
*/
private String realName;
}@TableName 表名注解,用于标识实体类对应的表。其说明如下,关于这些书写,常规情况基本很少用到,不做多余解释了:@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.ANNOTATION_TYPE})
public @interface TableName {
/**
* 实体对应的表名
*/
String value() default "";
/**
* schema
*
* @since 3.1.1
*/
String schema() default "";
/**
* 是否保持使用全局的 tablePrefix 的值
* <p> 只生效于 既设置了全局的 tablePrefix 也设置了上面 {@link #value()} 的值 </p>
* <li> 如果是 false , 全局的 tablePrefix 不生效 </li>
*
* @since 3.1.1
*/
boolean keepGlobalPrefix() default false;
/**
* 实体映射结果集,
* 只生效与 mp 自动注入的 method
*/
String resultMap() default "";
/**
* 是否自动构建 resultMap 并使用,
* 只生效与 mp 自动注入的 method,
* 如果设置 resultMap 则不会进行 resultMap 的自动构建并注入,
* 只适合个别字段 设置了 typeHandler 或 jdbcType 的情况
*
* @since 3.1.2
*/
boolean autoResultMap() default false;
/**
* 需要排除的属性名
*
* @since 3.3.1
*/
String[] excludeProperty() default {};
}@TableId 主键注解,看看其源码:@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.ANNOTATION_TYPE})
public @interface TableId {
/**
* 字段值(驼峰命名方式,该值可无)
*/
String value() default "";
/**
* 主键ID
* {@link IdType}
*/
IdType type() default IdType.NONE;
}其中IdType很重要:名称描述AUTO数据库自增IDNONE该类型为未设置主键类型(注解里等于跟随全局,全局里约等于 INPUT)INPUT用户自己设置的IDASSIGN_ID当用户传入为空时,自动分配类型为Number或String的主键(雪花算法)ASSIGN_UUID当用户传入为空时,自动分配类型为String的主键@TableFiled 表字段标识,下面看看其主要常用属性:名称描述value数据库字段名condition字段 where 实体查询比较条件,通过SqlCondition设置 如果未设置条件,则按照正常相等来查询 若设置则按照以下规则: 等于:EQUAL = "%s=#{%s}"; 不等于:NOT_EQUAL = "%s<>#{%s}"; 左右模糊:LIKE = "%s LIKE CONCAT('%%',#{%s},'%%')"; oracle左右模糊ORACLE_LIKE = "%s LIKE CONCAT(CONCAT('%%',#{%s}),'%%')"; 左模糊:LIKE_LEFT = "%s LIKE CONCAT('%%',#{%s})"; 右模糊:LIKE_RIGHT = "%s LIKE CONCAT(#{%s},'%%')";fill自动填充策略,通过FieldFill设置 不处理:FieldFill.DEFAULT 插入时填充字段:FieldFill.INSERT 更新时填充字段:FieldFill.UPDATE 插入或新增时填充字段:FieldFill.INSERT_UPDATE关于其他的属性,我不太推荐使用,用得越多,越容易蒙圈。可以通过wapper查询去设置。2.2 CRUDmybatis-plus封装好了一条接口供我们直接调用。关于内部的具体方法,在使用时候自己体会吧,此处不列举了。2.2.1 Service层CRUD我们使用的时候,需要在自己定义的service接口当中继承IService接口:import com.baomidou.mybatisplus.extension.service.IService;
import com.wjbgn.user.entity.UserDO;
/**
* @description: 用户服务接口
* @author:weirx
* @date:2022/1/17 15:02
* @version:3.0
*/
public interface IUserService extends IService<UserDO> {
}同时要在我们的接口实现impl当中继承ServiceImpl,实现自己的接口:import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.wjbgn.user.entity.UserDO;
import com.wjbgn.user.mapper.UserMapper;
import com.wjbgn.user.service.IUserService;
/**
* @description: 用户接口实现
* @author:weirx
* @date:2022/1/17 15:03
* @version:3.0
*/
public class UserServiceImpl extends ServiceImpl<UserMapper, UserDO> implements IUserService {
}2.2.2 Mapper层CRUDmybatis-plus将常用的CRUD接口封装成了BaseMapper接口,我们只需要在自己的Mapper中继承它就可以了:/**
* @description: 用户mapper
* @author:weirx
* @date:2022/1/17 14:55
* @version:3.0
*/
@Mapper
public interface UserMapper extends BaseMapper<UserDO> {
}2.3 分页使用分页话需要增加分页插件的配置:import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@MapperScan("com.wjbgn.*.mapper*")
public class MybatisPlusConfig {
@Bean
public MybatisPlusInterceptor mybatisPlusInterceptor() {
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
return interceptor;
}
}如上配置后,我们直接使用分页方法就行。2.4 逻辑删除配置很多情况下我们的系统都需要逻辑删除,方便恢复查找误删除的数据。通过mybatis-plus可以通过全局配置的方式,而不需要再去手动处理。针对更新和查询操作有效,新增不做限制。通常以我的习惯逻辑删除字段通常定义为is_delete,在实体类当中就是isDelete。那么在配置文件中就可以有如下的配置:mybatis-plus:
global-config:
db-config:
logic-delete-field: isDelete # 全局逻辑删除的实体字段名(since 3.3.0,配置后可以忽略不配置步骤2)
logic-delete-value: 1 # 逻辑已删除值(默认为 1)
logic-not-delete-value: 0 # 逻辑未删除值(默认为 0)或者通过注解@TableLogic@TableLogic
private Integer isDelete;2.5 通用枚举配置相信后端的同学都经历过一个情况,比如性别这个字段,分别值和名称对应1男、2女,这个字段在数据库时是数值类型,而前端展示则是展示字符串的名称。有几种常见实现方案呢?数据库查询sql通过case判断,返回名称,以前oracle经常这么做数据库返回的值,重新遍历赋值进去,这时候还需要判断这个值到底是男是女。前端写死,返回1就是男,返回2就是女。相信无论哪种方法都有其缺点,所以我们可以使用mybatis-plus提供的方式。我们在返回给前端时:只需要在遍历时get这个枚举,直接赋值其名称,不需要再次判断。直接返回给前端,让前端去去枚举的name这样大家都不需要写死这个值。下面看看如何实现这个功能:性别枚举,实现IEnum接口:import com.baomidou.mybatisplus.annotation.IEnum;
import com.fasterxml.jackson.annotation.JsonFormat;
/**
* @description: 性别枚举
* @author:weirx
* @date:2022/1/17 16:26
* @version:3.0
*/
@JsonFormat(shape = JsonFormat.Shape.OBJECT)
public enum SexEnum implements IEnum<Integer> {
MAN(1, "男"),
WOMAN(2, "女");
private Integer code;
private String name;
SexEnum(Integer code, String name) {
this.code = code;
this.name = name;
}
@Override
public Integer getValue() {
return code;
}
public String getName() {
return name;
}
}@JsonFormat注解为了解决枚举类返回前端只展示构造器名称的问题。实体类性别字段@TableName(value = "user")
public class UserDO {
/**
* 主键
*/
@TableId(value = "id", type = IdType.AUTO)
private Long id;
/**
* 昵称
*/
@TableField(value = "nickname",condition = SqlCondition.EQUAL)
private String nickname;
/**
* 性别
*/
@TableField(value = "sex")
private SexEnum sex;
/**
* 版本
*/
@TableField(value = "version",update = "%s+1")
private Integer version;
/**
* 时间字段,自动添加
*/
@TableField(value = "create_time",fill = FieldFill.INSERT)
private LocalDateTime createTime;
}配置文件扫描枚举mybatis-plus:
# 支持统配符 * 或者 ; 分割
typeEnumsPackage: com.wjbgn.*.enums定义配置文件@Bean
public MybatisPlusPropertiesCustomizer mybatisPlusPropertiesCustomizer() {
return properties -> {
GlobalConfig globalConfig = properties.getGlobalConfig();
globalConfig.setBanner(false);
MybatisConfiguration configuration = new MybatisConfiguration();
configuration.setDefaultEnumTypeHandler(MybatisEnumTypeHandler.class);
properties.setConfiguration(configuration);
};
}序列化枚举值为数据库值,以下我是使用的fastjson,全局(添加在前面的配置文件中):Bean
public MybatisPlusPropertiesCustomizer mybatisPlusPropertiesCustomizer() {
// 序列化枚举值为数据库存储值
FastJsonConfig config = new FastJsonConfig();
config.setSerializerFeatures(SerializerFeature.WriteEnumUsingToString);
return properties -> {
GlobalConfig globalConfig = properties.getGlobalConfig();
globalConfig.setBanner(false);
MybatisConfiguration configuration = new MybatisConfiguration();
configuration.setDefaultEnumTypeHandler(MybatisEnumTypeHandler.class);
properties.setConfiguration(configuration);
};
}局部JSONField(serialzeFeatures= SerializerFeature.WriteEnumUsingToString)
private SexEnum sex;2.6 自动填充还记得前面提到的实体类当中的注解@TableFeild吗?当中有个属性叫做fill,通过FieldFill设置属性,这个就是做自动填充用的。public enum FieldFill {
/**
* 默认不处理
*/
DEFAULT,
/**
* 插入填充字段
*/
INSERT,
/**
* 更新填充字段
*/
UPDATE,
/**
* 插入和更新填充字段
*/
INSERT_UPDATE
}但是这个直接是不能使用的,需要通过实现mybatis-plus提供的接口,增加如下配置:import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import org.apache.ibatis.reflection.MetaObject;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
/**
* description: 启动自动填充功能
* @return:
* @author: weirx
* @time: 2022/1/17 17:00
*/
@Component
public class MyMetaObjectHandler implements MetaObjectHandler {
@Override
public void insertFill(MetaObject metaObject) {
// 起始版本 3.3.0(推荐使用)
this.strictInsertFill(metaObject, "createTime", LocalDateTime.class, LocalDateTime.now());
}
@Override
public void updateFill(MetaObject metaObject) {
// 起始版本 3.3.0(推荐)
this.strictUpdateFill(metaObject, "updateTime", LocalDateTime.class, LocalDateTime.now());
}
}字段如下:/**
* 时间字段,自动添加
*/
@TableField(value = "create_time",fill = FieldFill.INSERT)
private LocalDateTime createTime;2.7 多数据源前面提到过,配置文件当中配置了主从的方式,其实mybatis-plus还支持更多的方式:多主多从spring:
datasource:
dynamic:
primary: master #设置默认的数据源或者数据源组,默认值即为master
strict: false #严格匹配数据源,默认false. true未匹配到指定数据源时抛异常,false使用默认数据源
datasource:
master_1:
master_2:
slave_1:
slave_2:
slave_3:多种数据库spring:
datasource:
dynamic:
primary: mysql #设置默认的数据源或者数据源组,默认值即为master
strict: false #严格匹配数据源,默认false. true未匹配到指定数据源时抛异常,false使用默认数据源
datasource:
mysql:
oracle:
postgresql:
h2:
sqlserver:混合配置spring:
datasource:
dynamic:
primary: master #设置默认的数据源或者数据源组,默认值即为master
strict: false #严格匹配数据源,默认false. true未匹配到指定数据源时抛异常,false使用默认数据源
datasource:
master_1:
slave_1:
slave_2:
oracle_1:
oracle_2:上面的三种方式,除了混合配置,我觉得都有肯能出现的吧。@DS注解可以注解在方法上或类上,同时存在就近原则 【方法上注解】 优先于 【类上注解】:@DS("slave_1")
public class UserServiceImpl extends ServiceImpl<UserMapper, UserDO> implements IUserService {
@DS("salve_1")
@Override
public List<UserDO> getList() {
return this.getList();
}
@DS("master")
@Override
public int saveUser(UserDO userDO) {
boolean save = this.save(userDO);
if (save){
return 1;
}else{
return 0;
}
}
}三、测试经过上面的配置,下面开始进入测试验证阶段。建立一张表:CREATE TABLE `user` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`nickname` varchar(255) NOT NULL COMMENT '昵称',
`sex` tinyint(1) NOT NULL COMMENT '性别,1男2女',
`create_time` datetime NOT NULL COMMENT '创建时间',
`is_delete` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否删除 1是,0否',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=50 DEFAULT CHARSET=utf8mb4;controller:/**
* @description: 用户controller
* @author:weirx
* @date:2022/1/17 17:39
* @version:3.0
*/
@RestController
@RequestMapping("/user")
public class UserController {
@Autowired
private IUserService userService;
/**
* description: 新增
* @return: boolean
* @author: weirx
* @time: 2022/1/17 19:11
*/
@RequestMapping("/save")
public boolean save() {
UserDO userDO = new UserDO();
userDO.setNickname("大漂亮");
userDO.setSex(SexEnum.MAN);
return userService.save(userDO);
}
/**
* description: 修改
* @param nickname
* @param id
* @return: boolean
* @author: weirx
* @time: 2022/1/17 19:11
*/
@RequestMapping("/update")
public boolean update(@RequestParam String nickname,@RequestParam Long id) {
UserDO userDO = new UserDO();
userDO.setNickname(nickname);
userDO.setId(id);
return userService.updateById(userDO);
}
/**
* description: 删除
* @param id
* @return: boolean
* @author: weirx
* @time: 2022/1/17 19:11
*/
@RequestMapping("/delete")
public boolean delete(@RequestParam Long id) {
UserDO userDO = new UserDO();
userDO.setId(id);
return userService.removeById(userDO);
}
/**
* description: 列表
* @return: java.util.List<com.wjbgn.user.entity.UserDO>
* @author: weirx
* @time: 2022/1/17 19:11
*/
@RequestMapping("/list")
public List<UserDO> list() {
return userService.list();
}
/**
* description: 分页列表
* @param current
* @param size
* @return: com.baomidou.mybatisplus.extension.plugins.pagination.Page
* @author: weirx
* @time: 2022/1/17 19:11
*/
@RequestMapping("/page")
public Page page(@RequestParam int current,@RequestParam int size) {
return userService.page(new Page<>(current,size), new QueryWrapper(new UserDO()));
}
}记过上面的接口验证,功能没有问题,集成成功。
浅谈系统性能提升的经验和方法
一、背景资金核对的数据组装-执行-应急链路,有着千万级TPS并发量,同时由于资金业务特性,对系统可用性和准确性要求非常高;日常开发过程中会遇到各种各样的高可用问题,也在不断地尝试做一些系统设计以及性能优化,在此期间总结了部分性能优化的经验和方法,跟大家一起分享和交流,后续遇到一些新的问题也会持续总结和补充。二、什么是高性能系统先理解一下什么是高性能设计,官方定义: 高可用(High Availability,HA)核心目标是保障业务的连续性,从用户视角来看,业务永远是正常稳定的对外提供服务,业界一般用几个9来衡量系统的可用性。通常采用一系列专门的设计(冗余、去单点等),减少业务的停工时间,从而保持其核心服务的高度可用性。高并发(High Concurrency)通常是指系统能够同时并行处理很多请求。一般用响应时间、并发吞吐量TPS, 并发用户数等指标来衡量。高性能是指程序处理速度非常快,所占内存少,CPU占用率低。高性能的指标经常和高并发的指标紧密相关,想要提高性能,那么就要提高系统发并发能力。本文主要对做“高性能、高并发、高可用”服务的设计进行介绍和分享。三、从哪几个方面做好性能提升每次谈到高性能设计,经常会面临几个名词:IO多路复用、零拷贝、线程池、冗余等等,关于这部分的文章非常的多,其实本质上是一个系统性的问题,可以从计算机体系结构的底层原来去思考,系统优化离不开计算性能(CPU)和存储性能(IO)两个维度,总结如下方法:如何设计高性能计算(CPU)减少计算成本: 代码优化计算的时间复杂度O(N^2)->O(N),合理使用同步/异步、限流减少请求次数等让更多的核参与计算: 多线程代替单线程、集群代替单机等等如何提升系统IO加快IO速度: 顺序读写代替随机读写、硬件上SSD提升等减少IO次数: 索引/分布式计算代替全表扫描、零拷贝减少IO复制次数、DB批量读写、分库分表增加连接数等减少IO存储: 数据过期策略、合理使用内存、缓存、DB等中间件,做好消息压缩等四、高性能优化策略1. 计算性能优化策略1.1 减少程序计算复杂度简单来看这段伪代码(业务代码facade做了脱敏)boolean result = true;
// 循环遍历请求的requests, 判断如果是A业务且A业务未达到终态返回false, 否则返回true
for(Requet request: requests){
// 1. query DB 获取TestDO
String id = request.getId();
TestDO testDO = queryDOById(id);
// 2. 如果是A业务且testDO未到达中态记录为false
if(StringUtils.equals("A", request.getBizType())){
// check是否到达终态
if(!StringUtils.equals("FINISHED", testDO.getStatus)){
result = result && false;
}
}
}
return result;代码中存在很明显的几个问题:每次请求过来在第6行都去查询DB,但是在第8行对请求做了判断和筛选,导致第6行的代码计算资源浪费,而且第6行访问DAO数据,是一个比较耗时的操作,可以先判断业务是否属于A再去查询DB当前的需求是只要有一个A业务未到达终态即可返回false, 11行可以在拿到false之后,直接break,减少计算次数优化后的代码boolean result = true;
// 循环遍历请求的requests, 判断如果是A业务且A业务未达到终态返回false, 否则返回true
for(Requet request: requests){
// 1. 不是A业务的不走查询DB的逻辑
if(!StringUtils.equals("A", request.getBizType())){
continue;
}
// 2. query DB 获取TestDO
String id = request.getId();
TestDO testDO = queryDOById(id);
// check是否到达终态
if(!StringUtils.equals("FINISHED", testDO.getStatus)){
result = false;
break;
}
}
return result;优化之后的计算耗时从平均270.75ms-->40.5ms日常优化代码可以用ARTHAS工具分析下程序的调用耗时,耗时大的任务尽可能做好过滤,减少不必要的系统调用。1.2 合理使用同步异步分析业务链路中,哪些需要同步等待结果,哪些不需要,核心依赖的调度可以同步,非核心依赖尽量异步场景:从链路上看A系统调用B系统,B系统调用C系统完成计算再把结论返回给A,A系统超时时间400ms,通常A系统调用B系统300ms,B系统调用C系统200ms现在C系统需要将调用结论返回给D系统,耗时150ms此时A系统- B系统- C系统已有的调用链路可能会超时失败,因为引入D系统之后,耗时增加了150ms,整个过程是同步调用的,因此需要C系统将调用D系统更新结论的非强依赖改成异步调用// C系统调用D系统更新结果
featureThreadPool.execute(()->{
try{
dSystemClient.updateResult(resultDTO);
}catch (Exception exception){
LogUtil.error(exception, logger, "dSystemClient.updateResult failed! resultDTO = {0}", JSON.toJSONString(resultDTO));
}
});1.3 做好限流保护故障场景:A系统调用B系统查询异常数据,日常10TPS左右甚至更少,某一天A系统改了定时任务触发逻辑,加上代码bug,调用频率达到了500TPS,并且由于ID传错,绕过了缓存直接查询了DB和Hbase, 造成了Hbase读热点,拖垮集群,存储和查询都受到了影响后续对A系统做了查询限流,保证并发量在15TPS以内,核心业务服务需要做好查询限流保护,同时也要做好缓存设计。1.4 多线程代替单线程场景: 应急定位场景下,A系统调用B系统获取诊断结论,TR超时时间是500ms,对于一个异常ID事件,需要执行多个诊断项服务,并记录诊断流水;每个诊断的耗时大概在100ms以内,随着业务的增长,超过5个诊断项,计算耗时累加到500ms+,这时候服务会出现高峰期短暂不可用。将这段代码改成异步执行,这样执行诊断的时间是耗时最大的诊断服务// 提交future任务并发执行
futures = executor.invokeAll(tasks, timeout, timeUnit);
// 遍历读取结果
for (Future<Res> future : futures) {
try {
// 获取结果
Res singleResult = future.get();
if (singleResult != null) {
result.add(singleResult);
}
} catch (Exception e) {
LogUtil.error(e, logger, "并发执行发生异常!,poolName={0}.", threadPoolName);
}
}1.5 集群计算代替单机这里可以使用三层分发,将计算任务分片后执行,Map-Reduce思想,减少单机的计算压力。2. 系统IO性能优化策略2.1 常见的FullGC解决系统常见的FullGC问题有很多,先讲一下JVM的垃圾回收机制: Heap区在设计上是分代设计的, 划分为了Eden、Survivor 和 Tenured/Old ,其中Eden区、Survivor(存活)属于年轻代,Tenured/Old区属于老年代或者持久代。一般我们将年轻代发生的GC称为Minor GC,对老年代进行GC称为Major GC,FullGC是对整个堆来说。内存分配策略:1. 对象优先在Eden区分配 2. 大对象直接进入老年代 3. 长期存活的对象将进入老年代4. 动态对象年龄判定(虚拟机并不会永远地要求对象的年龄都必须达到MaxTenuringThreshold才能晋升老年代,如果Survivor空间中相同年龄的所有对象的大小总和大于Survivor的一半,年龄大于或等于该年龄的对象就可以直接进入老年代)5. 只要老年代的连续空间大于(新生代所有对象的总大小或者历次晋升的平均大小)就会进行minor GC,否则会进行full GC。系统常见触发FullGC的case:(1)查询大对象:业务上历史巡检数据需要定期清理,删除策略是每天删除上个月之前的数据(业务上打上软删除标记),等数据库定时清理任务彻底回收;某一天修改了删除策略,从“删除上个月之前的数据”改成了“删除上周之前的数据”,因此删除的数据从1000条膨胀到了15万条,数据对象占用了80%以上的内存,直接导致系统的FullGC, 其他任务都有影响;很多系统代码对于查询数据没有数量限制,随着业务的不断增长,系统容量在不升级的情况下,经常会查询出来很多大的对象List,出现大对象频繁GC的情况(2)设置了用不回收的static方法A系统设置了static的List对象,本身是用来做DRM配置读取的,但是有个逻辑对配置信息做了查询之后,还进行了Put操作,导致随着业务的增长,static对象越来越大且属于类对象,无法回收,最终使得系统频繁GC本身用Object做Map的Key有一定的不合理性,同时key中的对象是不可回收的,导致出现了GC。当执行Full GC后空间仍然不足,则抛出如下错误【java.lang.OutOfMemoryError: Java heap space】,而为避免以上两种状况引起的Full GC,调优时应尽量做到让对象在Minor GC阶段被回收、让对象在新生代多存活一段时间及不要创建过大的对象及数组。2.2 顺序读写代替随机读写对于普通的机械硬盘而言,随机写入的性能会很差,时间久了还会出现碎片,顺序的写入会极大节省磁盘寻址及磁盘盘片旋转的时间,极大提升性能;这层其实本身中间件帮我们实现了,比如Kafka的日志文件存储消息,就是通过有序写入消息和不可变性,消息追加到文件的末尾,来保证高性能读写。2.3 DB索引设计设计表结构时,我们要考虑后期对表数据的查询操作,设计合理的索引结构,一旦表索引建立好了之后,也要注意后续的查询操作,避免索引失效。(1) 尽量不选择键值较少的列即区分度不明显,重复数据很少的做索引;比如我们用is_delete这种列做了索引,查询10万条数据,where is_delete=0,有9万条数据块,加上访问索引块带来的开销,不如全表扫描全部的数据块了; (2)避免使用前导like "%***"以及like "%***%", 因为前面的匹配是模糊的,很难利用索引的顺序去访问数据块,导致全表扫描;但是使用like "A**%"不影响,因为遇到"B"开头的数据就可以停止查找列,我们在做根据用户信息模糊查询数据时,遇到了索引失效的情况 (3) 其他可能的场景比如,or查询,多列索引不使用第一部分查询,查询条件中有计算操作,或者全表扫描比索引查询更快的情况下也会出现索引失效 目前AntMonitor以及Tars等工具已经帮我们扫描出来耗时和耗CPU很大的SQL,可以根据执行计划调整查询逻辑,频繁的少量数据查询利用好索引,当然建立过多的索引也有存储开销,对于插入和删除很频繁的业务,也要考虑减少不必要的索引设计。2.4 分库分表设计随着业务的增长,如果集群中的节点数量过多,最终会达到数据库的连接限制,导致集群中的节点数量受限于数据库连接数,集群节点无法持续增加和扩容,无法应对业务流量的持续增长;这也是蚂蚁做LDC架构的其中原因之一,在业务层做水平拆分和扩展,使得每个单元的节点只访问当前节点对应的数据库。2.5 避免大量的表JOIN阿里编码规约中超过三个表禁止JOIN,因为三个表进行笛卡尔积计算会出现操作复杂度呈几何数增长,多个表JOIN时要确保被关联的字段有索引。如果为了业务上某些数据的级联,可以适当根据主键在内存中做嵌套的查询和计算,操作非常频繁的流水表建议对部分字段做冗余,以空间复杂度换取时间复杂度。2.6 减少业务流水表大量耗时计算业务记录有时候会做一些count操作,如果对时效性要求不高的统计和计算,建议定时任务在业务低峰期做好计算,然后将计算结果保存在缓存。涉及到多个表JOIN的建议采用离线表进行Map-Reduce计算,然后再将计算结果回流到线上表进行展示。2.5 数据过期策略一张表的数据量太大的情况下,如果不按照索引和日期进行部分扫描而出现全表扫描的情况,对DB的查询性能是非常有影响的,建议合理的设计数据过期策略,历史数据定期放入history表,或者备份到离线表中,减少线上大量数据的存储。2.6 合理使用内存众所周知,关系型数据库DB查询底层是磁盘存储,计算速度低于内存缓存,缓存DB与业务系统连接有一定的调用耗时,速度低于本地内存;但是从存储量来看,内存存储数据容量低于缓存,长期持久化的数据建议放DB存在磁盘中,设计过程中考虑好成本和查询性能的平衡。说到内存,就会有数据一致性问题,DB数据和内存数据如何保证一致性,是强一致性还是弱一致性,数据存储顺序和事务如何控制都需要去考虑,尽量做到用户无感知。2.7 做好数据压缩很多中间件对数据的存储和传输采用了压缩和解压操作,减少数据传输中的带宽成本,这里对数据压缩不再做过多的介绍,想提的一点是高并发的运行态业务,要合理的控制日志的打印,不能够为了便于排查,打印过多的JSON.toJSONString(Object),磁盘很容易被打满,按照日志的容量过期策略也很容易被回收,更不方便排查问题;因此建议合理的使用日志,错误码仅可能精简,核心业务逻辑打印好摘要日志,结构化的数据也便于后续做监控和数据分析。打印日志的时候思考几个问题:这个日志有没有可能会有人看,看了这个日志能做什么,每个字段都是必须打印的吗,出现问题能不能提高排查效率。2.8 Hbase热点key问题HBase是一个高可靠、高性能、面向列、可伸缩的分布式存储系统,是一种非关系数据库,Hbase存储特点如下:列的可以动态增加,并且列为空就不存储数据,节省存储空间。HBase自动切分数据,使得数据存储自动具有水平scalability。HBase可以提供高并发读写操作的支持,分布式架构,读写锁等待的概率大大降低。不能支持条件查询,只支持按照Rowkey来查询。暂时不能支持Master server的故障切换,当Master宕机后,整个存储系统就会挂掉。Habse的存储结构如下:Table在行的方向上分割为多个HRegion,HRegion是HBase中分布式存储和负载均衡的最小单元,即不同的HRegion可以分别在不同的HRegionServer上,但同一个HRegion是不会拆分到多个HRegionServer上的。HRegion按大小分割,每个表一般只有一个HRegion,随着数据不断插入表,HRegion不断增大,当HRegion的某个列簇达到一个阈值(默认256M)时就会分成两个新的HRegion。HBase 中的行是按照 Rowkey 的字典顺序排序的,这种设计优化了 scan 操作,可以将相关的行以及会被一起读取的行存取在临近位置,便于scan。Rowkey这种固有的设计是热点故障的源头。 热点的热是指发生在大量的 client 直接访问集群的一个或极少数个节点(访问可能是读,写或者其他操作)。大量访问会使热点 Region 所在的单个机器超出自身承受能力,引起性能下降甚至 Region 不可用,这也会影响同一个 RegionServer 上的其他 Region,由于主机无法服务其他 Region 的请求,这样就造成数据热点(数据倾斜)现象。所以我们在向 HBase 中插入数据的时候,应优化 RowKey 的设计,使数据被写入集群的多个 region,而不是一个,尽量均衡地把记录分散到不同的 Region 中去,平衡每个 Region 的压力。常见的热点Key避免的方法: 反转,加盐和哈希反转:比如用户ID2088这种前缀,以及BBCRL开头的这种相同前缀,都可以适当的反转往后移动。加盐: RowKey 的前面增加一些前缀,比如时间戳Hash,加盐的前缀种类越多,才会根据随机生成的前缀分散到各个 region 中,避免了热点现象,但是也要考虑scan方便哈希:为了在业务上能够完整地重构 RowKey,前缀不可以是随机的。 所以一般会拿原 RowKey 或其一部分计算 Hash 值,然后再对 Hash 值做运算作为前缀。总之Rowkey在设计的过程中,尽量保证长度原则、唯一原则、排序原则、散列原则。五、实战-应急链路系统设计方案要保证整体服务的高可用,需要从全链路视角去看待高可用系统的设计,这里简单的分享一个上游多个系统调用异常处理系统执行应急的业务场景,分析其中的性能优化改造。以资金应急系统为例分析系统设计过程中的性能优化。如下图所示,异常处理系统涉及到多个上游App(1-N),这些App发“差异日志数据”给到消息队列, 异常处理系统订阅并消费消息队列中的“错误日志数据”,然后对这部分数据进行解析、加工聚合等操作,完成异常的发送及应急处理。发送阶段高可用设计生产消息阶段:本地队列缓存异常明细数据,守护线程定时拉取并批量发送(优化方案1中单条上报的性能问题)消息压缩发送:异常规则复用用一份组装的模型,按照规则则Code聚合压缩上报(优化业务层数据压缩复用能力)中间件帮你做好了消息的高效序列化机制以及发送的零拷贝技术存储阶段目前Kafka等中间件,采用IO多路复用+磁盘顺序写数据的机制,保证IO性能同时采用分区分段存储机制,提升存储性能消费阶段定时拉取一段数据批量处理,处理之后上报消费位点,继续计算内部好做数据的幂等控制,发布过程中的抖动或者单机故障保证数据的不重复计算为了提升DB的count性能,先用Hbase对异常数量做好累加,然后定时线程获取数据批量update为了提升DB的配置查询性能,首次查询配置放入本地内存存储20分钟,数据更新之后内存失效对于统计类的计算采用explorer存储,对于非结构化的异常明细采用Hbase存储,对于结构化且可靠性要求高的异常数据采用OB存储然后对系统的性能做好压测和容量评估,演练数据是异常数据的3-5倍做好流量隔离,对管道进行拆分,消费链路的线程池做好隔离对于单点的计算模块做好冗余和故障转移, 采取限流等措施限流能力,上报端采用开关控制限流和熔断故障转移能力对于系统内部可以提升的地方,可以参考高可用性能优化策略去逐个突破六、高性能设计总结1. 架构设计1.1 冗余能力做好集群的三副本甚至五副本的主动复制,保证全部数据冗余成功场景,任务才可以继续执行,如果对可用性要求很高,可以降低副本数以及任务的提交一执行约束。冗余很容易理解,如果一个系统的可用性为90%,两台机器的可用性为1-0.1*0.1=99%,机器越多,可用性会更高;对于DB这种对连接数有瓶颈的,我们需要在业务上做好分库分表也是一种冗余的水平扩展能力。1.2 故障转移能力部分业务场景对于DB的依赖性很高,在DB不可用的情况下,能不能转移到FO库或者先中断现场,保存上下文,对当前的业务场景上下文写入延迟队列,等故障恢复后再对数据进行消费和计算。有些不可抗力和第三方问题,可能会严重影响整个业务的可用性,因此要做好异地多话,冗余灾备以及定期演练。1.3 系统资源隔离性在异常处理的case中,经常会因为上游数据的大量上报导致队列阻塞,影响时效性,因此可以做好核心业务和非核心业务资源隔离,对于秒杀类的场景甚至可以单独部署独立的集群支撑业务。如果A系统可用性90%,B系统的可用性40%,A系统某服务强依赖B系统,那么A系统的可用性为P(A|B), 可用性大大降低。2. 事前防御2.1 做好监控对系统的CPU,线程CE、IO、服务调用TPS、DB计算耗时等设置合理的监控阈值,发现问题及时应急2.2 做好限流/熔断/降级等上游业务流量突增的场景,需要有一定的自我保护和熔断机制,前提是避免业务的强依赖,解决单点问题,在异常消费链路中,对上游做了DRM管控,下游也有一定的快速泄洪能力,防止因为单业务异常拖垮整个集群导致不可用。瞬间流量问题很容易引发故障,一定要做好压测和熔断能力,秒杀类的业务减少对核心系统的强依赖,提前做好预案管控,对于缓存的雪崩等也要有一定的预热和保护机制。同时有些业务开放了不合理的接口,采用爬虫等大量请求web接口,也要有识别和熔断的能力2.3 提升代码质量核心业务在大促期间做好封网、资金安全提前部署核对主动验证代码的可靠性,编码符合规范等等,都是避免线上问题的防御措施;代码的FullGC, 内存泄漏都会引发系统的不可用,尤其是业务低峰期可能不明显,业务流量高的情况下性能会恶化,提前做好压测和代码Review。3. 事后防御和恢复事前做好可监控和可灰度,事后做好任何场景下的故障可回滚。其他关于防御能力的还有:部署过程中如何做好代码的平滑发布,问题代码机器如何快速地摘流量;上下游系统调用的发布,如何保证依赖顺序;发布过程中,正常的业务已经在发布过的代码中执行,逆向操作在未发布的机器中执行,如何保证业务一致性,都要有充分的考虑。
命中率超高的题,建议收藏
github:https://github.com/doukoi-BDB文章底部有【技术社群&福利】,不定更新活动、源码,欢迎来撩~~~今日主题:22年考虑了一下后续公号技术文章的风格&技术内容,思考如何更好有效帮助到关注我的各位铁友们;后续的文章思考方式会不同,会考虑每一篇的价值。既然又到了面试的季节,那么面试前准备的资料这是必不可少。这项资料必须得掌握住了。今天分享一波22年php面试问题,答案不会写的那么详细,如有需要可以自查。文章内容全部梳理最新面试的一个总结。2022年 php面试之60题1、什么变量是存储在堆/栈?A:基本类型保存在栈中,引用类型保存到堆(细节自查)2、PHP中HashMap的结构是如何实现?A:HashMap是数组结构、链表结构与Hash算法的结合(细节自查)3、如何解决PHP内存溢出问题?A:1)增加内存大小;2)销毁变量释放内存。(细节自查)注意事项:unset 函数只能在变量值占用内存空间超过256字节时才会释放内存空间。4、PHP的字符串为什么是二进制安全的?A:ascii字符(细节自查)5、PHP的内存回收机制?A:5.3版本新的内存回收机制的出现,机制的三个基本规则:1)如果一个zval容器的refcount增加,说明有新的变量(符号)指向这个容器,那么这个容器当然不会是垃圾,它将被继续使用。2)如果一个zval容器的refcount减少到0了,那么说明没有变量(符号)指向这个容器,它就会被php引擎销毁。3)如果一个zval容易的refcount减少了,但是不是0,那么这个容器就有可能是垃圾,就会被垃圾回收机制所管理。6、PHP7中对zVal做了哪些修改?A:1)refcount的存放换了个位置,从zval全局换到了zend.value自身中。优点在于能更快的来做+1-1的操作;2)字节数减少了;3)PHP7把部分变量(局部变量,对象的键名)存放在栈中;4)PHP7标量数据类型(布尔,整形,字符串,浮点型)不再计数,不需要单独分配内存。7、设计模式场景及介绍A:工厂模式、建造者模式、单例模式、策略模式(细节自查)8、php的反射A:反射 API 提供了方法来取出函数、类和方法中的文档注释。(细节自查)9、自动加载实现方式A:自动加载的原理以及__autoload的使用(细节自查)10、PHP中创建多进程有哪些方式?A:pcntl_fork(子进程) — 在当前进程当前位置产生分支。(细节自查)11、Swoole 服务端启动后有哪些进程,完成什么工作?A:启动的这个服务使用了 8 个进程、2 个线程;(细节自查)16389 是 Master 进程。16390 是 Manager 进程。16391、16392 是 Reactor 线程。16393、16394、16395、16396、16397、16398 包括 3 个 Worker 进程,3 个 Task 进程。12、MySQL的查询需要遍历几次B+树,理论上需要几次磁盘I/O?A:主键索引从上至下遍历一次B+树,二级索引需要遍历两次B+树(细节自查)13、sql语句执行过程A:连接器-》查询缓存-〉分析器-》优化器-〉执行器14、字段为varchar类型,where num=111 能否用到索引A:表中字段为字符类型的时候,查询的值为整型时,无法走索引;15、mysql索引失效情况A:like 以%开头,索引无效;组合索引,不是使用第一列索引,索引失效;当or左右查询字段只有一个是索引,该索引失效(细节自查)16、mysql回表A:回表就是通过辅助索引拿到主键id之后,要再去遍历聚集索引的B+树,这个过程就是回表17、事务隔离级别A:4个级别,未提交读(Read Uncommitted)、提交读(Read Committed)、可重复读(Repeated Read)、序列化(Serializable18、mysql 集群的几种方式A:LVS+Keepalived+MySQL ,目前很多种,建议使用这种,相对案例多,坑少(细节自查)19、聚簇索引和非聚簇索引的区别A:聚簇索引并不是一种单独的索引类型,而是一种数据存储方式。具体细节依赖于其实现方式。(细节自查)20、事务的特性A:ACID(细节自查)21、mysql查询慢原因,优化建议A:1)查询慢 (细节自查):1-1 :没有用到索引;1-2:I/O吞吐量小,形成了瓶颈效应1-3:网络速度慢 1-4:查询出的数据量过大1-5:出现死锁2)优化建议(细节自查):2-1: 把数据、日志、索引放到不同的I/O设备上,增加读取速度,以前可以将Tempdb应放在RAID0上,SQL2000不在支持。数据量(尺寸)越大,提高I/O越重要2-2:纵向、横向分割表,减少表的尺寸(sp_spaceuse) 2-3:提升网速2-4:增加服务器CPU个数22、事务a嵌套事务b,会发生什么A:rollback回滚无效(细节自查)23、redis数据类型哪些?你用于的业务场景是?A:string(字符串),hash(哈希),list(列表),set(集合)及zset(sorted set:有序集合)。24、redis的IO模型A:基于多路复用(事件轮询)、非阻塞。(细节自查)25、redis的协议A:RESP (REdis Serialization Protocol)协议进行通讯26、redis的管道A:Redis 管道技术可以在服务端未响应时,客户端可以继续向服务端发送请求,并最终一次性读取所有服务端的响应。(细节自查)27、持久化策略哪些?怎么实现的持久化?A:rdb、aof ,自动执行&手动执行(细节自查)28、淘汰策略A:1)当内存不足以容纳新写入数据时,新写入操作会报错。2)当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的 key(这个是最常用的)。3)当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,随机移除某个 key。4)当内存不足以容纳新写入数据时,在设置了过期时间的键空间中,有更早过期时间的 key 优先移除。(细节自查)29、rabbitmq 如何保证消息不丢失A:1)消息持久化;2)ACK确认机制;3)设置集群镜像模式;4)消息补偿机制(细节自查)30、rabbitmq 如何保证消息的顺序性A: 搞3个Queue,每个消费者就消费其中的一个Queue。把需要保证顺序的数据发到1个Queue里去。31、rabbitmq 的心跳丢失A:分布式的tcp连接采取适中的时间(比如Linux默认配置大约11分钟),方便操作系统检测。32、Nginx中root和alias的区别A:1)使用alias时,目录名后面一定要加"/"。2)alias可以指定任何名称。33、Nginx正向代理和反向代理A:1)正向代理是一个位于客户端和原始服务器之间的服务器,为了从原始服务器取得内容,客户端向代理发送一个请求并指定目标(原始服务器),然后代理向原始服务器转交请求并将获得的内容返回给客户端。2)反向代理实际运行方式是代理服务器接受网络上的连接请求。它将请求转发给内部网络上的服务器,并将从服务器上得到的结果返回给网络上请求连接的客户端,此时代理服务器对外就表现为一个服务器。可以这么认为,对于正向代理,代理服务器和客户端处于同一个局域网内;而反向代理,代理服务器和源站则处于同一个局域网内。34、nginx负载均衡的5种策略A:轮询(默认)、指定权重、IP绑定 ip_hash、fair(第三方)、url_hash(第三方)35、nginx怎么实现反向代理用做内网域名转发A:指定内网ip+端口(细节自查)36、PHP中密码加密,使用什么方式加密?A:MD5加密、Crype加密、Sha1加密、URL加密37、对称加密和非对称加密A:对称加密是最快速、最简单的一种加密方式,加密(encryption)与解密(decryption)用的是同样的密钥(secret key)。38、mysql存储引擎 有哪些?区别点?A:InnoDB、MyISAM、MERGE、ARCHIVE、CSV、BLACKHOLE39、mysql使用行锁的条件A:主键索引需要加一把锁,使用二级索引需要在二级索引和主键索引上各加一把锁(细节自查)40、sql优化点A:数据库方面、sql 语句方面(细节自查)41、MVCC 介绍A:MVCC是一种多版本并发控制机制。(细节自查)42、分布式锁 介绍A:根据redis缓存实现 分布式锁 - Setnx(细节自查)43、fast-cgi,cgi 是什么?A:CGI是为了保证web server传递过来的数据是标准格式的,Fastcgi是用来提高CGI程序性能的。44、http 状态码,1、2、3、4、5 开头分别代表什么?A:1 开头:这一类型的状态码,代表请求已被接受,需要继续处理.这类响应是临时响应,使包含状态码行和某些可选的响应头信息,并以空行结束.2 开头:的状态码,请求以成功被接受,理解 3 开头:需要用户端采取进一步的操作才能完成请求.通常,这些状态码用来重定向,后续的请求地址在本,次响应的location域中指明.4 开头:语义有误,当前请求无法被服务器理解,除非进行修改,否则客服端不应该重复提交这个请求.5 开头:代表了服务器在处理请求的过程中有错误或者异常状态发生,也有可能是服务器意识到以的软硬件资源无法完成对请求的处理,除非这是一个HEAD请求,是服务器应当包含一个解释当前错误状态以及这个善是临时的还是永久的解释 信息实体.浏览器应当向用户展示任何在当前响应中被被包含的实体45、mysql主从延迟解决方案A:主库针对写操作,顺序写binlog,从库单线程去主库顺序读”写操作的binlog”,从库取到binlog在本地原样执行(随机写),来保证主从数据逻辑上一致46、redis缓存击穿、缓存穿透A:缓存穿透:key对应的数据在数据源并不存在,每次针对此key的请求从缓存获取不到,请求都会到数据源,从而可能压垮数据源。比如用一个不存在的用户id获取用户信息,不论缓存还是数据库都没有,若黑客利用此漏洞进行攻击可能压垮数据库。缓存击穿:key对应的数据存在,但在redis中过期,此时若有大量并发请求过来,这些请求发现缓存过期一般都会从后端DB加载数据并回设到缓存,这个时候大并发的请求可能会瞬间把后端DB压垮。47、熔断 介绍A:“熔断”就是为了避免”雪崩”而生的,它的思路是在调用方增加一种”避让”机制,当下游出现异常时能够停止(熔断)对下游的继续请求,当等待一段时间后缓慢放行部分的调用流量,并当这部分流量依旧正常的情况下,彻底解除”熔断”状态。48、三次握手A:第一次握手:客户端发送syn包(syn=j)到服务器,并进入SYN_SEND状态,等待服务器确认;第二次握手:服务器收到syn包,必须确认客户的syn(ack=j+1),同时自己也发送一个SYN包(syn=k),即SYN+ACK包,此时服务器进入SYN_RECV状态;第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED状态,完成三次握手。49、一次完整的http 请求过程A:1. 用户在浏览器地址栏中输入网站域名 2. 浏览器拿到该域名自动去请求 DNS服务器查询 用户输入的域名对应的 ip 地址 3. 浏览器拿到 ip 地址之后,通过ip地址+端口号(HTTP默认80)和服务器建立连接(通过 三次握手 ) 4. 三次握手建立连接成功之后 5. 浏览器将用户输入的 url 地址通过 HTTP 协议包装成 请求报文 ,然后通过 Socket(服务器ip地址和端口号) 发送到服务器 6. 当HTTP服务器接收到客户端浏览器发送过来的请求报文时候,按照 HTTP 协议将请求报文解析出来 7. 然后服务器拿到请求报文中的请求信息(例如请求路径url),做相应的业务逻辑处理操作 8. 当业务逻辑处理完毕之后,服务器将要发送给客户端的数据按照 HTTP 协议包装成 响应报文 9. 然后服务器通过 Socket(客户端的ip地址+端口号) 将响应报文数据发送给客户端浏览器 10. 当浏览器接收到服务器发送给自己的响应报文数据的时候,浏览器根据 HTTP 协议将报文内容解析出来 11. 浏览器拿到响应报文体中的数据开始 解析渲染html、css,执行 JavaScript 12. 如果在解析的过程(从上到下)中,发现有外链的标签(link、css、img) 13. 浏览器会自动对该标签指向的 路径地址 发起新的请求(还是通过 Socket )。50、Session 共享A:基于Cookie的Session共享、基于数据库的Session共享51、InnoDB引擎的4大特性有哪些A:插入缓冲、二次写、自适应哈希、预读52、非聚簇索引一定会回表查询吗?A:不一定,这涉及到查询语句所要求的字段是否全部命中了索引,如果全部命中了索引,那么不需要进行回表查询。53、mysql 碎片是如何产生的?如何解决?A:物理删除数据导致,数据也占用一定物理空间,解决方式根据存储引擎,写法也是不同54、数据库的乐观锁和悲观锁A:悲观锁,是因为这是一种对数据的修改抱有悲观态度的并发控制方式。我们一般认为数据被并发修改的概率比较大,所以需要在修改之前先加锁。悲观并发控制实际上是“先取锁再访问”的保守策略,为数据处理的安全提供了保证。相对于悲观锁,在对数据库进行处理的时候,乐观锁并不会使用数据库提供的锁机制。一般的实现乐观锁的方式就是记录数据版本。乐观并发控制相信事务之间的数据竞争(data race)的概率是比较小的,因此尽可能直接做下去,直到提交的时候才去锁定,所以不会产生任何锁和死锁。55、mongo 的业务场景,常用语法A:1)应用服务器的日志记录、2)第三方信息的抓取与存储、3)运维监控系统56、explain的用法A:EXPLAIN +SQL语句57、依赖注入 和 控制反转A: Ioc—Inversion of Control,即“控制反转”,不是什么技术,而是一种设计思想。在Java开发中,Ioc意味着将你设计好的对象交给容器控制,而不是传统的在你的对象内部直接控制。因为由容器帮我们查找及注入依赖对象,对象只是被动的接受依赖对象,所以是反转;哪些方面反转了?依赖对象的获取被反转了。58、PHP8的新特性A:命名参数、注解语法、构造函数参数改进、联合类型、匹配表达、空安全运算符、字符串和数字比较、函数内部一致性校验错误(细节自查)59、分库分表过程A:1)确认分库分表键;2)分片算法;3)确定容量,考虑扩容;4)唯一id;5)单库表 迁移 到分库;6)分库分表中间件(细节自查)60、php 数组的底层实现A:存储元素数组、散列函数(细节自查)分割线为了让各位方便:交流、交友、技术视频、资源分享、接私活 等等,可以扫下面二维码(wx:xzzs730),备注 “ 技术 ” 就可以通过审核。
主从不一致解决方案 && 如何降低主从延迟
“ 本文前半段主要参考和节选沈剑大佬的公众号的下面三篇文章,完整参考见文末”敢说你没遇到过,主从数据库不一致?DB主从一致性架构优化4种方法mysql并行复制降低主从同步延时的思路与启示其他文章参考见文末:Mysql复制方式(半同步复制,并行复制,多源复制)问:常见的数据库集群架构如何?一主多从,主从同步,读写分离。如上图:(1)一个主库提供写服务;(2)多个从库提供读服务,可以增加从库提升读性能;(3)主从之间同步数据;画外音:任何方案不要忘了本心,加从库的本心,是提升读性能。(主从架构是为了读写分离,再次架构上继续添加从库是为了提高读性能)问:为什么会出现不一致?主从同步有时延,这个时延期间读从库,可能读到不一致的数据。如上图:(1)服务发起了一个写请求;(2)服务又发起了一个读请求,此时同步未完成,读到一个不一致的脏数据;(3)数据库主从同步最后才完成;画外音:任何数据冗余,必将引发一致性问题。比如消息队列的分区,著名的 cap 理论等。问:如何避免这种主从延时导致的不一致?常见的方法有这么几种。方案一:忽略。任何脱离业务的架构设计都是耍流氓,绝大部分业务,例如:百度搜索,淘宝订单,QQ消息,58帖子都允许短时间不一致。画外音:如果业务能接受,最推崇此法。如果业务能够接受,别把系统架构搞得太复杂。方案二:放弃主从架构,强制读主。如上图:(1)不采用读写主从架构,只使用一个高可用主库提供数据库服务;(2)读和写都落到主库上;(3)采用缓存来提升系统读性能 存储;这是很常见的微服务架构,可以避免数据库主从一致性问题。方案三:半同步复制修改主库后至少同步修改完成一个从库,才返回请求,缓存每次都从那个同步更新的从库中读数据。方案优点:利用数据库原生功能,比较简单方案缺点:主库的写请求时延会增长,吞吐量会降低注意点:(1)主库和从库都要启用半同步复制才会进行半同步复制功能,否则主库会还原为默认的异步复制。(2)当主库等待超时时,也会还原为默认的异步复制。当至少有一个从库赶上时,主库会恢复到半同步复制。参考:Mysql复制方式(半同步复制,并行复制,多源复制)方案四:cache key 控制选择性读主。方案二“放弃主从架构,强制读主”过于粗暴,毕竟主从架构里,只有写请求会导致主从不一致,而写请求是占比比较小的,并且每次主从不一致的持续时间很短,如果因为少量的主从不一致而放弃主从架构,未免有点心疼。那有没有可能采用折中方案,保留主从架构,但是发生主从不一致时读主库,平时读从库呢?这样的话就需要能辨识什么时候可能发生主从不一致,很明显是发生了写操作时,那么当发生写操作时,我们可以利用一个缓存 key 标记那些不容许主从不一致,也就是必须读主的数据,发生了更新,且设置缓存 key 的超时时间,超时时间设置为“主从同步时延”;如上图,当写请求发生时:(1)写主库;(2)将哪个库,哪个表,哪个主键三个信息拼装一个key设置到cache里,这条记录的超时时间,设置为“主从同步时延”;画外音:key的格式为“db:table:PK”,假设主从延时为1s,这个key的cache超时时间也为1s。如上图,当读请求发生且缓存没有命中时:这时要读哪个库,哪个表,哪个主键的数据呢,也将这三个信息拼装一个key,到cache里去查询,如果,(1)cache里有这个key,说明1s内刚发生过写请求,数据库主从同步可能还没有完成,此时就应该去主库查询;(2)cache里没有这个key,说明最近没有发生过写请求,此时就可以去从库查询;以此,保证读到的一定不是不一致的脏数据。方案五:数据库中间件控制选择性读主如果有了数据库中间件,所有的数据库请求都走中间件。(1)所有的读写都走数据库中间件,通常情况下,写请求路由到主库,读请求路由到从库(2)记录所有路由到写库的key,在经验主从同步时间窗口内(假设是500ms),如果有读请求访问中间件,此时有可能从库还是旧数据,就把这个key上的读请求路由到主库(3)经验主从同步时间过完后,对应key的读请求继续路由到从库方案优点:能保证绝对一致方案缺点:数据库中间件的成本比较高总结数据库主库和从库不一致,常见有这么几种优化方案:(1)业务可以接受,系统不优化;(2)放弃主从架构,强制读主,高可用主库,用缓存提高读性能;(3)半同步复制,至少同步完成一个从库才算完成写操作,主从一致性要求特别高的数据从该从库读取数据。(4)cache key 控制选择性读主。在cache 里记录哪些记录发生了写请求,来路由读主还是读从;(5)数据库中间件控制选择性读主。利用数据库中间件来代理所有请求,由中间件来控制写请求发生后的读请求路由到从库还是主库。注:部署db一主多从的时候,可以对外提供两个接口: 强一致读(从主库读),弱一致读(从从库读)。 由调用方根据应用场景判断该调哪个接口。一般来说,需要强一致数据的场景很少的,主库的读写压力应该不大。如何降低主从延迟主从延迟来自两个方面:从库进行 binlog 复制,从库日志回放。从库复制 binlog 这个主要影响是网络带宽和网络稳定性,只能提高带宽来解决,没有什么更好的方式,所以这里更多讨论从库回放日志阶段导致的主从延迟。从库一般是单线程重放 sql 日志,所以如果主库写并发较大,可能导致从库单线程重放 sql 压力较大,主从复制延迟较高。所以优化思路就有2个,降低主库写并发和多线程重放 sql 日志,提高重放效率。水平分库(降低主库写并发)水平分库后单个分库的数据量降低,单个分库的写并发降低,从而降低从库重放 sql 的排队时延,因而降低延迟。从库并行复制(实际上应该叫并发复制)(多线程重放 sql 日志,提高重放效率)数据库之所以以默认选择用单线程回放 sql 日志,就是希望重放 sql 日志的顺序跟主库事务提交的顺序保持一致,从而保证主从数据的一致性,如果重放顺序遭到破坏,有可能导致从库数据发生错误。那能不能对 relay log 中的日志进行分类,有前后依赖关系的日志串行执行,没有前后依赖关系的并发执行呢?答案是有的。所谓并行复制,指的是从库开启多个 SQL 线程,并发读取和重放 relay log 中的日志,加快重放效率,提高重放效率,降低主从延迟。数据库级别库级别的并行复制很明显不同数据库之间的写操作是没有没有任何依赖的,所以数据库之间的日志重放是可以安全并发执行的。MySQL 5.5 版本不支持并行复制。MySQL 5.6版本开始支持库级别的并行复制,就是如果同个数据库应用中有多个数据库,可以通过配置,开启库级别的日志并发重放,加快重放速度。但是目前生产环境中大部分都是在单个数据库应用创建单个库,所以数据库级别的并行复制基本上没什么用。组级别的并行复制MySQL 5.7版本对并行复制进一步改进,细化到了单库内部,按照依赖关系对主库的事务进行分组,不同分组的事务没有依赖关系,可以安全并发复制,组内的事务日志则串行复制。问题:问:如果采用选择性读主,那么假设发生写库拥堵,或者网络抖动主导致从延迟突然增加,超过设置的经验值,怎么办主从同步延时一般是相对固定的,偶尔会有抖动(概率较小),可以根据实际稳定情况,适当加长主从同步延时经验值。问:数据库中间件需要有哪些功能代理所有读写请求,解析sql,路由到对应库,问:如果数据库中间件服务选择性读主,写操作的 key 存在哪,中间件服务本地吗?还是 redis 中?如果是在本地,那怎么保证下次读不会请求到其他的中间件服务器?我也很好奇,看样子是需要存在 redis 的全局缓冲中,这样可以全局可见,如果 key 存在的话,读请求无论请求哪个中间件服务都能请求到 key,但是这样是不是多了一次网络 IO,降低性能,而且这中实现好像跟方案 4 没啥区别了。如果存在本地,通过key路由,把相同 key 路由到同一个中间件服务器,这样好像中间件服务器的作用就有点不实际名归了,没有对外屏蔽掉所有的路由细节。问:如果查询,某个列表,怎么办?缓存 key 用不上,列表查询就只有强制读主库?暂时来看好像是的问:主库的 binlog 是事务提交之前写的,还是事务提交后写的,如果是提交前写的,万一写完 binlog,但是事务提交失败怎么办?事务提交成功的依据是什么?主库的 binlog 是事务提交之前写的,redo log 先于 binlog事务提交的依据是修改行记录隐藏字段中的last_transaction_id吗,这个应该是在写 binlog 时就已经写来才对问:半同步复制,每次完成半同步的从库是固定的一个吗,还是任何一个完成同步就行如果每次完成半同步复制的从库不一样,还是无法保证马上发生的读请求能读到最新的数据。只要有一台从写入relay-log,就返回給主库,这种方式一旦读请求落在其他从库,也是不一致的问:半同步复制的具体过程是怎么样的主库写binlog,从库复制 binglog 到自己的 relay log 文件,commit主库在提交后,执行事务提交的线程将一直等待,至少收到一个半同步从库将事件写入其中继日志(relay log)并刷新到磁盘的确认后,才会执行之后的事务提交,然后才响应客户端写操作成功。问:半同步复制除了提高主从一致性外,还有什么作用半同步复制保证了事务成功提交后,至少有两份日志记录,一份在主库的binlog上,另一份在至少一个从库的中继日志 relay log 上,这样就进一步保证了数据的持久性,进一步避免来主库宕机数据丢失。问:主从复制时,IO 线程和 SQL 线程都是单线程的吗IO 线程是多线程的,SQL 线程是单线程的。如果开了并行复制,那么 sql 线程就是多线程的,具体值由全局变量 slave_parallel_workers 决定。问:什么时候考虑用主从复制读操作称为瓶颈,需要提高读并发。或者读库需要做高可用时。问:主从复制可能有什么问题在数据量较大并发量较大的场景下,主从延时会比较严重。导致出现主从数据一致性问题,可能会发现,刚写入库的数据结果没查到。(如果不需要立刻读说明对主从一致性要求不高,可以考虑使用主从复制)主库宕机数据丢失问题。问:怎么判断现在主从延迟高不高通过监控 show slave status 命令输出的 Seconds_Behind_Master 参数的值来检测主从延时NULL:表示 io_thread 或是 sql_thread 其中一个发生故障;0:该值为零,表示主从复制良好;正值:表示主从已经出现延时,数字越大表示从库延迟越严重。问:怎么开启并行复制通过设置 slave_parallel_workers = 4 来开启数据库级别的并行复制分组的并行复制还需要额外设置 global.slave_parallel_type=‘LOGICAL_CLOCK’问:分组并发复制,从库怎么判断 log 是否处于同一个组根据 last_committed 来判断,relay log中 last_committed 相同的事务则认为是同一个事务组,last_committed 不同,则处在不同组。组内的事务通过 sequence_number 来标号。问:分组并发复制,主库分组的依据是什么未发生资源竞争,比如写操作不同表、不同行的记录的事务可以归到同一个组,主库上并发执行的写操作事务可以归为一个组,因为理论上在主库上能并发执行,在从库上也应该能并发执行。具体的实现算法有 2 种:基于逻辑时钟的并行回放因为MySQL本身事务具有ACID的特点,所以从主库同步到从库的事务,只要其执行的逻辑时间上有重叠,那么这两个事务就能安全的进行并行回放。基于writeSet的并行回放使用一个HashMap保存一定时间内针对某一块数据区域的事务的集合。如果事务在同一组内或者是逻辑时钟有重叠,说明没有冲突,其他情况不能确定是否有冲突。参考:MySQL Binlog日志与主从复制一文详解问:有哪些主从复制模型异步复制:master 把Binlog日志推送给slave,master不需要等到slave是否成功更新数据到Relay log,主库直接提交事务即可。这种模式牺牲了数据一致性。同步复制:每次用户操作时,必须要保证Master和Slave都执行成功才返回给用户。半同步复制:不要求Slave执行成功,而是成功接收Master日志就可以通知Master返回。问:什么是多源复制多个不同业务主库都复制到一个从库中。主要应用场景有三个:跨业务数据库汇总,方便数据分析部门做数据分析对水平分库的多个分库进行数据汇总,方便后期实现一些数据统计功能。多个主库数据汇总备份到一个从库,而不是多个从库,减少资源浪费和DBA的维护成本问:如果数据库有张表,频繁有新数据写入。同时高峰期这表查询请求也多,而且需要查询最新的数据。 我们遇到问题是高峰期查询请求大,导致写数据延迟了,同时也就导致查询出来的不是最新的数据。(因为新数据还在排队等待写入) 像这种写多读多的话,需要怎么优化呢?写多,先水平分库,读多主从复制。追求一致性那么需要从降低主从延迟和发生主从延迟怎么读到最新数据来思考,参考本文上面提到的降低延迟和解决主从一致性的方案。问:什么是 GTID全局事务标识符(GTID)事务 id 拼接主库实例 id1)全局事务标识:global transaction identifiers。2)GTID是一个事务一一对应,并且全局唯一ID。3)一个GTID在一个服务器上只执行一次,避免重复执行导致数据混乱或者主从不一致。4)GTID用来代替传统复制方法,不再使用MASTER_LOG_FILE+MASTER_LOG_POS开启复制。而是使用MASTER_AUTO_POSTION=1的方式开始复制。5)MySQL-5.6.5开始支持的,MySQL-5.6.10后开始完善。6)在传统的slave端,binlog是不用开启的,但是在GTID中slave端的binlog是必须开启的,目的是记录执行过的GTID(强制)。参考:MySQL主从复制,并行复制,半同步复制和组复制案例20160607 23:22 server_id 58 XXX GTID last_committed=0 sequence_numer=1
20160607 23:22 server_id 58 XXX GTID last_committed=0 sequence_numer=2
20160607 23:22 server_id 58 XXX GTID last_committed=0 sequence_numer=3
20160607 23:22 server_id 58 XXX GTID last_committed=0 sequence_numer=4GTID 使用过程GTID是指全局事务标志,用来标记主从同步的情况。master提交一个事务时会产生GTID,并且记录在Binlog日志中。从库的IO线程在读取Binlog日志时,会将其储存在自己的Relaylog中,并且将这个值设置到gtid_next中,即下一个要读取的GTID,从库读取这个gtid_next时,会对比自己的Binlog日志中是否有这个GTID:如果有这个记录,说明这个GTID的事务已经执行过了,可以忽略掉(幂等)。如果没有这个记录,slave就会执行该GTID事务,并记录到自己的Binlog日志中。参考:MySQL Binlog日志与主从复制一文详解问:什么是组复制 MGR(MRG, MySQL Group Replication)把主从关系变成分组内的成员关系,通过分布式共识算法,每个成员节点都可以读写。分布式一致性算法Paxos。由至少3个或更多个节点共同组成一个数据库集群,事务的提交必须经过半数以上节点同意方可提交提供,支持多写模式。MGR 是 share-nothing 的复制方案,基于分布式paxos协议实现,每个实例都有独立的完整数据副本,集群自动检查节点信息,做数据的同步。同时提供单主模式和多主模式,单主模式在主库宕机后能够自动选主,所有写入都在主节点进行,多主模式支持多节点写入。同时集群提供冗余的容错功能,保证集群大多数节点正常集群就可以正常提供服务。参考:MySQL Binlog日志与主从复制一文详解参考&特别致谢说你没遇到过,主从数据库不一致?DB主从一致性架构优化4种方法mysql并行复制降低主从同步延时的思路与启示https://zhuanlan.zhihu.com/p/373576459https://juejin.cn/post/7107653530588020767特别感谢沈剑大佬的博文,看完再总结一遍会有很大收获,强烈推荐关注沈剑大佬的公众号“架构师之路”,也感谢另外两篇参考文章的大佬。
MySQL性能优化(硬件,系统配置,表结构,SQL语句)
MySQL优化方案想必大家都知道,面试期间一提到数据库,就会聊到数据库优化相关问题。网上关于数据库优化的文章也是眼花缭乱,层出不穷。今天将会通过这篇文章细分几点给大家汇总整理出一套关于MySQL数据库的优化方案,让大家通过学习这篇文章不再被面试官吊打!成本:硬件优化 > 系统配置优化 > 表结构优化 > SQL语句优化 > 索引优化。效果:索引优化 > SQL语句优化 > 表结构优化 > 系统配置优化 > 硬件优化。下面由高成本到低成本详细讲解优化方案)一.硬件优化硬件优化无非就是对MySQL所在的服务器CPU,内存,磁盘进行优化。大内存,高IO,是现代基于web的数据库的必备 (百度的服务器内存 :96G —128个,2个实例 ,CPU8到16颗)。不同版本的MySQL对多核CPU的支持也不一样。服务器硬件对MySQL性能的影响及优化方案CPU对于MySQL应用,推荐使用S.M.P.架构的多路对称CPU,例如:可以使用两颗Intel Xeon 3.6GHz的CPU,现在我较推荐用4U的服务器来专门做数据库服务器,不仅仅是针对于mysql。物理内存对于一台使用MySQL的Database Server来说,服务器内存建议不要小于2GB,推荐使用4GB以上的物理内存,不过内存对于现在的服务器而言可以说是一个可以忽略的问题,工作中遇到了高端服务器基本上内存都超过了16G。磁盘寻道能力(磁盘I/O),以目前高转速SCSI硬盘(7200转/秒)为例,这种硬盘理论上每秒寻道7200次,这是物理特性决定的,没有办法改变。MySQL每秒钟都在进行大量、复杂的查询操作,对磁盘的读写量可想而知。所以,通常认为磁盘I/O是制约MySQL性能的最大因素之一,对于日均访问量在100万PV以上的系统,由于磁盘I/O的制约,MySQL的性能会非常低下!解决这一制约因素可以考虑以下几种解决方案: 使用RAID-0+1磁盘阵列,注意不要尝试使用RAID-5,MySQL在RAID-5磁盘阵列上的效率不会像你期待的那样快。注意:以上这些规划应该在初始设计系统时就应该考虑好。二.系统配置优化基本配置当然还有其他的设置可以起作用,取决于你的负载或硬件:在慢内存和快磁盘、高并发和写密集型负载情况下,你将需要特殊的调整。然而这里的目标是使得你可以快速地获得一个稳健的MySQL配置,而不用花费太多时间在调整一些无关紧要的MySQL设置或读文档找出哪些设置对你来说很重要上。Linux系统中MySQl配置文件一般位于/etc/my.cnfinnodb_buffer_pool_size这是你安装完InnoDB后第一个应该设置的选项。缓冲池是数据和索引缓存的地方。默认大小为128M。这个值越大越好决于CPU的架构,这能保证你在大多数的读取操作时使用的是内存而不是硬盘。典型的值是5-6GB(8GB内存),20-25GB(32GB内存),100-120GB(128GB内存)。innodb_log_file_size这是redo日志的大小。redo日志被用于确保写操作快速而可靠并且在崩溃时恢复。一直到MySQL 5.1,它都难于调整,因为一方面你想让它更大来提高性能,另一方面你想让它更小来使得崩溃后更快恢复。幸运的是从MySQL 5.5之后,崩溃恢复的性能的到了很大提升,这样你就可以同时拥有较高的写入性能和崩溃恢复性能了。一直到MySQL 5.5,redo日志的总尺寸被限定在4GB(默认可以有2个log文件)。这在MySQL 5.6里被提高。一开始就把innodb_log_file_size设置成512M(这样有1GB的redo日志)会使你有充裕的写操作空间。如果你知道你的应用程序需要频繁的写入数据并且你使用的时MySQL 5.6,你可以一开始就把它设置成4G。max_connections如果你经常看到Too many connections错误,是因为max_connections的值太低了。这非常常见因为应用程序没有正确的关闭数据库连接,你需要比默认的151连接数更大的值。max_connection值被设高了(例如1000或更高)之后一个主要缺陷是当服务器运行1000个或更高的活动事务时会变的没有响应。在应用程序里使用连接池或者在MySQL里使用进程池有助于解决这一问题。InnoDB配置从MySQL 5.5版本开始,InnoDB就是默认的存储引擎并且它比任何其他存储引擎的使用都要多得多。那也是为什么它需要小心配置的原因。innodb_file_per_table这项设置告知InnoDB是否需要将所有表的数据和索引存放在共享表空间里(innodb_file_per_table = OFF) 或者为每张表的数据单独放在一个.ibd文件(innodb_file_per_table = ON)。每张表一个文件允许你在drop、truncate或者rebuild表时回收磁盘空间。这对于一些高级特性也是有必要的,比如数据压缩。但是它不会带来任何性能收益。你不想让每张表一个文件的主要场景是:有非常多的表(比如10k+)。MySQL 5.6中,这个属性默认值是ON,因此大部分情况下你什么都不需要做。对于之前的版本你必需在加载数据之前将这个属性设置为ON,因为它只对新创建的表有影响。innodb_flush_log_at_trx_commit默认值为1,表示InnoDB完全支持ACID特性。当你的主要关注点是数据安全的时候这个值是最合适的,比如在一个主节点上。但是对于磁盘(读写)速度较慢的系统,它会带来很巨大的开销,因为每次将改变flush到redo日志都需要额外的fsyncs。将它的值设置为2会导致不太可靠(reliable)因为提交的事务仅仅每秒才flush一次到redo日志,但对于一些场景是可以接受的,比如对于主节点的备份节点这个值是可以接受的。如果值为0速度就更快了,但在系统崩溃时可能丢失一些数据:只适用于备份节点。innodb_flush_method这项配置决定了数据和日志写入硬盘的方式。一般来说,如果你有硬件RAID控制器,并且其独立缓存采用write-back机制,并有着电池断电保护,那么应该设置配置为O_DIRECT;否则,大多数情况下应将其设为fdatasync(默认值)。sysbench是一个可以帮助你决定这个选项的好工具。innodb_log_buffer_size这项配置决定了为尚未执行的事务分配的缓存。其默认值(1MB)一般来说已经够用了,但是如果你的事务中包含有二进制大对象或者大文本字段的话,这点缓存很快就会被填满并触发额外的I/O操作。看看Innodb_log_waits状态变量,如果它不是0,增加innodb_log_buffer_size。其它设置query_cache_sizequery cache(查询缓存)是一个众所周知的瓶颈,甚至在并发并不多的时候也是如此。 最佳选项是将其从一开始就停用,设置query_cache_size = 0(现在MySQL 5.6的默认值)并利用其他方法加速查询:优化索引、增加拷贝分散负载或者启用额外的缓存(比如memcache或redis)。如果你已经为你的应用启用了query cache并且还没有发现任何问题,query cache可能对你有用。这是如果你想停用它,那就得小心了。log_bin如果你想让数据库服务器充当主节点的备份节点,那么开启二进制日志是必须的。如果这么做了之后,还别忘了设置server_id为一个唯一的值。就算只有一个服务器,如果你想做基于时间点的数据恢复,这(开启二进制日志)也是很有用的:从你最近的备份中恢复(全量备份),并应用二进制日志中的修改(增量备份)。二进制日志一旦创建就将永久保存。所以如果你不想让磁盘空间耗尽,你可以用 PURGE BINARY LOGS 来清除旧文件,或者设置 expire_logs_days 来指定过多少天日志将被自动清除。记录二进制日志不是没有开销的,所以如果你在一个非主节点的复制节点上不需要它的话,那么建议关闭这个选项。skip_name_resolve当客户端连接数据库服务器时,服务器会进行主机名解析,并且当DNS很慢时,建立连接也会很慢。因此建议在启动服务器时关闭skip_name_resolve选项而不进行DNS查找。唯一的局限是之后GRANT语句中只能使用IP地址了,因此在添加这项设置到一个已有系统中必须格外小心。三.表结构优化由于MySQL数据库是基于行(Row)存储的数据库,而数据库操作 IO 的时候是以page(block)的方式,也就是说,如果我们每条记录所占用的空间量减小,就会使每个page中可存放的数据行数增大,那么每次 IO 可访问的行数也就增多了。反过来说,处理相同行数的数据,需要访问的page就会减少,也就是 IO 操作次数降低,直接提升性能。此外,由于我们的内存是有限的,增加每个page中存放的数据行数,就等于增加每个内存块的缓存数据量,同时还会提升内存换中数据命中的几率,也就是缓存命中率。数据类型的选择数据库操作中最为耗时的操作就是IO处理,大部分数据库操作 90% 以上的时间都花在了IO读写上面。所以尽可能减少IO读写量,可以在很大程度上提高数据库操作的性能。我们无法改变数据库中需要存储的数据,但是我们可以在这些数据的存储方式方面花一些心思。下面的这些关于字段类型的优化建议主要适用于记录条数较多,数据量较大的场景,因为精细化的数据类型设置可能带来维护成本的提高,过度优化也可能会带来其他的问题。数字类型非万不得已不要使用double,不仅仅只是存储长度的问题,同时还会存在精确性的问题。同样,固定精度的小数,也不建议使用decimal,建议乘以固定倍数转换成整数存储,可以大大节省存储空间,且不会带来任何附加维护成本。对于整数的存储,在数据量较大的情况下,建议区分开 tinyint / int / bigint 的选择,因为三者所占用的存储空间也有很大的差别,能确定不会使用负数的字段,建议添加unsigned定义。当然,如果数据量较小的数据库,也可以不用严格区分三个整数类型。字符类型非万不得已不要使用 text 数据类型,其处理方式决定了它的性能要低于char或者是varchar类型的处理。对于长度固定的字段,建议使用 char 类型,不定长度字段尽量使用 varchar,且仅仅设定适当的最大长度,而不是非常随意的给一个很大的最大长度限定,因为不同的长度范围,MySQL也会有不一样的存储处理。(注意:char(n) 不管该字段是否存储数据,都占n个字符的存储空间;varchar 不存的时候不占空间,存多长数据就占多少空间,可以节省存储空间。)时间类型尽量使用timestamp类型,因为其存储空间只需要 datetime类型的一半。但是timestamp存储的数据所以被限制在了1970~2038年之内。对于只需要精确到某一天的数据类型,建议使用date类型,因为他的存储空间只需要3个字节,比timestamp还少。enum与set对于状态字段,可以尝试使用enum来存放,因为可以极大的降低存储空间,而且即使需要增加新的类型,只要增加于末尾,修改结构也不需要重建表数据。如果是存放可预先定义的属性数据呢?可以尝试使用set类型,即使存在多种属性,同样可以游刃有余,同时还可以节省不小的存储空间。字符编码的选择字符集直接决定了数据在MySQL中的存储编码方式,由于同样的内容使用不同字符集表示所占用的空间大小会有较大的差异,所以通过使用合适的字符集,可以帮助我们尽可能减少数据量,进而减少IO操作次数。纯拉丁字符能表示的内容,没必要选择latin1之外的其他字符编码,因为这会节省大量的存储空间。如果我们可以确定不需要存放多种语言,就没必要非得使用utf8或者其他unicode字符编码,这会造成大量的存储空间浪费。数据库表适当拆分有些时候,我们可能会希望将一个完整的对象对应于一张数据库表,这对于应用程序开发来说是很有好的,但是有些时候可能会在性能上带来较大的问题。当我们的表中存在类似于text或者是很大的varchar类型的大字段的时候,如果我们大部分访问这张表的时候都不需要这个字段,我们就该义无反顾的将其拆分到另外的独立表中,以减少常用数据所占用的存储空间。这样做的一个明显好处就是每个数据块中可以存储的数据条数可以大大增加,既减少物理 IO 次数,也能大大提高内存中的缓存命中率。表数据适度冗余为什么我们要冗余?这不是增加了每条数据的大小,减少了每个数据块可存放记录条数吗?确实,这样做是会增大每条记录的大小,降低每条记录中可存放数据的条数,但是在有些场景下我们仍然还是不得不这样做。被频繁引用且只能通过 Join 2张(或者更多)大表的方式才能得到的独立小字段。这样的场景由于每次Join仅仅只是为了取得某个小字段的值,Join到的记录又大,会造成大量不必要的 IO,完全可以通过空间换取时间的方式来优化。不过,冗余的同时需要确保数据的一致性不会遭到破坏,确保更新的同时冗余字段也被更新。default 尽量使用 not null(默认值尽量设为非空)null类型比较特殊,SQL 难优化。虽然 MySQL null类型和 Oracle 的null有差异,会进入索引中,但如果是一个组合索引,那么这个NULL 类型的字段会极大影响整个索引的效率。此外,NULL 在索引中的处理也是特殊的,也会占用额外的存放空间。很多人觉得null会节省一些空间,所以尽量让null来达到节省IO的目的,但是大部分时候这会适得其反,虽然空间上可能确实有一定节省,倒是带来了很多其他的优化问题,不但没有将IO量省下来,反而加大了SQL的IO量。所以尽量确保 default值不是null,也是一个很好的表结构设计优化习惯。为每张表设置一个ID我们应该为数据库里的每张表都设置一个ID做为其主键,而且最好的是一个INT型的(推荐使用UNSIGNED),并设置上自动增加的AUTO_INCREMENT(自增)标志。就算是你 users 表有一个主键叫 “email”的字段,你也别让它成为主键。使用 VARCHAR 类型来当主键会使用得性能下降。另外,在你的程序中,你应该使用表的ID来构造你的数据结构。而且,在MySQL数据引擎下,还有一些操作需要使用主键,在这些情况下,主键的性能和设置变得非常重要,比如,集群,分区……在这里,只有一个情况是例外,那就是“关联表”的“外键”,也就是说,这个表的主键,通过若干个别的表的主键构成。我们把这个情况叫做“外键”。比 如:有一个“学生表”有学生的ID,有一个“课程表”有课程ID,那么,“成绩表”就是“关联表”了,其关联了学生表和课程表,在成绩表中,学生ID和课 程ID叫“外键”其共同组成主键。四.SQL语句优化1. 对查询进行优化,应尽量避免全表扫描,首先应考虑在 where 及 order by 涉及的列上建立索引。2. 应尽量避免在 where 子句中使用!=或<>操作符,否则将引擎放弃使用索引而进行全表扫描。3. 应尽量避免在 where 子句中对字段进行 null 值判断,否则将导致引擎放弃使用索引而进行全表扫描。如:select id from t where num is null可以在num上设置默认值0,确保表中num列没有null值,然后这样查询:select id from t where num=0应尽量避免在 where 子句中使用 or 来连接条件,否则将导致引擎放弃使用索引而进行全表扫描。如:select id from t where num=10 or num=20可以这样查询:select id from t where num=10
union all
select id from t where num=205. 下面的查询也将导致全表扫描。如:select id from t where name like '%abc%'若要提高效率,可以考虑全文检索。6. in 和 not in 也要慎用,否则会导致全表扫描。如:select id from t where num in(1,2,3)对于连续的数值,能用 between 就不要用in了select id from t where num between 1 and 37. 如果在 where 子句中使用参数,也会导致全表扫描。因为SQL只有在运行时才会解析局部变量,但优化程序不能将访问计划的选择推迟到运行时,它必须在编译时进行选择。然而,如果在编译时建立访问计划,变量的值还是未知的,因而无法作为索引选择的输入项。如下面语句将进行全表扫描:select id from t where num=@num可以改为强制查询使用索引:select id from t with(index(索引名)) where num=@num8. 应尽量避免在 where 子句中对字段进行表达式操作,这将导致引擎放弃使用索引而进行全表扫描。如:select id from t where num/2=100应改为:select id from t where num=100*29. 应尽量避免在where子句中对字段进行函数操作,这将导致引擎放弃使用索引而进行全表扫描。如:select id from t where substring(name,1,3)='abc' --name以abc开头的idselect id from t where datediff(day,createdate,'2005-11-30')=0 --'2005-11-30'生成的id
应改为:select id from t where name like 'abc%'select id from t where createdate>='2005-11-30' and createdate<'2005-12-1'10. 不要在 where 子句中的“=”左边进行函数、算术运算或其他表达式运算,否则系统将可能无法正确使用索引。11. 在使用索引字段作为条件时,如果该索引是复合索引,那么必须使用到该索引中的第一个字段作为条件时才能保证系统使用该索引,否则该索引将不会被使用,并且应尽可能的让字段顺序与索引顺序相一致。12. 不要写一些没有意义的查询。如需要生成一个空表结构:select col1,col2 into #t from t where 1=0这类代码不会返回任何结果集,但是会消耗系统资源的,应改成这样:create table #t(...)13. 很多时候用 exists 代替 in 是一个好的选择。select num from a where num in(select num from b)用下面的语句替换:select num from a where exists(select 1 from b where num=a.num)14. 并不是所有索引对查询都有效,SQL是根据表中数据来进行查询优化的,当索引列有大量数据重复时,SQL查询可能不会去利用索引,如一表中有字段sex,male、female几乎各一半,那么即使在sex上建了索引也对查询效率起不了作用。15. 索引并不是越多越好,索引固然可以提高相应的 select 的效率,但同时也降低了 insert 及 update 的效率,因为 insert 或 update 时有可能会重建索引,所以怎样建索引需要慎重考虑,视具体情况而定。一个表的索引数最好不要超过6个,若太多则应考虑一些不常使用到的列上建的索引是否有必要。16. 应尽可能的避免更新 clustered 索引数据列,因为 clustered 索引数据列的顺序就是表记录的物理存储顺序,一旦该列值改变将导致整个表记录的顺序的调整,会耗费相当大的资源。若应用系统需要频繁更新 clustered 索引数据列,那么需要考虑是否应将该索引建为 clustered 索引。17. 尽量使用数字型字段,若只含数值信息的字段尽量不要设计为字符型,这会降低查询和连接的性能,并会增加存储开销。这是因为引擎在处理查询和连接时会逐个比较字符串中每一个字符,而对于数字型而言只需要比较一次就够了。18. 尽可能的使用 varchar/nvarchar 代替 char/nchar ,因为首先变长字段存储空间小,可以节省存储空间,其次对于查询来说,在一个相对较小的字段内搜索效率显然要高些。19. 任何地方都不要使用select * from t,用具体的字段列表代替*,不要返回用不到的任何字段。20. 尽量使用表变量来代替临时表。如果表变量包含大量数据,请注意索引非常有限(只有主键索引)。21. 避免频繁创建和删除临时表,以减少系统表资源的消耗。22. 临时表并不是不可使用,适当地使用它们可以使某些例程更有效,例如,当需要重复引用大型表或常用表中的某个数据集时。但是,对于一次性事件,最好使用导出表。23. 在新建临时表时,如果一次性插入数据量很大,那么可以使用 select into 代替 create table,避免造成大量log ,以提高速度。如果数据量不大,为了缓和系统表的资源,应先create table,然后insert。24. 如果使用到了临时表,在存储过程的最后务必将所有的临时表显式删除,先 truncate table ,然后 drop table ,这样可以避免系统表的较长时间锁定。25. 尽量避免使用游标,因为游标的效率较差,如果游标操作的数据超过1万行,那么就应该考虑改写。26. 使用基于游标的方法或临时表方法之前,应先寻找基于集的解决方案来解决问题,基于集的方法通常更有效。27. 与临时表一样,游标并不是不可使用。对小型数据集使用 FAST_FORWARD 游标通常要优于其他逐行处理方法,尤其是在必须引用几个表才能获得所需的数据时。在结果集中包括“合计”的例程通常要比使用游标执行的速度快。如果开发时间允许,基于游标的方法和基于集的方法都可以尝试一下,看哪一种方法的效果更好。28. 在所有的存储过程和触发器的开始处设置 SET NOCOUNT ON ,在结束时设置 SET NOCOUNT OFF 。无需在执行存储过程和触发器的每个语句后向客户端发送 DONE_IN_PROC 消息。29. 尽量避免向客户端返回大数据量,若数据量过大,应该考虑相应需求是否合理。30. 尽量避免大事务操作,提高系统并发能力。
Redis(一)入门:NoSQL OR SQL,看完这篇你就懂了
前言关系型数据库(MySql、Oracle)无法满足我们对存储的所有要求,因此对底层存储的选型,对每种存储引擎的理解非常重要。MySQL不香吗,为什么还要有NoSQL?请听我慢慢细说!一、结构化数据、非结构化数据与半结构化数据文章的开始,了解一下结构化数据、非结构化数据与半结构化数据,因为数据特点的不同,将在技术上直接影响存储引擎的选型。1、结构化数据结构化数据,根据定义是指由二维表结构来逻辑表达和实现的数据,严格遵循数据格式与长度规范,也称为行数据。特点:数据以行为单位,二维表结构下一行数据就表示一个实体的信息,每一列数据的属性都是相同的。因此关系型数据库刚好契合了结构化数据的特点,关系型数据库也是关系型数据最主要的存储与管理引擎。2、非结构化数据非结构数据,根据定义是指数据结构不规则或不完整,没有任何预定义的数据模型,不方便用二维逻辑表来表现数据,例如网页日志、文本文档、图像、视频和音频文件等。特点:没有固定组织原则的未经过滤的信息,通常被称为原始数据。通过对非结构化数据进行搜索和分析,可以提取有用的信息。3、半结构化数据半结构化数据,根据定义是指以非传统方式捕获或格式化的数据。常见的半结构化数据有XML和JSON。特点:半结构化数据不遵循表格数据模型或关系型数据库的格式,因为它没有固定的架构。<person>
<name>linzy</name>
<age>18</age>
<phone>12345</phone>
</person>这种结构也被成为自描述的结构。半结构化数据的优点是,与结构化数据相比,它更灵活,更易于扩展。二、关系型数据库 SQL1、什么是关系型数据库?关系型数据库,是指采用了关系模型来组织数据的数据库,其以行和列的形式存储数据,以便于用户理解,关系型数据库这一系列的行和列被称为表,一组表组成了数据库。2、什么是关系模型?关系模型是采用二维表格结构表达实体类型及实体间联系的数据模型。关系模型允许设计者通过数据库规范化的提炼,去建立一个信息的一致性的模型。3、以关系型数据库的方式做存储的架构演进阶段一:企业刚发展的阶段,最简单,一个应用服务器配一个关系型数据库,每次直接访问数据库进行读写操作。阶段二:无论是使用MySQL还是Oracle还是别的关系型数据库,数据库通常不会先成为性能瓶颈,通常随着企业规模的扩大,一台应用服务器扛不住上游过来的流量且一台应用服务器会产生单点故障的问题,因此加应用服务器并且在流量入口使用Nginx做一层负载均衡,保证把流量均匀打到应用服务器上。阶段三:随着企业规模的继续扩大,此时由于读写都在同一个数据库上,数据库性能出现一定的瓶颈,此时简单地做一层读写分离,每次写主库,读备库,主备库之间通过binlog同步数据,就能很大程度上解决这个阶段的数据库性能问题。阶段四:企业发展越来越好了,业务越来越大了,做了读写分离数据库压力还是越来越大,这时候怎么办呢,一台数据库扛不住,那我们就分几台吧,做分库分表,对表做垂直拆分,对库做水平拆分。以扩数据库为例,扩出两台数据库,以一定的单号(例如交易单号),以一定的规则(例如取模),交易单号对2取模为0的丢到数据库1去,交易单号对2取模为1的丢到数据库2去,通过这样的方式将写数据库的流量均分到两台数据库上。一般分库分表会使用Shard的方式,通过一个中间件,便于连接管理、数据监控且客户端无需感知数据库ip。4、关系型数据库的优点易理解采用二维表结构非常贴近正常开发逻辑(关系型数据模型相对层次型数据模型和网状型数据模型等其他模型来说更容易理解)。操作方便支持通用的SQL(结构化查询语言)语句,通用的SQL语言使得操作关系型数据库非常方便,支持join等复杂查询,SQL + 二维关系是关系型数据库最无可比拟的优点,这种易用性非常贴近开发者。数据一致性支持ACID特性,事务开始前和结束后,数据库的完整性约束没有被破坏 。比如 A 向 B 转账,不可能 A 扣了钱,B 却没收到。在事务开始之前和事务结束以后,数据库的完整性没有被破坏。这表示写入的资料必须完全符合所有的预设约束、触发器、级联回滚等。 这里的一致性是指系统从一个正确的状态, 迁移到另一个正确的状态, 这是个逻辑层面的正确性。对事务的支持能保证系统中事务的正确执行,同时提供事务的恢复、回滚、并发控制和死锁问题的解决。数据稳定数据持久化到磁盘,没有丢失数据风险,支持海量数据存储,安全可靠。服务稳定最常用的关系型数据库产品MySql、Oracle服务器性能卓越,服务稳定,通常很少出现宕机异常。5、关系型数据库的不足随着互联网技术的不断发展,数据也日益增多,关系型数据库面对海量的数据时有些不足也体现出来高并发下IO压力大数据按行存储,即使只针对其中某一列进行运算,也会将整行数据从存储设备中读入内存,导致IO较高。对于网站的并发量高,往往达到每秒上万次的请求,对于传统关系型数据库来说,硬盘 I/O 有限,不能满足很多人同时连接。为维护索引付出的代价大为了提供丰富的查询能力,通常热点表都会有多个二级索引,一旦有了二级索引,数据的新增必然伴随着所有二级索引的新增,数据的更新也必然伴随着所有二级索引的更新,这不可避免地降低了关系型数据库的读写能力,且索引越多读写能力越差。为维护数据一致性付出的代价大数据一致性是关系型数据库的核心,但是同样为了维护数据一致性的代价也是非常大的。我们都知道SQL标准为事务定义了不同的隔离级别,从低到高依次是读未提交、读已提交、可重复度、串行化,事务隔离级别越低,可能出现的并发异常越多,但是通常而言能提供的并发能力越强。那么为了保证事务一致性,数据库就需要提供并发控制与故障恢复两种技术,前者用于减少并发异常,后者可以在系统异常的时候保证事务与数据库状态不会被破坏。对于并发控制,其核心思想就是加锁,无论是乐观锁还是悲观锁,只要提供的隔离级别越高,那么读写性能必然越差。可扩展性不足在基于web的结构中,数据库是最难以水平拓展的,当一个应用系统的用户量和访问量与日俱增的时候,数据库没有办法像web Server那样简单的通过添加更多的硬件和服务节点来拓展性能和负载能力。水平扩展后带来的种种问题难处理随着企业规模扩大,一种方式是对数据库做分库,做了分库之后,数据迁移(1个库的数据按照一定规则打到2个库中)、跨库join(订单数据里有用户数据,两条数据不在同一个库中)、分布式事务处理都是需要考虑的问题,尤其是分布式事务处理,业界当前都没有特别好的解决方案。表结构扩展不方便由于数据库存储的是结构化数据,因此表结构schema是固定的,扩展不方便,如果需要修改表结构,需要执行DDL(data definition language)语句修改,修改期间会导致锁表,部分服务不可用。全文搜索功能弱例如like “%中国真伟大%”,只能搜索到"2022年中国真伟大,爱祖国",无法搜索到"中国真是太伟大了"这样的文本,即不具备分词能力,且like查询在"%中国真伟大"这样的搜索条件下,无法命中索引,将会导致查询效率大大降低核心问题:关系型数据库在高并发下的性能时有瓶颈的,尤其在O/I读写频繁的情况下,出现的结果就是数据库占用CPU高,SQL执行效率变慢,客户端数据库连接池不够等错误。例如淘宝双十一的情况下,是绝对不可能直接对数据库进行O/I读写操作进行减去库存的。可能有朋友说,数据库在高并发下的能力有瓶颈,我公司有钱,加CPU、换固态硬盘、继续买服务器加数据库做分库不就好了,问题是这是一种性价比非常低的方式,花1000万达到的效果,换其他方式可能100万就达到了,不考虑人员、服务器投入产出比的Leader就是个不合格的Leader,且关系型数据库的方式,受限于它本身的特点,可能花了钱都未必能达到想要的效果。至于什么是花100万就能达到花1000万效果的方式呢?可以继续往下看,这就是我们要说的NoSQL。三、非关系型数据库 NoSQL像上文分析的,数据库作为一种关系型数据的存储引擎,存储的是关系型数据,它有优点,同时也有明显的缺点,因此通常在企业规模不断扩大的情况下,不会一味指望通过增强数据库的能力来解决数据存储问题,而是会引入其他存储,也就是我们说的NoSQL。1、什么是非关系型数据库?NoSQL 数据库(意即"不仅仅是SQL")并非表格格式,其存储数据的方式与关系表不同。NoSQL 数据库的类型因数据模型而异。主要类型包括文档、键值(KV)、列式和图形。它们提供了灵活的模式,可以随大量数据和高用户负载而轻松扩展。非关系型数据库,是对关系型数据库的一种补充,特别注意补充这两个字,这意味着NoSql与关系型数据库并不是对立关系,二者各有优劣,取长补短,在合适的场景下选择合适的存储引擎才是正确的做法。2、常见的NoSQL数据库1.键值数据库:Redis、Memcached、Riak2.图形数据库:Neo4j、InfoGrid3.列式数据库:Bigtable、HBase、Cassandra4.文档数据库:MongoDB、CouchDB、MarkLogic3、结合NoSql的方式做存储的架构演进对于那些读远多于写的数据,引入一层缓存,每次读从缓存中读取,缓存中读取不到,再去数据库中取,取完之后再写入到缓存,对数据做好失效机制通常就没有大问题了。通常来说,缓存是性能优化的第一选择也是见效最明显的方案。但是,缓存通常都是键值型存储且容量有限(基于内存),无法解决所有问题,于是再进一步的优化,我们继续引入其他NoSql:数据库、缓存与其他NoSQL并行工作,充分发挥每种NoSQL的特点。当然NoSQL在性能方面大大优于关系型数据库的同时,往往也伴随着一些特性的缺失,比较常见的就是事务功能的缺失。4、NoSQL 数据库四种类型1)键值(KV)NoSql(代表----Redis)键值数据库是一种较简单的数据库,其中每个项目都包含键和值。Redis又是键值数据库中应用最广泛的NoSQL,键值数据库以Redis为例,键值数据库的优点:数据基于内存,读写效率高。键值型数据,时间复杂度为O(1),查询速度快。键值数据库最大的优点就是高性能,利用Redis自带的BenchMark做基准测试,TPS可达到10万的级别,性能非常强劲,键值数据库非常适合需要存储大量数据但无需执行复杂查询来检索数据的使用案例。常见的使用案例包括存储用户首选项或缓存。Ridis也同样有所有键值数据库都有的比较明显的缺点:只能根据键去查值,key → value,无法根据值去查键。查询方式单一,只有key → value方式,不支持条件查询,多条查询的唯一做法就是数据冗余,但会极大浪费存储空间。内存始终是有限的,无法支持海量数据的存储。由于键值数据库的存储时基于内存的,所以存在数据丢失的风险。键值数据库非常适合需要存储大量数据但无需执行复杂查询来检索数据,也就是作为缓存来使用:读操作多于写操作读取能力强没有持久化的需求,可以容忍数据丢失的风险,丢失了就再查询写入即可键值数据库作为缓存的流程例子:根据用户id查询用户信息,每次根据用户id去缓存中查询一把,查到数据直接返回,查不到去关系型数据库里面根据id查询一把数据写到缓存中去。2)图形NoSql(代表----Neo4j)图形数据库是以点、边为基础存储单元,以高效存储、查询图数据为设计原理的数据管理系统。图形数据库可以直观地可视化关系,是存储、查询、分析高度互联数据的最优办法。Neo4j是一个高性能的,NOSQL图形数据库,它将结构化数据存储在网络上而不是表中。图形数据库以Neo4j为例,图形数据库的优点:更直观的模型图数据模型直接还原业务场景,相比传统数据模型更直观,提升产品与工程师的沟通效率。更简洁的查询语言图数据库支持查询语言在关联查询中更简洁,以最通用的Cypher图查询语言为例,复杂关联查询时代码量比SQL大幅降低,能够帮助程序员提升开发效率。更高效的关联查询性能图数据库在处理关联性强的数据以及天然的图问题场景时具有强大的·关联查询性能优势·。因为传统关系型数据库在进行关联查询时需要做表连接(JOIN),会把设计的表数据全部加载到内存中,涉及到大量的IO操作及内存消耗。而图数据库对关联查询有针对性的优化,能防止局部数据的查询引发全部数据的读取,可以高效查询关联数据。图形数据库不足之处:可能需要对整个图做计算,不利于图数据分布存储什么样的场景适合用图数据库需要高性能深度关系查询与分析时,例如:金融反欺诈、社交网络分析、网络安全等。业务动态,需要灵活数据模型时,例如:商品推荐、供应链管理、资产与权限管理等。需要执行复杂关系分析与推理时,例如:智能问答、新药研发、设备故障检测等。与关系数据库对比:在关系型数据库设计的时候需要进行严格的数据规范化,将数据分成不同的表并删除其中的重复数据,这种规范化保证了数据的强一致性并支持ACID事务。然而,这也对关系查询带来的限制。快速的实现逐行访问是关系型数据库的设计原理之一,当数据的数据之间形成复杂的关联时,跨表的关联查询增加,就会出现问题。虽然可以通过将存在不同表中的不同属性进行关联从而实行复杂查询,但是开销是非常大的。与关系型数据库相比,图数据库把关系也映射到数据结构中,对于关联度高的数据集查询更快,尤其适合那些面向对象的应用程序。同时图数据库可以更自然的扩展到大数据应用场景,因为图数据库Schema更加灵活,所以更加适合管理临时或不断变化的数据。关系型数据库对大量的数据元素进行相同的操作时通常更快,因为这是在其自然的数据结构中操作数据。图数据库在很多方面比关系型数据库更具有优势,而且变得越来越流行,但是图形数据库和关系型数据库并非是简单的替代关系,在具体应用场景中图数据库可以带来性能的提升和降低延迟才是适合的应用场景。3)列式NoSql(代表----HBase)列式数据库针对快速检索数据列进行了优化,通常用于分析应用程序。适用于数据库表的列式存储是分析查询性能的一个重要组成部分,因为它极大地降低了整体磁盘 I/O 要求,并减少了需要从磁盘载入的数据量。列式NoSql是基于列式存储的,那么什么是列式存储呢,列式NoSql和关系型数据库一样都有主键的概念,区别在于关系型数据库是按照行组织的数据:关系型数据库:关系型数据库是基于行存储方式的,例子中每行有name、phone、address三个字段,即使存在Null值的数据,它也是占空间的。列式数据库,它是按每一列进行组织的数据:列式数据库这样的好处:查询时只有指定的列会被读取,不会读取所有列(与关系数据库最主要的区别)。存储上节约空间,Null值不会被存储,一列中有时候会有很多重复数据(尤其是枚举数据,性别、状态等),这类数据可压缩,行式数据库压缩率通常在3:1–5:1之间,列式数据库的压缩率一般在8:1–30:1左右。列数据被组织到一起,一次磁盘IO可以将一列数据一次性读取到内存中。关于数据压缩,我们对字典表压缩作为举例:要构建表级别字典,系统会扫描表以查找重复模式。系统将检查所有行(而不仅仅是检查这些行的某些字段或者某些部分)以了解是否存在重复的条目或模式。收集重复的条目之后,数据库管理器将构建一个压缩字典,并为这些条目指定简短的数字键。通常来讲,文本字符串的压缩机率高于数字数据;压缩数字数据涉及将一个数字替换为另一个数字。根据要替换的数字的大小,节省的存储空间可能不像压缩文本时节省的存储空间那么多。关于列式数据库的优缺点总结:优点:海量数据无限存储,PB级别数据随便存,底层基于HDFS(Hadoop文件系统),数据持久化读写性能好,只要没有滥用造成数据热点,读写基本随便玩横向扩展在关系型数据库及非关系型数据库中都是最方便的之一,只需要添加新机器就可以实现数据容量的线性增长,且可用在廉价服务器上,节省成本本身没有单点故障,可用性高可存储结构化或者半结构化的数据列数理论上无限,HBase本身只对列族数量有要求,建议1~3个缺点:HBase是Hadoop生态的一部分,因此它本身是一款比较重的产品,依赖很多Hadoop组件,数据规模不大没必要用,运维还是有点复杂的KV式,不支持条件查询,或者说条件查询非常非常弱吧,HBase在Scan扫描一批数据的情况下还是提供了前缀匹配这种API的,条件查询除非定义多个RowKey做数据冗余不支持分页查询,因为统计不了数据总数4)文档型NoSql(代表----MongoDB)文档数据库将数据存储在类似于 JSON(JavaScript 对象表示法)对象的文档中。每个文档包含成对的字段和值。这些值通常可以是各种类型,包括字符串、数字、布尔值、数组或对象等,并且它们的结构通常与开发者在代码中使用的对象保持一致。由于字段值类型和强大的查询语言的多样性,因此文档数据库非常适合各种各样的使用案例,并且可以用作通用数据库。它们可以横向扩展以适应大量数据,因此文档型NoSql的出现是解决关系型数据库表结构扩展不方便的问题的。MongoDB是文档型NoSql的代表产品,同时也是所有NoSql产品中的明星产品之一,因此这里以MongoDB为例。关系型数据库是按部就班地每个字段一列存,在MongDB里面就是一个JSON字符串存储。关系型数据可以为name、phone建立索引,MongoDB使用createIndex命令一样可以为列建立索引,建立索引之后可以大大提升查询效率。其他方面而言,就大的基本概念,二者之间基本也是类似的:因此,对于MongDB,我们只要理解成一个Free-Schema的关系型数据库就完事了,它的优缺点比较一目了然。优点:没有预定义的字段,扩展字段容易相较于关系型数据库,读写性能优越,命中二级索引的查询不会比关系型数据库慢,对于非索引字段的查询则是全面胜出缺点:多表之间的关联查询不支持(虽然有嵌入文档的方式),join查询还是需要多次操作空间占用较大,这个是MongDB的设计问题,空间预分配机制 + 删除数据后空间不释放,只有用db.repairDatabase()去修复才能释放四、总结:关系型数据库与NoSQL间的对比1、何时选用关系型数据库,何时选用非关系型数据库?如何选择关系型数据库和非关系型数据库,需要考虑两个问题:1.数据间是否有一致性的需求?2.是否核心数据且有多字段组合查询场景1.非关系型数据库都是通过牺牲了ACID特性来获取更高的性能的,假设两张表之间有比较强的一致性需求,那么这类数据是不适合放在非关系型数据库中的。2.核心数据不走非关系型数据库,例如用户表、订单表,但是这有一个前提,就是这一类核心数据会有多种查询模式,例如用户表有ABCD四个字段,可能根据AB查,可能根据AC查,可能根据D查,假设核心数据,但是就是个KV形式,比如用户的聊天记录,那么HBase一存就完事了。3.非核心数据尤其是日志、流水一类中间数据千万不要写在关系型数据库中,这一类数据通常有两个特点:。写远高于读。写入量巨大4.一旦使用关系型数据库作为存储引擎,将大大降低关系型数据库的能力,正常读写QPS不高的核心服务会受这一类数据读写的拖累。2、选用非关系型数据库,使用哪种非关系型数据库?NoSQL代表产品解决关系型数据库的什么问题优点缺点键值(KV)NoSql键值(KV)NoSql(1)需要存储大量数据但无需执行复杂查询来检索数据的使用(2)热点KV型数据读写QPS高,读写性能受高QPS写数据的直接影响(1)数据基于内存,读写效率高。(2)键值型数据,时间复杂度为O(1),查询速度快。(3)原子命令的特性可用于分布式锁(1)数据无法持久化。(2)不支持条件查询。(3)基础内存存储,存储空间有限图形NoSql图形NoSql图形NoSql(1)利用图结构相关算法(最短路径、节点度关系查找等)。(2)图数据库支持查询语言在关联查询中更简洁。(3)图数据库在处理关联性强的数据以及天然的图问题场景时具有强大的关联查询性能优势。(1)可能需要对整个图做计算,不利于图数据分布存储。(2)高度结构化的数据处理能力不及关系型数据库。列式NoSqlHBase(1)海量数据存储后读写性能低。(1)支持海量数据存储,特别适合数据增长不可预估的场景。(2)KV方式读写性能高。(3)横向扩展方便。(4)可直接部署廉价服务器,成本低(1)基本没有条件查询能力。(2)不支持分页查询。(3)运维复杂。(4)需要开发者有一定的能力。文档型NoSqlMongoDB(1)表Schema扩展不方便(1)没有预定义的Schema,可随意变更字段。(2)命中二级索引时性能高于关系型数据库,非索引字段查询远远胜出。(1)空间占用大。参考文章:《Sql Or NoSql,看完这一篇你就懂了》NoSQL四种类型参考百度百科。
MySQL面试题汇总
1. 存储引擎选择MyISAM:适用于管理非事务表,不支持外键,只支持表锁,它提供高速存储和检索, 以及全文搜索能力的场景。比如博客系统、新闻门户网站。是非聚簇索引InnoDB:适用于新增,更新,删除操作频繁,或者要保证数据的完整性,并发量高,支持事务和外键的场景,且支持表锁行锁。比如OA自动化办公系统。2.索引2.1 索引的介绍索引是一种数据结构,是数据库管理系统中一个排序的数据结构,以协助快速查询数据库表中数据。索引方法的实现通常使用B+树或hash表。更通俗的说,索引就相当于目录。为了方便查找书中的内容,通过对内容建立索引形成目录。2.2 索引有哪些优缺点?索引的优点:可以大大加快数据的检索速度,这也是创建索引的最主要的原因。通过使用索引,可以在查询的过程中,使用优化器,提高系统的性能。索引的缺点:时间方面:创建索引和维护索引要耗费时间,具体地,当对表中的数据进行增加、删除和修改的时候,索引也要动态的维护,会降低增/删/改的执行效率;空间方面:索引需要占物理空间。2.3 索引有哪些类型?主键索引: 数据列不允许重复,不允许为NULL,一个表只能有一个主键。唯一索引: 数据列不允许重复,允许为NULL值,一个表允许多个列创建唯一索引。可以通过 ALTER TABLE table_name ADD UNIQUE (column); 创建唯一索引可以通过 ALTER TABLE table_name ADD UNIQUE (column1,column2); 创建唯一组合索引普通索引: 基本的索引类型,没有唯一性的限制,允许为NULL值,一个表允许多个列创建普通索引。可以通过ALTER TABLE table_name ADD INDEX index_name (column);创建普通索引,对应Navicat中的索引类型是NORMAL可以通过ALTER TABLE table_name ADD INDEX index_name(column1, column2, column3);创建组合索引,对应Navicat中的索引类型是NORMAL全文索引: 是目前搜索引擎使用的一种关键技术,MyISAM存储引擎才有全文索引。可以通过ALTER TABLE table_name ADD FULLTEXT (column);创建全文索引2.4 索引的数据结构(B+树,Hash)2.4.1 B+TreeB+Tree的优点:B+Tree的数据页只存储在叶子节点中,并且叶子节点之间通过指针相连,为双向链表结构。充分利用空间局部性原理,适合磁盘存储。树的高度很低,能够在存储大量数据情况下,进行较少的磁盘IO能够很好支持单值,范围查询,有序性查询。索引和数据分开存储,让更多的索引存储在内存中。B+树的叶子节点之间存在一个指针连接,那么顺着叶子节点从左往右即可完成对数据的遍历,极大了简化了排序操作。不仅仅能方便查找,而且有助于排序,在mysql的索引中叶子节点之间数双向链表可正反遍历,更加灵活.mysql采用B+树的优点:IO读取次数少(每次都是页读取),范围查找更快捷(相邻页之间有指针)需要注意点:InnoDB是通过B+Tree结构对主键创建索引,然后叶子节点中存储记录,如果没有主键,那么会选择唯一键,如果没有唯一键,那么会生成一个6字节的row_id来作为主键如果创建索引的键是其他字段,那么在叶子节点中存储的是该记录的主键,然后再通过主键索引找到对应的这条信息记录,这叫回表总上述所说==回表==:如:前述[id主键索引,name普通索引]select * from table_name where name = ?,他会先根据name字段的索引tree中找到对应的id值,再根据id值去主键字段所有tree中找到这条信息的记录,这就是回表,如果数据量少,反而会降低查询效率,因为回表造成了多次的IO的读取;==索引覆盖==:如:前述[id主键索引,name普通索引]select id from table_name where name = ?,他会去name字段的索引B+Tree中找到对应的id值,之后直接返回给用户;推荐使用,在某些场景中,可以考虑将要查询的所有列都变成组合索引,此时也会使用索引覆盖- ==索引下推==:在MySQL5.6版本以下则没有采取索引下推,5.6开始采取索引下推,索引下推指的是当`where name=? and age = ?`的时候都是在磁盘中操作的,没采用索引下推则是先根据`name`字段去查询,查询之后在MySQL的服务端再次筛选`age`字段,最终返回数据给用户;`总结`:索引下推在非主键索引上的优化,可以有效减少回表的次数,大大提升了查询的效率。
- ==最左匹配==:如上面的组合索引中,有两个name,age组合一起的索引,会采取最左匹配原则,说白了就是按照name,age的顺序去从左往右看
2.4.2 Hash缺点:需要做一个比较好的Hash算法,如果算法不好的话,会导致hash碰撞,hash冲突,导致数据散列不均匀,如下图,数据都在1,4了做范围查找的时候需要挨个遍历,效率非常低2.5 索引算法有 BTree算法和Hash算法BTree算法BTree是最常用的mysql数据库索引算法,也是mysql默认的算法。因为它不仅可以被用在=,>,>=,<,<=和between这些比较操作符上,而且还可以用于like操作符,只要它的查询条件是一个不以通配符开头的常量例如:-- 只要它的查询条件是一个不以通配符开头的常量
select * from user where name like 'jack%';
-- 如果一通配符开头,或者没有使用常量,则不会使用索引,例如:
select * from user where name like '%jack';Hash算法Hash算法只能用于对等比较,例如=,<=>(相当于=)操作符。由于是一次定位数据,不像BTree索引需要从根节点到枝节点,最后才能访问到叶子节点这样多次IO访问,所以检索效率远高于BTree索引。2.6 创建索引的原则?索引设计的原则?为常作为查询条件的字段建立索引,where子句中的列,或者连接子句中指定的列为经常需要排序、分组操作的字段建立索引更新频繁字段不适合创建索引不能有效区分数据的列不适合做索引列(如性别,男女未知,最多也就三种,区分度实在太低)对于定义为text、image和bit的数据类型的列不要建立索引最左前缀原则,就是最左边的优先。指的是联合索引中,优先走最左边列的索引。非空字段:应该指定列为NOT NULL,除非你想存储NULL。在mysql中,含有空值的列很难进行查询优化,因为它们使得索引、索引的统计信息以及比较运算更加复杂。你应该用0、一个特殊的值或者一个空串代替空值不要过度索引。索引需要额外的磁盘空间,并降低写操作的性能。在修改表内容的时候,索引会进行更新甚至重构,索引列越多,这个时间就会越长2.7 什么情况使用了索引,查询还是慢索引全表扫描索引过滤性不好频繁回表的开销like 以%开头,索引无效;当like前缀没有%,后缀有%时,索引有效。or语句前后没有同时使用索引。当or左右查询字段只有一个是索引,该索引失效,只有当or左右查询字段均为索引时,才会生效组合索引,不是使用第一列索引,索引失效。数据类型出现隐式转化。如varchar不加单引号的话可能会自动转换为int型,使索引无效,产生全表扫描。在索引列上使用 IS NULL 或 IS NOT NULL操作。索引是不索引空值的,所以这样的操作不能使用索引在索引字段上使用not,<>,!=。不等于操作符是永远不会用到索引的,因此对它的处理只会产生全表扫描。 优化方法: key<>0 改为 key>0 or key<0。对索引字段进行计算操作、字段上使用函数。当全表扫描速度比索引速度快时,mysql会使用全表扫描,此时索引失效。sql语句中select id from table where age + 1 = 5 此时age为普通索引,这样会走索引,如果select * from table where age + 1 = 5 ,查询所有,此时不会走索引or关联如果是单列索引or会使用索引,如果是组合索引如果是组合索引:全部列都是索引,那么会使用全部列所对应的索引如果部分列是组合索引,那么不会走索引遇到单列字段是索引的额话,可以用范围条件<,<=,>,>=,between,但是范围列后面的列无法使用索引,导致索引失效当表连接的时候,两张表同一个字段类型不一样的话,会导致索引失效2.7 MySQL使用自增主键的好处自增主键按顺序存放,增删数据速度快,对于检索非常有利;数字型,占用空间小,易排序;2.8 聚簇索引和非聚簇索引(主键索引)聚簇索引:将数据与索引放到了一块,索引结构的叶子节点存储了行数据,找到索引也就找到了数据非聚簇索引:将数据与索引分开存储,索引结构的叶子节点存储的是行数据的地址聚簇索引的优点数据访问更快。聚族索引将索引和数据保存在同一个B+树中,因此从聚簇索引中获取数据通常比非聚族索引中查找更快。使用覆盖索引扫描的查询可以直接使用节点中的主键值。聚簇索引的缺点插入速度严重依赖于插入顺序,按照主键的顺序插入是最快的方式,否则将会出现页分裂,严重影响性能。因此,对于InnoDB表,我们一般都会定义一个自增的ID列作为主键。更新主键的代价很高,因为将会导致被更新的行移动。因此,对于InnoDB表,我们一般定义主键为不可更新。通过辅助索引访问需要两次索引查找,第一次找到主键值,第二次根据主键值找到行数据。从而引起了回表几个概念对于普通索引,如 name 字段,则需要根据 name 字段的索引树(非聚簇索引)找到叶子节点对应的主键,然后再通过主键去主键索引树查询一遍,才可以得到要找的记录,这就叫回表查询。先定位主键值,再定位行记录,它的性能较扫描一遍索引树的效率更低InnoDB的行锁是建立在索引的基础之上的,行锁锁的是索引,不是数据,所以提高并发写的能力要在查询字段添加索引B+树:不管是什么索引,在mysql中的数据结构都是B+树的结构,可以充分利用数据块,来减少IO查询的次数,提升查询的效率。一个数据块data里面,存储了很多个相邻key的value值,所有的非叶子节点都不存储数据,都是指针。3. 常用SQL查询语句优化方法不要使用select * from t,用具体的字段列表代替“*”,使用星号会降低查询效率,如果数据库字段改变,可能出现不可预知隐患。应尽量避免在where子句中使用!=或<>操作符,避免在where子句中字段进行null值判断,存储引擎将放弃使用索引而进行全表扫描。避免使用左模糊,左模糊查询将导致全表扫描。IN语句查询时包含的值不应过多,否则将导致全表扫描。为经常作为查询条件的字段,经常需要排序、分组操作的字段建立索引。在使用联合索引字段作为条件时,应遵循最左前缀原则。OR前后两个条件都要有索引,整个SQL才会使用索引,只要有一个条件没索引整个SQL就不会使用索引。尽量用union all代替union,union需要将结果集合并后再进行唯一性过滤操作,这就会涉及到排序,增加大量的CPU运算,加大资源消耗及延迟。4. MySQL的优化细节加索引看执行计划优化sql语句5. 事务的四大特性(ACID)介绍一下特性说明原子性 Atomic事务是最小的执行单位,不允许分割。事务包含的所有操作要么全部成功,要么全部失败回滚。一致性 Consistency事务执行之前和执行之后都必须处于一致性状态。举例:拿转账来说,假设用户A和用户B两者的钱加起来一共是5000,那么不管A和B之间如何转账,转几次账,事务结束后两个用户的钱相加起来应该还得是5000,这就是事务的一致性。隔离性 Isolation隔离性是当多个用户并发访问数据库时,比如操作同一张表时,数据库为每一个用户开启的事务,不能被其他事务的操作所干扰,多个并发事务之间是相互隔离的。数据库规定了多种事务隔离级别,不同的隔离级别对应不同的干扰程度。隔离级别越高,数据一致性越好,但并发性越差。持久性 Durability持久性是指一个事务一旦被提交了,那么对数据库中的数据的改变就是永久性的,即便是在数据库系统遇到故障的情况下,也不会丢失提交事务的操作。5.1 什么是脏读?不可重复读?幻读?脏读(Dirty Read)一个事务读取到另外一个事务未提交的数据。举例:一个事务1读取了被另一个事务2修改但还未提交的数据。由于某种异常事务2回滚,则事务1读取的是无效数据。不可重复读(Non-repeatable read)一个事务读取同一条记录2次,得到的结果不一致。这可能是两次查询过程中间,另一个事务更新了这条记录。幻读(Phantom Read)幻读发生在两个完全相同的查询,得到的结果不一致。这可能是两次查询过程中间,另一个事务增加或者减少了行记录。不可重复读和幻读区别不可重复读的重点是 修改,幻读的重点在于新增或者删除。5.2 事务的隔离级别?MySQL的默认的隔离级别是什么?√表示会导致的问题隔离级别脏读不可重复读幻读 读未提交RU√√√读已提交 (Oracle默认)RC √√可重复读(MySQL默认)RR[用到MVCC多版本并发控制] √串行化 事务隔离机制的实现基于锁机制和并发调度。其中并发调度使用的是MVVC(多版本并发控制),通过保存修改的旧版本信息来支持并发一致性读和回滚等特性。MVCC多版本并发控制有三个隐藏字段隐藏字段名称解释DATA_TRX_ID事务,创建或者最后一次更新该条记录的事务idDB_ROW_ID隐藏主键(表示如果一个字段中没有主键字段,会选择唯一主键字段,如果唯一主键字段没有,就会生产一个6字节的ROW_ID)DATA_ROLL_PTR回滚指针,大小为 7 个字节(新插入的记录没有历史版本记录则会是NULL),与undolog有关undolog的回滚日志,会产生多个历史版本状态MVCC的实现,通过保存数据在某个时间点的快照来实现的。这意味着一个事务无论运行多长时间,在同一个事务里能够看到数据一致的视图。根据事务开始的时间不同,同时也意味着在同时刻不同事务看到的相同表里的数据可能是不同的。比如,在MySQL的可重复读的隔离级别情况下:事务A和事务B,同事开启事务,并且两个事务都第一次查询数据(数据快照),当事务B更改数据且提交,此时事务A再次(第二次)select的数据,它查询的数据并非是事务B提交后的新数据,依然是第一次的数据结果事务A和事务B,同事开启事务,事务B先去更改数据且提交,事务A第一次select查询(数据快照),则可以查看到事务B提交后的新数据MVCC多版本控制原理同一个事务中,多次查询的结果还是原来数据 底层采用MVCC多版本控制机制实现,读取原来快照数据。此时允许幻读,但不允许重复读与脏读。可重复读每次都是用的第一次快照吗?不是;事务A和事务B同时开启事务,事务AB分别查询数据,事务B开始新增数据且提交,此时事务A也去更改数据,(update from table set a = 1),会更新事务A第一次快照数据条数+事务B上一次新增数据的条数,然后事务A第二次做查询的时候就会查询最新的数据了MVCC的基础认识的当前读,快照读最终MVCC解决的是读写冲突,一个事务在写,另一个在读,读的是快照6. 锁6.1. 对MySQL的锁了解吗当数据库有并发事务的时候,可能会产生数据的不一致,这时候需要一些机制来保证访问的次序,锁机制就是这样的一个机制。6.2. 隔离级别与锁的关系读未提交(Read Uncommitted):读取数据不需要加共享锁,这样就不会跟被修改的数据上的排他锁冲突读已提交(Read Committed):读操作需要加共享锁,在语句执行完以后释放共享锁;可重复读(Repeatable Read):读操作需要加共享锁,事务执行完毕后才释放共享锁。串行化(SERIALIZABLE):是限制性最强的隔离级别,该级别下锁定整个范围的键,并一直持有锁,直到事务完成。6.3. 按照锁的粒度分数据库锁有哪些在关系型数据库中,可以按照锁的粒度把数据库锁分为行级锁(INNODB引擎)、表级锁(MYISAM引擎)和页级锁(BDB引擎 )。MyISAM和InnoDB存储引擎使用的锁:MyISAM采用表级锁(table-level locking)。InnoDB支持行级锁(row-level locking)和表级锁,默认采用行级锁(行锁加载索引上,如果没有索引则是表锁)行级锁,表级锁和页级锁对比行级锁:行级锁是MySQL中锁定粒度最细的一种锁,表示只针对当前操作的行进行加锁。行级锁能大大减少数据库操作的冲突。其加锁粒度最小,但加锁的开销也最大。行级锁分为共享锁(读锁) 和 排他锁(写锁)。==特点==:锁定粒度最小,对当前操作的行记录加锁,发生锁冲突的概率最低,并发度也最高;加锁开销大,加锁慢;会出现死锁;表级锁:表级锁是MySQL中锁定粒度最大的一种锁,表示对当前操作的整张表加锁,它实现简单,资源消耗较少,被大部分MySQL引擎支持。最常使用的MYISAM与INNODB都支持表级锁定。表级锁定分为表共享读锁(共享锁)与表独占写锁(排他锁)。==特点==:锁定粒度大,对当前操作的整张表加锁,发出锁冲突的概率最高,并发度最低;加锁开销小,加锁快;不会出现死锁;页级锁(间隙锁):页级锁是MySQL中锁定粒度介于行级锁和表级锁中间的一种锁。表级锁速度快,但冲突多,行级冲突少,但速度慢。所以取了折衷的页级,一次锁定相邻的一组记录。==特点==:销和加锁时间界于表锁和行锁之间;会出现死锁;锁定粒度界于表锁和行锁之间,并发度一般6.4. 共享锁和排他锁的区别共享锁(读锁)使用方式:在需要执行的语句后面加上 for update就可以了读取为什么要加读锁呢:防止数据在被读取的时候被别的线程加上写锁总结:共享锁 share lock 又称读锁 read lock,简称S锁,是读取操作创建的锁。其他用户可以并发读取数据,但任何事务都不能对数据进行修改(获取数据上的排他锁),直到已释放所有共享锁。示例:如果事务T对数据A加上共享锁后,则其他事务只能对A再加共享锁,不能加排他锁。获得共享锁的事务只能读数据,不能修改数据排他锁(写锁)排他锁 exclusive lock 又称写锁 writer lock,简称X锁。排他锁是悲观锁的一种实现。若事务T对数据A加上排他锁,则只允许事务T读取和修改数据A,其他任何事务都不能再对A加任何类型的锁,直到事务T释放X锁。排他锁会阻塞所有的排他锁和共享锁6.5. 数据库的乐观锁和悲观锁是什么?怎么实现的?乐观锁:假设不会发生并发冲突,每次去查询数据的时候都认为别人不会修改,所以不会上锁,在修改数据的时候才把事务锁起来。实现方式:乐观锁一般会使用版本号机制或CAS算法实现悲观锁:MySQL都是悲观锁假定会发生并发冲突,每次去查询数据的时候都认为别人会修改,每次查询完数据的时候就把事务锁起来,直到提交事务。实现方式:使用数据库中的锁机制两种锁的使用场景:乐观锁:适用于写比较少的情况下(多读场景)悲观锁:一般多写的场景下用悲观锁就比较合适6.6. 避免死锁现象的产生MySQL会智能发现死锁,若发现死锁就会将两个事务所执行的语句全部回滚业务中进来采取小的事务,避免使用大事务同一个事务中尽量做到一次锁定所需要的所有资源7. SQL优化7.1. SQL执行流程,SQL的生命周期连接器查询缓存分析器优化器执行器8. 日志分析8.1. 重要的日志模块:binlog,和InnoDB的redo logbinlog有几种录入格式statement模式下,每一条会修改数据的sql都会记录在binlog中。不需要记录每一行的变化,减少了binlog日志量,节约了IO,提高性能。由于sql的执行是有上下文的,因此在保存的时候需要保存相关的信息,同时还有一些使用了函数之类的语句无法被记录复制。row级别下,不记录sql语句上下文相关信息,仅保存哪条记录被修改。记录单元为每一行的改动,基本是可以全部记下来但是由于很多操作,会导致大量行的改动(比如alter table),因此这种模式的文件保存的信息太多,日志量太大。mixed,一种折中的方案,普通操作使用statement记录,当无法使用statement的时候使用row。这两种日志有以下三点不同。redo log 是 InnoDB 引擎特有的;binlog 是 MySQL 的 Server 层实现的,所有引擎都可以使用。redo log 是物理日志,记录的是“在某个数据页上做了什么修改”;binlog 是逻辑日志,记录的是这个语句的原始逻辑,比如“给 ID=2 这一行的 c 字段加 1 ”。redo log 是循环写的,空间固定会用完;binlog 是可以追加写入的。“追加写”是指 binlog 文件写到一定大小后会切换到下一个,并不会覆盖以前的日志。9. MySQL的主从复制原理以及流程9.1. 主从复制概念将主数据库中的DDL和DML操作通过二进制日志(BINLOG)传输到从数据库上,然后将这些日志重新执行,从而使得从数据库的数据与主数据库保持一致。主从复制的作用高可用和故障切换:主数据库出现问题,可以切换到从数据库。负载均衡:可以进行数据库层面的读写分离。数据备份:可以在从数据库上进行日常备份。
玖章算术CEO叶正盛:程序员必须掌握的数据库原理
本期分享嘉宾 叶正盛玖章算术科技公司CEO【嘉宾介绍】原阿里云资深技术与产品专家,数据库产品管理与解决方案部总经理。20年软件研发经验,曾主导过多个工业控制软件、ERP、大型电力计费系统研发,见证与实践了阿里巴巴去IOE、异地多活、云计算多次技术变革。演讲内容:合理的设计数据架构是程序员的核心竞争力,也是普通程序员走向技术专家的必修课。数据库一直是计算机核心基础软件,经历了40年的发展,从关系型数据库,到数据仓库、NoSQL、大数据以及云原生数据库,体系越来越复杂。本次主题重点介绍应用软件到底层数据库全链路的核心原理,希望帮助广大序员更好的理解并使用好数据库。数据库简介数据库管理系统(DBMS)是管理数据库的大型软件,可以建立、使用和维护数据库,其提供了数据采集、存储、查询和分析的功能。在没有数据库之前,如果我们要做库存管理,以小超市、夫妻店为例,我们可以用手工记账,但随着数据越来越多,我们可以用Excel管理,如果是大型超市或大型库存中心,Excel就不能应对了,需要使用数据库来管理。那么,数据库和Excel有什么区别呢?第一,管理的数据量不同,Excel可以管理上万条数据,而数据库可以轻松管理数亿条数据;第二,多用户,高性能,Excel一般是个人操作,数据库系统需要处理互联网平台上可能是数亿用户同时请求;第三,安全管理的不同,Excel只能设置密码保护,数据库支持用户的复杂权限控制;第四,开发接口不同,Excel开发接口比较多,但基本是应用在内置单机开发,数据库有面向应用软件的开发接口,适合大型应用软件或者互联网平台业务。主流的数据库有几十种,这张图描述了当前数据库主流产品定位,通过管理数据量大小和SQL功能强弱把各个产品分为四大类:OLTP:在线事务处理平台,一般都是关系型数据库来支撑,常见的数据库有:SQLite、MySQL、PostgreSQL、PolarDB、TiDB、OceanBaseOLAP:在线分析业务,一般是数据仓库,常见的产品有:Teradata、Clickhouse、Doris、Greenplum、Snowflake、AWS RedshiftNoSQL:新数据模型,互联网行业用得非常多,代表产品有:Redis、Neo4j、InfluxDB、MongoDB、Cassandra、AWS DynamoDBBigData:大数据业务,和数据仓库比较类似,但是更擅长处理大规模数据分析业务,主流产品有:HBase、Hadoop、ElasticSearch、Spark另外DB2、SQLServer、Oracle这几款产品在OLTP和OLAP领域都比较强,有时称为HTAP产品,也是目前数据库的领导者。不是说管理的数据量越大和SQL功能越强就越好,每一种数据库它是在特定的场景能够展开它的优势,这里只是把它划分在不同的区域。根据数据库计算节点和存储节点部署整体架构分为几大种类:单机: 计算节点和存储节点一般在同一台机器上,通常存储节点是本地硬盘,如单机版的MySQL、Oracle。单机模式没有高可用保障,常用于临时开发测试或者个人学习场景,生产环境不建议使用。主备读写分离:在单机模式上增加了备用节点,备用节点既可以作为高可用保障,也可以承担只读业务请求,主备之间通过数据库的log实时传输实现。比较常见的有MySQL的Master-Slave,Oracle的Active Dataguard,SQL Server的Always on,主备模式也是当前生产环境最常见的部署架构。随着数据量或者负载的增加,主备模式已经无法满足业务需求,因此诞生了分布式的软件架构,数据仓库和大数据产品通常都是分布式架构,主要由两种模式:分布式(Share everything):Share everything架构通常存储节点采用分布式的共享存储,比如专业的EMC存储设备或者是云存储OSS/S3。因为数据共享问题解决了,计算节点就能扩展多个,从而承担更多业务负载。典型代表有Oracle RAC、阿里云PolarDB、Snowflake、AWS Redshift(RA3)。IBM DB2也是典型的分布式架构,它同时还采用了集中式共享缓存的模式。分布式(Share nothing):在没有共享存储的情况下,通常会采用Share Nothing的架构设计。这类架构是存储数据分片存储在不同的节点,因此计算和存储节点都是独立运行,通过MPP计算引擎实现集群资源的调度运算。数据库内部结构数据库引擎主要包括以下组件:存储引擎:负责数据的存储管理,包括数据、索引、日志等数据的管理;缓存管理:负责数据库的缓存管理,包括数据缓存、SQL缓存、会话缓存、运算缓存等等;计算引擎:也称为查询引擎,负责业务请求的逻辑运算,包括SQL解析、执行计划生成、执行算子下发到存储引擎等等;事务处理:负责数据库的事务管理,ACID、锁等特性的实现;分布式管理:分布式数据库的核心组件,用于协调数据库各个节点通讯、事务状态一致性协调、分布式计算等等;安全管理:数据库的安全访问基础,包括基本的用户、角色认证,加密解密,以及高级的数据脱敏特性;会话管理:数据库连接和连接池状态、配置等管理;外部接口:负责对外提供标准的数据库访问接口,支持应用软件通过JDBC、ODBC、Socket等方式访问数据库。每种数据库都有自己的实现方案和技术架构,但通常都会包括以上组件功能。存储引擎最基础的是HEAP结构,这也是表数据文件通常的保存格式。常见的数据库,像Oracle,SQL Server,MySQL(ISAM),PostgreSQL表的基础结构都是堆表引擎。可以理解为一条记录过来,就把它按先后顺序堆进去。数据库由多个文件构成,一个文件由多个Extent/Segment构成,每个Extent包含多个Block/Page,OLTP通常在4K~32K大小,Block里面就是具体的行数据信息。B-TREE是数据库索引最常用的数据结构,MySQL(InnoDB)的表数据也直接采用了B-TREE的存储结构。B-TREE的核心特点是对数据排序,可以从根节点到叶子节点再定位到数据,叶子节点可以有多层,就像一棵树的树枝一样。我最喜欢的例子还是用新华字典来形容,大家小时候经常查字典,其实就是B树的原理,要查一个字,首先要找索引,如果知道它的拼音,根据拼音索引里面的位置去找到页码,再找到数据,查询效率还是非常高的。字典可能有很多种目录索引,常见的有拼音、部首、笔划,这样就可以按不同的方式快速找到要需要的字。数据库B-TREE原理跟查字典基本上是一样的,很多文章介绍不等于、大于、小于等条件能不能使用上索引,其实想想这个逻辑,用字典目录能不能查,就能大概判断出来能不能做,原理是完全一样的,没有什么特别之处。HEAP和BTREE通常是OLTP数据库使用,而OLAP通常数据量非常大,COLUMN-STORE(列存)是更常见的存储结构,因为按列存储数据可以做到更好的编码压缩,并且当SQL只是需要请求大宽表的部分字段时,COLUMN-STORE就非常有优势。开源的ORC和PARQUET是列式存储的典型模型,都比较常见。Greenplum、ClickHouse等数据仓库都支持列式存储。在我们生活中也有这样的例子,比如说大超市的货架,通常都是按品类摆放的,电器、水果、零食都是放在不同的位置,这就是一种列式分类管理的概率,查询效率也比较高,也可以摆放更多的货物。再谈谈LSM-TREE,上面是阿里内部X-DB的技术架构图,很好的描述LSM-TREE模型。业务往里面写数据时,先写日志,同时放在内存里面,内存积累到一定的数据,就排好序往底下磁盘刷盘,按同样的逻辑一层一层刷盘,这是典型的分层存储的概念。它在写方面非常强,尤其是顺序写,像日志这种数据比较适合。它的缺点是读性能比B树差,尤其是按范围读取数据。大量的传统OLTP商业数据库依然用B树去做,因为实践下来综合读写会更高效。LSM-Tree在日志存储场景比较合适,如Hbase,另外我们也会看到一些分布式数据库存储用LSM结构,如TiDB、OceanBase,这个还有待验证。LSM-Tree在面向传统硬盘上有顺序写入优势,对SSD写磨损也更友好,但在SSD硬盘上的整体性能提升收益没有传统硬盘突出,因为SSD的随机IO性能也不错。用一个表格汇总各种存储引擎的优劣势和应用场景。数据库缓存分为几大板块,第一是数据缓存,一般来说是全局性的,所有的会话都在统一的数据缓存里读取和写入数据。框的大小代表着占用内存的大小,比如数据块,这是占用最多的,其次是索引块,另外是日志,比如REDO LOG。也有一些开发者会问,这个缓存为什么数据库单独去管理?用操作系统不行吗?如果说不深度理解这个问题,确实操作系统缓存也做得很好,有些数据库确实没有缓存,但是我们看到非常强大的数据库都有自己的缓存管理模块,比如Oracle、SQLServer等等。数据库自己做缓存管理可以做得非常精细,比如说有全表扫描和按索引扫描,如果不做精细化的缓存,有可能一个大表的查询就把以前有效的缓存都给清出去了,对于数据库来讲是不能接受的,所以需要自己做管理,因为可以判断这个缓存的重要性。Oracle,MySQL(InnoDB)这方面都做了很多细的工作,这是操作系统不一定能解决好的问题。当然也会存在一定问题,数据库有缓存,操作系统还有缓存,会不会导致缓存重复使用,效率不高?因此有些数据库就把操作系统的缓存给旁路了,操作系统会提供DirectIO这样的接口。图中右上角是SQL与对象的缓存,有两个比较重要的,一个是SQL的文本和执行计划,第二是元数据,比如说表结构定义信息。SQL文本和执行计划缓存比较大,尤其像Oracle这些成熟的商业数据库,因为需要支持非常复杂的SQL最优执行计划制定,所以缓存管理就更加重要。反而开源的数据库执行计划缓存会做得非常弱,比如MySQL,可以说是几乎没有。如果有执行计划缓存,像Oracle这种数据库,效率是完全不一样的,每次执行,效率有可能会有10倍以上的提升。然后是会话缓存,这块比较好理解。还有是运算缓存,在数仓里非常重要,OLTP系统,一般数据缓存很重要,OLAP运算缓存会更重要,主要原因是OLAP系统的Hash与排序运算量比较大。数据库事务处理的核心是ACID:Atomicity:原子性,表示数据库一个事务操作要么成功,要么失败,不能出现部分成功或者部分失败的情况。通常采用WAL(Write Ahead Log)来实现,同时利用了硬件的原子写接口。Consistency:一致性,表示数据库要能保证定义的约束有效性,包括定义的主键、唯一约束、外键、NOT NULL等约束,在每个事务提交后都要确保约束有效。在事务过程中没有强制约束,因此不同数据库对约束的实现会有些区别,比如插入唯一约束重复的数据是立即失败,还是等事务commit再检查唯一约束?Isolation:隔离性,这个是事务最复杂的地方,国际标准定义了4种事务隔离级别。对于OLTP系统,大部分都采用了Read Committed的隔离级别,唯独MySQL(InnoDB)默认采用了Repeatable Read的隔离级别,这个也是由于历史原因,MySQL的主备同步使用了Statement模式导致的。现在MySQL基本都是采用ROW格式的Binlog格式,所以建议改为Read Committed隔离级别更好,也能减少Repeatable Read带来的各种锁和死锁问题。我在第一次使用MySQL就踩了一个坑,当时使用Create table as select备份一张线上表,然后表就被锁定了,导致业务故障,这个也是非常坑的地方。阿里巴巴集团内部和阿里云RDS也默认把事务隔离级别改为了Read Committed,系统也更加稳定。Durability:持久性,意思是事务提交后,数据库一定要确保生效,即使操作系统重启或者是服务器掉电。数据库通常也是使用WAL加硬件的持久性来实现,这里需要保证commit后刷盘立即写入,不能临时缓存在内存中,否则服务器掉电就丢失数据。查询引擎是数据库最复杂的模块,核心是处理业务请求逻辑,如SQL怎么解析、执行,可以称为数据库的大脑。每种数据库使用的查询语言不太一样。关系型数据库通常使用SQL语言,KV数据库一般使用get/set语义,文档数据库一般是用JSON模型表达,图数据库比较复杂,有OpenCypher、Gremiln、SPARQL等。时序数据库,也大部分是类SQL的模型,它在SQL的基础上增加了一些时序计算窗口类的表达模型。数据库设计与SQL优化SQL是数据库最常用的语言,也是数据库的核心资源开销,因此SQL优化是程序员的必备技能。SQL优化通常分3个阶段,我们常称为SQL优化三板斧,这三板斧下去,90%的问题都可以解决。第一是先找到问题SQL,有两种情况,如果是现在系统有问题,我们可以去看活跃的连接在干什么,把正在运行的SQL取出来。每种数据库通常都提供了查询当前活跃会话的接口,比如MySQL使用show processlist,Oracle可以查询v$session视图信息。如果说问题已经过去了,没有现场,可以通过Slow Log,或者TOP SQL,找到问题,这个需要数据库开启历史SQL记录功能。第二是分析问题SQL,最主要先看SQL的执行计划,再去看整个数据库的IO访问量是不是符合预期,缓冲命中率怎么样,如果不是99%以上,可能都有问题,有些程序员看到内存都用完了,其实也不是什么问题,比如有10G的内存,如果你的数据量超过10GB,那内存很快都会用满,关键要看缓存命中率。另外是网络IO,多集群的话,需要关注网络传输的延迟和带宽容量。如果有锁,要分析SQL锁的模型。CPU重点要看一下排序、函数计算等操作。最后是解决问题,就是优化SQL,可以修改SQL提升性能,如果没有索引就要增加索引,如果缓存命中率有问题就要调整内存配置。数据整理也是比较常见的,有些数据库历史数据非常多,影响了效率,要考虑怎么把历史数据归档,提升数据访问效率。再有是提升硬件性能,最后是分布式改造,分布式改造比较困难,如果前面几个步骤能解决最好,实在不行再去考虑比较复杂的分布式改造。接下来重点讲一下执行计划。有些初级程序员往往忽视这个问题,SQL执行计划是描述SQL详细执行路径和算法。数据库通常支持使用EXPLAIN语法来查看SQL的执行计划,会展示出SQL具体的执行路径和算法,包括访问表的顺序,每张表使用索引访问还是全表扫描,每次执行预计返回的行数等等,信息会比较详细。SQL访问单表主要有几种方式,包括:主键:示例SQL:select * from t where id=?唯一索引:select * from t where uk=?普通索引:select * from t where name=?索引范围扫描:select * from t where create_time>?全表扫描:select * from t对于多表连接,通常有以下几种JOIN算法:Nested loop join:适合两张表有非常好的索引访问路径,如:select * from t1 inner join t2 where t1.id=t2.id and t1.id=?Hash Join:适合两张表都没有好的索引,需要全表扫描,并且是等值关联查询,如:select * from t1 inner join t2 where t1.id=t2.idHash Join需要把一张表加载到内存后,另外一张表再通过Hash匹配到对应的记录,所以会需要临时内存,当临时内存不够时会产生临时磁盘转储的开销。为了解决Hash Join在分布式场景的问题,也诞生了Shuffle Hash Join和Broadcast Hash Join算法。Merge Join:适合两张表都没有好的索引,需要全表扫描,支持非等值关联查询,关联字段最好是有排序,因为Merge Join算法是需要把两边数据排序再Merge 。数据库如何生成SQL执行计划,如何选择最优的执行路径,通常是内部有个优化器的组件来完成。优化器类型有RBO和CBO两种。RBO:Rule Base Optimizer,意思是基于规则的优化器逻辑,早期数据库都是采用RBO,实现比较简单,但很依靠程序员对数据库的逻辑理解,在SQL非常复杂的情况下很容易走到糟糕的执行计划。CBO:Cost Base Optimizer,意思是基于成本的逻辑,优化器会计算各种不同的路径成本,最后选择一条最优的路径来执行。举个例子,我们通常的交通方案有飞机、火车、汽车、轮船、步行,每种方案的速度都不同。如果我们要从杭州去北京,那么该选择那种交通方式?对于RBO来说,它总是会选择飞机,不管是否发生交通管制,以及去机场的交通是否顺畅。对于CBO来说,它会评估从出发点到杭州机场/火车站,北京火车站/机场到目的地的交通时间,综合评估一个最短时间的交通方案。CBO是目前数据库主流的优化器模型,实现难度也更大,需要收集很多统计信息,如每张表的存储空间大小和记录数,字段的区分度等等,在数据经常变化时需要保证统计信息的准确性。RBO和CBO通常都是在生成执行计划后就不能再修改,即使执行过程中发现严重偏差也不会改变,因此后来有些数据库在CBO的基础上实现了更智能的Adaptive的逻辑,意思是在执行过程中,如果发现有更优路径,那优化器可以调整执行计划。就好像本来选择坐飞机从杭州去北京,但是到了机场,发现航班大量延误,起飞时间完全不确定,这个时候可以调整计划,选择做火车出发。开源数据库的优化器相对比较弱,商业数据库更领先。国内在这块技术相比国际落后很多。接下来讲一下数据库的设计,基础知识是要掌握范式,在教科书里面非常多,这里不多讲 。反范式我觉得是比较有意思,尤其是表设计里面,有非常多的反范式的案例。比如说标记组合是比较常见的,如果有多个标记状态位,可能会用bit的数据类型去存储,这其实就已经违反了第一范式,因为第一范式要求字段的内容要原子性,不能拆分。实际上数据库内核里面,它用了很多bit类型这种反范式来设计元数据,从而提升存储和查询效率。另外一个是日常习惯字段,拿身份证号码来举例,也是违反了第一范式,因为身份证号码已经包括所在的地区、出生年月、登记顺序以及校验码这4个信息。但是我们实际的表结构设计,还是会把身份证号码当做一个字段,不会说把身份证号码拆成4个字段,否则阅读习惯会不太方便。还有计算列,典型的违反了第三范式,如商品的金额=单价*数量,订单总金额,我们一般都会冗余去存储。数仓里面就更多的反范式的案例,历史数据快照,会把相关的信息都保存下来,也是违反范式,但我们基本都这么干。拆表也是一种常见设计,比如说一张表里面有常用和不常用的字段,如有个字段是大字段,我们就会把它拆成另外一张表。这些其实都是比较常见的反范式设计的场景。所以部分场景下我们不用去特别纠结一定要遵循范式,如果说你觉得合理,业务逻辑实现可控,适当的冗余数据其实也没什么问题。关于主键选择,比较推荐两种,一种是自增字段,比较适合内部系统的表,如果说是对外的互联网应用需要注意,有被猜测攻击的隐患。我们公司比较喜欢用雪花模型,根据时间序列加上顺序号产生主键,性能比较好,比较安全,实现相对复杂,需要应用程序产生。接下来介绍几个常见的数据模型:首先是平台型互联网数据模型,像淘宝、微信公众号、美团、BOSS直聘、滴滴等等,他们的数据模型都比较类似,主要包括以下对象。会员信息:买家(淘宝、美团)、乘客(滴滴)、读者(公众号)、求职人(BOSS直聘)。服务提供者信息:卖家(淘宝、美团)、博主(公众号)、司机(滴滴)、主播(抖音)、公司(BOSS直聘)服务内容信息:商品、文章、微博、用车、视频、职位库存信息:电商的概念互联网平台根据以上几张表信息,通过搜索、推荐等各种匹配算法,最后产生订单信息,接着是付款信息、物流/配送信息,最后是服务反馈信息。刚才是互联网的模型,企业软件不太一样,企业软件讲究两个东西,一个是安全,一个是流程规范。安全核心是权限模型设计,比较标准的是RBAC(Role Base Access Control),我们可以参考这套模型演进。核心是理解用户、权限、角色三个重要的对象。按照RBAC设计问题不会太大,有可能会有扩展的东西。比如面向公司的用户,会有组织和部门的关系。权限会扩展出权限分组,角色会有继承等。另外一个是数据域权限,同样的权限与角色,但是能访问的是不同范围的数据,就会有数据域等高级定义。我们再看企业软件里比较常见的流程规范,最常见的是工作流引擎。首先有工单表,不管是请假、权限申请还是生产变更,都有一个工单来存储你做什么事情。然后是流程模型定义表,例如数据库产生变更的流程,现在做生产发布,变更表结构,要提交变更申请,主管要审批,如果高风险还需要DBA的审批,这就是一个业务流程模型。流程模型会包括基本定义信息,有哪几个节点,流转路径,根据什么条件流转,哪些人受理等等。针对每个工单的流程处理,都有工单流转表,消息通知表,再加上用户权限系统,这样就可以形成工作流的待办工作表,基本上就是这么一个套路,大家理解这个概念,在企业级软件里就可以如鱼得水了,不会那么纠结。数据传输和安全管理前面是数据库架构和 SQL优化板块,最后一个板块我要讲一下数据传输和安全管理,这个也是我们开发者其实经常会遇到的问题。数据传输是一个统称,包括数据迁移、数据同步、数据分发等,也称为数据复制。比较常见的几个场景:数据库上云、更换数据库、数据同步到备库、异地数据多活、数据ETL等等。通常包括结构迁移、全量数据复制、增量数据复制几个核心模块,为了保障数据复制的正确性,还需要数据对比模块。这里面最难的增量数据复制模块,市场上很多产品会采用按日期增量抽取简化处理,只能做到定时复制,要做到实时数据复制需要使用CDC技术,要精通数据库的内部原理,适配各种主流数据库产品和版本,技术难度和工程难度都是非常有挑战。我在这个领域做了10年,从早期的去IOE、异地多活到发布阿里云DTS,里面有非常多的问题,尤其是在生产环境下,源端经常会有各种DDL变更的情况很容易链路中断或者数据错乱。目前市场上推荐的三款代表产品:阿里云DTS:功能完备,与阿里云集成度很好NineData(重点推荐):玖章算术公司研发的SaaS产品(www.ninedata.cloud),支持阿里云、华为云、腾讯云、AWS等多个云平台,技术指标上更领先,易用性也更好,免安装使用Canal:阿里开源的产品,影响力很大,不算是完整的产品,一般需要二次开发。数据安全管理,太重要了,我把这个单独列出来讲。先说软硬件的故障,比如服务器,磁盘肯定会坏,过保机器坏的可能性更高了,所以一定要做好灾备工作。再有是机房也有可能发生灾难,业界每个月都会有机房出问题,像火灾,地震,或者是断电、光缆断了等,都是机房故障。另外是软件Bug,一般是指数据库本身的软件Bug,成熟的数据库Oracle、MySQL每次发布都会说修复了几十个Bug,普通数据库的bug可能更多。如果用云平台会好一些,比人工管理更安全,云很注重服务器的故障检测,及时升级数据库最新的安全补丁,大型云厂商都有专业的团队去做这个事情。第二大类是人为故障,比如黑客入侵会造成数据泄露或者勒索,黑客通常会利用软件里面SQL注入,只要有注入的风险,黑客是全网扫描的,一旦扫到就进来了。还有是密码泄露,如果是弱密码就肯定能被黑客发现,只是等黑客啥时间要搞你。另外是如果有系统漏洞,黑客一般来说提前就知道了,会去看能不能攻破你。今天还有一点非常重要,就是内部数据泄露,还有删库跑路。在今天员工和公司对立的情况下,还是时常发生的,尤其是和主管关系不好,迟早都会发生删库跑路的事情,管数据库的员工还是要比较慎重。最后是数据误操作,比如说做软件的升级发布,不小心写错了,我做DBA的时候就碰到过非常多,每个月都会碰到程序员将提交的SQL给搞错了,需要恢复数据,这些都是人为的问题。数据备份是一定要做的,如果没有备份一定是不踏实的,总会发生问题。如果是非常重要的数据,要考虑异地容灾。如果是敏感的数据,要考虑数据加密,包括数据传输的加密。另外一点,流程规范非常重要,尤其是对生产系统的数据操作,最好有系统来支撑,靠人监督是没有用的。一定要通过平台来管控,不要把数据库账号密码放出去,包括只读权限的账号密码,这些都是系统隐患,说不准哪天你家公司的敏感数据就在网上被黑客贩卖…最后,希望让每个人用好数据和云,这个也是我们玖章算术公司的使命。NineData是我们研发的产品,希望可以帮助你解决相关的问题,欢迎访问www.ninedata.cloud,它提供了企业级的SQL开发流程管理,数据复制、数据备份、数据对比等功能。
基于 Flink CDC 实现海量数据的实时同步和转换
摘要:本文整理自 Apache Flink Committer,Flink CDC Maintainer,阿里巴巴高级开发工程师徐榜江(雪尽)在 5 月 21 日 Flink CDC Meetup 的演讲。主要内容包括:Flink CDC 技术传统数据集成方案的痛点基于 Flink CDC 的海量数据的实时同步和转换Flink CDC 社区发展点击查看直播回放 & 演讲PDF一、Flink CDC 技术CDC 是 Change Data Capture 的缩写,是一种捕获变更数据的技术,CDC 技术很早就存在,发展至今,业界的 CDC 技术方案众多,从原理上可以分为两大类:一类是基于查询的 CDC 技术 ,比如 DataX。随着当下场景对实时性要求越来越高,此类技术的缺陷也逐渐凸显。离线调度和批处理的模式导致延迟较高;基于离线调度做切片,因而无法保障数据的一致性;另外,也无法保障实时性。一类是基于日志的 CDC 技术,比如 Debezium、Canal、 Flink CDC。这种 CDC 技术能够实时消费数据库的日志,流式处理的模式可以保障数据的一致性,提供实时的数据,可以满足当下越来越实时的业务需求。上图为常见开源 CDC 的方案对比。可以看到 Flink CDC 的机制以及在增量同步、断点续传、全量同步的表现都很好,也支持全增量一体化同步,而很多其他开源方案无法支持全增量一体化同步。Flink CDC 是分布式架构,可以满足海量数据同步的业务场景。依靠 Flink 的生态优势,它提供了 DataStream API 以及 SQL API,这些 API 提供了非常强大的 transformation 能力。此外,Flink CDC 社区和 Flink 社区的开源生态非常完善,吸引了很多社区用户和公司在社区开发共建。Flink CDC 支持全增量一体化同步,为用户提供实时一致性快照。比如一张表里有历史的全量数据,也有新增的实时变更数据,增量数据不断地往 Binlog 日志文件里写,Flink CDC 会先同步全量历史数据,再无缝切换到同步增量数据,增量同步时,如果是新增的插入数据(上图中蓝色小块),会追加到实时一致性快照中;如果是更新的数据(上图中黄色小块),则会在已有历史数据里做更新。Flink CDC 相当于提供了实时物化视图,为用户提供数据库中表的实时一致性快照,用于可以对这些数据做进一步加工,比如清洗、聚合、过滤等,然后再写入下游。二、传统数据集成方案的痛点上图为传统数据入仓架构 1.0,主要使用 DataX 或 Sqoop 全量同步到 HDFS,再围绕 Hive 做数仓。此方案存在诸多缺陷:容易影响业务稳定性,因为每天都需要从业务表里查询数据;天级别的产出导致时效性差,延迟高;如果将调度间隔调成几分钟一次,则会对源库造成非常大的压力;扩展性差,业务规模扩大后极易出现性能瓶颈。上图为传统数据入仓 2.0 架构。分为实时和离线两条链路,实时链路做增量同步,比如通过 Canal 同步到 Kafka 后再做实时回流;全量同步一般只做一次,与每天的增量在 HDFS 上做定时合并,最后导入到 Hive 数仓里。此方式只做一次全量同步,因此基本不影响业务稳定性,但是增量同步有定时回流,一般只能保持在小时和天级别,因此它的时效性也比较低。同时,全量与增量两条链路是割裂的,意味着链路多,需要维护的组件也多,系统的可维护性会比较差。上图为传统 CDC ETL 分析架构。通过 Debezium、Canal 等工具采集 CDC 数据后,写入消息队列,再使用计算引擎做计算清洗,最终传输到下游存储,完成实时数仓、数据湖的构建。传统 CDC ETL 分析里引入了很多组件比如 Debezium、Canal,都需要部署和维护, Kafka 消息队列集群也需要维护。Debezium 的缺陷在于它虽然支持全量加增量,但它的单并发模型无法很好地应对海量数据场景。而 Canal 只能读增量,需要 DataX 与 Sqoop 配合才能读取全量,相当于需要两条链路,需要维护的组件也增加。因此,传统 CDC ETL 分析的痛点是单并发性能差,全量增量割裂,依赖的组件较多。三、基于 Flink CDC 的海量数据的实时同步和转换Flink CDC 的方案能够给海量数据的实时同步和转换带来什么改善?Flink CDC 2.0 在 MySQL CDC 上实现了增量快照读取算法,在最新的 2.2 版本里 Flink CDC 社区 将增量快照算法抽象成框架,使得其他数据源也能复用增量快照算法。增量快照算法解决了全增量一体化同步里的一些痛点。比如 Debezium 早期版本在实现全增量一体化同步时会使用锁,并且且是单并发模型,失败重做机制,无法在全量阶段实现断点续传。增量快照算法使用了无锁算法,对业务库非常友好;支持了并发读取,解决了海量数据的处理问题;支持了断点续传,避免失败重做,能够极大地提高数据同步的效率与用户体验。上图为全增量一体化的框架。整个框架简单来讲就是将数据库里的表按 PK 或 UK 切分成 一个个 chunk ,然后分给多个 task 做并行读取,即在全量阶段实现了并行读取。全量和增量能够自动切换,切换时通过无锁算法来做无锁一致性的切换。切换到增量阶段后,只需要单独的 task 去负责增量部分的数据解析,以此实现了全增量一体化读取。进入增量阶段后,作业不再需要的资源,用户可以修改作业并发将其释放。我们将全增量一体化框架与 Debezium 1.6 版本做 简单的 TPC-DS 读取测试对比,customer 单表数据量 6500 万,在 Flink CDC 用 8 个并发的情况下,吞吐提升了 6.8 倍,耗时仅 13 分钟,得益于并发读取的支持,如果用户需要更快的读取速度,用户可以增加并发实现。Flink CDC 在设计时,也考虑了面向存储友好的写入设计。在 Flink CDC 1.x 版本中,如果想实现 exactly-once 同步,需要配合 Flink 提供的 checkpoint 机制,全量阶段没有做切片,则只能在一个 checkpoint 里完成,这会导致一个问题:每个 checkpoint 中间要将这张表的全量数据吐给下游的 writer,writer 会将这张表的全量数据混存在内存中,会对其内存造成非常大的压力,作业稳定性也特别差。Flink CDC 2.0 提出了增量快照算法后,通过切片能够将 checkpoint 粒度降至 chunk, 并且 chunk 大小是用户可配置的,默认是 8096 条,用户可以将其调至更小,减轻 writer 的压力,减少内存资源的使用,提升下游写入存储时的稳定性。全增量一体化之后, Flink CDC 的入湖架构变得非常简单,且不会影响业务的稳定性;能够做到分钟级的产出,也就意味着可以实现近实时或实时分析;并发读取实现了更高的吞吐,在海量数据场景下有着不俗的表现;链路短,组件少,运维友好。有了 Flink CDC 之后,传统 CDC ETL 分析的痛点也得到了极大改善,不再需要 Canal、Kafka 消息队列等组件,只需要依赖 Flink,实现了全增量一体化同步和实时 ETL 加工的能力,且支持并发读取,整个架构链路短,组件少,易于维护。依托于 Flink DataStream API 以及易用的 SQL API ,Flink CDC 还提供了非常强大完善的 transformation 能力,且在 transformation 过程中能够保证 changelog 语义。在传统方案里,在 changelog 上做 transformation 并保证 changelog 语义是非常难以实现的。海量数据的实时同步和转换示例 1:Flink CDC 实现异构数据源的集成这个业务场景是业务表比如产品表和订单表在 MySQL 数据库里,物流表存在 PG 数据库里,要实现异构数据源的集成,并且在集成过程做打宽。需要将产品表、订单表与物流表做 Streaming Join 之后再将结果表写入库里。借助 Flink CDC,整个过程只需要用 5 行 Flink SQL 就能够实现。这里使用的下游存储是 Hudi,整个链路可以得到分钟级甚至更低的产出,使围绕 Hudi 做近实时的分析成为了可能。海量数据的实时同步和转换示例 2:Flink CDC 实现分库分表集成Flink CDC 对分库分表做了非常完善的支持,在声明 CDC 表时支持使用正则表达式匹配库名和表名,正则表达式意味着可以匹配多个库以及这多个库下的多张表。同时提供了 metadata column 的支持,可以知道数据来自于哪个 数据库、来自于哪张表,写入下游 Hudi 时,可以带上 metadata 声明的两个列,将 database_name、table_name 以及原始表中的 主键(例子中为 id 列)作为新的主键,只需三行 Flink SQL 即可实现分库分表数据的实时集成,非常简单。依托于 Flink 丰富的生态,能够实现很多上下游的扩展,Flink 自身就有丰富的 connector 生态。 Flink CDC 加入之后,上游有了更丰富的源可以摄取,下游也有丰富的目的端可以写入。海量数据的实时同步和转换示例 3:三行 SQL 实现单品累计销量实时排行榜这个 Demo 演示在无需任何依赖的前提下,通过 3 行 SQL 实现商品的实时排行榜。 首先在 Docker 里添加 MySQL 和 ElasticSearch 镜像, ElasticSearch 是目的端。将 Docker 拉起后,下载 Flink 包以及 MySQL CDC 和 ElasticSearch 的两个 SQL Connector jar。拉起 Flink 集群和 SQL Client。在 MySQL 内建库建表,灌入数据,更新后再用 Flink SQL 做一些实时加工和分析,写入 ES。在 MySQL 的数据库里构造一张订单表并插入数据。 上图第一行 SQL 是创建订单表,第二行是创建结果表,第三行是做 group by 的查询实现实时排行榜功能,再写入到第二行 SQL 创建的 ElasticSearch 表中。我们在 ElasticSearch 里做了可视化呈现,可以查看到随着 MySQL 中订单源源不断地更新,ElasticSearch 的排行榜会实时刷新。四、Flink CDC 社区发展在过去的一年多时间,社区发了 4 个大版本, contributor 和 commits数量在不断增长,社区也越来越活跃。我们一直坚持将核心的 feature 全部提供给社区版,比如 MySQL 的百亿级超大表、增量快照框架、MySQL 动态加表等高级功能。最新的 2.2 版本中同样新增了很多功能。首先,数据源方面,支持了 OceanBase、PolarDB-X、SqlServer、TiDB。此外,不断丰富了 Flink CDC 的生态,兼容了 Flink 1.13 和 1.14 集群,提供了增量快照读取框架。另外,支持了 MySQL CDC 动态加表以及对 MongoDB 做了完善,比如支持指定的集合,通过正则表达式使其更加灵活友好。除此之外,文档也是社区特别重要的一部分。我们提供了独立的版本化社区网站,在网站里不同版本对应不同版本的文档,提供了丰富的 demo 以及中英文的 FAQ,帮助新手快速入门。在社区的多个关键指标,比如创建的 issue 数,合并的 PR 数,Github Star 数上,Flink CDC 社区的表现都非常不错。Flink CDC 社区的未来规划主要包含以下三个方面:框架完善:增量快照框架目前只支持 MySQL CDC ,Oracle、PG 和 MongoDB 正在对接中,希望未来所有数据库都能够对接到更好的框架上;针对 Schema Evolution 和整库同步做了一些探索性的工作,成熟后将向社区提供。生态集成:提供更多 DB 和更多版本;数据湖集成方面希望链路更通畅;提供一些端到端的方案,用户无须关心 Hudi 和 Flink CDC 的参数。易用性:提供更多开箱即用的体验以及完善文档教程。问答Q:CDC 什么时候能够支持整库同步以及 DDL 的同步?A:正在设计中,因为它需要考虑到 Flink 引擎侧的支持与配合,不是单独在 Flink CDC 社区内开发就可以实现的,需要与 Flink 社区联动。Q:什么时候支持 Flink 1.15A:目前生产上的 Flink 集群还是以 1.13、1.14 为主。社区计划在 2.3 版本中支持 Flink 1.15,可以关注 issue:https://github.com/ververica/flink-cdc-connectors/issues/1363,也欢迎贡献。Q:有 CDC 结果表写入 Oracle 的实践吗?A:1.14 版本的 Flink 暂不支持,这个是因为 Sink 端的 JDBC Connector 不支持 Oracle dialect,Flink 1.15 版本的 JDBC Connector 已经支持了 Oracle dialect,1.15 版本的 Flink 集群可以支持。Q:下个版本能否支持读取 ES?A:还需要考察 transactional log 机制以及它是否适合作为 CDC 的数据源。Q:能做到单 job 监控多表 sink 多表吗?A:可以实现单作业监控多表 sink 到多个下游表;但如果是 sink 到多表,需要 DataStream 进行分流,不同的流写到不同的表。Q:Binlog 日志只有最近两个月的数据,能否支持先全量后增量读取?A:默认支持的就是先全量后增量,一般 binlog 保存七天或两三天都可以。Q:2.2 版本 MySQL 没有主键,全量如何同步?A:可以回退到不用增量快照框架;在增量快照框架上,社区已有组件的 issue,预计将在社区 2.3 版本提供支持。点击查看直播回放 & 演讲PDF 更多 Flink 相关技术问题,可扫码加入社区钉钉交流群第一时间获取最新技术文章和社区动态,请关注公众号~活动推荐阿里云基于 Apache Flink 构建的企业级产品-实时计算Flink版现开启活动:99 元试用 实时计算Flink版(包年包月、10CU)即有机会获得 Flink 独家定制卫衣;另包 3 个月及以上还有 85 折优惠!了解活动详情:https://www.aliyun.com/product/bigdata/sc
PB与各种数据库连接(一)
1.Power script 语言里的事务处理对象怎么理解PowerBuilder 程序与数据库之间传递信息的一个结构变量,共有15个成员.你可以详细列表它的所有成员看看它的组成,PB的应用程序会初始化一个全局的结构体变量,SQLCA,当然你也可以自定义一个自己的事务对象.1 DBMS string 所使用的数据库管理系统的名字,如Sybase,Oracle,ODBC。 2 Database string 要连接的数据库名字。 3 UserID string 连接数据库所用的用户名。有的DBMS不需要此项。 4 DBPass string 用户连接数据库的口令。 5 Lock string 这是数据库的保护级别,一般不必给出。 6 LogID string 登录到数据库服务器上的用户名,有的DBMS不需要此项,但Sybase和Oracle需要指定这个参数。 7 LogPass string 登录到数据库服务器上的用户口令。这个属性可设可不设,但Sybase和Oracle需要指定口令。 8 ServerName string 数据库服务器名。 9 AutoCommit boolean 指定是否将数据库设置成自动提交所有事务。默认是False,也就是说,必须在应用程序中进行事务管理,并在适当的时候对数据库提交事务。如果选择True,则每个事务都由系统自动提交。 10 DBParm string 用于向数据库传递特殊信息的属性。 11 SQLCode long 指示最近一次SQL操作失败或成功。它的取值为: 返回结果 0 无错误。 -1 出现一个错误。 100 没有检索到数据。 12 SQLNRows long 最近一次SQL操作影响的行数,数据库不同其含义也不同。 13 SQLDBCode long 数据库错误代码。不同的数据库的含义不同。 14 SQLErrText string 相应于SQLDBCode属性中错误码的文字说明。 15 SQLReturnData string 返回DBMS执行SQL的附加信息,不同的DBMS其值不同。2.我常见到做好的PB程序使用.ini文件来控制与数据库连接,可以方便的进行应用程序移植只需修改其中与数据库连接参数即可,我想问这些.ini文件只能使用手工编写吗,我见到很多.ini文件参数极多不象手写好象是机器生成的,不知道如何生成啊?请大家指教1、其实PB中在新建---点TOOL----FILE EDIT 可以生成(编辑)INI文件。最直接就是工具栏中的EDIT图标。2、机器生成代码是:点DATABASE图标-----选择连接方式。如:选 MSS MICROSOFT SQL SERVER 右键-NEW PROFILE 按要求填定一些参数后在--PREVIEW中可以看到代码,把它复制到INI文件中就行了。3.编程经验--PB数据库连接 通常在使用PB和数据库管理系统(DBMS)连接时,使用两种方式: 一、开放数据源接口(ODBC)连接 ODBC是通过支持美国微软公司开放服务结构(WOSA,Windows Open Services Architecture)中的一部分.在PB 中通过配置 SQLCA.DBMS=‘ODBC’对象的属性可使应用程序通过ODBC连接到数据库。 ODBC的具体配置包含了数据源、驱动程序类型、缓冲池等各种细节参数。例子:SQLCA.DBMS = "ODBC"
SQLCA.AutoCommit = False
SQLCA.DBParm="ConnectString='DSN=xxx_dsn;UID=xxx;PWD="',ConnectOption='SQL_DRIVER_CONNECT,SQL_DRIVER_NOPROMPT'" 二、专用数据库接口 每个数据库管理系统(DBMS)均提供相应的客户端驱动,为了更好的服务于数据库。 在这里我们使用的是Microsoft sqlserver 2000 的客户端程序。通过设置 SQLCA.DBMS ="MSS Microsoft SQL Server" 使客户端通过专用数据库接口连接到数据库。 该类接口的参数配置除了服务器名、数据库名、LogId、LogPass外还包含了连接协议等用户验证方式等,可通过服务器端或客户端的配置程序进行配置。SQLCA.dbms= "MSS Microsoft SQL Server" //接口类型
SQLCA.database= "master" //数据库
SQLCA.userid= ""
SQLCA.dbpass= ""
SQLCA.logid= "sa"
SQLCA.logpass= "xxxx"
SQLCA.serverName= ".\xxx"//服务器名
SQLCA.dbparm= "CommitOnDisconnect='No'"
SQLCA.autocommit= false
以上介绍的是PB与数据库连接时常用的方法。 往往根据不同的应用环境选择连接方式, 在单机环境下多采用的是ODBC连接,因为在发行环节上相对要容易些。在网络环境下多采用专用数据库连接,这样可以提高系统的可靠性与执行效率。专用数据库的发行环节往往要配上相应DBMS客户端的动态连接库(DLL),并把它存放在应用程序当前路径,或存放在%SYSTEM%下面。 我在开发中用到了对BLOB类型的数据进行存取,大家都知道POWERBUILDER 对BLOB字段的支持是有限的,每次只能处理32KB的数据块,如果一个文件大于32K必须编写相应的程序进行处理。当时的开发环境是PWOERBUILDER 9.0 + MS SQL SERVER 2000 +WINDOWS2000 。 当通过ODBC连接数据读取BLOB字段时,通过MESSAGEBOX弹出的字节数,观察到只要超过32K的文件出现,LEN(BLOB)就会出错,始终返回是32K字节的长度。当时很令人费解,随即怀疑系统问题,检查病毒,重新安装相应的开发环境,结果依旧,这才怀疑到ODBC可能存在问题,因为微软是问题专家,同时也是解决问题的专家。当通过SQLSERVER 专用接口连接后,一切正常。由此证明了ODBC 与专用数据库接口之间存在着一定的问题。 由此可说明各种同类技术之间的细微差别,可能会给我们带来意想不到的问题。在开发时还需谨慎、全面的考虑所用技术的可靠性。PowerBuilder 与 Sybase ASA 数据库连接问题QUOTE:Sybse ASA (Adaptive Server Anywhere) 在安装完整版 PowerBuilder 时可以选择安装 , PowerBuilder 的例程也是以 ASA 作为数据库的.一. ASA 数据库连接步骤QUOTE:1. 添加数据源。在WINDOWS中点击我的电脑,选择控制面版,选择ODBC数据源32位,选择系统DSN,点击"添加",然后选择你使用的数据库.2. 在工具条上点击DB PROFILE 选择已建立好的数据库连接,单击 EDIT 在弹出的对话框中选PREVIEW 里面就是连接数据库的语句,直接 COPY就OK了。二. ASA DB_profile写法 (以lin.com的tax例程为例)tax.ini
CODE:
[Database]
DBMS=ODBC
Database=test
DbParm=connectstring='dsn=sybase_lin;UID=dba;PWD=sql'
[Copy to clipboard]三. 打包所需文件参看以下文章:http://www.laozang.com/pbbbs/read.php?tid-233.html四. 容易出现的问题及解决方法导致ASA数据库无法连接的问题有以下几种:1、ODBC配置错误。没有配置数据源,数据库的用户名或口令错误等都会导致数据库无法连接。解决的办法:检查数据源的配置,如果没有在ODBC中配置数据源则按照向导添加数据源即可;口令错误只需改为正确的即可。2、连接时提示LOG文件错误。这样的问题大多出现在重装系统后、源码移植到其他系统、数据库文件路径改变之后。解决的办法:在创建ASA数据库的时候不创建LOG文件,如果已经创建了LOG文件则可以利用PB自带的工具Sybase central来去掉LOG文件和数据库文件的关联。操作步骤如下:a)启动Sybase central在左边的树型目录中选择Utilities;b)双击右边出现的条目中的change log file settings,直接next;c)点Browse选择需要去除log文件的数据库文件,选好后next;d)这里你会看到一些数据库的信息,log文件名,文件大小等。next;e)去掉Maintin the following transaction log file前面的对勾,Finish;f)配置ODBC连接数据库就可以了。3、数据库文件损坏。此类错误一般不常见,可能由于病毒破坏或误操作引起。解决的办法:如果以前有备份用备份文件覆盖原文件即可,如果没有备份文件就只能重新建库了!PB8如何使用OLE DB练到ASA数据库 (本文来自sybase网站 翻译 by 金色年华)原文出处:http://www.sybase.com.cn/cn/content/support/exp_jszc_pb_dbms_00017.htm Connecting to ASA Server (7.01 GA) via OLE DB in PowerBuilder 8
Adaptive Server Anywhere includes an OLE DB provider named ASAProv.
One of the features of ASAProv (dboledb7.dll) is that you do not
have to deploy ODBC. In other words, you can connect to an ASA
database either via OLE DB or via OLE DB/ODBC bridge.在pb中通过OLE DB连接到ASA数据库的步骤如下:在命令提示符下键入如下命令,启动 ASADEMO:dbsrv7 asademo -x tcpip -n asademo("asademo" - 你选择运行的数据库. 确定你没有正在运行一个同名的数据库,否则会出错) 有两个方法通过 OLE DB 连接到 ASA Server:1) OLE DB/ODBC bridge对于这种连接,你必须在数据库参数中,指定有效的 ODBC 数据源,连接的脚本如下:// Profile asa_oledb
SQLCA.DBMS = "OLE DB"
SQLCA.LogPass = "sql"
SQLCA.LogId = "dba"
SQLCA.AutoCommit = False
SQLCA.DBParm = "PROVIDER='ASAProv',DATASOURCE='asa'"2) OLE DB (不需要配置 ODBC)你可以通过一个扩展名为.udl的单独的文件,提供连接信息,在OLE DB中访问数据.这个文件与 Microsoft DataLink (.udl) file 类似. 你必须在系统中安装 Datalink API 以便于创建和使用 .udl 文件.创建 .udl文件的方法:在当前目录里的空白处单击鼠标右键,选择新建文件,选择 Microsoft Data Link.如果没有创建 Microsoft Data Link 的选项,需要创建一个文本文件,然后更改扩展名为 .udl.双击这个文件,添加连接信息.详情请访问msdn.microsoft.com 搜索 Data Link(此处原文有乱码,具体内容不详).在pb中使用 .udl 文件时,要确认在数据库中已经创建了 Catalog Tables (由pbcat...名字开头,然后用这几张表存放一些PB中的信息,如果这几张表无法创建,就会出现出错信息).你可以通过连接到系统中已有的ODBC 数据源. 如果这些表不存在,就会提示 Catalog Tables没有被创建.用以下信息来创建.udl文件:Provider Tab:
Select "Adaptive Server Anywhere Provider"
Connection Tab:
Location: eng=asademo;dbn=asademo;Links=TCPIP{};
Select radio button for一.连接步骤1)服务器安装SQL Server并启动,创建数据库。2)客户机安装SQL Server客户端。3)启动PB,配置数据源描述(通过直连接口,不建议用ODBC)。4)连接。二.DB_profile的写法在ini文件里设置[Database]
DBMS=MSS Microsoft SQL Server 6.x
Database=databasename
UserId=
LogID=
DatabasePassword=
LogPass=
ServerName=
AutoCommit=False在程序里用profilestring读取附加资料:ms sql server 配置文件设置ms sql server 配置文件设置:dbms="mss"//只须在*.ini文件中用这个代码便告诉pb使用的ms sql serverDatabase:所用数据库LogId:sql server的登陆账号LogPass=口令servername:允许数据库服务器连接的计算机名autocommit:控制pb 是工作在事务处理范围内还是工作在事务处理范围外dbparm:dbms的专用连接参数以下参数:language:在显示错误消息和日期格式时指定要使用的语言,在服务器上必须设置该直.lock:事务处理隔离层log:文本和图像数据的更新是否应该纪录到事务日志中.systemprocs:系统存储过程和用户定义的存储过程是否显示在各种各样的pb画板中.pbcatalogowner:pb存储中表的缺省者.async:允许在服务器上进行同步操作.0:同步,1:异步dbgettime:当async=1时,使用该参数设置用户在检索行时pb等待来自pb的响应的秒数.cursorlock:release和cursorscroll参数一起使用可设置光标的锁定选项.lock,opt,optval,readonlycursorscroll:设置光标的滚动选项.staticbind:控制pb是否在检索数据前获取dbms中的结果集描述.dbtextlimit:控制返回的文本字段的最大长度而不用将文本作为二进制大型数据对象来处理.appname:设置连接时所使用的应用程序名.host:设置连接似的工作站名.packetsize:设置使服务器向pb传送数据时所设置的包大小.secure:设置是否想使用winnt集成逻辑安全性和安全sql server连接 缺省0使用标准安全性,1集成安全性.PowerBuilder与Oracle 7.3 的连接PowerBuilder与Oracle的连接 PowerBuilder(PB)和Oracle分别是前端开发工具和RDBMS的主流产品。PB提供了两种与Oracle连接的接口:PowerSoft内置的数据库接口(Native Database Interface)和ODBC接口。本文介绍使用PB6.0内置Oracle接口的方法,包括数据描述文件的设置、存储过程的调用和存储过程作为数据窗口数据源的操作方法等内容,使用的RDBMS的Oracle 7.3。 PowerBuilder与Oracle的连接 假定已安装Oracle客户端应用程序。可用Sqlplus或Tnsping等是否能连接到Oracle数据库,确定在SQLNET配置文件中使用的数据库别名(Database Alias,又称服务器名,Server Name)。如有疑问,可在Oracle客户端程序目录下tnsname.ora文件中找到。另外保证搜索路径已包括SQLNET应用程序的安装目录(如C:\ORAWIN95\BIN)。进入PB的Database Profiles画笔,可看到所有已安装的数据库接口(PB6.0缺省安装的是Oracle 7.3版的接口,如使用低版本的Oracle,需在安装时指定),选择“O73 Oracle 7.3”,点击“New”按钮,进入Database Profile Setup对话框。在“Connection”页输入下列信息: Profile Name:为该Database Profile起一个有意义的名称; Server:@TNS:ServerName,其中ServerName为上述数据库别名(服务器名),如@TNS:ORA73; Login ID:Oracle数据库中实际的用户名,由于PB初次连接到数据库时要自动建立五个系统表(PowerBuilder Catalog Table:PBCATTBL,PBCATCOL,PBCATEDT,PBCATFMT,PBCATVLD,存储表的扩展属性),因此第一个连接到Oracle的用户必须具有建表、给PUBLIC角色授权等权限。例如可用SYSTEM用户进行第一次连接,此后的连接对用户则无特殊要求;Password:该用户的口令。 设置上述内容后,即可连上Oracle。为优化数据库连接,还可设置下列选项: Prompt for Database Information:连接时是否提示用户输入用户名和口令; Generate Trace:启动DB跟踪工具,跟踪连接; Thread Safe:开发需要多线程环境支持的分布式应用时,选择该项。缺省为未选,适用于非分布应用; PBDBMS:与存储过程调用方式有关的参数。Oracle为7.2或更高版本时,清除该选项,此时带IN OUT参数的存储过程可作为数据窗口数据源。7.2版本以下,选择该项,调用PBDBMS.Put-Line建立存储过程的SQL语句,缺省是选中; Commit on Disconnect:断开连接时,指定提交或回退未提交的事务; Case Sensitive:连接的Oracle服务器是否区分大小写。注意该项选中时,所有主键、包含主键的表名、外键须全为大写字符。 PowerBuilder Catalog Tables Owner:指定拥有PB系统表的用户,缺省为“SYSTEM”。如果要使用多种显示格式或有效性规则,可以在不同的用户下建立几套系统表;Table Criteria:指定满足哪些条件的表、视图和同义词可在“Select Tables”对话框中显示出来。例如DEV用户下销售子系统的表都以SALE开头,则可以用SALE%、DEV、“TABLE”、“VIEW”指定只显示DEV用户以SALE开头的表和视图; Asynchronous:选择该项,可在一个复杂的SQL语句执行过程中,返回第一行结果前,切换到其他操作; Number of Seconds to Wait:若上一项选中,还可进一步通过该项指定检索数据时,等待数据库响应的时间; Retrieve Blocking Factor:决定数据窗口对象一次可从数据库取出的记录数; Number of SQL Staments Cached:PB可将数据窗口对象产生的SQL语句和嵌入式SQL语句保存在SQL语句缓冲区,该参数指定缓冲区为PB保留的SQL语句数目。该数值可由下式计算:SQLCache=服务器OPEN—CURSORS数-5(保留的游标数)-本连接预期使用的最大游标数; Disable Bind:指定是否将输入变量与SQL语句绑定,此参数影响PB为数据窗口对象生成INSERT语句的方式; Static Bind:数据窗口对象检索数据前是否检测SELECT语句的合法性; 在Syntax页,还可指定日期、时间的格式等。在Preview页可查看Database Profile设置对应的PowerScript写法。Oracle存储过程的使用 归纳起来PB6.0中调用Oracle存储过程有以下四种方法。 方法一:以关键字RPCFUNC声明存储过程; 方法二:以DECLARE PROCEDURE语句声明存储过程; 方法三:以动态SQL语句实现; 方法四:调用函数PBDBMS.Put-Line。