@Autowired
@Autowired是一种注解,可以对成员变量、方法和构造函数进行标注,来完成自动装配的工作,@Autowired标注可以放在成员变量上,也可以放在成员变量的set方法上,也可以放在任意方法上表示,自动执行当前方法,如果方法有参数,会在IOC容器中自动寻找同类型参数为其传值。通过 @Autowired的使用来消除 set ,get方法。
springboot mybatis, mybatisplus,pagehelper集成
1、@controller 控制器(web层)
用于标注控制层,相当于struts中的action层
2、@service 服务(service层)
用于标注服务层,主要用来进行业务的逻辑处理
3、@repository(dao层)
用于标注数据访问层,也可以说用于标注数据访问组件,即DAO组件.
4、@component (把普通pojo实例化到spring容器中,相当于配置文件中的 )
泛指各种组件,就是说当我们的类不属于各种归类的时候(不属于@Controller、@Services等的时候),我们就可以使用@Component来标注这个类。
ssm
1.1、Spring
Spring是一个开源框架,Spring是于2003 年兴起的一个轻量级的Java 开发框架,由Rod Johnson 在其著作Expert One-On-One J2EE Development and Design中阐述的部分理念和原型衍生而来。它是为了解决企业应用开发的复杂性而创建的。Spring使用基本的JavaBean来完成以前只可能由EJB完成的事情。然而,Spring的用途不仅限于服务器端的开发。从简单性、可测试性和松耦合的角度而言,任何Java应用都可以从Spring中受益。 简单来说,Spring是一个轻量级的控制反转(IoC)和面向切面(AOP)的容器框架。
1.2、SpringMVC
Spring MVC属于SpringFrameWork的后续产品,已经融合在Spring Web Flow里面。Spring MVC 分离了控制器、模型对象、分派器以及处理程序对象的角色,这种分离让它们更容易进行定制。
1.3、MyBatis
MyBatis 本是apache的一个开源项目iBatis, 2010年这个项目由apache software foundation 迁移到了google code,并且改名为MyBatis 。MyBatis是一个基于Java的持久层框架。iBATIS提供的持久层框架包括SQL Maps和Data Access Objects(DAO)MyBatis 消除了几乎所有的JDBC代码和参数的手工设置以及结果集的检索。MyBatis 使用简单的 XML或注解用于配置和原始映射,将接口和 Java 的POJOs(Plain Old Java Objects,普通的 Java对象)映射成数据库中的记录。
CRUD
mybatis动态参数 注解
springboot 事务
- 事务说明
在Spring中,事务有两种实现方式,分别是编程式事务管理和声明式事务管理两种方式。
编程式事务管理:编程式事务管理使用TransactionTemplate或者直接使用底层的PlatformTransactionManager。对于编程式事务管理,spring推荐使用TransactionTemplate。
声明式事务管理:建立在AOP之上的。其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。
声明式事务管理不需要入侵代码,通过@Transactional就可以进行事务操作,更快捷而且简单。推荐使用 - 如何使用
在Mybatis中使用事务,非常简单,只需要在函数增加注解@Transactional,无需任何配置。我们通过在controller层增加事务例子看下:
@Transactional可以作用于接口、接口方法、类以及类方法上。当作用于类上时,该类的所有 public 方法将都具有该类型的事务属性,同时,我们也可以在方法级别使用该标注来覆盖类级别的定义。因此可以在Service层和Controller层使用,上述例子我们在Controller层实现,我们模拟了一个订单提交的接口,其中JsonBean是统一返回错误码基类,ErrorCodeException是自定义异常。
1.通过@Transactional,实现了事务操作。
2.Spring的AOP即声明式事务管理默认是针对unchecked exception回滚。也就是默认对RuntimeException()异常或是其子类进行事务回滚;checked异常,即Exception可try{}捕获的不会回滚,因此对于我们自定义异常,通过rollbackFor进行设定,后续会单独讲
3.如果我们需要捕获异常后,同时进行回滚,通过TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();进行手动回滚操作。
4.使用Object savePoint = TransactionAspectSupport.currentTransactionStatus().createSavepoint();
设置回滚点,使用TransactionAspectSupport.currentTransactionStatus().rollbackToSavepoint(savePoint);回滚到savePoint。
3.常用配置
- 事务属性
事务隔离级别
隔离级别是指若干个并发的事务之间的隔离程度。TransactionDefinition 接口中定义了五个表示隔离级别的常量:
TransactionDefinition.ISOLATION_DEFAULT:这是默认值,表示使用底层数据库的默认隔离级别。对大部分数据库而言,通常这值就是TransactionDefinition.ISOLATION_READ_COMMITTED。
TransactionDefinition.ISOLATION_READ_UNCOMMITTED:该隔离级别表示一个事务可以读取另一个事务修改但还没有提交的数据。该级别不能防止脏读,不可重复读和幻读,因此很少使用该隔离级别。比如PostgreSQL实际上并没有此级别。
TransactionDefinition.ISOLATION_READ_COMMITTED:该隔离级别表示一个事务只能读取另一个事务已经提交的数据。该级别可以防止脏读,这也是大多数情况下的推荐值。
TransactionDefinition.ISOLATION_REPEATABLE_READ:该隔离级别表示一个事务在整个过程中可以多次重复执行某个查询,并且每次返回的记录都相同。该级别可以防止脏读和不可重复读。
TransactionDefinition.ISOLATION_SERIALIZABLE:所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别。
事务传播行为
所谓事务的传播行为是指,如果在开始当前事务之前,一个事务上下文已经存在,此时有若干选项可以指定一个事务性方法的执行行为。在TransactionDefinition定义中包括了如下几个表示传播行为的常量:
TransactionDefinition.PROPAGATION_REQUIRED:如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。这是默认值。
TransactionDefinition.PROPAGATION_REQUIRES_NEW:创建一个新的事务,如果当前存在事务,则把当前事务挂起。
TransactionDefinition.PROPAGATION_SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
TransactionDefinition.PROPAGATION_NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,则把当前事务挂起。
TransactionDefinition.PROPAGATION_NEVER:以非事务方式运行,如果当前存在事务,则抛出异常。
TransactionDefinition.PROPAGATION_MANDATORY:如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。
TransactionDefinition.PROPAGATION_NESTED:如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于TransactionDefinition.PROPAGATION_REQUIRED。
事务超时
所谓事务超时,就是指一个事务所允许执行的最长时间,如果超过该时间限制但事务还没有完成,则自动回滚事务。在 TransactionDefinition 中以 int 的值来表示超时时间,其单位是秒。
默认设置为底层事务系统的超时值,如果底层数据库事务系统没有设置超时值,那么就是none,没有超时限制。
事务只读属性
只读事务用于客户代码只读但不修改数据的情形,只读事务用于特定情景下的优化,比如使用Hibernate的时候。
默认为读写事务。
mybatisplus
https://blog.csdn.net/DDDDeng_/article/details/107707545
如果你号称自己精通ssm
那会问你:
aop,ioc 默写全文
声明事务以及事物传播的方式 默写全文
spring事务不生效的原因 默写全文
什么是数据库连接池,你怎么用(hikari,druid)
背诵并默写全文
spring多数据源配置 请简述主要步骤.
代理模式:
静态代理的好处:
*可以使得我们的真实角色更加纯粹 . 不再去关注一些公共的事情 .
*公共的业务由代理来完成 . 实现了业务的分工 ,
*公共业务发生扩展时变得更加集中和方便 .
缺点 :
*类多了 , 多了代理类 , 工作量变大了 . 开发效率降低 .
我们在不改变原来的代码的情况下,实现了对原有功能的增强,这是AOP中最核心的思想。
动态代理:
*动态代理的角色和静态代理的一样 .
*动态代理的代理类是动态生成的 . 静态代理的代理类是我们提前写好的
*动态代理分为两类 : 一类是基于接口动态代理 , 一类是基于类的动态代理
*基于接口的动态代理----JDK动态代理
*基于类的动态代理–cglib
*现在用的比较多的是 javasist 来生成动态代理 . 百度一下javasist
*我们这里使用JDK的原生代码来实现,其余的道理都是一样的!
JDK的动态代理需要了解两个类
核心 : InvocationHandler和Proxy。
InvocationHandler是由代理实例的调用处理程序实现的接口。
每个代理实例都有一个关联的调用处理程序。当在代理实例上调用方法时,方法调用将被编码并分派到其调用处理程序的involved方法。
Proxy提供了创建动态代理类和实例的静态方法,它也是由这些方法创建的所有动态代理类的超类。
每个代理实例都有一个关联的调用处理程序对象,它实现了接口InvocationHandler。
核心:一个动态代理 , 一般代理某一类业务 , 一个动态代理可以代理多个类,代理的是接口!
动态代理的好处
*静态代理有的它都有,静态代理没有的,它也有!
*可以使得我们的真实角色更加纯粹 . 不再去关注一些公共的事情 .
*公共的业务由代理来完成 . 实现了业务的分工 ,
*公共业务发生扩展时变得更加集中和方便 .
*一个动态代理 , 一般代理某一类业务
*一个动态代理可以代理多个类,代理的是接口!
aop
AOP(Aspect Oriented Programming)意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
Aop在Spring中的作用
提供声明式事务;允许用户自定义切面
SpringAOP中,通过Advice定义横切逻辑,Spring中支持5种类型的Advice:
Spring的Aop就是将公共的业务 (日志 , 安全等) 和领域业务结合起来 , 当执行领域业务时 , 将会把公共业务加进来 . 实现公共业务的重复利用 . 领域业务更纯粹 , 程序猿专注领域业务 , 其本质还是动态代理 .
Aop实现方式:
第一种方式:通过 Spring API 实现
第二种方式:自定义类来实现Aop
第三种方式:使用注解实现
AOP 解决了什么问题
通过上面的分析可以发现,AOP 主要用来解决:在不改变原有业务逻辑的情况下,增强横切逻辑代码,根本上解耦合,避免横切逻辑代码重复。
AOP为什么叫面向切面编程
切:指的是横切逻辑,原有业务逻辑代码不动,只能操作横切逻辑代码,所以面向横切逻辑
面:横切逻辑代码往往要影响的是很多个方法,每个方法如同一个点,多个点构成一个面。这里有一个面的概念
ioc
IoC (Inversion of control )控制反转/反转控制。它是一种思想不是一个技术实现。描述的是:Java 开发领域对象的创建以及管理的问题。
使用 IoC 思想的开发方式:不通过 new 关键字来创建对象,而是通过 IoC 容器(Spring 框架) 来帮助我们实例化对象。我们需要哪个对象,直接从 IoC 容器里面过去即可。
为什么叫控制反转
控制 :指的是对象创建(实例化、管理)的权力
反转 :控制权交给外部环境(Spring 框架、IoC 容器)
IoC 解决了什么问题
IoC 的思想就是两方之间不互相依赖,由第三方容器来管理相关资源。这样有什么好处呢?
对象之间的耦合度或者说依赖程度降低;
资源变的容易管理;比如你用 Spring 容器提供的话很容易就可以实现一个单例。
IoC 和 DI 的区别
IoC(Inverse of Control:控制反转)是一种设计思想 或者说是某种模式。这个设计思想就是 将原本在程序中手动创建对象的控制权,交由 Spring 框架来管理。 IoC 在其他语言中也有应用,并非 Spring 特有。IoC 容器是 Spring 用来实现 IoC 的载体, IoC 容器实际上就是个 Map(key,value),Map 中存放的是各种对象。
IoC 最常见以及最合理的实现方式叫做依赖注入(Dependency Injection,简称 DI)。
IOC 和 DI 描述的是同一件事情(对象实例化以及依赖关系的维护),只不过角度不同。
IOC (Inversion of control ) 控制反转/反转控制。是站在对象的角度,对象实例化以及管理的权限(反转)交给了容器。
DI (Dependancy Injection)依赖注入。是站在容器的角度,容器会把对象依赖的其他对象注入(送进去)。例如:对象A 实例化过程中因为声明了一个B类型的属性,那么就需要容器把B对象注入到A中。
声明事务以及事物传播的方式
事务传播行为用来描述由某一个事务传播行为修饰的方法被嵌套进另一个方法时事务如何传播。
spring声明式事务中七种传播行为
什么是数据库连接池,你怎么用(hikari,druid)
1、数据库连接池的基本思想:
就是为数据库连接建立一个“缓冲池”。预先在缓冲池中放入一定数量的连接,当需要建立数据库连接时,只需从“缓冲池”中取出一个,使用完毕之后再放回去。
数据库连接池负责分配、管理和释放数据库连接,它允许应用程序重复使用一个现有的数据库连接,而不是重新建立一个。
数据库连接池在初始化时将创建一定数量的数据库连接放到连接池中,这些数据库连接的数量是由最小数据库连接数来设定的。无论这些数据库连接是否被使用,连接池都将一直保证至少拥有这么多的连接数量。连接池的最大数据库连接数量限定了这个连接池能占有的最大连接数,当应用程序向连接池请求的连接数超过最大连接数量时,这些请求将被加入到等待队列中。
2、数据库连接池的工作原理:
3、数据库连接池技术的优点:
(1)资源重用
由于数据库连接得以重用,避免了频繁创建,释放连接引起的大量性能开销。在减少系统消耗的基础上,另一方面也增加了系统运行环境的平稳性。
(2)更快的系统反应速度
数据库连接池在初始化过程中,往往已经创建了若干数据库连接置于连接池中备用。此时连接的初始化工作均已完成。对于业务请求处理而言,直接利用现有可用连接,避免了数据库连接初始化和释放过程的时间开销,从而减少了系统的响应时间
(3)新的资源分配手段
对于多应用共享同一数据库的系统而言,可在应用层通过数据库连接池的配置,实现某一应用最大可用数据库连接数的限制,避免某一应用独占所有的数据库资源
(4)统一的连接管理,避免数据库连接泄漏
在较为完善的数据库连接池实现中,可根据预先的占用超时设定,强制回收被占用连接,从而避免了常规数据库连接操作中可能出现的资源泄露
Druid(德鲁伊)数据库连接池
Druid是阿里巴巴开源平台上一个数据库连接池实现,它结合了C3P0、DBCP、Proxool等DB池的优点,同时加入了日志监控,可以很好的监控DB池连接和SQL的执行情况,可以说是针对监控而生的DB连接池,可以说是目前最好的连接池之一
spring多数据源配置步骤
- 在Spring注册多个数据源
- 新建类继承AbstractRoutingDataSource,并配置
- 给TheadLocal赋值
(过时)
SQL
mysql innodb原理
一、先从B+Tree入手
B+树的特性
B+树是为磁盘或其他直接存取辅助设备而设计的一种平衡查找树,在B+树中,所有记录节点都是按键值的大小顺序存放在同一层的叶节点中,各叶节点指针进行连接。
二、InnoDB数据页结构
1.页介绍
页是InnoDB存储引擎管理数据库的最小磁盘单位。
页类型为B-Tree node的页,存放的即是表中行的实际数据了。InnoDB中的页大小为16KB,且不可以更改InnoDB可以将一条记录中的某些数据存储在真正的数据页面之外,即作为行溢出数据。MySQL的varchar数据类型可以存放65535个字节,但实际只能存储65532个。同时InnoDB是B+树结构的,因此每个页中至少应该有两个行记录,否则失去了B+树的意义,变成了链表,所以一行记录最大长度的阈值是8098,如果大于这个值就会将其存到溢出行中。
2.InnoDB数据页组成部分
File Header(文件头)
Page Header(页头)
Infimun + Supremum Records
User Records(用户记录,即行记录)
Free Space(空闲空间)
Page Directory(页目录)
File Trailer(文件结尾信息)
这也是我摘抄的书上的内容,下面我只介绍一下会帮助理解底层原理的部分。
1.在File header中,FIL+PAGE_PREV,FIL_PAGE_NEXT两个表示当前页的上一页和下一页由此可以看出叶子节点是双向链表串起来的。如下图
2.Infimum和Supremum记录
在InnoDB存储引擎中,每个数据页中有两个虚拟的行记录,用来限定记录的边界。Infimum记录是比该页中任何主键值都要小的值,Supremum指比任何可能大的值还要大的值。这两个值在页创建时被建立,并且在任何情况下不会被删除。
由上图可以看出,行记录是记录在页中的,同时是在页内行记录之间也是双向链表链接的(在网上有看到说是单链表的)
3.Page Directory
页目录中存放了记录的相对位置,有些时候这些记录指针称为Slots(槽)或者目录槽,与其他数据库不同的是,InnoDB并不是每个记录拥有一个槽,InnoDB中的槽是一个稀疏目录,即一个槽中可能属于多个记录,最少属于4个目录,最多属于8个目录。槽中记录按照键顺序存放,这样可以利用二叉查找迅速找到记录的指针。但是由于InnoDB中的Slots是稀疏目录,二叉查找的结果只是一个粗略的结果,所以InnoDB必须通过recorder header中的next_record来继续查找相关记录。同时slots很好的解释了recorder header中的n_owned值的含义,即还有多少记录需要查找,因为这些记录并不包括在slots中。
三、查询B+树索引的流程
首先通过B+树索引找到叶节点,再找到对应的数据页,然后将数据页加载到内存中,通过二分查找Page Directory中的槽,查找出一个粗略的目录,然后根据槽的指针指向链表中的行记录,之后在链表中依次查找。
需要注意的地方是,B+树索引不能找到具体的一条记录,而是只能找到对应的页。把页从磁盘装入到内存中,再通过Page Directory进行二分查找,同时此二分查找也可能找不到具体的行记录(有可能会找到),只是能找到一个接近的链表中的点,再从此点开始遍历链表进行查找。
四、聚簇索引与非聚簇索引
B+树索引可以分为聚集索引和辅助索引,他们不同点是,聚集索引的行数据和主键B+树存储在一起,辅助索引只存储辅助键和主键。
1.聚集索引
聚集索引是按每张表的主键构造的一颗B+树,并且叶节点中存放着整张表的行记录数据,因此也让聚集索引的节点成为数据页,这个特性决定了索引组织表中数据也是索引的一部分。由于实际的数据页只能按照一颗B+树进行排序,所以每张表只能拥有一个聚集索引。查询优化器非常倾向于采用聚集索引,因为其直接存储行数据,所以主键的排序查询和范围查找速度非常快。
不是物理上的连续,而是逻辑上的,不过在刚开始时数据是顺序插入的所以是物理上的连续,随着数据增删,物理上不再连续。
2.辅助索引
辅助索引页级别不包含行的全部数据。叶节点除了包含键值以外,每个叶级别中的索引行中还包含了一个书签,该书签用来告诉InnoDB哪里可以找到与索引相对应的行数据。其中存的就是聚集索引的键。
辅助索引的存在并不影响数据在聚集索引的结构组织。InnoDB会遍历辅助索引并通过叶级别的指针获得指向主键索引的主键,然后通过主键索引找到一个完整的行记录。当然如果只是需要辅助索引的值和主键索引的值,那么只需要查找辅助索引就可以查询出索要的数据,就不用再去查主键索引了。
五、索引的管理
索引在创建或者删除时,MySQL会先创建一个新的临时表,然后把数据导入临时表,删除原表,再把临时表更名为原表名称。
但是在InnoDB Plugin版本开始,支持快速创建索引。其原理是先在InnoDB上加一个s锁,在创建过程中不需要建表,所以速度会很快。创建过程中由于加了s锁,所以只能进行读操作,不能写操作。
show index form table;是查看表中索引的信息的。
Table:索引所在的表名
Non_unique:非唯一的索引,可以看到primary key 是0,因为必须是唯一的
Key_name:索引名称
Seq_in_index:索引中该列的位置
Column_name:索引的列
Collation:列以什么方式存储在索引中。可以是A或者NULL,B+树索引总是A,即排序的。
Cardinality:表示索引中唯一值的数目的估计值。如果非常小,那么需要考虑是否还需要建立这个索引了。优化器也会根据这个值来判断是否使用这个索引。
Sub_part:是否是列的部分被索引。100表示只索引列的前100个字符。
Packed:关键字如果被压缩。
Null:是否索引的列含有NULL值。
Index_type:索引的类型。InnoDB只支持B+树索引,所以显示BTREE
六、Hash索引
InnoDB中自适应哈希索引使用的是散列表的数据结构,并且DBA无法干预。
其实这一部分的原理,非常简单,在此就不做过多介绍了
mysql事务隔离级别
什么是事务?
事务(Transaction),一般是指要做的或所做的事情。在计算机术语中是指访问并可能更新数据库中各种数据项的一个程序执行单元。事务通常由高级数据库操作语言或编程语言(如 SQL,C++ 或 Java)书写的用户程序的执行所引起,并用形如begin transaction和end transaction语句(或函数调用)来界定。事务由事务开始(begin transaction)和事务结束(end transaction)之间执行的全部操作组成。
对于 MySQL 数据库来说,事务是指以执行start transaction命令开始,到执行commit或者rollback命令结束之间的全部 SQL 操作,如果这些 SQL 操作全部执行成功,则执行commit命令提交事务,表示事务执行成功;如果这些 SQL 操作中任一操作执行失败,则执行rollback命令回滚事务,表示事务执行失败,并将数据库回滚到执行start transaction命令之前的状态。特别地,在现阶段的 MySQL 数据库中,仅 InnoDB 和 NDB 两个存储引擎是支持事务的。
以 MySQL 的 InnoDB 存储引擎为例,其默认是开启autocommit配置的,即自动提交事务。在自动提交模式下,如果没有以start transaction显式地开始一个事务,那么每条 SQL 语句都会被当做一个事务执行提交操作。通过set autocommit = 0命令可以关闭自动提交模式,如果关闭了autocommit,则所有的 SQL 语句都在一个事务中,直到执行commit或rollback,该事务结束,并同时开始另外一个新的事务。在此,需要我们注意的是,autocommit参数是针对连接的,在一个连接中修改了参数,不会对其他连接产生影响。
除此之外,在 MySQL 中,还存在一些特殊的命令,如果在事务中执行了这些命令,则会强制执行commit命令提交事务,如 DDL 语句(create table/drop table/alter table)、lock tables语句等。不过,我们常用的select、insert、update和delete命令,都不会强制提交事务。
事务的四个特性:ACID
通过上面的内容,我们已经知道了什么是事务,但实际上,事务还具有以下四个特性,即:
*原子性(Atomicity)
*一致性(Consistency)
*隔离性(Isolation)
*持久性(Durability)
按照严格的标准,只有同时满足 ACID 特性才是事务,但是在各大数据库厂商的实现中,真正满足 ACID 的事务少之又少。例如,MySQL 的 NDB 事务不满足持久性和隔离性;InnoDB 默认的事务隔离级别是“可重复读”,不满足隔离性;Oracle 默认的事务隔离级别为“读提交”,不满足隔离性等等,因此与其说 ACID 是事务必须满足的条件,不如说它们是衡量事务的四个维度。
我们刚刚提到的“隔离级别”在后文中会进行详细的讲解,下面我们先详细介绍 ACID 特性及其实现原理。
原子性(Atomicity)
定义
原子性,是指一个事务是一个不可分割的工作单位,其中的操作要么都做,要么都不做;如果事务中一个 SQL 语句执行失败,则已执行的语句也必须回滚,数据库回退到事务开始前的状态。
实现原理:undo log
在说明原子性的实现原理之前,我们先来了解一下 MySQL 的事务日志。MySQL 的日志有很多种,如二进制日志、错误日志、查询日志、慢查询日志等,此外 InnoDB 存储引擎还提供了两种事务日志:redo log(重做日志)和undo log(回滚日志)。其中,redo log用于保证事务持久性;undo log则是事务原子性和隔离性实现的基础。
实现原子性的关键,是当事务回滚时能够撤销所有已经成功执行的 SQL 语句。InnoDB 实现回滚,靠的是undo log:当事务对数据库进行修改时,InnoDB 会生成对应的undo log;如果事务执行失败或调用了rollback,导致事务需要回滚,便可以利用undo log中的信息将数据回滚到修改之前的样子。
undo log属于逻辑日志,它记录的是 SQL 执行的相关信息。当发生回滚时,InnoDB 会根据undo log的内容做与之前相反的工作:对于每个insert,回滚时会执行delete;对于每个delete,回滚时会执行insert;对于每个update,回滚时会执行一个相反的update,把数据改回去。
以update操作为例:当事务执行update时,其生成的undo log中会包含被修改行的主键(以便知道修改了哪些行)、修改了哪些列、这些列在修改前后的值等信息,回滚时便可以使用这些信息将数据还原到update之前的状态。
持久性(Durability)
定义
持久性,是指事务一旦提交,它对数据库的改变就应该是永久性的,接下来的其他操作或故障不应该对其有任何影响。
实现原理:redo log
redo log和undo log都属于InnoDB 的事务日志。下面先聊一下redo log存在的背景。
InnoDB 作为 MySQL 的存储引擎,数据是存放在磁盘中的,但如果每次读写数据都需要磁盘 IO,效率会很低。为此,InnoDB提供了缓存(Buffer Pool),Buffer Pool中包含了磁盘中部分数据页的映射,作为访问数据库的缓冲:当从数据库读取数据时,会首先从Buffer Pool中读取,如果Buffer Pool中没有,则从磁盘读取后放入Buffer Pool;当向数据库写入数据时,会首先写入Buffer Pool,Buffer Pool中修改的数据会定期刷新到磁盘中,这一过程称为“刷脏”。
Buffer Pool的使用大大提高了读写数据的效率,但是也带了新的问题:如果 MySQL 宕机,而此时Buffer Pool中修改的数据还没有刷新到磁盘,就会导致数据的丢失,事务的持久性无法保证。
于是,redo log被引入来解决这个问题:当数据修改时,除了修改Buffer Pool中的数据,还会在redo log记录这次操作;当事务提交时,会调用fsync接口对redo log进行刷盘。如果 MySQL 宕机,重启时可以读取redo log中的数据,对数据库进行恢复。redo log采用的是 WAL(Write-ahead logging,预写式日志),所有修改先写入日志,再更新到Buffer Pool,保证了数据不会因 MySQL 宕机而丢失,从而满足了持久性要求。
既然redo log也需要在事务提交时将日志写入磁盘,为什么它比直接将Buffer Pool中修改的数据写入磁盘(即刷脏)要快呢?主要有以下两方面的原因:
*刷脏是随机IO,因为每次修改的数据位置随机,但写redo log是追加操作,属于顺序 IO。
*刷脏是以数据页(Page)为单位的,MySQL 默认页大小是 16 KB,一个Page上一个小修改都要整页写入;而redo log中只包含真正需要写入的部分,无效 IO 大大减少。
我们知道,在 MySQL 中还存在binlog(二进制日志)也可以记录写操作并用于数据的恢复,但二者是有着根本的不同的:
*作用不同:redo log是用于crash recovery的,保证 MySQL 宕机也不会影响持久性;binlog是用于point-in-time recovery的,保证服务器可以基于时间点恢复数据,此外binlog还用于主从复制。
*层次不同:redo log是 InnoDB 存储引擎实现的,而binlog是 MySQL 的服务器层实现的,同时支持 InnoDB 和其他存储引擎。
*内容不同:redo log是物理日志,内容基于磁盘的Page;binlog的内容是二进制的,根据binlog_format参数的不同,可能基于 SQL 语句、基于数据本身或者二者的混合。
*写入时机不同:binlog在事务提交时写入;redo log的写入时机相对多元:
*前面曾提到当事务提交时会调用fsync对redo log进行刷盘,这是默认情况下的策略,修改innodb_flush_log_at_trx_commit参数可以改变该策略,但事务的持久性将无法保证。
*除了事务提交时,还有其他刷盘时机,如master thread每秒刷盘一次redo log等,这样的好处是不一定要等到commit时刷盘,commit速度大大加快。
隔离性(Isolation)
定义
与原子性、持久性侧重于研究事务本身不同,隔离性研究的是不同事务之间的相互影响。隔离性,是指事务内部的操作与其他事务是隔离的,并发执行的各个事务之间不能互相干扰。严格的隔离性,对应了事务隔离级别中的Serializable(可串行化),但实际应用中出于性能方面的考虑很少会使用可串行化。
隔离性追求的是并发情形下事务之间互不干扰。简单起见,我们仅考虑最简单的读操作和写操作(暂时不考虑带锁读等特殊操作),那么隔离性的探讨,主要可以分为两个方面:
*(一个事务)写操作对(另一个事务)写操作的影响:锁机制保证隔离性
*(一个事务)写操作对(另一个事务)读操作的影响:MVCC 保证隔离性
锁机制
首先来看两个事务的写操作之间的相互影响。隔离性要求同一时刻只能有一个事务对数据进行写操作,InnoDB 通过锁机制来保证这一点。
锁机制的基本原理可以概括为:事务在修改数据之前,需要先获得相应的锁;获得锁之后,事务便可以修改数据;该事务操作期间,这部分数据是锁定的,其他事务如果需要修改数据,需要等待当前事务提交或回滚后释放锁。
按照粒度,锁可以分为表锁、行锁以及其他位于二者之间的锁。表锁在操作数据时会锁定整张表,并发性能较差;行锁则只锁定需要操作的数据,并发性能好。但是由于加锁本身需要消耗资源(获得锁、检查锁、释放锁等都需要消耗资源),因此在锁定数据较多情况下使用表锁可以节省大量资源。MySQL中不同的存储引擎支持的锁是不一样的,例如 MyIsam 只支持表锁,而 InnoDB 同时支持表锁和行锁,且出于性能考虑,绝大多数情况下使用的都是行锁。
此时,查看锁的情况:
通过上述命令可以查看事务24052和24053占用锁的情况,其中lock_type为RECORD,代表锁为行锁(记录锁);lock_mode为X,代表排它锁(写锁)。除了排它锁(写锁)之外,MySQL 中还有共享锁(读锁)的概念。
介绍完写操作之间的相互影响,下面讨论写操作对读操作的影响。
脏读、不可重复读和幻读
首先来看并发情况下,读操作可能存在的三类问题。
*脏读:当前事务(A)中可以读到其他事务(B)未提交的数据(脏数据),这种现象是脏读。举例如下(以账户余额表为例):
*不可重复读:在事务A中先后两次读取同一个数据,两次读取的结果不一样,这种现象称为不可重复读。脏读与不可重复读的区别在于:前者读到的是其他事务未提交的数据,后者读到的是其他事务已提交的数据。举例如下:
*幻读:在事务A中按照某个条件先后两次查询数据库,两次查询结果的条数不同,这种现象称为幻读。不可重复读与幻读的区别可以通俗的理解为:前者是数据变了,后者是数据的行数变了。举例如下:
事务隔离级别
SQL标准中定义了四种隔离级别,并规定了每种隔离级别下上述几个问题是否存在。一般来说,隔离级别越低,系统开销越低,可支持的并发越高,但隔离性也越差。隔离级别与读问题的关系如下:
在实际应用中,读未提交在并发时会导致很多问题,而性能相对于其他隔离级别提高却很有限,因此使用较少。可串行化强制事务串行,并发效率很低,只有当对数据一致性要求极高且可以接受没有并发时使用,因此使用也较少。因此在大多数数据库系统中,默认的隔离级别是读已提交(如 Oracle)或可重复读(后文简称RR)。
可以通过如下两个命令分别查看全局隔离级别和本次会话的隔离级别:
*select @@global.tx_isolation # 查询全局隔离级别
*select @@tx_isolation # 查询本次会话隔离级别
InnoDB 默认的隔离级别是RR,后文会重点介绍RR。需要注意的是,在 SQL 标准中,RR是无法避免幻读问题的,但是 InnoDB 实现的RR避免了幻读问题。
MVCC
RR解决脏读、不可重复读、幻读等问题,使用的是 MVCC:MVCC 全称Multi-Version Concurrency Control,即多版本的并发控制协议。下面的例子很好的体现了 MVCC 的特点:在同一时刻,不同的事务读取到的数据可能是不同的(即多版本)—— 在 T5 时刻,事务A和事务C可以读取到不同版本的数据。
MVCC 最大的优点是读不加锁,因此读写不冲突,并发性能好。InnoDB 实现 MVCC,多个版本的数据可以共存,主要是依靠数据的隐藏列(也可以称之为标记位)和undo log。其中,数据的隐藏列包括了该行数据的版本号、删除时间、指向undo log的指针等等;当读取数据时,MySQL 可以通过隐藏列判断是否需要回滚并找到回滚需要的undo log,从而实现 MVCC;隐藏列的详细格式不再展开。
下面结合前文提到的几个问题分别说明。
*脏读
当事务A在 T3 时间节点读取zhangsan的余额时,会发现数据已被其他事务修改,且状态为未提交。此时事务A读取最新数据后,根据数据的undo log执行回滚操作,得到事务B修改前的数据,从而避免了脏读。
*不可重复读
当事务A在 T2 节点第一次读取数据时,会记录该数据的版本号(数据的版本号是以row为单位记录的),假设版本号为1;当事务B提交时,该行记录的版本号增加,假设版本号为2;当事务A在 T5 再一次读取数据时,发现数据的版本号2大于第一次读取时记录的版本号1,因此会根据undo log执行回滚操作,得到版本号为1时的数据,从而实现了可重复读。
*幻读
InnoDB 实现的RR通过next-key lock机制避免了幻读现象。next-key lock是行锁的一种,实现相当于record lock(记录锁) + gap lock(间隙锁);其特点是不仅会锁住记录本身(record lock的功能),还会锁定一个范围(gap lock的功能)。当然,这里我们讨论的是不加锁读:此时的next-key lock并不是真的加锁,只是为读取的数据增加了标记(标记内容包括数据的版本号等),准确起见姑且称之为类next-key lock机制。还是以前面的例子来说明:
当事务A在 T2 节点第一次读取0<id<5数据时,标记的不只是id=1的数据,而是将范围(0, 5)进行了标记,这样当 T5 时刻再次读取0<id<5数据时,便可以发现id=2的数据比之前标记的版本号更高,此时再结合undo log执行回滚操作,避免了幻读。
小结
概括来说,InnoDB 实现的RR,通过锁机制、数据的隐藏列、undo log和类next-key lock,实现了一定程度的隔离性,可以满足大多数场景的需要。不过需要说明的是,RR虽然避免了幻读问题,但是毕竟不是Serializable,不能保证完全的隔离,下面是一个例子,大家可以自己验证一下。
一致性(Consistency)
定义
一致性,是指事务执行结束后,数据库的完整性约束没有被破坏,事务执行的前后都是合法的数据状态。数据库的完整性约束包括但不限于:实体完整性(如行的主键存在且唯一)、列完整性(如字段的类型、大小、长度要符合要求)、外键约束、用户自定义完整性(如转账前后,两个账户余额的和应该不变)。
实现方式
可以说,一致性是事务追求的最终目标:前面提到的原子性、持久性和隔离性,都是为了保证数据库状态的一致性。此外,除了数据库层面的保障,一致性的实现也需要应用层面进行保障。实现一致性的措施包括:
*保证原子性、持久性和隔离性,如果这些特性无法保证,事务的一致性也无法保证;
*数据库本身提供保障,例如不允许向整形列插入字符串值、字符串长度不能超过列的限制等;
*应用层面进行保障,例如如果转账操作只扣除转账者的余额,而没有增加接收者的余额,无论数据库实现的多么完美,也无法保证状态的一致。
总结
下面总结一下 ACID 特性及其实现原理:
*原子性:保证事务要么全执行,要么全不执行,是事务最核心的特性,事务本身就是以原子性来定义的,实现主要基于undo log日志;
*持久性:保证事务提交后不会因为宕机等原因导致数据丢失,实现主要基于redo log日志;
*隔离性:保证事务执行尽可能不受其他事务影响,InnoDB 默认的隔离级别是RR,RR的实现主要基于锁机制、数据的隐藏列、undo log日志和类next-key lock机制;
*一致性:事务追求的最终目标,一致性的实现既需要数据库层面的保障,也需要应用层面的保障。
进一步了解事务的隔离级别
在上文中,我们已经大致介绍了事务的四种隔离级别,下面我们在来看看这四种隔离级别的实现原理。
事务隔离的原理是什么?
我们都知道事务的四种性质,数据库为了维护这些性质,尤其是一致性和隔离性,一般使用加锁这种方式。同时数据库又是个高并发的应用,同一时间会有大量的并发访问,如果加锁过度,会极大的降低并发处理能力。所以,对于加锁的处理,可以说就是数据库对于事务处理的精髓所在。
一次封锁 or 两段锁?
因为有大量的并发访问,为了预防死锁,一般应用中推荐使用一次封锁法,就是在方法的开始阶段,已经预先知道会用到哪些数据,然后全部锁住,在方法运行之后,再全部解锁。这种方式可以有效的避免循环死锁,但在数据库中却不适用,因为在事务开始阶段,数据库并不知道会用到哪些数据。
数据库遵循的是两段锁协议,将事务分成两个阶段,分别为:
*加锁阶段:在该阶段可以进行加锁操作。在对任何数据进行读操作之前要申请并获得S锁(共享锁,其它事务可以继续加共享锁,但不能加排它锁),在进行写操作之前要申请并获得X锁(排它锁,其它事务不能再获得任何锁)。加锁不成功,则事务进入等待状态,直到加锁成功才继续执行。
*解锁阶段:当事务释放了一个封锁以后,事务进入解锁阶段,在该阶段只能进行解锁操作不能再进行加锁操作。
这种方式虽然无法避免死锁,但是两段锁协议可以保证事务的并发调度是串行化(串行化很重要,尤其是在数据恢复和备份的时候)的。
事务中的加锁方式
在数据库操作中,为了有效保证并发读取数据的正确性,提出的事务隔离级别,而数据库锁,就是为了构建这些隔离级别存在的。
读未提交
Read Uncommitted,虽然其也是隔离级别中的一种,但因为其可能引发的问题比较多,所以数据库一般都不会使用这种隔离级别,而且其任何操作都不会加锁,这里就不讨论了。
读已提交
Read Committed,在这种隔离级别中,数据的读取都是不加锁的,但是数据的写入、修改和删除是需要加锁的。
可重复读
Repeatable Read,这是 MySQL 中 InnoDB 存储引擎默认的隔离级别。我们姑且分“读”和“写”两个模块来讲解。
读(快照读)
读就是可重复读,其解决了脏读和不可重复读的问题,但却可能引发幻读的问题。
讲到这里,我们先来好好地说明下不可重复读和幻读的区别:
*很多人容易搞混不可重复读和幻读,两者确实有些相似,但不可重复读的重点在于update和delete操作,而幻读的重点则在于insert操作。
*如果使用锁机制来实现这两种隔离级别,在可重复读中,该 SQL 第一次读取到数据后,就将这些数据加锁,其它事务无法修改这些数据,就可以实现可重复读了。但这种方法却无法锁住insert的数据,所以当事务A先前读取了数据,或者修改了全部数据,事务B还是可以insert数据提交,这时事务A就会发现莫名其妙多了一条之前没有的数据,这就是幻读,不能通过行锁来避免。
*需要Serializable隔离级别 ,读用读锁,写用写锁,读锁和写锁互斥,这么做可以有效的避免幻读、不可重复读、脏读等问题,但却会极大的降低数据库的并发能力。
所以说,不可重复读和幻读最大的区别,就在于如何通过锁机制来解决他们产生的问题。MySQL、Oracle、PostgreSQL 等成熟的数据库,出于性能考虑,都是使用了以乐观锁为理论基础的 MVCC(多版本并发控制)来避免这两种问题。
这里继续扩展下悲观锁和乐观锁的知识。
*悲观锁:正如其名,它指的是对数据被外界(包括本系统当前的其他事务,以及来自外部系统的事务处理)修改持保守态度,因此,在整个数据处理过程中,将数据处于锁定状态。
*悲观锁的实现,往往依靠数据库提供的锁机制(也只有数据库层提供的锁机制才能真正保证数据访问的排他性,否则,即使在本系统中实现了加锁机制,也无法保证外部系统不会修改数据)。
*在悲观锁的情况下,为了保证事务的隔离性,就需要一致性锁定读。读取数据时要加锁,其它事务无法修改这些数据;修改删除数据时也要加锁,其它事务无法读取这些数据。
*乐观锁:相对悲观锁而言,乐观锁机制采取了更加宽松的加锁机制。悲观锁大多数情况下依靠数据库的锁机制实现,以保证操作最大程度的独占性。但随之而来的就是数据库性能的大量开销,特别是对长事务而言,这样的开销往往无法承受。而乐观锁机制在一定程度上解决了这个问题。
*乐观锁大多是基于数据版本(version)记录机制实现。何谓数据版本?即为数据增加一个版本标识,在基于数据库表的版本解决方案中,一般是通过为数据库表增加一个version字段来实现。
*读取出数据时,将此版本号一同读出,之后更新时,对此版本号加一。此时,将提交数据的版本数据与数据库表对应记录的当前版本信息进行比对,如果提交的数据版本号大于数据库表当前版本号,则予以更新,否则认为是过期数据。
需要说明的是,MVCC 的实现没有固定的规范,每个数据库都会有不同的实现方式,这里讨论的是 InnoDB 的 MVCC。接下来,讲解 MVCC 在 MySQL 的 InnoDB 中的实现:
*在 InnoDB 中,会在每行数据后添加两个额外的隐藏的值来实现 MVCC,这两个值一个记录这行数据何时被创建,另外一个记录这行数据何时过期(或者被删除)。 在实际操作中,存储的并不是时间,而是事务的版本号,每开启一个新事务,事务的版本号就会递增。 在RR事务隔离级别下:
*insert时,保存当前事务版本号为行的创建版本号;
*delete时,保存当前事务版本号为行的删除版本号;
*select时,读取创建版本号<=当前事务版本号,删除版本号为空或>当前事务版本号;
*update时,插入一条新纪录,保存当前事务版本号为行创建版本号,同时保存当前事务版本号到原来删除的行。
通过 MVCC,虽然每行记录都需要额外的存储空间、更多的行检查工作以及一些额外的维护工作,但可以减少锁的使用,大多数读操作都不用加锁,读数据操作很简单,性能很好,并且也能保证只会读取到符合标准的行,也只锁住必要行。
事务的隔离级别其实都是对于读数据的定义,但到了这里,就被拆成了读和写两个模块来讲解。这主要是因为 MySQL 中的读,和事务隔离级别中的读,是不一样的。
我们且看,在RR级别中,通过 MVCC 机制,虽然让数据变得可重复读,但我们读到的数据可能是历史数据,是不及时的数据,不是数据库当前的数据。这在一些对于数据的时效特别敏感的业务中,就很可能出问题。
对于这种读取历史数据的方式,我们叫它快照读(snapshot read),而读取数据库当前版本数据的方式,叫当前读(current read)。很显然,在 MVCC 中:
*快照读:就是select操作;
*当前读:特殊的读操作,insert、update和delete操作,属于当前读,处理的都是当前的数据,需要加锁。
事务的隔离级别实际上都是定义了当前读的级别,MySQL 为了减少锁处理(包括等待其它锁)的时间,提升并发能力,引入了快照读的概念,使得select不用加锁。而update、insert这些“当前读”,就需要另外的模块来解决了。因为更新数据、插入数据是针对当前数据的,所以不能以快照的历史数据为参考,此处就是这个意思。
写(当前读)
事务的隔离级别中虽然只定义了读数据的要求,实际上这也可以说是写数据的要求。上文的“读”,实际是讲的快照读,而这里说的“写”就是当前读了。
为了解决当前读中的幻读问题,MySQL 事务使用了next-key lock锁,我们在上文中已经介绍过了,它是行锁和间隙锁的组合。
行锁可以防止不同事务版本的数据修改提交时造成数据冲突的情况。但如何避免别的事务插入数据就成了问题。行锁防止别的事务修改或删除,间隙锁防止别的事务新增,行锁和间隙锁结合形成的的next-key lock锁就共同解决了RR级别在写数据时的幻读问题。
可串行化
Serializable,这个级别很简单,读加共享锁,写加排他锁,读写互斥。使用悲观锁的理论,实现简单,数据更加安全,但是并发能力非常差。如果你的业务并发的特别少或者没有并发,同时又要求数据及时可靠的话,可以使用这种模式。
在这里需要注意改变一个观念,不要看到select就说不会加锁了,在Serializable这个级别中,select还是会加锁的。
mysql索引原理(就是背诵b树,b+树)
B树
大多数存储引擎都支持B树索引。B树通常意味着所有的值都是按顺序存储的,并且每一个叶子到根的距离相同。B树索引能够加快访问数据的速度,因为存储引擎不再需要进行全表扫描来获取数据。下图就是一颗简单的B数。
B树的查询流程:
如上图我要从找到E字母,查找流程如下:
1.获取根节点的关键字进行比较,当前根节点关键字为M,E<M(26个字母顺序),所以往找到指向左边的子节点(二分法规则,左小右大,左边放小于当前节点值的子节点、右边放大于当前节点值的子节点);
2.拿到关键字D和G,D<E<G 所以直接找到D和G中间的节点;
3.拿到E和F,因为E=E 所以直接返回关键字和指针信息(如果树结构里面没有包含所要查找的节点则返回null);
4.通过指针信息取出这条记录的所有信息;
B+树
下图为B+树的结构,B+树是B树的升级版,我们可以观察一下,B树和B+树的区别是什么?
B+树和B树的区别是:
1.B树的节点(根节点/父节点/中间节点/叶子节点)中没有重复元素,B+树有。
2.B树的中间节点会存储数据指针信息,而B+树只有叶子节点才存储。
3.B+树的每个叶子节点有一个指针指向下一个节点,把所有的叶子节点串在了一起。
从下图我们可以直观的看到B树和B+树的区别:紫红色的箭头是指向被索引的数据的指针,大红色的箭头即指向下一个叶子节点的指针。
我们假设被索引的列是主键,现在查找主键为5的记录,模拟一下查找的过程:
B树,在倒数第二层的节点中找到5后,可以立刻拿到指针获取行数据,查找停止。
B+树,在倒数第二层的节点中找到5后,由于中间节点不存有指针信息,则继续往下查找,在叶子节点中找到5,拿到指针获取行数据,查找停止。
B+树每个父节点的元素都会出现在子节点中,是子节点的最大(或最小)元素。叶子节点存储了被索引列的所有的数据。
那B+树比起B树有什么优点呢?
- 由于中间节点不存指针,同样大小的磁盘页可以容纳更多的节点元素,树的高度就小。(数据量相同的情况下,B+树比B树更加“矮胖”),查找起来就更快。
- B+树每次查找都必须到叶子节点才能获取数据,而B树不一定,B树可以在非叶子节点上获取数据。因此B+树查找的时间更稳定。
- B+树的每一个叶子节点都有指向下一个叶子节点的指针,方便范围查询和全表查询:只需要从第一个叶子节点开始顺着指针一直扫描下去即可,而B树则要对树做中序遍历。
sql优化
苹果翻译,然后第八章开始阅读,然后默写全文…
这么多怎么默写?你玩我???
毫无疑问会忘记其中百分99.99…
没关系,这只是救命稻草,入职后再用,现在先混一个脸熟。
8.1优化概述
https://dev.mysql.com/doc/refman/8.0/en/optimization.html
===========================================================
百度执行计划
sql有哪几个type,谁更快
首先类型有许多,这里我只给大家介绍企业里面用的最多的类型:
system>const>eq_ref>ref>range>index>ALL
越往左边,性能越高,比如system就比ALL类型性能要高出许多,其中system、const只是理想类型,基本达不到;
我们自己实际能优化到ref>range这两个类型,就是你自己写SQL,如果你没优化基本上就是ALL,如果你优化了,那就尽量达到ref>range这两个级别;
这里我强调一下,左边基本达不到!
所以,要对type优化的前提是,你需要有索引,如果你连索引都没有创建,那你就不用优化了,肯定是ALL…;
system:系统表,少量数据,往往不需要进行磁盘IO
const:常量连接
eq_ref:主键索引(primary key)或者非空唯一索引(unique not null)等值扫描
ref:非主键非唯一索引等值扫描
range:范围扫描
index:索引树扫描
ALL:全表扫描(full table scan)
Type级别详解
一.system级别
索引类型能是system的只有两种情况:
1.只有一条数据的系统表
只有一条数据的系统表,就是系统里自带一张表,并且这个表就一条数据,这个基本上就达不到,这个是系统自带的表,而且就一条数据,所以基本达不到;
2.或衍生表只能有一条数据的主查询
这个是可以实现的,但是在实际开发当中,你不可能去写一个这么个玩意儿,不可能公司的业务去让你把SQL索引类型写实system…
SQL语句:select * From (select * From test01) t where tid = 1;//前面需要加explain
执行结果:
就是把它凑出来即可;
我之所以能达到system,是因为我满足了它的第二个条件;
二.const级别
const条件稍微低一点,但是基本上也达不到;
1.仅仅能查出一条的SQL语句并且用于Primary key 或 unique索引;
这个我就不说了把,都知道,所以在企业里根本不可能实现,能查出来一条SQL语句,你的索引还必须是Primary key或unique;
但是我们可以把它凑出来,我再强调一点,在公司,你们的业务不可能去让你凑type级别!
SQL语句:select * tid From test01 where tid = 1;//前面需要加explain
执行结果:
根据tid找,因为tid是我设置的主键,主键就是Primary key,并且只能有一条数据,我表里面本来就一条,所以我满足了;
三.eq_ref级别
唯一性索引:对于每个索引键的查询,返回匹配唯一行数据(有且只有1个,不能多,不能0);
解说:比如你select …from 一张表 where 比方说有一个字段 name = 一个东西,也就是我们以name作为索引,假设我之前给name加了一个索引值,我现在根据name去查,查完后有20条数据,我就必须保证这二十条数据每行都是唯一的,不能重复不能为空!
只要满足以上条件,你就能达到eq_ref,当然前提是你要给name建索引,如果name连索引都没,那你肯定达不到eq_ref;
此种情况常见于唯一索引和主键索引;
比如我根据name去查,但是一个公司里面或一个学校里面叫name的可能不止一个,一般你想用这个的时候,就要确保你这个字段是唯一的,id就可以,你可以重复两个张三,但是你身份证肯定不会重复;
添加唯一键语法:alter table 表名 add constraint 索引名 unique index(列名)
检查字段是否唯一键:show index form 表名;被展示出来的皆是有唯一约束的;
以上级别,均是可遇不可求!!!!
四 .ref级别
到ref还是问题不大的,只要你上点心,就可以达到;
非唯一性索引:对于每个索引键的查询,返回匹配的所有行(可以是0,或多个)
假设我现在要根据name查询,首先name可能有多个,因为一个公司或学校叫小明的不止一个人,但是你要用name去查,你必须name是索引,我们先给它加个索引,因为要达到ref级别,所以这里我给它加一个单值索引,关于单值索引的介绍我在前几篇文章讲过:
单值索引语法:alter table 表名 索引类型 索引名(字段)
现在我们根据索引来查数据,这里我假设我写的单值索引;
alter table student add index index_name (name);
这个时候我们再去编写sql语句:
alter table student add index index_name (name);
因为name是索引列,这里假设有两个叫张三的,ref级别规则就是能查出多个或0个,很显然能查出来多个,那这条SQL语句,必然是ref级别!
执行结果:
数据:
五.range级别
检索指定范围的行,查找一个范围内的数据,where后面是一个范围查询 (between,in,> < >=);
注:in 有时会失效,导致为ALL;
现在我们写一个查询语句,前提是,tid一定是一个索引列,如果是id的话,就用主键索引,也就是唯一索引,值不可以重复,这个时候我们范围查询的时候要用它来做条件:
EXPLAIN SELECT t.* FROM student t WHERE t.tid BETWEEN 1 AND 2; ;//查询tid是1到2;
查看执行结果:
我表示,我在这试了好几次都是index级别,我也不知道为什么,我即便满足条件仍是index级别,可能是数据库版本?
六.index级别
查询全部索引中的数据
讲解:假设我有一张表,里面有id name age,这个时候name是一个单值索引,一旦name被设定成索引,它就会成为B树一样,经过各种算法将name里面的值像树一样进行分类,这个时候我where name = **,就相当于把这颗B树查了一个遍,
也就是说,你把name这一列给查了一遍;
SQL语句:select id From student;//我只查被索引声明的列,必然就是index了;
执行结果:
七.ALL级别
查询全部表数据,就是select name From student;
其中 name 不是索引;
如果你查的这一列不是索引,就会导致全表扫描,所以要避免全表扫描;
执行结果:
msql不走索引的原因
1、order by 和 limit 结合使用,如果where 字段,order by字段都是索引,那么有limit索引会使用order by字段所在的索引。没有limit会使用where 条件的索引。遇到此类状况可以考虑用子查询将order by 和 limit 分开。这种情况主要发生在你用了多个索引,那么你需要注意了。它可能不执行你希望的走索引。(我觉得mysql会自动计算索引)
2、DATE_FORMAT()格式化时间,格式化后的时间再去比较,可能会导致索引失效。
3、子查询中order by的索引会失效,同时可能导致子查询中的where条件索引都不能用。
4、字符集的使用导致不走索引,有时你会发现用一个SQL 条件值不同可能会有天的差别(我之前遇到的 两个不同的ID号,一个查询80s,一个不到1s)
5、like语句
6、列类型为字符串类型,查询时没有用单引号引起来
7、在where查询语句中使用表达式
8、在where查询语句中对字段进行NULL值判断
9、在where查询中使用了or关键字, myisam表能用到索引, innodb不行;(用UNION替换OR,可以使用索引)
10、全表扫描快于索引扫描(数据量小时)
再加一个乐观锁
何谓悲观锁与乐观锁
乐观锁对应于生活中乐观的人总是想着事情往好的方向发展,悲观锁对应于生活中悲观的人总是想着事情往坏的方向发展。这两种人各有优缺点,不能不以场景而定说一种人好于另外一种人。
悲观锁
总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁(共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程)。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。Java中synchronized和ReentrantLock等独占锁就是悲观锁思想的实现。
乐观锁
总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号机制和CAS算法实现。乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库提供的类似于write_condition机制,其实都是提供的乐观锁。在Java中java.util.concurrent.atomic包下面的原子变量类就是使用了乐观锁的一种实现方式CAS实现的。
两种锁的使用场景
从上面对两种锁的介绍,我们知道两种锁各有优缺点,不可认为一种好于另一种,像乐观锁适用于写比较少的情况下(多读场景),即冲突真的很少发生的时候,这样可以省去了锁的开销,加大了系统的整个吞吐量。但如果是多写的情况,一般会经常产生冲突,这就会导致上层应用会不断的进行retry,这样反倒是降低了性能,所以一般多写的场景下用悲观锁就比较合适。
乐观锁常见的两种实现方式
乐观锁一般会使用版本号机制或CAS算法实现。 - 版本号机制
一般是在数据表中加上一个数据版本号version字段,表示数据被修改的次数,当数据被修改时,version值会加一。当线程A要更新数据值时,在读取数据的同时也会读取version值,在提交更新时,若刚才读取到的version值为当前数据库中的version值相等时才更新,否则重试更新操作,直到更新成功。
当前最后更新的version与操作员第一次的版本号是否相等,来判断是否有权利更新,更新之后version++。
举一个简单的例子:
假设数据库中帐户信息表中有一个 version 字段,当前值为 1 ;而当前帐户余额字段( balance )为 $100 。当需要对账户信息表进行更新的时候,需要首先读取version字段。
1.操作员 A 此时将其读出( version=1 ),并从其帐户余额中扣除 $50( $100-$50 )。
2.在操作员 A 操作的过程中,操作员B 也读入此用户信息( version=1 ),并从其帐户余额中扣除 $20 ( $100-$20 )。
3.操作员 A 完成了修改工作,提交更新之前会先看数据库的版本和自己读取到的版本是否一致,一致的话,就会将数据版本号加1( version=2 ),连同帐户扣除后余额( balance=$50 ),提交至数据库更新,此时由于提交数据版本大于数据库记录当前版本,数据被更新,数据库记录 version 更新为 2 。
4.操作员 B 完成了操作,提交更新之前会先看数据库的版本和自己读取到的版本是否一致,但此时比对数据库记录版本时发现,操作员 B 提交的数据版本号为 2 ,而自己读取到的版本号为1 ,不满足 “ 当前最后更新的version与操作员第一次读取的版本号相等 “ 的乐观锁策略,因此,操作员 B 的提交被驳回。
这样,就避免了操作员 B 用基于 version=1 的旧数据修改的结果覆盖操作员A 的操作结果的可能。 - CAS算法
即compare and swap(比较与交换),是一种有名的无锁算法。无锁编程,即不使用锁的情况下实现多线程之间的变量同步,也就是在没有线程被阻塞的情况下实现变量的同步,所以也叫非阻塞同步(Non-blocking Synchronization)。CAS算法涉及到三个操作数
*需要读写的内存值 V
*进行比较的值 A
*拟写入的新值 B
当且仅当 V 的值等于 A时,CAS通过原子方式用新值B来更新V的值,否则不会执行任何操作(比较和替换是一个原子操作)。一般情况下是一个自旋操作,即不断的重试。
乐观锁的缺点
ABA 问题是乐观锁一个常见的问题
1 ABA 问题
如果一个变量V初次读取的时候是A值,并且在准备赋值的时候检查到它仍然是A值,那我们就能说明它的值没有被其他线程修改过了吗?很明显是不能的,因为在这段时间它的值可能被改为其他值,然后又改回A,那CAS操作就会误认为它从来没有被修改过。这个问题被称为CAS操作的 "ABA"问题。
JDK 1.5 以后的 AtomicStampedReference 类就提供了此种能力,其中的 compareAndSet 方法就是首先检查当前引用是否等于预期引用,并且当前标志是否等于预期标志,如果全部相等,则以原子方式将该引用和该标志的值设置为给定的更新值。
2 循环时间长开销大
自旋CAS(也就是不成功就一直循环执行直到成功)如果长时间不成功,会给CPU带来非常大的执行开销。 如果JVM能支持处理器提供的pause指令那么效率会有一定的提升,pause指令有两个作用,第一它可以延迟流水线执行指令(de-pipeline),使CPU不会消耗过多的执行资源,延迟的时间取决于具体实现的版本,在一些处理器上延迟时间是零。第二它可以避免在退出循环的时候因内存顺序冲突(memory order violation)而引起CPU流水线被清空(CPU pipeline flush),从而提高CPU的执行效率。
3 只能保证一个共享变量的原子操作
CAS 只对单个共享变量有效,当操作涉及跨多个共享变量时 CAS 无效。但是从 JDK 1.5开始,提供了AtomicReference类来保证引用对象之间的原子性,你可以把多个变量放在一个对象里来进行 CAS 操作.所以我们可以使用锁或者利用AtomicReference类把多个共享变量合并成一个共享变量来操作。
CAS与synchronized的使用情景
简单的来说CAS适用于写比较少的情况下(多读场景,冲突一般较少),synchronized适用于写比较多的情况下(多写场景,冲突一般较多)
1.对于资源竞争较少(线程冲突较轻)的情况,使用synchronized同步锁进行线程阻塞和唤醒切换以及用户态内核态间的切换操作额外浪费消耗cpu资源;而CAS基于硬件实现,不需要进入内核,不需要切换线程,操作自旋几率较少,因此可以获得更高的性能。
2.对于资源竞争严重(线程冲突严重)的情况,CAS自旋的概率会比较大,从而浪费更多的CPU资源,效率低于synchronized。
补充: Java并发编程这个领域中synchronized关键字一直都是元老级的角色,很久之前很多人都会称它为 “重量级锁” 。但是,在JavaSE 1.6之后进行了主要包括为了减少获得锁和释放锁带来的性能消耗而引入的 偏向锁 和 轻量级锁 以及其它各种优化之后变得在某些情况下并不是那么重了。synchronized的底层实现主要依靠 Lock-Free 的队列,基本思路是 自旋后阻塞,竞争切换后继续竞争锁,稍微牺牲了公平性,但获得了高吞吐量。在线程冲突较少的情况下,可以获得和CAS类似的性能;而线程冲突严重的情况下,性能远高于CAS。
mvvc
mysql事务
一、事务的基本要素(ACID)
1、原子性(Atomicity):事务开始后所有操作,要么全部做完,要么全部不做,不可能停滞在中间环节。事务执行过程中出错,会回滚到事务开始前的状态,所有的操作就像没有发生一样。也就是说事务是一个不可分割的整体,就像化学中学过的原子,是物质构成的基本单位。
2、一致性(Consistency):事务开始前和结束后,数据库的完整性约束没有被破坏 。比如A向B转账,不可能A扣了钱,B却没收到。
3、隔离性(Isolation):同一时间,只允许一个事务请求同一数据,不同的事务之间彼此没有任何干扰。比如A正在从一张银行卡中取钱,在A取钱的过程结束前,B不能向这张卡转账。
4、持久性(Durability):事务完成后,事务对数据库的所有更新将被保存到数据库,不能回滚。
二、事务的并发问题
1、脏读:事务A读取了事务B未提交的数据,然后B回滚操作,那么A读取到的数据是脏数据 ----解决的基础隔离级别:读已提交
2、不可重复读:事务 A 多次读取同一数据,事务 B 在事务A多次读取的过程中,对数据作了更新并提交,导致事务A多次读取同一数据时,结果 不一致。----解决的基础隔离级别:可重复读
3、幻读:是针对数据插入操作来说的,假设在事务A中按照某个条件先后两次查询数据库,两次查询结果的条数不同(事务B在事务A两次查询期间插入符合查询条件的新数据),这种现象称为幻读。—解决的基础隔离级别:串行化(针对一张表的操作,每一个session依次完成自己的操作:行锁 + 间隙锁)MySQL事务隔离级别和实现原理
小结:不可重复读与幻读的区别可以通俗的理解为:前者是数据变了,后者是数据的行数变了。不可重复读的和幻读很容易混淆,不可重复读侧重于修改,幻读侧重于新增或删除。
三、MySQL事务隔离级别
一、前提概要
什么是MVCC?
MVCC,全称Multi-Version Concurrency Control,即多版本并发控制。MVCC是一种并发控制的方法,一般在数据库管理系统中,实现对数据库的并发访问。
什么是当前读和快照读?
在学习MVCC多版本并发控制之前,我们必须先了解一下,什么是MySQL InnoDB下的当前读和快照读?
*当前读
像select lock in share mode(共享锁), select for update ; update, insert ,delete(排他锁)这些操作都是一种当前读,它读取的是记录的最新版本,读取时还要保证其他并发事务不能修改当前记录,会对读取的记录进行加锁。
*快照读
简单的select操作就是快照读,即不加锁的非阻塞读;快照读的前提是隔离级别不是串行级别,串行级别下的快照读会退化成当前读;之所以出现快照读的情况,是基于提高并发性能的考虑,快照读的实现是基于多版本并发控制,即MVCC,可以认为MVCC是行锁的一个变种,但它在很多情况下,避免了加锁操作,降低了开销;既然是基于多版本,即快照读可能读到的并不一定是数据的最新版本,而有可能是之前的历史版本
MVCC能解决什么问题,好处是?
数据库并发场景有三种,分别为:
*读-读:不存在任何问题,也不需要并发控制
*读-写:有线程安全问题,可能会造成事务隔离性问题,可能遇到脏读,幻读,不可重复读
*写-写:有线程安全问题,可能会存在更新丢失问题
MVCC带来的好处是?
多版本并发控制(MVCC)是一种用来解决读-写冲突的无锁并发控制,也就是为事务分配单向增长的时间戳,为每个修改保存一个版本,版本与事务时间戳关联,读操作只读该session的数据库的快照。 所以MVCC可以为数据库解决以下问题
*在并发读写数据库时,可以做到在读操作时不用阻塞写操作,写操作也不用阻塞读操作,提高了数据库并发读写的性能
二、MVCC的实现原理
它的实现原理主要是依赖记录中的 3个隐式字段,undo日志 ,Read View 来实现的。
隐式字段
每行记录除了我们自定义的字段外,还有数据库隐式定义的DB_TRX_ID,DB_ROLL_PTR,DB_ROW_ID等字段
*DB_TRX_ID
6byte,最近修改(修改/插入)事务ID:记录创建这条记录/最后一次修改该记录的事务ID
*DB_ROLL_PTR
7byte,回滚指针,指向这条记录的上一个版本(存储于rollback segment里)
*DB_ROW_ID
6byte,隐含的自增ID(隐藏主键),如果数据表没有主键,InnoDB会自动以DB_ROW_ID产生一个聚簇索引
*实际还有一个删除flag隐藏字段, 既记录被删除并不代表真的删除,而是删除flag变了
如上图,DB_ROW_ID是数据库默认为该行记录生成的唯一隐式主键,DB_TRX_ID是当前操作该记录的事务ID,而DB_ROLL_PTR是一个回滚指针,用于配合undo日志,指向上一个旧版本
undo日志
undo log主要分为两种:
*insert undo log
代表事务在insert新记录时产生的undo log, 只在事务回滚时需要,并且在事务提交后可以被立即丢弃
*update undo log
事务在进行update或delete时产生的undo log; 不仅在事务回滚时需要,在快照读时也需要;所以不能随便删除
对MVCC有帮助的实质是update undo log ,undo log实际上就是存在rollback segment中旧记录链,它的执行流程如下:
一、 比如有个事务在persion表插入了一条新记录,记录如下,name为Jerry, age为24岁,隐式主键是1,事务ID和回滚指针,我们假设为NULL。为演示,插入提交后,该undo log被删除
二、 现在来了一个事务1对该记录的name做出了修改,改为Tom
*在事务1修改该行(记录)数据时,数据库会先对该行加排他锁
*然后把该行数据拷贝到undo log中,作为旧记录,既在undo log中有当前行的拷贝副本
*拷贝完毕后,修改该行name为Tom,并且修改隐藏字段的事务ID为当前事务1的ID, 我们假设从1开始,之后递增,回滚指针指向拷贝到undo log的副本记录,既表示我的上一个版本就是它
*事务提交后,释放锁
三、 又来了个事务2修改person表的同一个记录,将age修改为30岁
*在事务2修改该行数据时,数据库也先为该行加锁
*然后把该行数据拷贝到undo log中,作为旧记录,发现该行记录已经有undo log了,那么最新的旧数据作为链表的表头,插在该行记录的undo log最前面
*修改该行age为30岁,并且修改隐藏字段的事务ID为当前事务2的ID, 那就是2,回滚指针指向刚刚拷贝到undo log的副本记录
*事务提交,释放锁
记录版本链
从上面,我们就可以看出,同一记录的多次修改,会导致该记录的undo log成为一条记录版本线性表,既链表,undo log的链首就是最新的旧记录,链尾就是最早的旧记录
Read View(读视图)
什么是Read View?(对照表?)
*什么是Read View,Read View是session进行快照读操作的时候生产的读视图(Read View),在该session执行的快照读的那一刻,会生成数据库系统当前的一个快照。
*Read View主要是用来做可见性判断的, 即当执行快照读的时候,对该记录创建一个Read View读视图,把它比作条件用来判断当前session能够看到哪个版本的数据,既可能是当前最新的数据,也有可能是该行记录的undo log里面的某个版本的数据。
Read View:执行查询时【所有】未提交的事务Id数组(数组里最小的id为min_id)和已创建的最大事务id(max_id:未提交、已提交)组成
mvcc遵循一个可见性算法,查询时候,需要用read-view和undo log 做对比得到结果
比较规则:
依次比较记录版本链中每一条记录,符合规则就返回该条数据,不符合根据回滚指针取链路中的下一条数据;
trx_id为记录版本链里的DB_TRX_ID
1、如果trx_id < min_id(落在绿色部分),表示这个版本是已经提交的事务生成的,这个数据肯定是可见的;
2、如果trx_id > max_id (落在红色部分),表示这个版本是由将来的启动的事务生成的,是肯定不可见的;
3、如果min_id <= trx_id <= max_id (落在黄色部分),那就包括两种情况
a、若trx_id在未提交的事务Id数组里,表示这个版本是由未提交的事务产生的,不可见,当前自己的事务是可见的;
b、若trx_id不在未提交的事务Id数组里,表示这个版本是已经提交的事务生成的,可见
对于删除的情况,可以认为是update的特殊情况,会将版本链上最新的数据复制一份,然后将其trx_id修改成删除的trx_id,同时在该条记录的头信息(record_header)里的(delete_flag)标记位上写上true,来表示当前记录已经被删除,在查询时按照上面的规则查到对应的记录,如果delete_flag标记位等于true,意味着该条记录已被删除,则不返回数据
例子
默认RR级别
https://www.processon.com/view/link/5eef4a2d6376891e81dc7d28
三、MVCC相关问题
RC,RR级别下的InnoDB快照读有什么不同?
正是Read View生成时机的不同,从而造成RC,RR级别下快照读的结果的不同
*在RR级别下的某个session对某条记录的第一次快照读会创建一个Read View, 将当前系统活跃的其他事务记录起来,此后再快照读的时候,还是使用的是同一个Read View,换一句话说,只要当前session在其他事务提交更新之前使用过快照读,那么之后的快照读使用的都是同一个Read View,所以对之后的修改不可见;
*而在RC级别下的,每次快照读都会新生成一个Read View, 这就是我们在RC级别下的事务中可以看到别的事务提交的更新的原因总之在RC隔离级别下,是每个快照读都会生成并获取最新的Read View;而在RR隔离级别下,则是同一个session中的第一个快照读才会创建Read View, 之后的快照读获取的都是同一个Read View。
百度inner join,left join,right join区别
知道下哪怕leftjoin也会有重复的
前言
对于inner join、left join、right join的解释,书本的解释讲得比较专业,有时候难以理解,以下将会以最简单的方式帮助你理解这三者的区别
准备
这里先准备两张表
student(学生表)
stu_score(学生成绩表)
我们接下来都将使用两表的 id 属性进行连接
- inner join (内连接)
简单理解:在做表连接时,选取两个表都存在的数据
对于inner join的sql语句如下:
执行结果如下:
可见,student表中的4、5行并没有连接进来,因为在stu_score表中没有对应数据
2. left join(左连接)
简单理解:以左表为主,在做表连接时,保留左表中的数据,如果右表中没有对应的值就用空值代替
对于left join的sql语句如下(from 中 student 在左边 ,stu_score 在右边 ,与图示对应 ):
执行结果如下:
可见,保留了左表(student)中的值,但是由于右表(stu_student)中没有与之对应的id值,所以赋值为null,这样我们就可以得到所有学生的信息,并且还能知道谁没有参加考试
3. right join(右连接)
简单理解:与左连接相对应,以右表为主,在做表连接时,保留右表中的数据,如果左表中没有对应的值就用空值代替
对于right join的sql语句如下(from 中 student 在左边 ,stu_score 在右边 ,与图示对应 ):
执行结果如下:
可见右表所有数据保留,左表中没有对应关系的数据则使用了null表示
Mysql最左匹配原则
看了好多博客,讲讲自己的理解:索引的底层是一颗B+树,那么联合索引当然还是一颗B+树,只不过联合索引的健值数量不是一个,而是多个。构建一颗B+树只能根据一个值来构建,因此数据库依据联合索引最左的字段来构建B+树。
例子:假如创建一个(a,b)的联合索引,那么它的索引树是这样的
可以看到a的值是有顺序的,1,1,2,2,3,3,而b的值是没有顺序的1,2,1,4,1,2。所以b = 2这种查询条件没有办法利用索引,因为联合索引首先是按a排序的,b是无序的。
同时我们还可以发现在a值相等的情况下,b值又是按顺序排列的,但是这种顺序是相对的。所以最左匹配原则遇上范围查询就会停止,剩下的字段都无法使用索引。例如a = 1 and b = 2 a,b字段都可以使用索引,因为在a值确定的情况下b是相对有序的,而a>1and b=2,a字段可以匹配上索引,但b值不可以,因为a的值是一个范围,在这个范围中b是无序的。
最左匹配原则:最左优先,以最左边的为起点任何连续的索引都能匹配上。同时遇到范围查询(>、<、between、like)就会停止匹配。
假如建立联合索引(a,b,c)
1 全值匹配查询时
用到了索引
where子句几个搜索条件顺序调换不影响查询结果,因为Mysql中有查询优化器,会自动优化查询顺序
2 匹配左边的列时
都从最左边开始连续匹配,用到了索引
这些没有从最左边开始,最后查询没有用到索引,用的是全表扫描
如果不连续时,只用到了a列的索引,b列和c列都没有用到
3 匹配列前缀
如果列是字符型的话它的比较规则是先比较字符串的第一个字符,第一个字符小的哪个字符串就比较小,如果两个字符串第一个字符相通,那就再比较第二个字符,第二个字符比较小的那个字符串就比较小,依次类推,比较字符串。
如果a是字符类型,那么前缀匹配用的是索引,后缀和中缀只能全表扫描了
4 匹配范围值
可以对最左边的列进行范围查询
多个列同时进行范围查找时,只有对索引最左边的那个列进行范围查找才用到B+树索引,也就是只有a用到索引,在1<a<3的范围内b是无序的,不能用索引,找到1<a<3的记录后,只能根据条件 b > 1继续逐条过滤
5 精确匹配某一列并范围匹配另外一列
如果左边的列是精确查找的,右边的列可以进行范围查找
a=1的情况下b是有序的,进行范围查找走的是联合索引
6 排序
一般情况下,我们只能把记录加载到内存中,再用一些排序算法,比如快速排序,归并排序等在内存中对这些记录进行排序,有时候查询的结果集太大不能在内存中进行排序的话,还可能暂时借助磁盘空间存放中间结果,排序操作完成后再把排好序的结果返回客户端。Mysql中把这种再内存中或磁盘上进行排序的方式统称为文件排序。文件排序非常慢,但如果order子句用到了索引列,就有可能省去文件排序的步骤
因为b+树索引本身就是按照上述规则排序的,所以可以直接从索引中提取数据,然后进行回表操作取出该索引中不包含的列就好了
order by的子句后面的顺序也必须按照索引列的顺序给出,比如
这种颠倒顺序的没有用到索引
这种用到部分索引
联合索引左边列为常量,后边的列排序可以用到索引
mysql大小表查询怎么优化
单表优化
除非单表数据未来会一直不断上涨,否则不要一开始就考虑拆分,拆分会带来逻辑、部署、运维的各种复杂度,一般以整型值为主的表在千万级以下,字符串为主的表在五百万以下是没有太大问题的。而事实上很多时候MySQL单表的性能依然有不少优化空间,甚至能正常支撑千万级以上的数据量:
字段
1.尽量使用TINYINT、SMALLINT、MEDIUM_INT作为整数类型而非INT,如果非负则加上UNSIGNED
2.VARCHAR的长度只分配真正需要的空间
3.使用枚举或整数代替字符串类型
4.尽量使用TIMESTAMP而非DATETIME,
5.单表不要有太多字段,建议在20以内
6.避免使用NULL字段,很难查询优化且占用额外索引空间
7.用整型来存IP
索引
1.索引并不是越多越好,要根据查询有针对性的创建,考虑在WHERE和ORDER BY命令上涉及的列建立索引,可根据EXPLAIN来查看是否用了索引还是全表扫描
2.应尽量避免在WHERE子句中对字段进行NULL值判断,否则将导致引擎放弃使用索引而进行全表扫描
3.值分布很稀少的字段不适合建索引,例如"性别"这种只有两三个值的字段
4.字符字段只建前缀索引
5.字符字段最好不要做主键
6.不用外键,由程序保证约束
7.尽量不用UNIQUE,由程序保证约束
8.使用多列索引时主意顺序和查询条件保持一致,同时删除不必要的单列索引
查询SQL
1.可通过开启慢查询日志来找出较慢的SQL
2.不做列运算:SELECT id WHERE age + 1 = 10,任何对列的操作都将导致表扫描,它包括数据库教程函数、计算表达式等等,查询时要尽可能将操作移至等号右边
3.sql语句尽可能简单:一条sql只能在一个cpu运算;大语句拆小语句,减少锁时间;一条大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来分页,每页数量也不要太大
引擎
目前广泛使用的是MyISAM和InnoDB两种引擎:
MyISAM
MyISAM引擎是MySQL 5.1及之前版本的默认引擎,它的特点是:
1.不支持行锁,读取时对需要读到的所有表加锁,写入时则对表加排它锁
2.不支持事务
3.不支持外键
4.不支持崩溃后的安全恢复
5.在表有读取查询的同时,支持往表中插入新纪录
6.支持BLOB和TEXT的前500个字符索引,支持全文索引
7.支持延迟更新索引,极大提升写入性能
8.对于不会进行修改的表,支持压缩表,极大减少磁盘空间占用
InnoDB
InnoDB在MySQL 5.5后成为默认索引,它的特点是:
1.支持行锁,采用MVCC来支持高并发
2.支持事务
3.支持外键
4.支持崩溃后的安全恢复
5.不支持全文索引
总体来讲,MyISAM适合SELECT密集型的表,而InnoDB适合INSERT和UPDATE密集型的表
系统调优参数
可以使用下面几个工具来做基准测试:
1.sysbench:一个模块化,跨平台以及多线程的性能测试工具
2.iibench-mysql:基于 Java 的 MySQL/Percona/MariaDB 索引进行插入性能测试工具
3.tpcc-mysql:Percona开发的TPC-C测试工具
4.具体的调优参数内容较多,具体可参考官方文档,这里介绍一些比较重要的参数:
- back_log:back_log值指出在MySQL暂时停止回答新请求之前的短时间内多少个请求可以被存在堆栈中。也就是说,如果MySql的连接数据达到max_connections时,新来的请求将会被存在堆栈中,以等待某一连接释放资源,该堆栈的数量即back_log,如果等待连接的数量超过back_log,将不被授予连接资源。可以从默认的50升至500
- wait_timeout:数据库连接闲置时间,闲置连接会占用内存资源。可以从默认的8小时减到半小时
- max_user_connection: 最大连接数,默认为0无上限,最好设一个合理上限
- thread_concurrency:并发线程数,设为CPU核数的两倍
- skip_name_resolve:禁止对外部连接进行DNS解析,消除DNS解析时间,但需要所有远程主机用IP访问
- key_buffer_size:索引块的缓存大小,增加会提升索引处理速度,对MyISAM表性能影响最大。对于内存4G左右,可设为256M或384M,通过查询show status like ‘key_read%’,保证key_reads / key_read_requests在0.1%以下最好
- innodb_buffer_pool_size:缓存数据块和索引块,对InnoDB表性能影响最大。通过查询show status like ‘Innodb_buffer_pool_read%’,保证(Innodb_buffer_pool_read_requests –
- Innodb_buffer_pool_reads) / Innodb_buffer_pool_read_requests越高越好
- innodb_additional_mem_pool_size:InnoDB存储引擎用来存放数据字典信息以及一些内部数据结构的内存空间大小,当数据库对象非常多的时候,适当调整该参数的大小以确保所有数据都能存放在内存中提高访问效率,当过小的时候,MySQL会记录Warning信息到数据库的错误日志中,这时就需要该调整这个参数大小
- innodb_log_buffer_size:InnoDB存储引擎的事务日志所使用的缓冲区,一般来说不建议超过32MB
- query_cache_size:缓存MySQL中的ResultSet,也就是一条SQL语句执行的结果集,所以仅仅只能针对select语句。当某个表的数据有任何任何变化,都会导致所有引用了该表的select语句在Query Cache中的缓存数据失效。所以,当我们的数据变化非常频繁的情况下,使用Query Cache可能会得不偿失。根据命中率(Qcache_hits/(Qcache_hits+Qcache_inserts)*100))进行调整,一般不建议太大,256MB可能已经差不多了,大型的配置型静态数据可适当调大. 可以通过命令show status like 'Qcache_%'查看目前系统Query catch使用大小
- read_buffer_size:MySql读入缓冲区大小。对表进行顺序扫描的请求将分配一个读入缓冲区,MySql会为它分配一段内存缓冲区。如果对表的顺序扫描请求非常频繁,可以通过增加该变量值以及内存缓冲区大小提高其性能
- sort_buffer_size:MySql执行排序使用的缓冲大小。如果想要增加ORDER BY的速度,首先看是否可以让MySQL使用索引而不是额外的排序阶段。如果不能,可以尝试增加sort_buffer_size变量的大小
- read_rnd_buffer_size:MySql的随机读缓冲区大小。当按任意顺序读取行时(例如,按照排序顺序),将分配一个随机读缓存区。进行排序查询时,MySql会首先扫描一遍该缓冲,以避免磁盘搜索,提高查询速度,如果需要排序大量数据,可适当调高该值。但MySql会为每个客户连接发放该缓冲空间,所以应尽量适当设置该值,以避免内存开销过大。
- record_buffer:每个进行一个顺序扫描的线程为其扫描的每张表分配这个大小的一个缓冲区。如果你做很多顺序扫描,可能想要增加该值
- thread_cache_size:保存当前没有与连接关联但是准备为后面新的连接服务的线程,可以快速响应连接的线程请求而无需创建新的
- table_cache:类似于thread_cache_size,但用来缓存表文件,对InnoDB效果不大,主要用于MyISAM
升级硬件
Scale up,这个不多说了,根据MySQL是CPU密集型还是I/O密集型,通过提升CPU和内存、使用SSD,都能显著提升MySQL性能
读写分离
也是目前常用的优化,从库读主库写,一般不要采用双主或多主引入很多复杂性,尽量采用文中的其他方案来提高性能。同时目前很多拆分的解决方案同时也兼顾考虑了读写分离
缓存
缓存可以发生在这些层次:
1.MySQL内部:在系统调优参数介绍了相关设置
2.数据访问层:比如MyBatis针对SQL语句做缓存,而Hibernate可以精确到单个记录,这里缓存的对象主要是持久化对象Persistence Object
3.应用服务层:这里可以通过编程手段对缓存做到更精准的控制和更多的实现策略,这里缓存的对象是数据传输对象Data Transfer Object
4.Web层:针对web页面做缓存
5.浏览器客户端:用户端的缓存
可以根据实际情况在一个层次或多个层次结合加入缓存。这里重点介绍下服务层的缓存实现,目前主要有两种方式:
1.直写式(Write Through):在数据写入数据库后,同时更新缓存,维持数据库与缓存的一致性。这也是当前大多数应用缓存框架如Spring Cache的工作方式。这种实现非常简单,同步好,但效率一般。
2.回写式(Write Back):当有数据要写入数据库时,只会更新缓存,然后异步批量的将缓存数据同步到数据库上。这种实现比较复杂,需要较多的应用逻辑,同时可能会产生数据库与缓存的不同步,但效率非常高。
表分区
MySQL在5.1版引入的分区是一种简单的水平拆分,用户需要在建表的时候加上分区参数,对应用是透明的无需修改代码
对用户来说,分区表是一个独立的逻辑表,但是底层由多个物理子表组成,实现分区的代码实际上是通过对一组底层表的对象封装,但对SQL层来说是一个完全封装底层的黑盒子。MySQL实现分区的方式也意味着索引也是按照分区的子表定义,没有全局索引
用户的SQL语句是需要针对分区表做优化,SQL条件中要带上分区条件的列,从而使查询定位到少量的分区上,否则就会扫描全部分区,可以通过EXPLAIN PARTITIONS来查看某条SQL语句会落在那些分区上,从而进行SQL优化,如下图5条记录落在两个分区上:
分区的好处是:
1.可以让单表存储更多的数据
2.分区表的数据更容易维护,可以通过清楚整个分区批量删除大量数据,也可以增加新的分区来支持新插入的数据。另外,还可以对一个独立分区进行优化、检查、修复等操作
3.部分查询能够从查询条件确定只落在少数分区上,速度会很快
4.分区表的数据还可以分布在不同的物理设备上,从而利用多个硬件设备
5.可以使用分区表赖避免某些特殊瓶颈,例如InnoDB单个索引的互斥访问、ext3文件系统的inode锁竞争
6.可以备份和恢复单个分区
分区的限制和缺点:
1.一个表最多只能有1024个分区
2.如果分区字段中有主键或者唯一索引的列,那么所有主键列和唯一索引列都必须包含进来
3.分区表无法使用外键约束
4.NULL值会使分区过滤无效
5.所有分区必须使用相同的存储引擎
分区的类型:
1.RANGE分区:基于属于一个给定连续区间的列值,把多行分配给分区
2.LIST分区:类似于按RANGE分区,区别在于LIST分区是基于列值匹配一个离散值集合中的某个值来进行选择
3.HASH分区:基于用户定义的表达式的返回值来进行选择的分区,该表达式使用将要插入到表中的这些行的列值进行计算。这个函数可以包含MySQL中有效的、产生非负整数值的任何表达式
4.KEY分区:类似于按HASH分区,区别在于KEY分区只支持计算一列或多列,且MySQL服务器提供其自身的哈希函数。必须有一列或多列包含整数值
分区适合的场景有:
最适合的场景数据的时间序列性比较强,则可以按时间来分区,如下所示:
查询时加上时间范围条件效率会非常高,同时对于不需要的历史数据能很容的批量删除。
如果数据有明显的热点,而且除了这部分数据,其他数据很少被访问到,那么可以将热点数据单独放在一个分区,让这个分区的数据能够有机会都缓存在内存中,查询时只访问一个很小的分区表,能够有效使用索引和缓存
另外MySQL有一种早期的简单的分区实现 - 合并表(merge table),限制较多且缺乏优化,不建议使用,应该用新的分区机制来替代
垂直拆分
垂直分库是根据数据库里面的数据表的相关性进行拆分,比如:一个数据库里面既存在用户数据,又存在订单数据,那么垂直拆分可以把用户数据放到用户库、把订单数据放到订单库。垂直分表是对数据表进行垂直拆分的一种方式,常见的是把一个多字段的大表按常用字段和非常用字段进行拆分,每个表里面的数据记录数一般情况下是相同的,只是字段不一样,使用主键关联
比如原始的用户表是:
垂直拆分后是:
垂直拆分的优点是:
1.可以使得行数据变小,一个数据块(Block)就能存放更多的数据,在查询时就会减少I/O次数(每次查询时读取的Block 就少)
2.可以达到最大化利用Cache的目的,具体在垂直拆分的时候可以将不常变的字段放一起,将经常改变的放一起
3.数据维护简单
缺点是:
1.主键出现冗余,需要管理冗余列
2.会引起表连接JOIN操作(增加CPU开销)可以通过在业务服务器上进行join来减少数据库压力
3.依然存在单表数据量过大的问题(需要水平拆分)
4.事务处理复杂
水平拆分
概述
水平拆分是通过某种策略将数据分片来存储,分库内分表和分库两部分,每片数据会分散到不同的MySQL表或库,达到分布式的效果,能够支持非常大的数据量。前面的表分区本质上也是一种特殊的库内分表
库内分表,仅仅是单纯的解决了单一表数据过大的问题,由于没有把表的数据分布到不同的机器上,因此对于减轻MySQL服务器的压力来说,并没有太大的作用,大家还是竞争同一个物理机上的IO、CPU、网络,这个就要通过分库来解决
前面垂直拆分的用户表如果进行水平拆分,结果是:
实际情况中往往会是垂直拆分和水平拆分的结合,即将Users_A_M和Users_N_Z再拆成Users和UserExtras,这样一共四张表
水平拆分的优点是:
1.不存在单库大数据和高并发的性能瓶颈
2.应用端改造较少
3.提高了系统的稳定性和负载能力
缺点是:
1.分片事务一致性难以解决
2.跨节点Join性能差,逻辑复杂
3.数据多次扩展难度跟维护量极大
分片原则
1.能不分就不分,参考单表优化
2.分片数量尽量少,分片尽量均匀分布在多个数据结点上,因为一个查询SQL跨分片越多,则总体性能越差,虽然要好于所有数据在一个分片的结果,只在必要的时候进行扩容,增加分片数量
3.分片规则需要慎重选择做好提前规划,分片规则的选择,需要考虑数据的增长模式,数据的访问模式,分片关联性问题,以及分片扩容问题,最近的分片策略为范围分片,枚举分片,一致性Hash分片,这几种分片都有利于扩容
4.尽量不要在一个事务中的SQL跨越多个分片,分布式事务一直是个不好处理的问题
5.查询条件尽量优化,尽量避免Select * 的方式,大量数据结果集下,会消耗大量带宽和CPU资源
6.查询尽量避免返回大量结果集,并且尽量为频繁使用的查询语句建立索引。
7.通过数据冗余和表分区赖降低跨库Join的可能
这里特别强调一下分片规则的选择问题,如果某个表的数据有明显的时间特征,比如订单、交易记录等,则他们通常比较合适用时间范围分片,因为具有时效性的数据,我们往往关注其近期的数据,查询条件中往往带有时间字段进行过滤,比较好的方案是,当前活跃的数据,采用跨度比较短的时间段进行分片,而历史性的数据,则采用比较长的跨度存储。
总体上来说,分片的选择是取决于最频繁的查询SQL的条件,因为不带任何Where语句的查询SQL,会遍历所有的分片,性能相对最差,因此这种SQL越多,对系统的影响越大,所以我们要尽量避免这种SQL的产生。
解决方案
由于水平拆分牵涉的逻辑比较复杂,当前也有了不少比较成熟的解决方案。这些方案分为两大类:客户端架构和代理架构。
客户端架构
通过修改数据访问层,如JDBC、Data Source、MyBatis,通过配置来管理多个数据源,直连数据库,并在模块内完成数据的分片整合,一般以Jar包的方式呈现
这是一个客户端架构的例子:
可以看到分片的实现是和应用服务器在一起的,通过修改Spring JDBC层来实现
客户端架构的优点是:
1.应用直连数据库,降低外围系统依赖所带来的宕机风险
2.集成成本低,无需额外运维的组件
缺点是:
1.限于只能在数据库访问层上做文章,扩展性一般,对于比较复杂的系统可能会力不从心
将分片逻辑的压力放在应用服务器上,造成额外风险
代理架构
通过独立的中间件来统一管理所有数据源和数据分片整合,后端数据库集群对前端应用程序透明,需要独立部署和运维代理组件
这是一个代理架构的例子:
代理组件为了分流和防止单点,一般以集群形式存在,同时可能需要Zookeeper之类的服务组件来管理
代理架构的优点是:
1.能够处理非常复杂的需求,不受数据库访问层原来实现的限制,扩展性强
2.对于应用服务器透明且没有增加任何额外负载
缺点是:
1.需部署和运维独立的代理中间件,成本高
2.应用需经过代理来连接数据库,网络上多了一跳,性能有损失且有额外风险
各方案比较
如此多的方案,如何进行选择?可以按以下思路来考虑:
1.确定是使用代理架构还是客户端架构。中小型规模或是比较简单的场景倾向于选择客户端架构,复杂场景或大规模系统倾向选择代理架构
2.具体功能是否满足,比如需要跨节点ORDER BY,那么支持该功能的优先考虑
3.不考虑一年内没有更新的产品,说明开发停滞,甚至无人维护和技术支持
4.最好按大公司->社区->小公司->个人这样的出品方顺序来选择
5.选择口碑较好的,比如github星数、使用者数量质量和使用者反馈
6.开源的优先,往往项目有特殊需求可能需要改动源代码
按照上述思路,推荐以下选择:
1.客户端架构:ShardingJDBC
2.代理架构:MyCat或者Atlas
兼容MySQL且可水平扩展的数据库
目前也有一些开源数据库兼容MySQL协议,如:
1.TiDB
2.Cubrid
3.但其工业品质和MySQL尚有差距,且需要较大的运维投入,如果想将原始的MySQL迁移到可水平扩展的新数据库中,可以考虑一些云数据库:
4.阿里云PetaData
5.阿里云OceanBase
6.腾讯云DCDB
NoSQL
在MySQL上做Sharding是一种戴着镣铐的跳舞,事实上很多大表本身对MySQL这种RDBMS的需求并不大,并不要求ACID,可以考虑将这些表迁移到NoSQL,彻底解决水平扩展问题,例如:
1.日志类、监控类、统计类数据
2.非结构化或弱结构化数据
3.对事务要求不强,且无太多关联操作的数据
mysql数据超过4-5千万会很慢解决方案
问题概述
使用阿里云rds for MySQL数据库(就是MySQL5.6版本),有个用户上网记录表6个月的数据量近2000万,保留最近一年的数据量达到4000万,查询速度极慢,日常卡死。严重影响业务。
问题前提:老系统,当时设计系统的人大概是大学没毕业,表设计和sql语句写的不仅仅是垃圾,简直无法直视。原开发人员都已离职,到我来维护,这就是传说中的维护不了就跑路,然后我就是掉坑的那个!!!
我尝试解决该问题,so,有个这个日志。
方案概述
方案一:优化现有mysql数据库。优点:不影响现有业务,源程序不需要修改代码,成本最低。缺点:有优化瓶颈,数据量过亿就玩完了。
方案二:升级数据库类型,换一种100%兼容mysql的数据库。优点:不影响现有业务,源程序不需要修改代码,你几乎不需要做任何操作就能提升数据库性能,缺点:多花钱
方案三:一步到位,大数据解决方案,更换newsql/nosql数据库。优点:没有数据容量瓶颈,缺点:需要修改源程序代码,影响业务,总成本最高。
以上三种方案,按顺序使用即可,数据量在亿级别一下的没必要换nosql,开发成本太高。三种方案我都试了一遍,而且都形成了落地解决方案。该过程心中慰问跑路的那几个开发者一万遍 😃
方案一详细说明:优化现有mysql数据库
跟阿里云数据库大佬电话沟通 and Google解决方案 and 问群里大佬,总结如下(都是精华):
*1.数据库设计和表创建时就要考虑性能
*2.sql的编写需要注意优化
*3.分区
*4.分表
*5.分库
1.数据库设计和表创建时就要考虑性能
mysql数据库本身高度灵活,造成性能不足,严重依赖开发人员能力。也就是说开发人员能力高,则mysql性能高。这也是很多关系型数据库的通病,所以公司的dba通常工资巨高。
设计表时要注意:
*表字段避免null值出现,null值很难查询优化且占用额外的索引空间,推荐默认数字0代替null。
*尽量使用INT而非BIGINT,如果非负则加上UNSIGNED(这样数值容量会扩大一倍),当然能使用TINYINT、SMALLINT、MEDIUM_INT更好。
*使用枚举或整数代替字符串类型
*尽量使用TIMESTAMP而非DATETIME
*单表不要有太多字段,建议在20以内
*用整型来存IP
索引
*索引并不是越多越好,要根据查询有针对性的创建,考虑在WHERE和ORDER BY命令上涉及的列建立索引,可根据EXPLAIN来查看是否用了索引还是全表扫描
*应尽量避免在WHERE子句中对字段进行NULL值判断,否则将导致引擎放弃使用索引而进行全表扫描
*值分布很稀少的字段不适合建索引,例如"性别"这种只有两三个值的字段
*字符字段只建前缀索引
*字符字段最好不要做主键
*不用外键,由程序保证约束
*尽量不用UNIQUE,由程序保证约束
*使用多列索引时主意顺序和查询条件保持一致,同时删除不必要的单列索引
简言之就是使用合适的数据类型,选择合适的索引
选择合适的数据类型
(1)使用可存下数据的最小的数据类型,整型 < date,time < char,varchar < blob
(2)使用简单的数据类型,整型比字符处理开销更小,因为字符串的比较更复杂。如,int类型存储时间类型,bigint类型转ip函数
(3)使用合理的字段属性长度,固定长度的表会更快。使用enum、char而不是varchar
(4)尽可能使用not null定义字段
(5)尽量少用text,非用不可最好分表
选择合适的索引列
(1)查询频繁的列,在where,group by,order by,on从句中出现的列
(2)where条件中<,<=,=,>,>=,between,in,以及like 字符串+通配符(%)出现的列
(3)长度小的列,索引字段越小越好,因为数据库的存储单位是页,一页中能存下的数据越多越好
(4)离散度大(不同的值多)的列,放在联合索引前面。查看离散度,通过统计不同的列值来实现,count越大,离散程度越高:
2.sql的编写需要注意优化
*使用limit对查询结果的记录进行限定
*避免select *,将需要查找的字段列出来
*使用连接(join)来代替子查询
*拆分大的delete或insert语句
*可通过开启慢查询日志来找出较慢的SQL
*不做列运算:SELECT id WHERE age + 1 = 10,任何对列的操作都将导致表扫描,它包括数据库教程函数、计算表达式等等,查询时要尽可能将操作移至等号右边
*sql语句尽可能简单:一条sql只能在一个cpu运算;大语句拆小语句,减少锁时间;一条大sql可以堵死整个库
*OR改写成IN:OR的效率是n级别,IN的效率是log(n)级别,in的个数建议控制在200以内
*不用函数和触发器,在应用程序实现
*避免%xxx式查询
*少用JOIN
*使用同类型进行比较,比如用’123’和’123’比,123和123比
*尽量避免在WHERE子句中使用!=或<>操作符,否则将引擎放弃使用索引而进行全表扫描
*对于连续数值,使用BETWEEN不用IN:SELECT id FROM t WHERE num BETWEEN 1 AND 5
*列表数据不要拿全表,要使用LIMIT来分页,每页数量也不要太大
引擎
引擎
目前广泛使用的是MyISAM和InnoDB两种引擎:
1.MyISAM
2.MyISAM引擎是MySQL 5.1及之前版本的默认引擎,它的特点是:
*不支持行锁,读取时对需要读到的所有表加锁,写入时则对表加排它锁
*不支持事务
*不支持外键
*不支持崩溃后的安全恢复
*在表有读取查询的同时,支持往表中插入新纪录
*支持BLOB和TEXT的前500个字符索引,支持全文索引
*支持延迟更新索引,极大提升写入性能
*对于不会进行修改的表,支持压缩表,极大减少磁盘空间占用
1.InnoDB
2.InnoDB在MySQL 5.5后成为默认索引,它的特点是:
*支持行锁,采用MVCC来支持高并发
*支持事务
*支持外键
*支持崩溃后的安全恢复
*不支持全文索引
总体来讲,MyISAM适合SELECT密集型的表,而InnoDB适合INSERT和UPDATE密集型的表
3.分区
MySQL在5.1版引入的分区是一种简单的水平拆分,用户需要在建表的时候加上分区参数,对应用是透明的无需修改代码
对用户来说,分区表是一个独立的逻辑表,但是底层由多个物理子表组成,实现分区的代码实际上是通过对一组底层表的对象封装,但对SQL层来说是一个完全封装底层的黑盒子。MySQL实现分区的方式也意味着索引也是按照分区的子表定义,没有全局索引
用户的SQL语句是需要针对分区表做优化,SQL条件中要带上分区条件的列,从而使查询定位到少量的分区上,否则就会扫描全部分区,可以通过EXPLAIN PARTITIONS来查看某条SQL语句会落在那些分区上,从而进行SQL优化,我测试,查询时不带分区条件的列,也会提高速度,故该措施值得一试。
分区的好处是:
*可以让单表存储更多的数据
*分区表的数据更容易维护,可以通过清楚整个分区批量删除大量数据,也可以增加新的分区来支持新插入的数据。另外,还可以对一个独立分区进行优化、检查、修复等操作
*部分查询能够从查询条件确定只落在少数分区上,速度会很快
*分区表的数据还可以分布在不同的物理设备上,从而高效利用多个硬件设备
*可以使用分区表赖避免某些特殊瓶颈,例如InnoDB单个索引的互斥访问、ext3文件系统的inode锁竞争
*可以备份和恢复单个分区
分区的限制和缺点:
*一个表最多只能有1024个分区
*如果分区字段中有主键或者唯一索引的列,那么所有主键列和唯一索引列都必须包含进来
*分区表无法使用外键约束
*NULL值会使分区过滤无效
*所有分区必须使用相同的存储引擎
分区的类型:
*RANGE分区:基于属于一个给定连续区间的列值,把多行分配给分区
*LIST分区:类似于按RANGE分区,区别在于LIST分区是基于列值匹配一个离散值集合中的某个值来进行选择
*HASH分区:基于用户定义的表达式的返回值来进行选择的分区,该表达式使用将要插入到表中的这些行的列值进行计算。这个函数可以包含MySQL中有效的、产生非负整数值的任何表达式
*KEY分区:类似于按HASH分区,区别在于KEY分区只支持计算一列或多列,且MySQL服务器提供其自身的哈希函数。必须有一列或多列包含整数值
*具体关于mysql分区的概念请自行google或查询官方文档,我这里只是抛砖引玉了。
4.分表
分表就是把一张大表,按照如上过程都优化了,还是查询卡死,那就把这个表分成多张表,把一次查询分成多次查询,然后把结果组合返回给用户。
分表分为垂直拆分和水平拆分,通常以某个字段做拆分项。比如以id字段拆分为100张表:表名为 tableName_id%100
但:分表需要修改源程序代码,会给开发带来大量工作,极大的增加了开发成本,故:只适合在开发初期就考虑到了大量数据存在,做好了分表处理,不适合应用上线了再做修改,成本太高!!!而且选择这个方案,都不如选择我提供的第二第三个方案的成本低!故不建议采用。
5.分库
把一个数据库分成多个,建议做个读写分离就行了,真正的做分库也会带来大量的开发成本,得不偿失!不推荐使用。
方案二详细说明:升级数据库,换一个100%兼容mysql的数据库
mysql性能不行,那就换个。为保证源程序代码不修改,保证现有业务平稳迁移,故需要换一个100%兼容mysql的数据库。
1.开源选择
*tiDB https://github.com/pingcap/tidb
*Cubrid https://www.cubrid.org/
*开源数据库会带来大量的运维成本且其工业品质和MySQL尚有差距,有很多坑要踩,如果你公司要求必须自建数据库,那么选择该类型产品。
1.云数据选择
*阿里云POLARDB
*https://www.aliyun.com/product/polardb?spm=a2c4g.11174283.cloudEssentials.47.7a984b5cS7h4wH
我开通测试了一下,支持免费mysql的数据迁移,无操作成本,性能提升在10倍左右,价格跟rds相差不多,是个很好的备选解决方案!
*阿里云OcenanBase
*淘宝使用的,扛得住双十一,性能卓著,但是在公测中,我无法尝试,但值得期待
*阿里云HybridDB for MySQL (原PetaData)
*https://www.aliyun.com/product/petadata?spm=a2c4g.11174283.cloudEssentials.54.7a984b5cS7h4wH
我也测试了一下,是一个olap和oltp兼容的解决方案,但是价格太高,每小时高达10块钱,用来做存储太浪费了,适合存储和分析一起用的业务。
*腾讯云DCDB
*https://cloud.tencent.com/product/dcdb_for_tdsql
腾讯的我不喜欢用,不多说。原因是出了问题找不到人,线上问题无法解决头疼!但是他价格便宜,适合超小公司,玩玩。
方案三详细说明:去掉mysql,换大数据引擎处理数据
数据量过亿了,没得选了,只能上大数据了。
1.开源解决方案
2.hadoop家族。hbase/hive怼上就是了。但是有很高的运维成本,一般公司是玩不起的,没十万投入是不会有很好的产出的!
3.云解决方案
4.这个就比较多了,也是一种未来趋势,大数据由专业的公司提供专业的服务,小公司或个人购买服务,大数据就像水/电等公共设施一样,存在于社会的方方面面。
5.国内做的最好的当属阿里云。
6.我选择了阿里云的MaxCompute配合DataWorks,使用超级舒服,按量付费,成本极低。
7.MaxCompute可以理解为开源的Hive,提供sql/mapreduce/ai算法/python脚本/shell脚本等方式操作数据,数据以表格的形式展现,以分布式方式存储,采用定时任务和批处理的方式处理数据。DataWorks提供了一种工作流的方式管理你的数据处理任务和调度监控。
8.当然你也可以选择阿里云hbase等其他产品,我这里主要是离线处理,故选择MaxCompute,基本都是图形界面操作,大概写了300行sql,费用不超过100块钱就解决了数据处理问题。
数据库分库分表中间件对比
分区:对业务透明,分区只不过把存放数据的文件分成了许多小块,例如mysql中的一张表对应三个文件.MYD,MYI,frm。
根据一定的规则把数据文件(MYD)和索引文件(MYI)进行了分割,分区后的表呢,还是一张表。分区可以把表分到不同的硬盘上,但不能分配到不同服务器上。
*优点:数据不存在多个副本,不必进行数据复制,性能更高。
*缺点:分区策略必须经过充分考虑,避免多个分区之间的数据存在关联关系,每个分区都是单点,如果某个分区宕机,就会影响到系统的使用。
分片:对业务透明,在物理实现上分成多个服务器,不同的分片在不同服务器上
个人感觉跟分库没啥区别,只是叫法不一样而已,值得一提的是关系型数据库和nosql数据库分片的概念以及处理方式是一样的吗?
请各位看官自行查找相关资料予以解答
分表:当数据量大到一定程度的时候,都会导致处理性能的不足,这个时候就没有办法了,只能进行分表处理。也就是把数据库当中数据根据按照分库原则分到多个数据表当中,
这样,就可以把大表变成多个小表,不同的分表中数据不重复,从而提高处理效率。
分表也有两种方案:
- 同库分表:所有的分表都在一个数据库中,由于数据库中表名不能重复,因此需要把数据表名起成不同的名字。
*优点:由于都在一个数据库中,公共表,不必进行复制,处理更简单
*缺点:由于还在一个数据库中,CPU、内存、文件IO、网络IO等瓶颈还是无法解决,只能降低单表中的数据记录数。
表名不一致,会导后续的处理复杂(参照mysql meage存储引擎来处理) - 不同库分表:由于分表在不同的数据库中,这个时候就可以使用同样的表名。
*优点:CPU、内存、文件IO、网络IO等瓶颈可以得到有效解决,表名相同,处理起来相对简单
*缺点:公共表由于在所有的分表都要使用,因此要进行复制、同步。
一些聚合的操作,join,group by,order等难以顺利进行
参考博客:http://www.cnblogs.com/langtianya/p/4997768.html,http://blog.51yip.com/mysql/949.html
分库:分表和分区都是基于同一个数据库里的数据分离技巧,对数据库性能有一定提升,但是随着业务数据量的增加,
原来所有的数据都是在一个数据库上的,网络IO及文件IO都集中在一个数据库上的,因此CPU、内存、文件IO、网络IO都可能会成为系统瓶颈。
当业务系统的数据容量接近或超过单台服务器的容量、QPS/TPS接近或超过单个数据库实例的处理极限等
此时,往往是采用垂直和水平结合的数据拆分方法,把数据服务和数据存储分布到多台数据库服务器上。
分库只是一个通俗说法,更标准名称是数据分片,采用类似分布式数据库理论指导的方法实现,对应用程序达到数据服务的全透明和数据存储的全透明
读写分离方案
海量数据的存储及访问,通过对数据库进行读写分离,来提升数据的处理能力。读写分离它的方案特点是数据库产生多个副本,数据库的写操作都集中到一个数据库上,而一些读的操作呢,可以分解到其它数据库上。这样,只要付出数据复制的成本,就可以使得数据库的处理压力分解到多个数据库上,从而大大提升数据处理能力。
1>Cobar 是提供关系型数据库(MySQL)分布式服务的中间件,它可以让传统的数据库得到良好的线性扩展,并看上去还是一个数据库,对应用保持透明。
Cobar以Proxy的形式位于前台应用和实际数据库之间,对前台的开放的接口是MySQL通信协议,将前台SQL语句变更并按照数据分布规则发到合适的后台数据分库,再合并返回结果,模拟单库下的数据库行为。
Cobar属于中间层方案,在应用程序和MySQL之间搭建一层Proxy。中间层介于应用程序与数据库间,需要做一次转发,而基于JDBC协议并无额外转发,直接由应用程序连接数据库,性能上有些许优势。这里并非说明中间层一定不如客户端直连,除了性能,需要考虑的因素还有很多,中间层更便于实现监控、数据迁移、连接管理等功能。
Cobar属于阿里B2B事业群,始于2008年,在阿里服役3年多,接管3000+个MySQL数据库的schema,集群日处理在线SQL请求50亿次以上。
由于Cobar发起人的离职,Cobar停止维护。后续的类似中间件,比如MyCAT建立于Cobar之上,包括现在阿里服役的RDRS其中也复用了Cobar-Proxy的相关代码。
2>MyCAT是社区爱好者在阿里cobar基础上进行二次开发,解决了cobar当时存 在的一些问题,并且加入了许多新的功能在其中。目前MyCAT社区活 跃度很高,目前已经有一些公司在使用MyCAT。总体来说支持度比 较高,也会一直维护下去,发展到目前的版本,已经不是一个单纯的MySQL代理了,它的后端可以支持MySQL, SQL Server, Oracle, DB2, PostgreSQL等主流数据库,也支持MongoDB这种新型NoSQL方式的存储,未来还会支持更多类型的存储。
MyCAT是一个强大的数据库中间件,不仅仅可以用作读写分离,以及分表分库、容灾管理,而且可以用于多租户应用开发、云平台基础设施,让你的架构具备很强的适应性和灵活性,借助于即将发布的MyCAT只能优化模块,系统的数据访问瓶颈和热点一目了然,根据这些统计分析数据,你可以自动或手工调整后端存储,将不同的表隐射到不同存储引擎上,而整个应用的代码一行也不用改变。
MyCAT是在Cobar基础上发展的版本,两个显著提高:后端由BIO改为NIO,并发量有大幅提高; 增加了对Order By, Group By, Limit等聚合功能(虽然Cobar也可以支持Order By, Group By, Limit语法,但是结果没有进行聚合,只是简单返回给前端,聚合功能还是需要业务系统自己完成)
3>TDDL是Tabao根据自己的业务特点开发了(Tabao Distributed Data Layer, 外号:头都大了)。主要解决了分库分表对应用的透明化以及异构数据库之间的数据复制,它是一个基于集中式配置的jdbc datasourcce实现,具有主备,读写分离,动态数据库配置等功能。
TDDL并非独立的中间件,只能算作中间层,处于业务层和JDBC层中间,是以Jar包方式提供给应用调用,属于JDBC Shard的思想。
TDDL源码:https://github.com/alibaba/tb_tddl
TDDL复杂度相对较高。当前公布的文档较少,只开源动态数据源,分表分库部分还未开源,还需要依赖diamond,不推荐使用。
4>DRDS是阿里巴巴自主研发的分布式数据库服务(此项目不开源),DRDS脱胎于阿里巴巴开源的Cobar分布式数据库引擎,吸收了Cobar核心的Cobar-Proxy源码,实现了一套独立的类似MySQL-Proxy协议的解析端,能够对传入的SQL进行解析和处理,对应用程序屏蔽各种复杂的底层DB拓扑结构,获得单机数据库一样的使用体验,同时借鉴了淘宝TDDL丰富的分布式数据库实践经验,实现了对分布式Join支持,SUM/MAX/COUNT/AVG等聚合函数支持以及排序等函数支持,通过异构索引、小表广播等解决分布式数据库使用场景下衍生出的一系列问题,最终形成了完整的分布式数据库方案。
5>Atlas是一个位于应用程序与MySQL之间的基于MySQL协议的数据中间层项目,它是在mysql-proxy 0.8.2版本上对其进行优化,360团队基于mysql proxy 把lua用C改写,它实现了MySQL的客户端和服务端协议,作为服务端与应用程序通讯,同时作为客户端与MySQL通讯。它对应用程序屏蔽了DB的细节。
Altas不能实现分布式分表,所有的字表必须在同一台DB的同一个DataBase里且所有的字表必须实现建好,Altas没有自动建表的功能。
原有版本是不支持分库分表, 目前已经放出了分库分表版本。在网上看到一些朋友经常说在高并 发下会经常挂掉,如果大家要使用需要提前做好测试。
6>DBProxy是美团点评DBA团队针对公司内部需求,在奇虎360公司开源的Atlas做了很多改进工作,形成了新的高可靠、高可用企业级数据库中间件。其特性主要有:读写分离、负载均衡、支持分表、IP过滤、sql语句黑名单、DBA平滑下线DB、从库流量配置、动态加载配置项
项目的Github地址是https://github.com/Meituan-Dianping/DBProxy
7>sharding-JDBC是当当应用框架ddframe中,从关系型数据库模块dd-rdb中分离出来的数据库水平分片框架,实现透明化数据库分库分表访问。
Sharding-JDBC是继dubbox和elastic-job之后,ddframe系列开源的第3个项目。
Sharding-JDBC直接封装JDBC API,可以理解为增强版的JDBC驱动,旧代码迁移成本几乎为零:
*可适用于任何基于Java的ORM框架,如JPA、Hibernate、Mybatis、Spring JDBC Template或直接使用JDBC。
*可基于任何第三方的数据库连接池,如DBCP、C3P0、 BoneCP、Druid等。
*理论上可支持任意实现JDBC规范的数据库。虽然目前仅支持MySQL,但已有支持Oracle、SQLServer等数据库的计划。
Sharding-JDBC定位为轻量Java框架,使用客户端直连数据库,以jar包形式提供服务,无proxy代理层,无需额外部署,无其他依赖,DBA也无需改变原有的运维方式。
Sharding-JDBC分片策略灵活,可支持等号、between、in等多维度分片,也可支持多分片键。
SQL解析功能完善,支持聚合、分组、排序、limit、or等查询,并支持Binding Table以及笛卡尔积表查询。
知名度较低的:
Heisenberg
Baidu.
其优点:分库分表与应用脱离,分库表如同使用单库表一样,减少db连接数压力,热重启配置,可水平扩容,遵守MySQL原生协议,读写分离,无语言限制,
mysqlclient, c, java都可以使用Heisenberg服务器通过管理命令可以查看,如连接数,线程池,结点等,并可以调整采用velocity的分库分表脚本进行自定义分库表,相当的灵活。
https://github.com/brucexx/heisenberg(开源版已停止维护)
CDS
JD. Completed Database Sharding.
CDS是一款基于客户端开发的分库分表中间件产品,实现了JDBC标准API,支持分库分表,读写分离和数据运维等诸多共,提供高性能,高并发和高可靠的海量数据路由存取服务,业务系统可近乎零成本进行介入,目前支持MySQL, Oracle和SQL Server.
(架构上和Cobar,MyCAT相似,直接采用jdbc对接,没有实现类似MySQL协议,没有NIO,AIO,SQL Parser模块采用JSqlParser, Sql解析器有:druid>JSqlParser>fdbparser.)
DDB
网易. Distributed DataBase.
DDB经历了三次服务模式的重大更迭:Driver模式->Proxy模式->云模式。
Driver模式:基于JDBC驱动访问,提供一个db.jar, 和TDDL类似, 位于应用层和JDBC之间. Proxy模式:在DDB中搭建了一组代理服务器来提供标准的MySQL服务,在代理服务器内部实现分库分表的逻辑。应用通过标准数据库驱动访问DDB Proxy, Proxy内部通过MySQL解码器将请求还原为SQL, 并由DDB Driver执行得到结果。
私有云模式:基于网易私有云开发的一套平台化管理工具Cloudadmin, 将DDB原先Master的功能打散,一部分分库相关功能集成到proxy中,
如分库管理、表管理、用户管理等,一部分中心化功能集成到Cloudadmin中,如报警监控,此外,Cloudadmin中提供了一键部署、自动和手动备份,版本管理等平台化功能。
OneProxy:
数据库界大牛,前支付宝数据库团队领导楼方鑫开发,基于mysql官方 的proxy思想利用c进行开发的,OneProxy是一款商业收费的中间件, 楼总舍去了一些功能点,专注在性能和稳定性上。有朋友测试过说在 高并发下很稳定。
Oceanus(58同城数据库中间件)
Oceanus致力于打造一个功能简单、可依赖、易于上手、易于扩展、易于集成的解决方案,甚至是平台化系统。拥抱开源,提供各类插件机制集成其他开源项目,新手可以在几分钟内上手编程,分库分表逻辑不再与业务紧密耦合,扩容有标准模式,减少意外错误的发生。
Vitess:
这个中间件是Youtube生产在使用的,但是架构很复杂。 与以往中间件不同,使用Vitess应用改动比较大要 使用他提供语言的API接口,我们可以借鉴他其中的一些设计思想。
Kingshard:
Kingshard是前360Atlas中间件开发团队的陈菲利用业务时间 用go语言开发的,目前参与开发的人员有3个左右, 目前来看还不是成熟可以使用的产品,需要在不断完善。
MaxScale与MySQL Route:
这两个中间件都算是官方的吧,MaxScale是mariadb (MySQL原作者维护的一个版本)研发的,目前版本不支持分库分表。
MySQL Route是现在MySQL 官方Oracle公司发布出来的一个中间件。
MySQL常用数据库引擎
*查看MySQL数据库使用的引擎
*查看数据库默认使用哪个引擎
1.MyISAM存储引擎
MyISAM基于ISAM存储引擎,并对其进行扩展。它是在Web、数据仓储和其他应用环境下最常使用的存储引擎之一。MyISAM拥有较高的插入、查询速度,但不支持事物。MyISAM主要特性有:
1.大文件(达到63位文件长度)在支持大文件的文件系统和操作系统上被支持
2.当把删除和更新及插入操作混合使用的时候,动态尺寸的行产生更少碎片。这要通过合并相邻被删除的块,以及若下一个块被删除,就扩展到下一块自动完成
3.每个MyISAM表最大索引数是64,这可以通过重新编译来改变。每个索引最大的列数是16
4.最大的键长度是1000字节,这也可以通过编译来改变,对于键长度超过250字节的情况,一个超过1024字节的键将被用上
5.BLOB和TEXT列可以被索引
6.NULL被允许在索引的列中,这个值占每个键的0~1个字节
7.所有数字键值以高字节优先被存储以允许一个更高的索引压缩
8.每个MyISAM类型的表都有一个AUTO_INCREMENT的内部列,当INSERT和UPDATE操作的时候该列被更新,同时AUTO_INCREMENT列将被刷新。所以说,MyISAM类型表的AUTO_INCREMENT列更新比InnoDB类型的AUTO_INCREMENT更快
9.可以把数据文件和索引文件放在不同目录
10.每个字符列可以有不同的字符集
11.有VARCHAR的表可以固定或动态记录长度
12.VARCHAR和CHAR列可以多达64KB
13.使用MyISAM引擎创建数据库,将产生3个文件。文件的名字以表名字开始,扩展名之处文件类型:frm文件存储表定义、数据文件的扩展名为.MYD(MYData)、索引文件的扩展名时.MYI(MYIndex)
2.InnoDB存储引擎
InnoDB是事务型数据库的首选引擎,支持事务安全表(ACID),支持行锁定和外键,上图也看到了,InnoDB是默认的MySQL引擎。InnoDB主要特性有:
1.InnoDB给MySQL提供了具有提交、回滚和崩溃恢复能力的事物安全(ACID兼容)存储引擎。InnoDB锁定在行级并且也在SELECT语句中提供一个类似Oracle的非锁定读。这些功能增加了多用户部署和性能。在SQL查询中,可以自由地将InnoDB类型的表和其他MySQL的表类型混合起来,甚至在同一个查询中也可以混合
2.InnoDB是为处理巨大数据量的最大性能设计。它的CPU效率可能是任何其他基于磁盘的关系型数据库引擎锁不能匹敌的
3.InnoDB存储引擎完全与MySQL服务器整合,InnoDB存储引擎为在主内存中缓存数据和索引而维持它自己的缓冲池。InnoDB将它的表和索引在一个逻辑表空间中,表空间可以包含数个文件(或原始磁盘文件)。这与MyISAM表不同,比如在MyISAM表中每个表被存放在分离的文件中。InnoDB表可以是任何尺寸,即使在文件尺寸被限制为2GB的操作系统上
4.InnoDB支持外键完整性约束,存储表中的数据时,每张表的存储都按主键顺序存放,如果没有显示在表定义时指定主键,InnoDB会为每一行生成一个6字节的ROWID,并以此作为主键
5.InnoDB被用在众多需要高性能的大型数据库站点上
6.InnoDB不创建目录,使用InnoDB时,MySQL将在MySQL数据目录下创建一个名为ibdata1的10MB大小的自动扩展数据文件,以及两个名为ib_logfile0和ib_logfile1的5MB大小的日志文件
3.MEMORY存储引擎
MEMORY存储引擎将表中的数据存储到内存中,未查询和引用其他表数据提供快速访问。MEMORY主要特性有:
1.MEMORY表的每个表可以有多达32个索引,每个索引16列,以及500字节的最大键长度
2.MEMORY存储引擎执行HASH和BTREE缩影
3.可以在一个MEMORY表中有非唯一键值
4.MEMORY表使用一个固定的记录长度格式
5.MEMORY不支持BLOB或TEXT列
6.MEMORY支持AUTO_INCREMENT列和对可包含NULL值的列的索引
7.MEMORY表在所由客户端之间共享(就像其他任何非TEMPORARY表)
8.MEMORY表内存被存储在内存中,内存是MEMORY表和服务器在查询处理时的空闲中,创建的内部表共享
9.当不再需要MEMORY表的内容时,要释放被MEMORY表使用的内存,应该执行DELETE FROM或TRUNCATE TABLE,或者删除整个表(使用DROP TABLE)
存储引擎的选择
不同的存储引擎都有各自的特点,以适应不同的需求,如下表所示:
1.如果要提供提交、回滚、崩溃恢复能力的事物安全(ACID兼容)能力,并要求实现并发控制,InnoDB是一个好的选择
2.如果数据表主要用来插入和查询记录,则MyISAM引擎能提供较高的处理效率
3.如果只是临时存放数据,数据量不大,并且不需要较高的数据安全性,可以选择将数据保存在内存中的Memory引擎,MySQL中使用该引擎作为临时表,存放查询的中间结果
4.如果只有INSERT和SELECT操作,可以选择Archive,Archive支持高并发的插入操作,但是本身不是事务安全的。Archive非常适合存储归档数据,如记录日志信息可以使用Archive
redis
https://blog.csdn.net/liqingtx/article/details/60330555
第一装逼大法 sql
1列转行
=========================================================
一、整理测试数据
二、行转列
主要思路是分组后使用case进行条件判断处理
三、列转行
主要思路也是分组后使用case
四、列转行详解
1.1、初始测试数据
表结构:TEST_TB_GRADE2
Sql代码
create table TEST_TB_GRADE2
(
ID NUMBER(10) not null,
USER_NAME VARCHAR2(20 CHAR),
CN_SCORE FLOAT,
MATH_SCORE FLOAT,
EN_SCORE FLOAT
)
初始数据如下图:
1.2、 如果需要实现如下的查询效果图:
这就是最常见的列转行,主要原理是利用SQL里面的union,具体的sql语句如下:
Sql代码
select user_name, ‘语文’ COURSE , CN_SCORE as SCORE from test_tb_grade2
union select user_name, ‘数学’ COURSE, MATH_SCORE as SCORE from test_tb_grade2
union select user_name, ‘英语’ COURSE, EN_SCORE as SCORE from test_tb_grade2
order by user_name,COURSE
也可以利用【 insert all into … select 】来实现,首先需要先建一个表TEST_TB_GRADE3:
Sql代码
create table TEST_TB_GRADE3
(
USER_NAME VARCHAR2(20 CHAR),
COURSE VARCHAR2(20 CHAR),
SCORE FLOAT
)
再执行下面的sql:
Sql代码
insert all
into test_tb_grade3(USER_NAME,COURSE,SCORE) values(user_name, ‘语文’, CN_SCORE)
into test_tb_grade3(USER_NAME,COURSE,SCORE) values(user_name, ‘数学’, MATH_SCORE)
into test_tb_grade3(USER_NAME,COURSE,SCORE) values(user_name, ‘英语’, EN_SCORE)
select user_name, CN_SCORE, MATH_SCORE, EN_SCORE from test_tb_grade2;
commit;
别忘记commit操作,然后再查询TEST_TB_GRADE3,发现表中的数据就是列转成行了。
开窗函数
一、什么是开窗函数
开窗函数/分析函数:over()
开窗函数也叫分析函数,有两类:一类是聚合开窗函数,一类是排序开窗函数。
开窗函数的调用格式为:
函数名(列名) OVER(partition by 列名 order by列名) 。
如果你没听说过开窗函数,看到上面开窗函数的调用方法,你可能还会有些疑惑。但只要你了解聚合函数,那么理解开窗函数就非常容易了。
我们知道聚合函数对一组值执行计算并返回单一的值,如sum(),count(),max(),min(), avg()等,这些函数常与group by子句连用。除了 COUNT 以外,聚合函数忽略空值。
但有时候一组数据只返回一组值是不能满足需求的,如我们经常想知道各个地区的前几名、各个班或各个学科的前几名。这时候需要每一组返回多个值。用开窗函数解决这类问题非常方便。
开窗函数和聚合函数的区别如下:
(1)SQL 标准允许将所有聚合函数用作开窗函数,用OVER 关键字区分开窗函数和聚合函数。
(2)聚合函数每组只返回一个值,开窗函数每组可返回多个值。
注:常见主流数据库目前都支持开窗函数,但mysql数据库目前还不支持。
二、常见面试题
1.分区排序:row_number () over()
有如下学生成绩表:students_grades
查询每门课程course_name前三名的学生姓名及成绩,要求输出列格式如下:
course_name, number, stu_name, grades
查询语句如下:
2.几个排序函数row_number() over()、rank() over()、dense_rank() over()、ntile() over()的区别
(1)row_number() over():对相等的值不进行区分,相等的值对应的排名相同,序号从1到n连续。
(2)rank() over():相等的值排名相同,但若有相等的值,则序号从1到n不连续。如果有两个人都排在第3名,则没有第4名。
(3) dense_rank() over():对相等的值排名相同,但序号从1到n连续。如果有两个人都排在第一名,则排在第2名(假设仅有1个第二名)的人是第3个人。
(4) ntile( n ) over():可以看作是把有序的数据集合平均分配到指定的数量n的桶中,将桶号分配给每一行,排序对应的数字为桶号。如果不能平均分配,则较小桶号的桶分配额外的行,并且各个桶中能放的数据条数最多相差1。
学生成绩表同上,查询语句如下:
查询结果如下:
limit分页与between分页(结合大数据分页很慢怎么解决一起装逼)
- limit分页
(1) 直接limit,这时实际上是依赖了默认排序,但是mysql实际上并没有明确保证“每次查询分页时,总的排序不变”
(2) 先排序再limit(主键排序)
(3) 先排序再limit(与where同索引排序)
(1)的执行计划与(3)相同。可见,默认排序是使用了where同索引排序。
(2)的执行计划优于(1)(3),原因见《mysql的where、order by和limit的隐藏优化》
2. between 分页
(1) between索引(非唯一索引)
(2) between主键(唯一索引)
三、分析
- limit分页有问题
要想减少大数据集的查询对数据库的压力,光靠limit做分页是不行的。不仅不会减小数据库的压力,还会起反作用,因为当limit查到最后几页的时候,还是需要把所有符合条件的内容load到内存里。本来没有分页的时候,只需要load一次所有数据就可以了,有了分页之后,反而需要load好几次所有数据。
所以,limit实际上只能减少数据传输(带宽)的压力,和应用服务器内存的压力(不用一次性创建太多对象)。如果不是出于上面的两种原因,不要用limit做分页,而是应该从业务的角度出发,将大数据集拆分为小数据集,分批查取。
其实,limit的本意是为了减少浪费,它最适用的场景就是取前N条数据,不用的数据先不取。显然,对大数据集采用limit分页的用法违背了limit的本意 (此时要使用的是全量数据,不存在“不用的数据先不取”)。 - between分页是正解
between分页无论翻到多少页,扫描的条数都是固定的。但是注意要在唯一索引上进行分页,不然每页的大小可能会不一致。
自己百度或者意淫一些统计sql题
1求出每个班三科成绩都是90分的数量
2求出全年级每个班数学95分以上的人
3.90分以上的优秀,80以上良,70以上中 60以下 差,求出差生最多的三个班级
https://blog.csdn.net/qq_41936662/article/details/80393172
批量导入insert into
mysql结合mybatis
https://blog.csdn.net/weixin_43878332/article/details/107615069
第二装逼大法jvm
首先是jvm内存模型
堆栈这两个字:堆放对象,栈放静态。
串形收集器 s开头
并行收集器 p开头
cms 1.7 并行标记回收算法
g1 jdk10 混合 可预测,延迟小
zgc jdk16 牛逼,神一般,甚至1-2毫秒
第三装逼大法 jdk各代新特性
1.8
Default
Lambda,stream
双冒号
LocalDate
optional
1.9
模块化
10
Var
g1
11
字符串去除首位空格,判断空
12
switch 多条件
13
Scala 三引号
14 instanceof 内部自动转类型
record 省略getset
switch 函数作为条件
15
zgc 性能更牛逼
16 上面的那些转正或者优化
这些玩意,我可以明确告诉你1.9以上几乎没人用。