MySQL数据库面试题
https://blog.csdn.net/a745233700/article/details/114242960
数据库是存放数据的仓库。分为关系型数据库和非关系型数据库。
关系数据库
关系型数据库,存储的格式可以直观地反映实体间的关系。关系型数据库和常见的表格比较相似,关系型数据库中表与表之间是有很多复杂的关联关系的。 常见的关系型数据库有Mysql,Oracle等。在轻量或者小型的应用中,使用不同的关系型数据库对系统的性能影响不大,但是在构建大型应用时,则需要根据应用的业务需求和性能需求,选择合适的关系型数据库。
非关系型数据库(NoSQL)
随着近些年技术方向的不断拓展,大量的NoSql数据库如MongoDB、Redis、Memcache出于简化数据库结构、避免冗余、影响性能的表连接、摒弃复杂分布式的目的被设计。
NoSQL数据库适合追求速度和可扩展性、业务多变的应用场景。对于非结构化数据的处理更合适,如文章、评论,这些数据如全文搜索、机器学习通常只用于模糊处理,并不需要像结构化数据一样,进行精确查询,而且这类数据的数据规模往往是海量的,数据规模的增长往往也是不可能预期的,而NoSQL数据库的扩展能力几乎也是无限的,所以NoSQL数据库可以很好的满足这一类数据的存储。NoSQL数据库利用key-value可以大量的获取大量的非结构化数据,并且数据的获取效率很高,但用它查询结构化数据效果就比较差。
数据保存在内存优缺点
优点: 存取速度快
缺点: 数据不能永久保存
数据保存在文件优缺点
优点: 数据永久保存
缺点:
1)速度比内存操作慢,频繁的IO操作。
2)查询数据不方便
数据保存在数据库
1)数据永久保存
2)使用SQL语句,查询方便效率高。
3)管理数据方便
超键(super key):在关系中能唯一标识元组的属性集称为关系模式的超键。一个属性可以为作为一个超键,多个属性组合在一起也可以作为一个超键。超键包含候选键和主键。
候选键(candidate key):是最小超键,即没有冗余元素的超键。也就是在候选键中,若再删除属性,就不是键了!
主键(primary key):用户选作元组标识的一个候选键程序主键, 数据库表中对储存数据对象予以唯一和完整标识的数据列或属性的组合。一个数据列只能有一个主键,且主键的取值不能缺失,即不能为空值(Null)。
外键(foreign key):在一个表中存在的另一个表的主键称此表的外键。
咱们创建简单的两个表,说明一下各个键!
学生信息(学号 身份证号 姓名 性别 年龄 身高 体重 宿舍号)和 宿舍信息(宿舍号 楼号)
学号 | 身份证号 | 姓名 | 性别 | 年龄 | 身高 | 体重 | 宿舍号 |
---|---|---|---|---|---|---|---|
1 | 1xxxxx | 张三 | 男 | 9 | 140 | 50 | 1 |
2 | 2xxxxx | 李四 | 女 | 22 | 120 | 60 | 2 |
宿舍号 | 楼号 |
---|---|
1 | 1 |
2 | 1 |
超键:只要含有“学号”或者“身份证号”两个属性的集合就叫超键,例如R1(学号 性别)、R2(身份证号 身高)、R3(学号 身份证号)等等都可以称为超键!
候选键:不含有多余的属性的超键,比如(学号)、(身份证号)都是候选键,又比如R1中学号这一个属性就可以唯一标识元组了,而有没有性别这一属性对是否唯一标识元组没有任何的影响!
主键:就是用户从很多候选键选出来的一个键就是主键,比如你要求学号是主键,那么身份证号就不可以是主键了!
外键:宿舍号就是学生信息表的外键
第一范式(1NF):指表的列不可再分
,数据库中表的每一列都是不可分割的基本数据项,同一列中不能有多个值;
第二范式(2NF):在 1NF 的基础上,还包含两部分的内容:一是表必须有一个主键
;二是表中非主键列必须完全依赖于主键,不能只依赖于主键的一部分;
第三范式(3NF):在 2NF 的基础上,消除传递依赖,消除冗余数据
,非主键列必须直接依赖于主键。
BC范式(BCNF):在 3NF 的基础上,消除主属性对于码部分的传递依赖
反范式: 就是降低范式标准
有时为了提高运行效率,就必须降低范式标准,适当保留冗余数据
。具体做法是: 在概念数据模型设计时遵守第三范式,降低范式标准的工作放到物理数据模型设计时考虑。降低范式就是增加字段,减少了查询时的关联,提高查询效率。
范式的优点和缺点
优点:
缺点:
通常需要关联,稍微复杂一些的查询语句在符合范式的schema上都可能需要至少一次关联,也许更多。这不但代价昂贵,也可能使一些索引策略无效。例如,范式化可能将列存放在不同的表中,而这些列如果在一个表中本可以属于同一个索引。
反范式的优点和缺点
优点:
减少了查询时的关联,提高查询效率。
缺点:
增加了冗余字段, 储存和维护成本提高
结构化查询语言(Structured Query Language)简称SQL,是一种数据库查询语言。
作用:用于存取数据、查询、更新和管理关系数据库系统。
MySQL是一个关系型数据库管理系统,由瑞典MySQL AB 公司开发,属于 Oracle 旗下产品。MySQL 是最流行的关系型数据库管理系统之一,在 WEB 应用方面,MySQL是最好的 RDBMS (Relational Database Management System,关系数据库管理系统) 应用软件之一。在Java企业级开发中非常常用,因为 MySQL 是开源免费的,并且方便扩展。
最上层是一些客户端和连接服务,包含本地sock通信和大多数基于客户端/服务端工具实现的类似于tcp/ip的通信。主
要完成一些类似于连接处理、授权认证、及相关的安全方案。在该层上引入了线程池的概念,为通过认证安全接入的客户端
提供线程。同样在该层上可以实现基于SSL的安全链接。服务器也会为安全接入的每个客户端验证它所具有的操作权限。
2. 服务层
Management Serveices & Utilities 系统管理和控制工具 系统管理和控制工具
SQL Interface
SQL接口 接受用户的SQL命令,并且返回用户需要查询的结果。比如select from就是调用SQL Interface
Parser
解析器 SQL命令传递到解析器的时候会被解析器验证和解析。
Optimizer
查询优化器。 SQL语句在查询之前会使用查询优化器对查询进行优化。 用一个例子就可以理解: select uid,name from user where gender= 1; 优化器来决定先投影还是先过滤。
Cache和Buffer
查询缓存。 如果查询缓存有命中的查询结果,查询语句就可以直接去查询缓存中取数据。 这个缓存机制是由一系列小缓存组成的。比如表缓存,记录缓存,key缓存,权限缓存等
3. 引擎层
存储引擎层,存储引擎真正的负责了MySQL中数据的存储和提取,服务器通过API与存储引擎进行通信。
不同的存储引擎具有的功能不同,这样我们可以根据自己的实际需要进行选取。后面介绍MyISAM和InnoDB
4. 存储层(文件系统层)
数据存储层,主要是将数据存储在运行于裸设备的文件系统之上,并完成与存储引擎的交互。
mysql有哪些数据类型
分类 | 类型名称 | 说明 |
---|---|---|
整数类型 | tinyInt | 很小的整数(8位二进制) |
smallint | 小的整数(16位二进制) | |
mediumint | 中等大小的整数(24位二进制) | |
int(integer) | 普通大小的整数(32位二进制) | |
小数类型 | float | 单精度浮点数 |
double | 双精度浮点数 | |
decimal(m,d) | 压缩严格的定点数 | |
日期类型 | year | YYYY |
time | HH:MM:SS -838:59:59~838:59:59 | |
date | YYYY-MM-DD 1000-01-01~9999-12-3 | |
datetime | YYYY-MM-DD HH:MM:SS 1000-01-01 00:00:00~ 9999-12-31 23:59:59 | |
timestamp | YYYY-MM-DD HH:MM:SS 19700101 00:00:01 UTC~2038-01-19 03:14:07UTC | |
文本、二进制类型 | CHAR(M) | M为0~255之间的整数 |
VARCHAR(M) | M为0~65535之间的整数 | |
TINYBLOB | 允许长度0~255字节 | |
BLOB | 允许长度0~65535字节 | |
MEDIUMBLOB | 允许长度0~167772150字节 | |
LONGBLOB | 允许长度0~4294967295字节 | |
TINYTEXT | 允许长度0~255字节 | |
TEXT | 允许长度0~65535字节 | |
MEDIUMTEXT | 允许长度0~167772150字节 | |
LONGTEXT | 允许长度0~4294967295字节 | |
VARBINARY(M) | 允许长度0~M个字节的变长字节字符串 | |
BINARY(M) | 允许长度0~M个字节的定长字节字符串 |
1、整数类型,包括TINYINT、SMALLINT、MEDIUMINT、INT、BIGINT,分别表示1字节、2字节、3字节、4字节、8字节整数。任何整数类型都可以加上UNSIGNED属性,表示数据是无符号的,即非负整数。
长度:整数类型可以被指定长度,例如:INT(11)表示长度为11的INT类型。长度在大多数场景是没有意义的,它不会限制值的合法范围,只会影响显示字符的个数,而且需要和UNSIGNED ZEROFILL属性配合使用才有意义。
例子,假定类型设定为INT(5),属性为UNSIGNED ZEROFILL,如果用户插入的数据为12的话,那么数据库实际存储数据为00012。
2、实数类型,包括FLOAT、DOUBLE、DECIMAL。
DECIMAL可以用于存储比BIGINT还大的整型,能存储精确的小数。
而FLOAT和DOUBLE是有取值范围的,并支持使用标准的浮点进行近似计算。
计算时FLOAT和DOUBLE相比DECIMAL效率更高一些,DECIMAL你可以理解成是用字符串进行处理。
3、字符串类型,包括VARCHAR、CHAR、TEXT、BLOB
VARCHAR
用于存储可变长字符串
,它比定长类型更节省空间。
VARCHAR使用额外1或2个字节存储字符串长度。列长度小于255字节时,使用1字节表示,否则使用2字节表示。
VARCHAR存储的内容超出设置的长度时,内容会被截断。
CHAR
是定长的
,根据定义的字符串长度分配足够的空间。
CHAR会根据需要使用空格进行填充方便比较。
CHAR适合存储很短的字符串,或者所有值都接近同一个长度。
CHAR存储的内容超出设置的长度时,内容同样会被截断。
使用策略:
对于经常变更的数据来说,CHAR比VARCHAR更好,因为CHAR不容易产生碎片
。
对于非常短的列
,CHAR比VARCHAR在存储空间上更有效率。
使用时要注意只分配需要的空间,更长的列排序时会消耗更多内存。
尽量避免使用TEXT/BLOB类型,查询时会使用临时表,导致严重的性能开销。
4、枚举类型(ENUM),把不重复的数据存储为一个预定义的集合。
有时可以使用ENUM代替常用的字符串类型。
ENUM存储非常紧凑,会把列表值压缩到一个或两个字节。
ENUM在内部存储时,其实存的是整数
。
尽量避免使用数字作为ENUM枚举的常量,因为容易混乱。
排序是按照内部存储的整数
5、日期和时间类型,尽量使用timestamp,空间效率高于datetime,
用整数保存时间戳通常不方便处理。
如果需要存储微妙,可以使用bigint存储。
看到这里,这道真题是不是就比较容易回答了。
一条SQL语句在MySQL中执行过程全解析
MySQL探秘(二):SQL语句执行过程详解
1、客户端的数据库驱动与数据库连接池:
(1)客户端与数据库进行通信前,通过数据库驱动与MySQL建立连接,建立完成之后,就发送SQL语句
(2)为了减少频繁创建和销毁连接造成系统性能的下降,通过数据库连接池维护一定数量的连接线程,当需要进行连接时,就直接从连接池中获取,使用完毕之后,再归还给连接池。常见的数据库连接池有 Druid、C3P0、DBCP, Mybatis自带连接池
(1)连接器:主要负责跟客户端建立连接、获取权限、维持和管理连接
(2)查询缓存:如果命中了缓存,则立刻返回存储在缓存中的结果。否则进入下一阶段。
MySQL缓存是默认关闭的,也就是说不推荐使用缓存,并且在MySQL8.0 版本已经将查询缓存的整块功能删掉了。这主要是它的使用场景限制造成的:
先说下缓存中数据存储格式:key(sql语句)- value(数据值),所以如果SQL语句(key)只要存在一点不同之处就会直接进行数据库查询了;
由于表中的数据不是一成不变的,大多数是经常变化的,而当数据库中的数据变化了,那么相应的与此表相关的缓存数据就需要移除掉;
(3)解析器/预处理器: SQL解析、预处理,再由优化器生成对应的执行计划。
解析器的工作主要是对要执行的SQL语句进行词法解析、语法解析,最终得到抽象语法树,然后再使用预处理器对抽象语法树进行语义校验,判断抽象语法树中的表是否存在,如果存在的话,在接着判断select投影列字段是否在表中存在等。
预处理器则根据一些MySQL规则进行进一步检查解析书是否合法,例如检查数据表和数据列是否存在,还会解析名字和别名,看看它们是否有歧义。
(4)优化器:通过数据字典
和统计信息
的内容,再经过一系列运算 ,最终得出一个执行计划
,包括选择使用哪个索引
在分析是否走索引查询时,是通过进行动态数据采样统计分析出来;只要是统计分析出来的,那就可能会存在分析错误的情况,所以在SQL执行不走索引时,也要考虑到这方面的因素
(5)执行器:根据一系列的执行计划去调用存储引擎提供的API接口去调用操作数据,完成SQL的执行。
3、Innodb存储引擎的执行过程:(假设是一条更新sql)
(1)首先MySQL执行器根据 执行计划 调用存储引擎的API查询数据
(2)存储引擎先从缓存池buffer pool中查询数据,如果没有就会去磁盘中查询,如果查询到了就将其放到缓存池中
(3)在数据加载到 Buffer Pool 的同时,会将这条数据的原始记录保存到 undo 日志文件中
(4)innodb 会在 Buffer Pool 中执行更新操作
(5)更新后的数据会记录在 redo log buffer 中
(6)提交事务在提交的同时会做以下三件事
(7)(第一件事)将redo log buffer中的数据刷入到redo log file文件中
(8)(第二件事)将本次操作记录写入到 bin log文件中
(9)(第三件事)将bin log文件名字和更新内容在 bin log 中的位置记录到redo log中,同时在 redo log 最后添加 commit 标记
(10)使用一个后台线程,它会在某个时机将我们Buffer Pool中的更新后的数据刷到 MySQL 数据库中,这样就将内存和数据库的数据保持统一了
详细内容请阅读这篇文章:https://blog.csdn.net/a745233700/article/details/113927318
事务是一个不可分割的数据库操作序列,也是数据库并发控制的基本单位,其执行的结果必须使数据库从一种一致性状态变到另一种一致性状态。事务是逻辑上的一组操作,要么都执行,要么都不执行。
事务最经典也经常被拿出来说例子就是转账了。
假如小明要给小红转账1000元,这个转账会涉及到两个关键操作就是:将小明的余额减少1000元,将小红的余额增加1000元。万一在这两个操作之间突然出现错误比如银行系统崩溃,导致小明余额减少而小红的余额没有增加,这样就不对了。事务就是保证这两个关键操作要么都成功,要么都要失败。
关系性数据库需要遵循ACID规则,具体内容如下:
原子性(Atomicity)
: 事务是最小的执行单位,不允许分割。事务的原子性确保动作要么全部完成,要么完全不起作用;
一致性(Consistency)
: 执行事务前后,数据保持一致,多个事务对同一个数据读取的结果是相同的;
隔离性(Isolation)
: 并发访问数据库时,一个用户的事务不被其他事务所干扰,各并发事务之间数据库是独立的;
持久性(Durability)
: 一个事务被提交之后。它对数据库中数据的改变是持久的,即使数据库发生故障也不应该对其有任何影响。
ACID的实现原理:
1、原子性:原子性是通过MySQL的回滚日志undo log来实现的:当事务对数据库进行修改时,InnoDB会生成对应的undo log;如果事务执行失败或调用了rollback,导致事务需要回滚,便可以利用undo log中的信息将数据回滚到修改之前的样子。
2、隔离性:
(a)事务的隔离级别:
为保证在并发环境下读取数据的完整性和一致性,数据库提供了四种事务隔离级别,隔离级别越高,越能保证数据的完整性和一致性,但对高并发性能影响也越大,执行效率越低。(四种隔离级别从上往下依次升高)
读未提交(Read uncommitted)
:允许事务在执行过程中,读取其他事务尚未提交的数据;
任何问题都无法解决;
读已提交(Read committed)
:允许事务在执行过程中读取其他事务已经提交的数据;
可解决脏读的问题,即可避免脏读;
可重复读(Repeatable read)
(默认级别):在同一个事务内,任意时刻的查询结果都是一致的;
可解决脏读和不可重复读的问题;
读序列化(Serializable)
:所有事务逐个依次执行,每次读都需要获取表级共享锁,读写会相互阻塞。
可解决脏读、不可重复读、幻读的问题。
(b)事务的并发问题:
如果不考虑事务的隔离性,在事务并发的环境下,可能存在问题有:
更新丢失
:两个或多个事务操作相同的数据,然后基于选定的值更新该行时,由于每个事务都不知道其他事务的存在,就会发生丢失更新问题:最后的更新覆盖了其他事务所做的更新。
脏读
:指事务A正在访问数据,并且对数据进行了修改(事务未提交),这时,事务B也使用这个数据,后来事务A撤销回滚,并把修改后的数据恢复原值,B读到的数据就与数据库中的数据不一致,即B读到的数据是脏数据。
不可重复读
:在一个事务内,多次读取同一个数据,但是由于另一个事务在此期间对这个数据做了修改并提交,导致前后读取到的数据不一致;
幻读
:在一个事务中,先后两次进行读取相同的数据(一般是范围查询),但由于另一个事务新增或者删除了数据,导致前后两次结果不一致。
(c)事务隔离性的实现原理:
Innodb事务的隔离级别是由 MVCC
和 锁机制
实现的:
① MVCC(Multi-Version Concurrency Control,多版本并发控制)是 MySQL 的 InnoDB 存储引擎实现事务隔离级别的一种具体方式,用于实现读已提交和可重复读这两种隔离级别。而读未提交隔离级别总是读取最新的数据行,无需使用 MVCC。读序列化隔离级别需要对所有读取的行都加锁,单纯使用 MVCC 无法实现。
MVCC是通过在每行记录后面保存两个隐藏列
来实现的,一个保存了行的事务ID
,一个保存了行的回滚段指针
。每开始一个新的事务,都会自动递增产生一个新的事务ID。事务开始时会把该事务ID放到当前事务影响的行事务ID字段中,而回滚段的指针有该行记录上的所有版本数据,在undo log回滚日志中通过链表形式组织
,也就是说回滚段的指针实际指向undo log中该行的历史记录链表。
在并发访问数据库时,对正在事务中的数据做MVCC多版本的管理,以
避免写操作阻塞读操作,并且可以通过比较版本解决幻读
。
② 锁机制:
MySQL锁机制的基本工作原理就是:事务在修改数据库之前,需要先获得相应的锁,获得锁的事务才可以修改数据;在该事务操作期间,这部分的数据是锁定,其他事务如果需要修改数据,需要等待当前事务提交或回滚后释放锁。
排它锁解决脏读
共享锁解决不可重复读
临键锁解决幻读
3、持久性:
持久性的依靠redo log
日志实现,在执行SQL时会保存已执行的SQL语句到一个redo log文件,但是为了提高效率,将数据写入到redo log之前,会先写入到内存中的redo log buffer缓存区中
。
写入过程如下:
当向数据库写入数据时,执行过程会首先写入redo log buffer,redo log buffer中修改的数据会定期刷新到磁盘的redo log文件中
,这一过程称为刷盘(即redo log buffer写日志到磁盘的redo log file中 )。
redo log buffer的使用可以大大提高了读写数据的效率,但是也带了新的问题:如果MySQL宕机,而此时redo log buffer中修改的数据在内存还没有刷新到磁盘,就会导致数据的丢失,事务的持久性无法保证。为了确保事务的持久性,在当事务提交时
,会调用 fsync
接口对redo log进行刷盘 ,刷新频率由 innodb_flush_log_at_trx_commit
变量来控制的:
0:表示不刷入磁盘;
1:事务每次提交的时候,就把缓冲池中的数据刷新到磁盘中;
2:提交事务的时候,把缓冲池中的数据写入磁盘文件对应的 os cache 缓存里去,而不是直接进入磁盘文件。可能 1 秒后才会把 os cache 里的数据写入到磁盘文件里去。
4、一致性:
一致性指的是事务不能破坏数据的完整性和业务的一致性 :
数据的完整性: 实体完整性、列完整性(如字段的类型、大小、长度要符合要求)、外键约束等
业务的一致性:例如在银行转账时,不管事务成功还是失败,双方钱的总额不变。
该部分详情可参考这篇博客:https://blog.csdn.net/a745233700/article/details/84207186
脏读(Drity Read):某个事务已更新一份数据, 但还未提交,另一个事务在此时读取了同一份数据,由于某些原因,前一个RollBack了操作,则后一个事务所读取的数据就会是不正确的。
不可重复读(Non-repeatable read): 在一个事务的两次查询之中数据不一致,这可能是两次查询过程中间插入了一个事务更新的原有的数据。
幻读(Phantom Read): 在一个事务的两次查询中数据笔数不一致,例如有一个事务查询了几列(Row)数据,而另一个事务却在此时插入了新的几列数据,先前的事务在接下来的查询中,就会发现有几列数据是它先前所没有的。
为了达到事务的四大特性,数据库定义了4种不同的事务隔离级别,由低到高依次为Read uncommitted、Read committed、Repeatable read、Serializable,这四个级别可以逐个解决脏读、不可重复读、幻读这几类问题。
隔离级别 | 脏读 | 不可重复读 | 幻影读 |
---|---|---|---|
READ-UNCOMMITTED | √ | √ | √ |
READ-COMMITTED | × | √ | √ |
REPEATABLE-READ | × | × | √ |
SERIALIZABLE | × | × | × |
SQL 标准定义了四个隔离级别:
READ-UNCOMMITTED(读取未提交)
: 最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读。
READ-COMMITTED(读取已提交)
: 允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生。
REPEATABLE-READ(可重复读)
: 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。
SERIALIZABLE(可串行化)
: 最高的隔离级别,完全服从ACID的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。
这里需要注意的是:
Mysql
默认采用的 REPEATABLE_READ隔离级别
Oracle
默认采用的 READ_COMMITTED隔离级别
事务隔离机制的实现基于 锁机制
和 并发调度
。其中并发调度使用的是MVCC(多版本并发控制)
,通过保存修改的旧版本信息来支持并发一致性读和回滚等特性。
因为隔离级别越低,事务请求的锁越少,所以大部分数据库系统的隔离级别都是READ-COMMITTED(读取提交内容):,但是你要知道的是InnoDB 存储引擎默认使用 REPEATABLE-READ(可重读)并不会有任何性能损失。
InnoDB 存储引擎在 分布式事务 的情况下一般会用到SERIALIZABLE(可串行化)隔离级别。
mysql REPEATABLE READ对幻读的解决
REPEATABLE READ 隔离级别通过MVCC 和 next-key lock 实现解决幻读
MVCC允许数据具有多个版本,版本可以是时间戳或者是全局递增的事务ID。在MVCC协议下,每个读操作会看到一个一致的数据快照,这意味着,在同一个时间点,不同的事务看到的数据可能是不同的。
在 REPEATABLE READ下的实现
innodb会为每一行记录添加两个隐藏的列,分别表示该行记录创建的版本(数据何时被创建)和删除的版本(数据何时被删除),填入的是事务的版本号,这个版本号随着事务的创建不断递增。
在REPEATABLE READ的隔离级别下,具体各种数据库操作的实现如下:
select 返回的记录必须符合下面两个条件:
该行的创建版本号小于等于当前版本号,用于保证在事务创建时或事务开始之前这行数据是存在的。
该行的删除版本号大于当前版本或者为空。用于保证在事务开始之前这行数据没有被删除。
insert 将新插入的行的创建版本号设置为当前系统的版本号,删除版本号为空
delete 将该行的删除版本号设置为当前系统的版本号,并不把数据实际删除
update 转换成insert + delete。新拷贝这行数据,将新行insert同时设置创建版本号为当前版本号,将旧行的删除版本号设置为当前版本号
这种额外的记录优点是对于大多数查询来说根本就不需要获得一个锁,以最快的速度来读取数据,确保只选择符合条件的行。缺点在于存储引擎必须为每一行存储更多的数据,做更多的检查工作,处理更多的善后操作。 innodb会开启一个后台线程(purge线程)执行清理工作,具体的规则是将删除版本号小于当前系统版本的行删除,这个过程叫做purge。
next-key lock 即 行记录锁 + 记录前的gap锁(间隙锁)。
为了防止幻读,next-key lock阻止特定条件的新记录的插入,因为插入时要获取Insert Intention Locks,与已持有的next-key lock冲突。Insert Intention Locks,插入意向锁并非意向锁,而是一种特殊的间隙锁。
当数据库中多个事务并发存取同一数据的时候,若对并发操作不加控制就可能会读取和存储不正确的数据,破坏数据库的一致性。MySQL锁机制的基本工作原理就是,事务在修改数据库之前,需要先获得相应的锁,获得锁的事务才可以修改数据;在该事务操作期间,这部分的数据是锁定,其他事务如果需要修改数据,需要等待当前事务提交或回滚后释放锁。
按照不同的分类方式,锁的种类可以分为以下几种:
按锁的粒度划分:表级锁、行级锁、页级锁;
按锁的类型划分:共享(锁S锁)、排他锁(X锁);
按锁的使用策略划分:乐观锁、悲观锁;
Read Uncommitted级别下,读取数据不需要加共享锁,这样就不会跟被修改的数据上的排他锁冲突
Read Committed级别下,读操作需要加共享锁,但是在语句执行完以后释放共享锁;
Repeatable Read级别下,读操作需要加共享锁,但是在事务提交之前并不释放共享锁,也就是必须等待事务执行完毕以后才释放共享锁。
Serializable 是限制性最强的隔离级别,因为该级别锁定整个范围的键,并一直持有锁,直到事务完成。
在关系型数据库中,可以按照锁的粒度把数据库锁分为行级锁(INNODB引擎)、表级锁(MYISAM引擎)和页级锁(BDB引擎 )。
MyISAM和InnoDB存储引擎使用的锁:
MyISAM采用
表级锁
(table-level locking)。
InnoDB支持行级锁
(row-level locking)和表级锁
,默认为行级锁
行级锁,表级锁和页级锁对比
行级锁
: 是Mysql中锁定粒度最细的一种锁,表示只针对当前操作的行进行加锁。行级锁能大大减少数据库操作的冲突。其加锁粒度最小,但加锁的开销也最大。行级锁分为共享锁 和 排他锁。
行级锁特点:开销大,加锁慢;会出现死锁;锁定粒度最小,发生锁冲突的概率最低,并发度也最高。
表级锁
: 是MySQL中锁定粒度最大的一种锁,表示对当前操作的整张表加锁,它实现简单,资源消耗较少,被大部分MySQL引擎支持。最常使用的MYISAM与INNODB都支持表级锁定。表级锁定分为表共享读锁(共享锁)与表独占写锁(排他锁)。
表级锁特点:开销小,加锁快;不会出现死锁;锁定粒度大,发出锁冲突的概率最高,并发度最低。
页级锁
: 是MySQL中锁定粒度介于行级锁和表级锁中间的一种锁。表级锁速度快,但冲突多,行级冲突少,但速度慢。所以取了折衷的页级,一次锁定相邻的一组记录。
页级锁特点:开销和加锁时间界于表锁和行锁之间;会出现死锁;锁定粒度界于表锁和行锁之间,并发度一般
InnoDB的行锁:
InnoDB的行锁有两种类型:
共享锁(S锁、读锁)
:多个事务可以对同一数据行共享一把S锁,但只能进行读不能修改;
排它锁(X锁、写锁)
:一个事务获取排它锁之后,可以对锁定范围内的数据行执行写操作,在锁定期间,其他事务不能再获取这部分数据行的锁(共享锁、排它锁),只允许获取到排它锁的事务进行更新数据。
对于update,delete,insert 操作,InnoDB会自动给涉及的数据行加排他锁;
对于普通SELECT语句,InnoDB不会加任何锁。
InnoDB的表锁与意向锁:
因为InnoDB引擎允许行锁和表锁共存,实现多粒度的锁机制,但是表锁和行锁虽然锁定范围不同,但是会相互冲突。当你要加表锁时,势必要先遍历该表的所有记录,判断是否有排他锁。这种遍历检查的方式显然是一种低效的方式,MySQL引入了意向锁,来检测表锁和行锁的冲突
。
意向锁也是表级锁,分为读意向锁(IS锁)和写意向锁(IX锁)
。当事务要在记录上加上行锁时,则先在表上加上对应的意向锁。之后事务如果想进行锁表,只要先判断是否有意向锁存在,存在时则可快速返回该表不能启用表锁,否则就需要等待,提高效率。
InnoDB行锁的实现与临键锁:
InnoDB的行锁是通过给索引上的索引项加锁来实现的。只有通过索引检索数据,才能使用行锁,否则将使用表锁。
在InnoDB中,为了解决幻读的现象,引入了临键锁(next-key)
。根据索引,划分为一个个左开右闭的区间。当进行范围查询的时候,若命中索引且能够检索到数据,则锁住记录所在的区间和它的下一个区间。其实,临键锁(Next-Key) = 记录锁(Record Locks) + 间隙锁(Gap Locks)
间隙锁
:当使用范围查询而不是精准查询进行检索数据,并请求共享或排它锁时,InnoDB会给符合范围条件的已有数据记录的索引项加锁;对于键值在条件范围内但并不存在的记录,叫做间隙(GAP)。
记录锁
:当使用唯一索引,且记录存在的精准查询时,使用记录锁
利用锁机制解决并发问题:
X锁解决脏读
S锁解决不可重复读
临键锁解决幻读
InnoDB存储引擎锁机制的详细内容和MyISAM存储引擎的锁机制的详细内容可以阅读这篇文章:https://blog.csdn.net/a745233700/article/details/84504209
从锁的类别上来讲,有共享锁
和排他锁
。
共享锁: 又叫做读锁。 当用户要进行数据的读取时,对数据加上共享锁。共享锁可以同时加上多个。
排他锁: 又叫做写锁。 当用户要进行数据的写入时,对数据加上排他锁。排他锁只可以加一个,他和其他的排他锁,共享锁都相斥。
用上面的例子来说就是用户的行为有两种,一种是来看房,多个用户一起看房是可以接受的。 一种是真正的入住一晚,在这期间,无论是想入住的还是想看房的都不可以。
锁的粒度取决于具体的存储引擎,InnoDB实现了行级锁,页级锁,表级锁。
他们的加锁开销从大到小,并发能力也是从大到小。
答:InnoDB 的行锁,就是通过锁住主键索引来实现的。
例: select * from tab_with_index where id = 1 for update;
for update 可以根据条件来完成行锁锁定,并且 id 是有索引键的列,如果 id 不是索引键那么InnoDB将完成表锁,并发将无从谈起
Record lock
:单个行记录上的锁
Gap lock
:间隙锁,锁定一个范围,不包括记录本身
Next-key lock
:record+gap 锁定一个范围,包含记录本身
相关知识点:
innodb对于行的查询使用next-key lock
Next-locking keying为了解决Phantom Problem幻读问题
当查询的索引含有唯一属性时(且命中),将next-key lock 降级为 record key
Gap锁设计的目的是为了阻止多个事务将记录插入到同一范围内,而这会导致幻读问题的产生
有两种方式显式关闭gap锁:(除了外键约束和唯一性检查外,其余情况仅使用record lock)
MySQL 里面表级别的锁有表锁
,元数据锁
, 意向锁
, 自增锁
。
表锁
的语法是 lock tables。。。read/write,与 FTWRL 类似,可以用 unlock tables 主动释放锁,也可以在客户端断开的时候自动释放。
在还没有出现更细粒度的锁的时候,表锁是最常用的处理并发的方式,而对于 InnoDB 这种支持行锁的引擎,一般不使用 lock tables 命令来控制并发,毕竟锁住整个表的影响面还是很大。
元数据锁
MDL (meta data lock)不需要显示使用,在访问一个表的时候就会被自动加上。MDL 的作用是,保证读写的正确性。你可以想象一下,如果一个查询正在遍历一个表中的数据,而执行期间另一个线程对这个表结构做变更,删了一列,那么查询线程拿到的结果跟表结构对应不上,肯定是不行的。
因此在 MySQL 5.5 之后加入了MDL,当对一个表做增删改查操作的时候,加 MDL 读锁,当对表做结构变更操作的时候,加了 MDL 写锁。
读锁之间不互斥,因此你可以有多个线程同时对一张表增删改查
读写之间、写写之间是互斥的,用来保证变更结构操作的安全性。因此如果有两个线程要同时给一个表加字段,其中一个要等另一个执行完才能开始执行。
意向锁
也是表级锁,分为读意向锁(IS锁)和写意向锁(IX锁)当事务要在记录上加上行锁时,则先在表上加上对应的意向锁。之后事务如果想进行锁表,只要先判断是否有意向锁存在,存在时则可快速返回该表不能启用表锁,否则就需要等待,提高效率。
自增锁
: 如果存在自增字段,MySQL便会自动维护一个自增锁,和自增锁相关的一个参数
共享锁(S Lock)
:允许多个事务向被锁定的记录加共享锁
SELECT … LOCK IN SHARE MODE:对读取的行记录加一个共享锁,其他事务可以向被锁定的记录加共享锁,但是想要加排它锁。则会被阻塞。
排它锁(X Lock)
:只允许一个事务进行加锁
SELECT … FOR UPDATE:对读取的行记录加一个排它锁,其他事务想要在这些行上加任何锁都会被阻塞
MySQL引入了意向锁,来检测表锁和行锁的冲突
。
意向锁也是表级锁
,分为读意向锁(IS锁)和写意向锁(IX锁)当事务要在记录上加上行锁时,则先在表上加上对应的意向锁。之后事务如果想进行锁表,只要先判断是否有意向锁存在,存在时则可快速返回该表不能启用表锁,否则就需要等待,提高效率。
自增锁(Auto-inc Locks): 是当向含有AUTO_INCREMENT 列的表中插入数据时, 需要获取的一种特殊的表级锁. 在最简单的情况下, 如果一个事务正在向表中插入值, 则任何其他事务必须等待对该表执行自己的插入操作, 以便第一个事务插入的行的自增字段值是连续的
.
特殊的
表级锁
: insert出现在事务中,自增锁是在insert之后立即释放, 而不是等事务提交才释放
如果存在自增字段,MySQL便会自动维护一个自增锁,和自增锁相关的一个参数innodb_autoinc_lock_mode:可以设定3个值,0,1,2(5.1.22版本之后加入)
这个参数值控制InnoDB引擎的设置,所有Myisam均为traditonal,每次均会进行表锁。但是Innodb会视参数不通二产生不通的锁。目前MySQL默认的配置为1。
InnoDB 的行锁,就是通过锁住主键索引
来实现的。
InnoDB 行锁是通过给索引上的索引项加锁来实现的,如果没有索引,InnoDB 将通过隐藏的聚簇索引来对记录加锁。所以一旦某个加锁操作没有使用索引,那么该锁就会退化为表锁
除了直接在主键索引加锁,我们还可以通过辅助索引找到相应主键索引后再加锁
1、行锁是什么?
字面意思,给一行数据加锁。
InnoDB 行锁是通过给索引上的索引项加锁来实现的,如果没有索引,InnoDB 将通过隐藏的聚簇索引来对记录加锁。
注意:行锁必须有索引才能实现,否则会自动锁全表,那么就不是行锁了。 两个事务不能锁同一个索引。
2、三种算法
(1)Record lock:记录锁,单个行记录上的锁
锁住具体的索引项,当SQL执行按照唯一性索引进行数据的检索时,查询条件等值匹配且查询的数据是存在,这时SQL语句加上的锁即为记录锁Record locks,锁住具体的索引项
BEGIN;-- 开始事务
SELECT * FROM my_table WHERE id = 2021 FOR UPDATE; -- 排它锁的记录锁算法
ROLLBACk;-- 回滚事务
# 普通事务执行
SELECT * FROM my_table WHERE id = 2021 FOR UPDATE;
SELECT * FROM my_table WHERE id = 2020 FOR UPDATE;
(2)Gap lock:间隙锁,锁定一个范围,但不包含记录本身
当sql执行按照索引进行数据的检索时,查询条件的数据不存在,这时SQL语句加上的锁即为 Gap locks,锁住索引不存在的区间(左开右开的区间)
Gap只在RR事务隔离级别/repeatable read存在
BEGIN;
SELECT * FROM my_table WHERE id > 2001 AND id < 2021 FOR UPDATE; --排它锁的间隙锁算法
SELECT * FROM my_table WHERE id = 2021 FOR UPDATE; -- 另一种方式
ROLLBACK;
(3)Next-key lock:record+gap,临建锁,锁定一个范围,且包含记录本身
锁住记录+区间(左开右闭的区间)
当sql执行按照索引进行数据的检索时,查询条件为范围查找(between and、<、>等)并有数据命中则此时SQL语句加上的锁为Next-key locks,锁住索引的记录+区间(左开右闭的区间)
BEGIN;
SELECT * FROM my_table WHERE id > 2001 AND id < 2021 FOR UPDATE; --临建锁的间隙锁算法
-- 其他事务执行
set session autocommit=off;
select * from t2 where id=4 for update; --没锁住
select * from t2 where id=7 for update; --锁住
select * from t2 where id=10 for update; --锁住
INSERT INTO `t2` (`id`, `name`) VALUES (9, '9');
ROLLBACK;
死锁是指两个或多个事务在同一资源上相互占用,并请求锁定对方的资源,从而导致恶性循环的现象。
常见的解决死锁的方法
1、如果不同程序会并发存取多个表,尽量约定以相同的顺序访问表
,可以大大降低死锁机会。
2、在同一个事务中,尽可能做到一次锁定所需要的所有资源
,减少死锁产生概率;
3、对于非常容易产生死锁的业务部分,可以尝试使用升级锁定颗粒度,通过表级锁定来减少死锁产生的概率
;
如果业务处理不好可以用分布式事务锁
或者使用乐观锁
当数据库发生死锁时,可以通过以下命令获取死锁日志:
show engine innodb status;
1.show engine innodb status;查看死锁日志
2.找出死锁SQL
3.SQL加锁分析
4.分析死锁日志(持有什么锁,等待什么锁)
5.熟悉锁模式兼容矩阵,InnoDB存储引擎中锁的兼容性矩阵。
数据库管理系统(DBMS)中的并发控制的任务是确保在多个事务同时存取数据库中同一数据时不破坏事务的隔离性和统一性以及数据库的统一性。乐观并发控制(乐观锁)和悲观并发控制(悲观锁)是并发控制主要采用的技术手段。
悲观锁
:假定会发生并发冲突,屏蔽一切可能违反数据完整性的操作。在查询完数据的时候就把事务锁起来,直到提交事务。实现方式:使用数据库中的锁机制
乐观锁
:假设不会发生并发冲突,只在提交操作时检查是否违反数据完整性。在修改数据的时候把事务锁起来,通过version的方式来进行锁定。实现方式:乐观锁一般会使用版本号机制或CAS算法实现。
两种锁的使用场景
乐观锁适用于写比较少的情况下(多读场景),即冲突真的很少发生的时候,这样可以省去了锁的开销,加大了系统的整个吞吐量。
如果是多写的情况,一般会经常产生冲突,这就会导致上层应用会不断的进行retry,这样反倒是降低了性能,所以一般多写的场景下用悲观锁就比较合适。
索引是一种特殊的文件
(InnoDB数据表上的索引是表空间的一个组成部分),它们包含着对数据表里所有记录的引用指针。
索引是一种数据结构
。数据库索引,是数据库管理系统中一个排序的数据结构,以协助快速查询、更新数据库表中数据。索引的实现通常使用B树及其变种B+树。
更通俗的说,索引就相当于目录
。为了方便查找书中的内容,通过对内容建立索引形成目录。索引是一个文件,它是要占据物理空间的。
索引的分类:
(1)普通索引、唯一索引、主键索引、全文索引、组合索引。
主键索引
:一种特殊的唯一索引,不允许有空值。
唯一索引
:但索引列的值必须唯一,允许有空值,可以有多个NULL值。如果是组合索引,则列值的组合必须唯一。
普通索引
:最基本的索引,没有任何限制
组合索引
:主要是为了提高mysql效率,创建组合索引时应该将最常用作限制条件的列放在最左边,依次递减。
全文索引
:全文索引仅可用于 MyISAM 表,并只支持从CHAR、VARCHAR或TEXT类型,用于替代效率较低的like 模糊匹配操作,而且可以通过多字段组合的全文索引一次性全模糊匹配多个字段。
(2)聚簇索引 与 非聚簇索引:
如果按数据存储的物理顺序与索引值的顺序分类,可以将索引分为聚簇索引与非聚簇索引两类:
聚簇索引
:表中数据存储的物理顺序
与索引值
的顺序一致
,一个基本表最多只能有一个聚簇索引,更新聚簇索引列上的数据时,往往导致表中记录的物理顺序的变更,代价较大,因此对于经常更新的列不宜建立聚簇索引
非聚簇索引
:表中数据的物理顺序
与索引值
的顺序不一致
的索引组织,一个基本表可以有多个聚簇索引。
索引的长度限制:
对于 Innodb 的组合索引,如果各个列中的长度超过767字节的,则会对超过767字节的列取前缀索引;对于 Innodb 的单列索引,如果列的长度超过767的,则取前缀索引(取前255字符) 对于 MyISAM 的组合索引,所创建的索引长度和不能超过1000 bytes,否则会报错,创建失败;对于 MyISAM 的单列索引,最大长度也不能超过1000,否则会报警,但是创建成功,最终创建的是前缀索引(取前333个字符)
索引的实现原理详细阅读:https://blog.csdn.net/a745233700/article/details/80798181
(1)索引的优点:
(2)索引的缺点:
索引本质上就是一种通过高效的数据结构和算法减少查询需要遍历行数
,加快查询性能,避免数据库进行全表扫描,好比书的目录,让你更快的找到内容。(一个表最多16个索引)
上图中,根据id查询记录,因为id字段仅建立了主键索引,因此此SQL执行可选的索引只有主键索引,如果有多个,最终会选一个较优的作为检索的依据。
-- 增加一个没有建立索引的字段
alter table innodb1 add sex char(1);
-- 按sex检索时可选的索引为null
EXPLAIN SELECT * from innodb1 where sex='男';
可以尝试在一个字段未建立索引时,根据该字段查询的效率,然后对该字段建立索引(alter table 表名 add index(字段名)),同样的SQL执行的效率,你会发现查询效率会有明显的提升(数据量越大越明显)。
order by
当我们使用order by将查询结果按照某个字段排序时,如果该字段没有建立索引,那么执行计划会将查询出的所有数据使用外部排序
(将数据从硬盘分批读取到内存使用内部排序,最后合并排序结果
),这个操作是很影响性能的,因为需要将查询涉及到的所有数据从磁盘中读到内存(如果单条数据过大或者数据量过多都会降低效率),更无论读到内存之后的排序了。
但是如果我们对该字段建立索引alter table 表名 add index(字段名),那么由于索引本身是有序的,因此直接按照索引的顺序和映射关系逐条取出数据即可
。而且如果分页的,那么只用取出索引表某个范围内的索引对应的数据,而不用像上述那取出所有数据进行排序再返回某个范围内的数据。(从磁盘取数据是最影响性能的)
join
对join语句匹配关系(on)涉及的字段建立索引能够提高效率
索引覆盖
如果要查询的字段都建立过索引,能直接在索引中获取, 那么引擎会直接在索引表中查询而不会访问原始数据
(否则只要有一个字段没有建立索引就会做全表扫描),这叫索引覆盖。因此我们需要尽可能的在select后只写必要的查询字段,以增加索引覆盖的几率。
这里值得注意的是不要想着为每个字段建立索引,因为优先使用索引的优势就在于其体积小。
(1)在哪些列上面创建索引:
WHERE
子句中经常出现的列上面创建索引,加快条件的判断速度。group by
或order by
中使用的列,因为索引已经排序,这样可以利用索引加快排序查询时间。join语句匹配关系(on)
涉及的字段这些列, 主要是一些外键,可以加快连接的速度;唯一性
和组织表中数据的排列结构;(2)不在哪些列建索引?
区分度不高的列
。由于这些列的取值很少,例如性别,在查询的结果中,结果集的数据行占了表中数据行的很大比例,即需要在表中搜索的数据行的比例很大。增加索引,并不能明显加快检索速度。很少使用到
的列不应该创建索引。由于这些列很少使用到,但增加了索引,反而降低了系统的维护速度和增大了空间需求。修改成本的提高
远远大于 检索性能的提高时,不应该创建索引。当增加索引时,会提高检索性能,但是会降低修改性能。当减少索引时,会提高修改性能,降低检索性能。text, image和bit数据类型
的列不应该增加索引。这些列的数据量要么相当大,要么取值很少。主键索引 PRIMARY KEY
: 数据列不允许重复,不允许为NULL,一个表只能有一个主键。
唯一索引 UNIQUE
: 数据列不允许重复,允许为NULL值,一个表允许多个列创建唯一索引。
可以通过 ALTER TABLE table_name ADD UNIQUE (column); 创建唯一索引
可以通过 ALTER TABLE table_name ADD UNIQUE (column1,column2); 创建唯一组合索引
普通索引 INDEX
: 基本的索引类型,没有唯一性的限制,允许为NULL值。
可以通过ALTER TABLE table_name ADD INDEX index_name (column);创建普通索引
可以通过ALTER TABLE table_name ADD INDEX index_name(column1, column2, column3);创建组合索引
全文索引 FULLTEXT
: 是目前搜索引擎使用的一种关键技术。
- 可以通过ALTER TABLE table_name ADD FULLTEXT (column);创建全文索引
深入理解MySQL索引之B+Tree
MySQL索引背后的数据结构及算法原理
深入理解MySQL索引原理和实现
1. 哈希索引
Hash索引
:MySQL中只有Memory存储引擎支持hash索引,是Memory表的默认索引类型。hash索引把数据以hash值形式组织起来,因此查询效率非常高,可以一次定位。
hash索引的缺点:
- Hash索引
仅能满足等值的查询
,不能满足范围查询、排序。因为数据在经过Hash算法后,其大小关系就可能发生变化。- 当创建组合索引时,
不能只使用组合索引的部分列进行查询
。因为hash索引是把多个列数据合并后再计算Hash值
,所以对单独列数据计算Hash值是没有意义的。- 当发生Hash碰撞时,Hash索引不能避免表数据的扫描。因为仅仅比较Hash值是不够的,需要比较实际的值以判定是否符合要求。
2. MySQL数据库为什么要使用B+TREE作为索引的数据结构?
2.1 二叉树为什么不可行
对数据的加速检索,首先想到的就是二叉树,二叉树的查找时间复杂度可以达到O(log2(n))。下面看一下二叉树的存储结构:
二叉树搜索相当于一个二分查找。二叉查找能大大提升查询的效率,但是它有一个问题:二叉树以第一个插入的数据作为根节点
,如上图中,如果只看右侧,就会发现,就是一个线性链表结构
。如果我们现在的数据只包含1, 2, 3, 4,就会出现以下情况:
如果我们要查询的数据为4,则需要遍历所有的节点才能找到4,即,相当于全表扫描,就是由于存在这种问题,所以二叉查找树不适合用于作为索引的数据结构。
2.2 平衡二叉树为什么不可行
为了解决二叉树存在线性链表的问题,会想到用平衡二叉查找树来解决。下面看看平衡二叉树是怎样的:
平衡二叉查找树定义为:节点的子节点高度差不能超过1
, 如上图中的节点20,左节点高度为1,右节点高度0,差为1,所以上图没有违反定义,它就是一个平衡二叉树。保证二叉树平衡的方式为左旋,右旋等操作
,至于如何左旋右旋,可以自行去搜索相关的知识。
如果上图中平衡二叉树保存的是id索引,现在要查找id = 8的数据,过程如下:
索引保存数据的方式一般有两种:
到这里,平衡二叉树解决了存在线性链表的问题,数据查询的效率好像也还可以,基本能达到O(log2(n)), 那为什么mysql不选择平衡二叉树作为索引存储结构,他又存在什么样的问题呢?
搜索效率不足
。一般来说,在树结构中,数据所处的深度,决定了搜索时的IO次数(MySql中将每个节点大小设置为一页大小,一次IO读取一页 / 一个节点)。如上图中搜索id = 8的数据,需要进行3次IO。当数据量到达几百万的时候,树的高度就会很恐怖。
查询不不稳定
。如果查询的数据落在根节点,只需要一次IO,如果是叶子节点或者是支节点,会需要多次IO才可以。
单个节点存储的数据内容太少
。没有很好利用操作系统和磁盘数据交换特性,也没有利用好磁盘IO的预读能力。因为操作系统和磁盘之间一次数据交换是以页为单位的,一页大小为 4K,即每次IO操作系统会将4K数据加载进内存。但是,在二叉树每个节点的结构只保存一个关键字,一个数据区,两个子节点的引用,并不能够填满4K的内容。幸幸苦苦做了一次的IO操作,却只加载了一个关键字。在树的高度很高,恰好又搜索的关键字位于叶子节点或者支节点的时候,取一个关键字要做很多次的IO。
那有没有一种结构能够解决二叉树的这种问题呢?有,那就是多路平衡查找树。
2.3 多路平衡查找树(Balance Tree)
B Tree 是一个绝对平衡树,所有的叶子节点在同一高度,如下图所示:
上图为一个2-3树(每个节点存储2个关键字,有3路),多路平衡查找树也就是多叉的意思,从上图中可以看出,每个节点保存的关键字的个数和路数关系为:关键字个数 = 路数 – 1。
假设要从上图中查找id = X的数据,B TREE 搜索过程如下:
为什么说这种结构能够解决平衡二叉树存在的问题呢?
B Tree 能够很好的利用操作系统和磁盘的交互特性
, MySQL为了很好的利用磁盘的预读能力
,将页大小设置为16K,即将一个节点(磁盘块)的大小设置为16K,一次IO将一个节点(16K)内容加载进内存。这里,假设关键字类型为 int,即4字节,若每个关键字对应的数据区也为4字节,不考虑子节点引用的情况下,则上图中的每个节点大约能够存储(16 * 1000)/ 8 = 2000个关键字,共2001个路数。对于二叉树,三层高度,最多可以保存7个关键字,而对于这种有2001路的B树,三层高度能够搜索的关键字个数远远的大于二叉树。
这里顺便说一下:在B Tree保证树的平衡的过程中,每次关键字的变化,都会导致结构发生很大的变化,这个过程是特别浪费时间的,所以创建索引一定要创建合适的索引,而不是把所有的字段都创建索引,创建冗余索引只会在对数据进行新增,删除,修改时增加性能消耗。
B树确实已经很好的解决了问题,我先这里先继续看一下B+Tree结构,再来讨论BTree和B+Tree的区别。
先看看B+Tree是怎样的,B+Tree是B Tree的一个变种,在B+Tree中,B树的路数和关键字的个数的关系不再成立了,数据检索规则采用的是左闭合区间,路数和关键个数关系为1比1,具体如下图所示:
如果上图中是用ID做的索引,如果是搜索X = 1的数据,搜索规则如下:
2.4 B TREE和B+TREE区别是什么?
B+Tree 关键字的搜索采用的是左闭合区间,之所以采用左闭合区间是因为他要最好的去支持自增id,这也是mysql的设计初衷。即,如果id = 1命中,会继续往下查找,直到找到叶子节点中的1。
B+Tree 根节点和支节点没有数据区,关键字对应的数据只保存在叶子节点中。即只有叶子节点中的关键字数据区才会保存真正的数据内容或者是内容的地址。而在B树种,如果根节点命中,则会直接返回数据。
在B+Tree中,叶子节点不会去保存子节点的引用。
B+Tree叶子节点是顺序排列的,并且相邻的节点具有顺序引用的关系,如上图中叶子节点之间有指针相连接。
2.5 MySQL为什么最终要去选择B+Tree?
B+Tree是B TREE的变种,B TREE能解决的问题,B+TREE也能够解决(降低树的高度,增大节点存储数据量)
B+Tree扫库和扫表能力更强。如果我们要根据索引去进行数据表的扫描,对B TREE进行扫描,需要把整棵树遍历一遍,而B+TREE只需要遍历他的所有叶子节点即可(叶子节点之间有引用)。
B+Tree磁盘读写能力更强。他的根节点和支节点不保存数据区,所以根节点和支节点同样大小的情况下,保存的关键字要比B TREE要多。而叶子节点不保存子节点引用,能用于保存更多的关键字和数据。所以,B+TREE读写一次磁盘加载的关键字比B TREE更多。
B+Tree排序能力更强。上面的图中可以看出,B+Tree天然具有排序功能。
B+Tree查询性能稳定。B+Tree数据只保存在叶子节点,每次查询数据,查询IO次数一定是稳定的。当然这个每个人的理解都不同,因为在B TREE如果根节点命中直接返回,确实效率更高。
MySQL是如何保存索引和数据的?
MYISAM
存储引擎存储数据库数据,一共有三个文件:
Innodb
存储引擎存储数据库数据,一共有两个文件(没有专门保存数据的文件):
索引用来快速地寻找那些具有特定值的记录。如果没有索引,一般来说执行查询时遍历整张表。
索引通过数据结构和算法, 来减少查询时遍历的行数, 实现快速查询
就是把无序的数据变成有序的查询
把创建了索引的列的内容进行排序
对排序结果生成倒排表
在倒排表内容上拼上数据地址链
在查询的时候,先拿到倒排表内容,再取出数据地址链,从而拿到具体数据
索引虽好,但也不是无限制的使用,最好符合一下几个原则
1) 最左前缀匹配原则,组合索引非常重要的原则,mysql会一直向右匹配直到遇到范围查询( >、<、between、like)就停止匹配,比如a = 1 and b = 2 and c > 3 and d = 4 如果建立(a,b,c,d)顺序的索引,d是用不到索引的,如果建立(a,b,d,c)的索引则都可以用到,a,b,d的顺序可以任意调整。
2)较频繁作为查询条件的字段才去创建索引
3)更新频繁字段不适合创建索引
4)若是不能有效区分数据的列不适合做索引列(如性别,男女未知,最多也就三种,区分度实在太低)
5)尽量的扩展索引,不要新建索引。比如表中已经有a的索引,现在要加(a,b)的索引,那么只需要修改原来的索引即可。
6)定义有外键的数据列一定要建立索引。
7)对于那些查询中很少涉及的列,重复值比较多的列不要建立索引。
8)对于定义为text、image和bit的数据类型的列不要建立索引。
9)不要过度索引。索引需要额外的磁盘空间,并降低写操作的性能。在修改表内容的时候,索引会进行更新甚至重构,索引列越多,这个时间就会越长。所以只保持需要的索引有利于查询即可。
10)在满足需要的前提尽量使用短索引,如果对长字符串列进行索引,应该指定一个前缀长度,这样能够节省大量索引空间
第一种方式:在执行CREATE TABLE时创建索引
CREATE TABLE user_index2 (
id INT auto_increment PRIMARY KEY,
first_name VARCHAR (16),
last_name VARCHAR (16),
id_card VARCHAR (18),
information text,
KEY name (first_name, last_name),
FULLTEXT KEY (information),
UNIQUE KEY (id_card)
);
第二种方式:使用ALTER TABLE命令去增加索引
ALTER TABLE table_name ADD INDEX index_name (column_list);
ALTER TABLE table_name ADD UNIQUE index_name (column_list);
ALTER TABLE table_name ADD PRIMARY KEY index_name (column_list);
ALTER TABLE用来创建普通索引INDEX、UNIQUE索引或PRIMARY KEY索引。
其中table_name是要增加索引的表名,column_list指出对哪些列进行索引,多列时各列之间用逗号分隔。
索引名index_name可自己命名,缺省时,MySQL将根据第一个索引列赋一个名称。另外,ALTER TABLE允许在单个语句中更改多个表,因此可以在同时创建多个索引。
第三种方式:使用CREATE INDEX命令创建
CREATE INDEX index_name ON table_name (column_list);
CREATE UNIQUE index_name ON table_name (column_list);
CREATE INDEX可对表增加普通索引或UNIQUE索引。(但是,不能创建PRIMARY KEY索引)
删除索引
根据索引名删除普通索引、唯一索引、全文索引:alter table 表名 drop KEY 索引名
alter table user_index drop KEY name;
alter table user_index drop KEY id_card;
alter table user_index drop KEY information;
删除主键索引:alter table 表名 drop primary key(因为主键只有一个)。这里值得注意的是,如果主键自增长,那么不能直接执行此操作(自增长依赖于主键索引)
:
alter table user_index
-- 重新定义字段
MODIFY id int,
drop PRIMARY KEY
但通常不会删除主键,因为设计主键一定与业务逻辑无关。
非空字段:应该指定列为NOT NULL,除非你想存储NULL。在mysql中,含有空值的列很难进行查询优化,因为它们使得索引、索引的统计信息以及比较运算更加复杂。你应该用0、一个特殊的值或者一个空串代替空值;
取值离散大的字段:(变量各个取值之间的差异程度)的列放到联合索引的前面,可以通过count()函数查看字段的差异值,返回值越大说明字段的唯一值越多字段的离散程度高;
索引字段越小越好:数据库的数据存储以页为单位一页存储的数据越多一次IO操作获取的数据越大效率越高。
通常,通过索引查询数据比全表扫描要快。但是我们也必须注意到它的代价。
索引需要空间来存储,也需要定期维护, 每当有记录在表中增减或索引列被修改时,索引本身也会被修改。 这意味着每条记录的INSERT,DELETE,UPDATE将为此多付出4,5 次的磁盘I/O。 因为索引需要额外的存储空间和处理,那些不必要的索引反而会使查询反应时间变慢。使用索引查询不一定能提高查询性能,索引范围查询(INDEX RANGE SCAN)适用于两种情况:
基于一个范围的检索,一般查询返回结果集小于表中记录数的30%
基于非唯一性索引的检索
表记录太少
经常插入、删除、修改的表
数据重复且分布平均的表字段, 区分度不高的字段
假如一个表有10万行记录,有一个字段A只有T和F两种值,且每个值的分布概率大约为50%,那么对这种表A字段建索引一般不会提高数据库的查询速度。
经常和主字段一起查询, 但主字段索引值比较多的表字段
关于索引:由于索引需要额外的维护成本,因为索引文件是单独存在的文件,所以当我们对数据的增加,修改,删除,都会产生额外的对索引文件的操作,这些操作需要消耗额外的IO,会降低增/改/删的执行效率。所以,在我们删除数据库百万级别数据的时候,查询MySQL官方手册得知删除数据的速度和创建的索引数量是成正比的。
所以我们想要删除百万数据的时候可以先删除索引(此时大概耗时三分多钟)
然后删除其中无用数据
删除完成后重新创建索引(此时数据较少了)创建索引也非常快,约十分钟左右。
与之前的直接删除绝对是要快速很多,更别说万一删除中断,一切删除会回滚。那更是坑了。
语法:index(field(10)),使用字段值的前10个字符建立索引,默认是使用字段的全部内容建立索引。
前提:前缀的标识度高。比如密码就适合建立前缀索引,因为密码几乎各不相同。
实操的难度:在于前缀截取的长度。
我们可以利用select count(*)/count(distinct left(password,prefixLen));,通过从调整prefixLen的值(从1自增)查看不同前缀长度的一个平均匹配度,接近1时就可以了(表示一个密码的前prefixLen个字符几乎能确定唯一一条记录)
顾名思义,就是最左优先,在创建多列索引时,要根据业务需求,where子句中使用最频繁的一列放在最左边。
最左前缀匹配原则,非常重要的原则,mysql会一直向右匹配直到遇到范围查询( >、<、between、like)就停止匹配,比如a = 1 and b = 2 and c > 3 and d = 4 如果建立(a,b,c,d)顺序的索引,d是用不到索引的
如果建立(a,b,d,c)的索引则都可以用到,a,b,d的顺序可以任意调整。
=和in可以乱序,比如a = 1 and b = 2 and c = 3 建立(a,b,c)索引可以任意顺序,mysql的查询优化器会帮你优化成索引可以识别的形式
在B+树的索引中,叶子节点可能存储了当前的key值,也可能存储了当前的key值以及整行的数据,这就是聚簇索引和非聚簇索引。 在InnoDB中,只有主键索引是聚簇索引,如果没有主键,则挑选一个唯一键建立聚簇索引。如果没有唯一键,则隐式的生成一个键来建立聚簇索引。
当查询使用聚簇索引时,在对应的叶子节点,可以获取到整行数据,因此不用再次进行回表查询
。
聚簇索引:将数据存储与索引放到了一块,找到索引也就找到了数据
非聚簇索引:将数据存储于索引分开结构,索引结构的叶子节点指向了数据的对应行
myisam通过key_buffer把索引先缓存到内存中,当需要访问数据时(通过索引访问数据),在内存中直接搜索索引,然后通过索引找到磁盘相应数据,这也就是为什么索引不在key buffer命中时,速度慢的原因
澄清一个概念:innodb中,在聚簇索引之上创建的索引称之为辅助索引,辅助索引访问数据总是需要二次查找,非聚簇索引都是辅助索引,像复合索引、前缀索引、唯一索引,辅助索引叶子节点存储的不再是行的物理位置,而是主键值
不一定,如果所查询的字段全部命中了索引(索引覆盖),那么就不必再进行回表查询。
举个简单的例子,假设我们在员工表的年龄上建立了索引,那么当进行select age from employee where age < 20的查询时,在索引的叶子节点上,已经包含了age信息,不会再次进行回表查询。
最左匹配原则
MySQL可以使用多个字段同时建立一个索引,叫做联合索引。在联合索引中,如果想要命中索引,需要按照建立索引时的字段顺序挨个使用,否则无法命中索引。
具体原因为:
MySQL使用索引时需要索引有序,假设现在建立了"name,age,school"的联合索引,那么索引的排序为: 先按照name排序,如果name相同,则按照age排序,如果age的值也相等,则按照school进行排序。
当进行查询时,此时索引仅仅按照name严格有序,因此必须首先使用name字段进行等值查询,之后对于匹配到的列而言,其按照age字段严格有序,此时可以使用age字段用做索引查找,以此类推。因此在建立联合索引的时候应该注意索引列的顺序,一般情况下,将查询需求频繁或者字段选择性高的列放在前面。此外可以根据特例的查询或者表结构进行单独的调整。
1、使用like关键字时: like查询是以%开头,索引失效;以%结尾,索引有效
2、使用or关键字时: 要么or两边的字段均为索引,否则不会走索引。
3、组合索引: 要么条件中包含组合索引的所有字段, 且没有被范围查询打断, 要么就符合最左匹配原则, 否则索引失效
4、数据类型: 数据类型出现隐式转化。
如某个索引字段的数据类型为varchar,查询条件为123,如果没加引号的话可能会自动转换为int型,使索引无效,产生全表扫描。
5、范围判断之后的索引失效, 可以把范围条件放在最后
6、索引无法存储null值,所以where的判断条件如果对字段进行了null值判断, is null, is not null
(B-tree索引 is null不会走,is not null会走,位图索引 is null,is not null 都会走, 联合索引 is not null 只要在建立的索引列(不分先后)都会走, in null时必须要和建立索引第一列一起使用,当建立索引第一位置条件是is null 时,其他建立索引的列可以是is null(但必须在所有列 都满足is null的时候),或者=一个值; 当建立索引的第一位置是=一个值时,其他索引列可以是任何情况(包括is null =一个值),以上两种情况索引都会走。其他情况不会走。
7、in 和 not in 也要慎用,否则会导致全表扫描(in括号中的值没有命中)
8、where语句中使用 <> 和 !=, 引擎放弃使用索引而进行全表扫描
9、对索引字段进行计算操作、字段上使用函数,索引失效。
10、全表扫描的速度大于索引速度时,索引失效。如表内数据极少。
打油诗:
全职匹配我最爱,最左前缀要遵守;
带头大哥不能死,中间兄弟不能断;
索引列上少计算,范围之后全失效;
LIKE百分写最右,覆盖索引不写*;
不等空值还有OR,索引影响要注意;
VAR引号不可丢, SQL优化有诀窍。
查看索引的使用情况
show status like ‘Handler_read%’;
可以关注:
handler_read_key: 这个值越高越好,越高表示使用索引查询到的次数
handler_read_rnd_next: 这个值越高,说明查询低效
存储引擎Storage engine:MySQL中的数据、索引以及其他对象是如何存储的,是一套文件系统的实现。
常用的存储引擎有以下:
Innodb引擎:Innodb引擎提供了对数据库ACID事务的支持。并且还提供了行级锁和外键的约束。它的设计的目标就是处理大数据容量的数据库系统。
MyIASM引擎(原本Mysql的默认引擎):不提供事务的支持,也不支持行级锁和外键。
MEMORY引擎:所有的数据都在内存中,数据的处理速度快,但是安全性不高。
MyISAM与InnoDB区别
|\|MyISAM |Innodb|
|-|-|-|
|存储结构 |每张表被存放在三个文件:frm-表格定义、MYD(MYData)-数据文件、MYI(MYIndex)-索引文件 |所有的表都保存在同一个数据文件中(也可能是多个文件,或者是独立的表空间文件),InnoDB表的大小只受限于操作系统文件的大小,一般为2GB|
|存储空间 |MyISAM可被压缩,存储空间较小 |InnoDB的表需要更多的内存和存储,它会在主内存中建立其专用的缓冲池用于高速缓冲数据和索引|
|可移植性、备份及恢复 |由于MyISAM的数据是以文件的形式存储,所以在跨平台的数据转移中会很方便。在备份和恢复时可单独针对某个表进行操作 |免费的方案可以是拷贝数据文件、备份 binlog,或者用 mysqldump,在数据量达到几十G的时候就相对痛苦了|
|文件格式 |数据和索引是分别存储的,数据.MYD,索引.MYI |数据和索引是集中存储的,.ibd|
|记录存储顺序 |按记录插入顺序保存 |按主键大小有序插入|
|外键 |不支持 |支持|
|事务 |不支持 |支持|
|锁支持(锁是避免资源争用的一个机制,MySQL锁对用户几乎是透明的) |表级锁定 |行级锁定、表级锁定,锁定力度小并发能力高|
|SELECT |MyISAM更优 ||
|INSERT、UPDATE、DELETE ||InnoDB更优|
|select count(*) |myisam更快,因为myisam内部维护了一个计数器,可以直接调取。 ||
|索引的实现方式 |B+树索引,myisam 是堆表 |B+树索引,Innodb 是索引组织表|
|哈希索引 |不支持 |支持|
|全文索引 |支持 |不支持|
(1)事务:
- MyISAM不支持事务,
- InnoDB支持事务
(2)锁级别:
- MyISAM只支持表级锁,
- InnoDB支持行级锁和表级锁,默认使用行级锁,但是行锁只有通过索引查询数据才会使用,否则将使用表锁。行级锁在每次获取锁和释放锁的操作需要消耗比表锁更多的资源。使用行锁可能会存在死锁的情况,但是表级锁不存在死锁
(3)主键和外键:
- MyISAM 允许没有任何索引和主键的表存在,不支持外键。
- InnoDB的主键不能为空且支持主键自增长,如果没有设定主键或者非空唯一索引,就会自动生成一个6字节的主键,支持外键完整性约束
(4)索引结构:
- MyISAM 和 InnoDB 都是使用B+树索引,
- MyISAM的主键索引和辅助索引的Data域都是保存行数据记录的地址。
- InnoDB的主键索引的Data域保存的不是行数据记录的地址,而是保存该行的所有数据内容,而辅助索引的Data域保存的则是主索引的值。
- 由于InnoDB的辅助索引保存的是主键索引的值,所以使用辅助索引需要检索两遍索引:首先检索辅助索引获得主键,然后用主键到主索引中检索获得记录。这也是为什么不建议使用过长的字段作为主键的原因:由于辅助索引包含主键列,所以,如果主键使用过长的字段,将会导致其他辅助索变得更大,所以争取尽量把主键定义得小一些。
(5)全文索引:
- MyISAM支持全文索引,
- InnoDB在5.6版本之前不支持全文索引,5.6版本及之后的版本开始支持全文索引
(6)表的具体行数:
① MyISAM:保存有表的总行数,如果使用 select count() from table 会直接取出出该值,不需要进行全表扫描。
② InnoDB:没有保存表的总行数,如果使用 select count() from table 需要会遍历整个表,消耗相当大。
(7)存储结构:
① MyISAM: 在磁盘上存储成三个文件:.frm文件存储表定义,.MYD文件存储数据,.MYI文件存储索引。
② InnoDB:在磁盘上存储成两个文件: Frm文件存储表定义,Ibd文件存储数据和索引存储文件。所有的表都保存在同一个数据文件中,InnoDB表的大小只受限于操作系统文件的大小,一般为2GB。
(8)存储空间:
① MyISAM:可被压缩,存储空间较小。支持三种不同的存储格式:静态表(默认,但是注意数据末尾不能有空格,会被去掉)、动态表、压缩表。
② InnoDB:需要更多的内存和存储,它会在主内存中建立其专用的缓冲池用于高速缓冲数据和索引。
(9)适用场景:
① 如果需要提供回滚、崩溃恢复能力的ACID事务能力,并要求实现行锁级别并发控制,InnoDB是一个好的选择;
② 如果数据表主要用来查询记录,读操作远远多于写操作且不需要数据库事务的支持,则MyISAM引擎能提供较高的处理效率;
备注:在mysql8.0版本中已经废弃了MyISAM存储引擎
插入缓冲(insert buffer)
二次写 (double write)
自适应哈希索引 (ahi)
预读 (read ahead)
如果没有特别的需求,使用默认的Innodb即可。
MyISAM:读操作远远多于写操作且不需要数据库事务的支持,比如博客系统、新闻门户网站。
Innodb:更新(删除)操作频率也高,或者要保证数据的完整性;并发量高,支持事务和外键。比如OA自动化办公系统。
char的特点
char表示
定长字符串
,长度是固定的;如果插入数据的长度小于char的固定长度时,则
用空格填充
;因为长度固定,所以存取速度要
比varchar快很多
,甚至能快50%,但正因为其长度固定,所以会占据多余的空间,是空间换时间
的做法;对于char来说,最多能存放的字符个数为255,和编码无关
varchar的特点
varchar表示
可变长字符串
,长度是可变的;插入的数据是多长,就按照多长来存储;
varchar在存取方面与char相反,它
存取慢
,因为长度不固定,但正因如此,不占据多余的空间,是时间换空间
的做法;对于varchar来说,最多能存放的字符个数为65532
varchar(50)中50的涵义
最多存放50个字符,
varchar(50)和(200)存储hello所占空间一样,但后者在排序时会消耗更多内存
,因为order by col采用fixed_length计算col长度(memory引擎也一样)。在早期 MySQL 版本中, 50 代表字节数,现在代表字符数。
总之,结合性能角度(char更快)和节省磁盘空间角度(varchar更小),具体情况还需具体来设计数据库才是妥当的做法。
mysql中的 in 语句是把外表和内表作 hash 连接,而 exists 语句是对外表作 loop 循环,每次 loop 循环再对内表进行查询。一直大家都认为 exists 比 in 语句的效率要高,这种说法其实是不准确的。这个是要区分环境的。
如果查询的两个表大小相当,那么用 in 和 exists 差别不大。
如果两个表中一个较小,一个是大表,则子查询表大的用 exists,子查询表小的用 in。
not in 和 not exists:如果查询语句使用了not in,那么内外表都进行全表扫描,没有用到索引;而 not extsts 的子查询依然能用到表上的索引。所以无论那个表大,用 not exists 都比 not in 要快。
三者都表示删除,但是三者有一些差别:
|\|Delete |Truncate |Drop|
|-|-|-|-|
|类型 |属于DML |属于DDL |属于DDL|
|回滚 |可回滚 |不可回滚 |不可回滚|
|删除内容 |表结构还在,删除表的全部或者一部分数据行 |表结构还在,删除表中的所有数据 |从数据库中删除表,所有的数据行,索引和权限也会被删除|
|删除速度 |删除速度慢,需要逐行删除 |删除速度快 |删除速度最快|
因此,在不再需要一张表的时候,用drop;在想删除部分数据行时候,用delete;在保留表而删除所有数据的时候用truncate。
int(10)的10表示显示的数据的长度,不是存储数据的大小;仍占4字节存储,存储范围不变;不影响内部存储,只是影响带 zerofill 定义的 int 时,前面补多少个 0,易于报表展示
chart(10)和varchar(10)的10表示存储数据的大小,即表示存储多少个字符。
int(10) 10位的数据长度 9999999999,占4个字节,32位
char(10) 10位固定字符串,不足补空格,最多10个字符
varchar(10) 10位可变字符串,实际多长就多长, 最多10个字符
varchar(10)空格也按一个字符存储,char(10)的空格表示占位不算一个字符
FLOAT类型数据可以存储至多8位精度,并在内存中占4字节。
DOUBLE类型数据可以存储至多18位精度,并在内存中占8字节。
UNION 操作符用于合并两个或多个 SELECT 语句的结果集。
UNION 内部的 SELECT 语句必须拥有相同数量的列。列也必须拥有相似的数据类型。同时,每条 SELECT 语句中的列的顺序必须相同。
例如:
SELECT column_name(s) FROM table_name1
UNION
SELECT column_name(s) FROM table_name2
UNION 结果集中的列名总是等于 UNION 中第一个 SELECT 语句中的列名。
UNION 操作符选取不重复的值。如果允许重复的值,请使用 UNION ALL
。
UNION ALL 命令和 UNION 命令几乎是等效的,不过 UNION ALL 命令会列出所有的值
。
SELECT column_name(s) FROM table_name1
UNION ALL
SELECT column_name(s) FROM table_name2
LIMIT 子句可以被用于强制 SELECT 语句返回指定的记录数。LIMIT 接受一个或两个数字参数。参数必须是一个整数常量。如果给定两个参数,第一个参数指定第一个返回记录行的偏移量,第二个参数指定返回记录行的最大数目。初始记录行的偏移量是 0(而不是 1)
mysql> SELECT * FROM table LIMIT 5,10; // 检索记录行 6-15
为了检索从某一个偏移量到记录集的结束所有的记录行,可以指定第二个参数为 -1:
mysql> SELECT * FROM table LIMIT 95,-1; // 检索记录行 96-last.
如果只给定一个参数,它表示返回最大的记录行数目:
mysql> SELECT * FROM table LIMIT 5; //检索前 5 个记录行
换句话说,LIMIT n 等价于 LIMIT 0,n。
超大分页怎么处理?
超大的分页一般从两个方向上来解决.
数据库层面,这也是我们主要集中关注的(虽然收效没那么大),类似于select * from table where age > 20 limit 1000000,10这种查询其实也是有可以优化的余地的. 这条语句需要load1000000数据然后基本上全部丢弃,只取10条当然比较慢. 当时我们可以修改为select * from table where id in (select id from table where age > 20 limit 1000000,10).这样虽然也load了一百万的数据,但是由于索引覆盖,要查询的所有字段都在索引中,所以速度会很快. 同时如果ID连续的好,我们还可以select * from table where id > 1000000 limit 10,效率也是不错的,优化的可能性有许多种,但是核心思想都一样,就是减少load的数据.
从需求的角度减少这种请求…主要是不做类似的需求(直接跳转到几百万页之后的具体某一页.只允许逐页查看或者按照给定的路线走,这样可预测,可缓存)以及防止ID泄漏且连续被人恶意攻击.
解决超大分页,其实主要是靠缓存,可预测性的提前查到内容,缓存至redis等k-V数据库中,直接返回即可.
在阿里巴巴《Java开发手册》中,对超大分页的解决办法是类似于上面提到的第一种.
【推荐】利用延迟关联或者子查询优化超多分页场景。
说明:MySQL并不是跳过offset行,而是取offset+N行,然后返回放弃前offset行,返回N行,那当offset特别大的时候,效率就非常的低下,要么控制返回的总页数,要么对超过特定阈值的页数进行SQL改写。
正例:先快速定位需要获取的id段,然后再关联:
SELECT a.* FROM 表1 a, (select id from 表1 where 条件 LIMIT 100000,20 ) b where a.id=b.id
五大常见的MySQL高可用方案
Lvs+Keepalived+MySQL 单点写入主主同步高可用方案
Lvs+Keepalived+MySQL 单点写入读负载均衡主主同步高可用方案
Heartbeat高可用MySQL主主同步方案
Heartbeat+DRBD+MySQL高可用方案
用于记录执行时间超过某个临界值的SQL日志,用于快速定位慢查询,为我们的优化做参考。
开启慢查询日志
配置项:slow_query_log
查看:show VARIABLES like ‘slow_query_log’
设置:set GLOBAL slow_query_log = on
可以使用show variables like ‘slov_query_log’查看是否开启,如果状态值为OFF,可以使用set GLOBAL slow_query_log = on来开启,它会在datadir下产生一个xxx-slow.log的文件。
设置临界时间
配置项:long_query_time
查看:show VARIABLES like ‘long_query_time’,单位秒
设置:set long_query_time=0.5
实操时应该从长时间设置到短的时间,即将最慢的SQL优化掉
查看日志,一旦SQL超过了我们设置的临界时间就会被记录到xxx-slow.log中
在业务系统中,除了使用主键进行的查询,其他的我都会在测试库上测试其耗时,慢查询的统计主要由运维在做,会定期将业务中的慢查询反馈给我们。
慢查询的优化首先要搞明白慢的原因是什么? 是查询条件没有命中索引?是load了不需要的数据列?还是数据量太大?
所以优化也是针对这三个方向来的,
explain
命令, 查看语句的执行计划。 执行计划是SQL语句经过查询解析器后得到的 抽象语法树 和 相关表的统计信息 作出的一个查询方案,这个方案是由查询优化器自动分析产生的。由于是动态数据采样统计分析出来的结果,所以可能会存在分析错误的情况,也就是存在执行计划并不是最优的情况。通过explain关键字知道MySQL是如何执行SQL查询语句的,分析select 语句的性能瓶颈,从而改进我们的查询
id
: 表示一个查询中各个子查询的执行顺序;
select_type
查询类型
id | select_type | description |
---|---|---|
1 | SIMPLE | 不包含任何子查询或union等查询 |
2 | PRIMARY | 包含子查询最外层查询就显示为 PRIMARY |
3 | SUBQUERY | 在select或 where字句中包含的查询 |
4 | DERIVED | from字句中包含的查询 |
5 | UNION | 出现在union后的查询语句中 |
6 | UNION RESULT | 从UNION中获取结果集,例如上文的第三个例子 |
table
查询的数据表,当从衍生表中查数据时会显示 x 表示对应的执行计划id partitions 表分区、表创建的时候可以指定通过那个列进行表分区。 举个例子:
create table tmp (
id int unsigned not null AUTO_INCREMENT,
name varchar(255),
PRIMARY KEY (id)
) engine = innodb
partition by key (id) partitions 5;
type
(非常重要,可以看到有没有走索引) 访问类型
system
:系统表,表中只有一行数据。这是const连接类型的特殊情况。
const
:读常量,且最多只会有一条记录匹配。表中的一个记录的最大值能够匹配这个查询(索引可以是主键或惟一索引)。因为只有一行,这个值实际就是常数,因为MYSQL先读这个值然后把它当做常数来对待。
eq_ref
:最多只会有一条匹配结果,一般是通过主键或者唯一键索引来访问;在连接中,MYSQL在查询时,从前面的表中,对每一个记录的联合都从表中读取一个记录,它在查询使用了索引为主键或惟一键的全部时使用。
ref
:Join 语句中被驱动表索引引用查询,这个连接类型只有在查询使用了不是惟一或主键的键或者是这些类型的部分(比如,利用最左边前缀)时发生。对于之前的表的每一个行联合,全部记录都将从表中读出。这个类型严重依赖于根据索引匹配的记录多少—越少越好。
range
:索引范围扫描,这个连接类型使用索引返回一个范围中的行,比如使用>或<查找东西时发生的情况。
ref_or_null
:与ref 的唯一区别就是在使用索引引用查询之外再增加一个空值的查询。
unique_subquery
:子查询中的返回结果字段组合是主键或者唯一约束
index_merge
:查询中同时使用两个(或更多)索引,然后对索引结果进行merge 之后再读取表数据;
index_subquery
:子查询中的返回结果字段组合是一个索引(或索引组合),但不是一个主键或者唯一索引;
index
:全索引扫描,这个连接类型对前面的表中的每一个记录联合进行完全扫描(比ALL更好,因为索引一般小于表数据)。
ALL
:全表扫描,这个连接类型对于前面的每一个记录联合进行完全扫描,这一般比较糟糕,应该尽量避免。结果值从最好到最坏依次是:
system > const > eq_ref > ref > fulltext > ref_or_null > index_merge > unique_subquery > index_subquery > range > index > ALL
需要记忆的
system > const > eq_ref > ref > range > index_merge > index > ALL
possible_keys
可能使用的索引,注意不一定会使用。查询涉及到的字段上若存在索引,则该索引将被列出来。当该列为 NULL时就要考虑当前的SQL是否需要优化了。
key
实际使用的索引,若没有使用索引,显示为NULL。
TIPS
:查询中若使用了覆盖索引(覆盖索引:索引的数据覆盖了需要查询的所有数据),则该索引仅出现在key列表中
key_length
索引长度
ref
索引的哪些列被使用了
rows
读取的行数, 返回估算的结果集数目,并不是一个准确的值。
extra
关于MYSQL如何解析查询的额外信息,常见的有:
Distinct :一旦mysql找到了与行相联合匹配的行,就不再搜索了。
Not exists :mysql优化了LEFT JOIN,一旦它找到了匹配LEFT JOIN标准的行,就不再搜索了。
No tables:Query 语句中使用FROM DUAL 或者不包含任何FROM 子句;
Using filesort
:当我们的Query 中包含ORDER BY 操作,而且无法利用索引完成排序操 作的时候,MySQL Query Optimizer 不得不选择相应的排序算法来实现。看到这个的时候,查询就需要优化了。mysql需要进行额外的步骤来发现如何对返回的行排序。它根据连接类型以及存储排序键值和匹配条件的全部行的行指针来排序全部行。
Using index
:所需要的数据只需要在Index 即可全部获得而不需要再到表中取数据。列数据是从仅仅使用了索引中的信息而没有读取实际的行动的表返回的,这发生在对表的全部的请求列都是同一个索引的部分的时候。
Using temporary
:当MySQL 在某些操作中必须使用临时表的时候,在Extra 信息中就会 出现Using temporary 。主要常见于GROUP BY 和ORDER BY 等操作中。看到这个的时候,查询需要优化了。这里,mysql需要创建一个临时表来存储结果,这通常发生在对不同的列集进行ORDER BY上,而不是GROUP BY上。
Using where
:如果我们不是读取表的所有数据,或者不是仅仅通过索引就可以获取所有需 要的数据,则会出现Using where 信息;Where used:使用了WHERE从句来限制哪些行将与下一张表匹配或者是返回给用户。如果不想返回表中的全部行,并且连接类型ALL或index,这就会发生,或者是查询有问题。
Using index for group-by
:数据访问和Using index 一样,所需数据只需要读取索引即 可,而当Query 中使用了GROUP BY 或者DISTINCT 子句的时候,如果分组字段也在索引 中,Extra 中的信息就会是Using index for group-by;Using where with pushed condition:这是一个仅仅在NDBCluster 存储引擎中才会出现 的信息,而且还需要通过打开Condition Pushdown 优化功能才可能会被使用。控制参数为engine_condition_pushdown 。
Full scan on NULL key:子查询中的一种优化方式,主要在遇到无法通过索引访问null 值的使用使用;
Impossible WHERE noticed after reading const tables:MySQL Query Optimizer 通过 收集到的统计信息判断出不可能存在结果;
Select tables optimized away:当我们使用某些聚合函数来访问存在索引的某个字段的 时候,MySQL Query Optimizer 会通过索引而直接一次定位到所需的数据行完成整个查 询。当然,前提是在Query 中不能有GROUP BY 操作。如使用MIN()或者MAX()的时 候;
Range checked for each Record(index map:#):没有找到理想的索引,因此对从前面表中来的每一个行组合,mysql检查使用哪个索引,并用它来从表中返回行。这是使用索引的最慢的连接之一。
SQL性能优化的目标:至少要达到 range 级别,要求是ref级别,如果可以是consts最好。 说明:
1) consts 单表中最多只有一个匹配行(主键或者唯一索引),在优化阶段即可读取到数据。
2) ref 指的是使用普通的索引(normal index)。
3) range 对索引进行范围检索。
反例:explain表的结果,type=index,索引物理文件全扫描,速度非常慢,这个index级别比较range还低,与全表扫描是小巫见大巫。
对explain执行计划详请感兴趣的读者可以阅读这篇文章:https://blog.csdn.net/a745233700/article/details/84335453
MySQL的SQL优化和索引优化:https://blog.csdn.net/a745233700/article/details/84455241
MySQL的表结构优化:https://blog.csdn.net/a745233700/article/details/84405087
优化shema、sql语句+索引;
第二加缓存,memcached, redis;也可以使用高性能磁盘, 比如固态硬盘
主从复制,读写分离;
垂直拆分,根据你模块的耦合度,将一个大的系统分为多个小的系统,也就是分布式系统;
水平切分,针对数据量大的表,这一步最麻烦,最能考验技术水平,要选择一个合理的sharding key, 为了有好的查询效率,表结构也要改动,做一定的冗余,应用也要改,sql中尽量带sharding key,将数据定位到限定的表上去查,而不是扫描全部的表;
MySQL属于 IO 密集型的应用程序,主要职责就是数据的管理及存储工作。而我们知道,从内存中读取一个数据库的时间是微秒级别,而从一块普通硬盘上读取一个IO是在毫秒级别,二者相差3个数量级。所以,要优化数据库,首先第一步需要优化的就是 IO,尽可能将磁盘IO转化为内存IO。所以对于MySQL数据库的参数优化上,主要针对减少磁盘IO的参数做优化
:
例如:
使用 query_cache_size 调整查询缓存的大小,
使用 innodb_buffer_pool_size 调整缓冲区的大小;
MySQL的参数优化:https://blog.csdn.net/a745233700/article/details/114506553
应用服务器与数据库服务器建立一个连接
数据库进程拿到请求sql
先查询缓存, 如果没有命中缓存则继续向下
解析sql, 预处理后, 优化器生成执行计划,执行器调用引擎API执行
读取数据到内存并进行逻辑处理
通过步骤一的连接,返回结果到客户端, 并放入缓存
关掉连接,释放资源
主键是数据库确保数据行在整张表唯一性的保障
,即使业务上本张表没有主键,也建议添加一个自增长的ID列作为主键。设定了主键之后,在后续的删改查的时候可能更加快速以及确保操作数据范围安全
。
推荐使用自增ID,不要使用UUID。
因为在InnoDB存储引擎中,主键索引是作为聚簇索引存在的,也就是说,主键索引的B+树叶子节点上存储了主键索引以及全部的数据(按照顺序),如果主键索引是自增ID,那么只需要不断向后排列即可,如果是UUID,由于到来的ID与原来的大小不确定,会造成非常多的数据插入,数据移动,然后导致产生很多的内存碎片,索引维护成本大大提高,进而造成插入性能的下降。
总之,在数据量大一些的情况下,用自增主键性能会好一些。
关于主键是聚簇索引,如果没有主键,InnoDB会选择一个唯一键来作为聚簇索引,如果没有唯一键,会生成一个隐式的主键。
访问数据太多导致查询性能下降
确定应用程序是否在检索大量超过需要的数据,可能是太多行或列
确认MySQL服务器是否在分析大量不必要的数据行
避免犯如下SQL语句错误
查询不需要的数据。解决办法:使用limit解决
多表关联返回全部列。解决办法:指定列名
总是返回全部列。解决办法:避免使用SELECT *
重复查询相同的数据。解决办法:可以缓存数据,下次直接读取缓存
是否在扫描额外的记录。解决办法:
使用explain进行分析,如果发现查询需要扫描大量的数据,但只返回少数的行,可以通过如下技巧去优化:
- 使用索引覆盖扫描,把所有的列都放到索引中,这样存储引擎不需要回表获取对应行就可以返回结果。 改变数据库和表的结构,修改数据表范式
- 重写SQL语句,让优化器可以以更优的方式执行查询。
缓存的效率更高
。减少锁的竞争
。count()会忽略所有的列,直接统计所有列数,不要使用count(列名)
MyISAM中,没有任何where条件的count()非常快。
当有where条件时,MyISAM的count统计不一定比其它引擎快。
可以使用explain查询近似值,用近似值替代count(*)
增加汇总表
使用缓存
确定ON或者USING子句中是否有索引。
确保GROUP BY和ORDER BY只有一个表中的列,这样MySQL才有可能使用索引。
用关联查询替代
优化GROUP BY和DISTINCT
这两种查询据可以使用索引来优化,是最有效的优化方法
关联查询中,使用标识列分组的效率更高
如果不需要ORDER BY,进行GROUP BY时加ORDER BY NULL,MySQL不会再进行文件排序。
WITH ROLLUP超级聚合,可以挪到应用程序处理
LIMIT偏移量大的时候,查询效率较低
可以记录上次查询的最大ID,下次查询时直接根据该ID来查询
UNION ALL的效率高于UNION
解题方法
对于此类考题,先说明如何定位低效SQL语句,然后根据SQL语句可能低效的原因做排查,先从索引着手,如果索引没有问题,考虑以上几个方面,数据访问的问题,长难查询句的问题还是一些特定类型优化的问题,逐一回答。
SQL语句优化的一些方法?
1.对查询进行优化,应尽量避免全表扫描,首先应考虑在 where 及 order by 涉及的列上建立索引。
2.应尽量避免在 where 子句中对字段进行 null 值判断,否则将导致引擎放弃使用索引而进行全表扫描,如:
select id from t where num is null
-- 可以在num上设置默认值0,确保表中num列没有null值,然后这样查询:
select id from t where num=
3.应尽量避免在 where 子句中使用!=或<>操作符,否则引擎将放弃使用索引而进行全表扫描。
4.应尽量避免在 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=20
5.in 和 not in 也要慎用,否则会导致全表扫描,如:
select id from t where num in(1,2,3)
-- 对于连续的数值,能用 between 就不要用 in 了:
select id from t where num between 1 and 3
6.下面的查询也将导致全表扫描:select id from t where name like ‘%李%’若要提高效率,可以考虑全文检索。
7.如果在 where 子句中使用参数,也会导致全表扫描。因为SQL只有在运行时才会解析局部变量,但优化程序不能将访问计划的选择推迟到运行时;它必须在编译时进行选择。然 而,如果在编译时建立访问计划,变量的值还是未知的,因而无法作为索引选择的输入项。如下面语句将进行全表扫描:
select id from t where num=@num
-- 可以改为强制查询使用索引:
select id from t with(index(索引名)) where num=@num
8.应尽量避免在 where 子句中对字段进行表达式操作,这将导致引擎放弃使用索引而进行全表扫描。如:
select id from t where num/2=100
-- 应改为:
select id from t where num=100*2
9.应尽量避免在where子句中对字段进行函数操作,这将导致引擎放弃使用索引而进行全表扫描。如:
select id from t where substring(name,1,3)=’abc’
-- name以abc开头的id应改为:
select id from t where name like ‘abc%’
10.不要在 where 子句中的“=”左边进行函数、算术运算或其他表达式运算,否则系统将可能无法正确使用索引。
密码散列,盐,用户身份证号等固定长度的字符串应该使用char而不是varchar来存储,这样可以节省空间且提高检索效率。
NULL值并不是不占用原有的字段空间存储,而是额外申请一个字节去标注,这个字段添加了NULL约束.(就像额外的标志位一样)
null值会占用更多的字节,且会在程序中造成很多与预期不符的情况。
可以使用0或者空字符串来代替NULL
在数据类型的数据范围内能够满足需求不会溢出的前提下,我们应该优先考虑最小数据类型。
因为小数据类型相比大数据类型,它们占用更少的磁盘、内存和CPU缓存空间,并且处理小数据类型的数据是占用CPU周期也更少。
MySQL事务与MVCC如何实现的隔离级别?
MVCC和事务隔离级别的关系
借助MVCC,数据库可以实现READ COMMITTED,REPEATABLE READ等隔离级别,用户可以查看当前数据的前一个或者前几个历史版本,保证了ACID中的I特性(隔离性)。
MariaDB数据库管理系统是MySQL的一个分支(这里一定要注意,分支分支),主要由开源社区在维护,采用GPL授权许可 MariaDB的目的是完全兼容MySQL,包括API和命令行,使之能轻松成为MySQL的代替品。在存储引擎方面,使用XtraDB来代替MySQL的InnoDB。 MariaDB由MySQL的创始人Michael Widenius主导开发,
他早前曾以10亿美元的价格,将自己创建的公司MySQL AB卖给了SUN,此后,随着SUN被甲骨文收购,MySQL的所有权也落入Oracle的手中。MariaDB名称来自Michael Widenius的女儿Maria的名字。
MariaDB基于事务的Maria存储引擎,替换了MySQL的MyISAM存储引擎,它使用了Percona的 XtraDB,InnoDB的变体,分支的开发者希望提供访问即将到来的MySQL 5.4 InnoDB性能。这个版本还包括了 PrimeBase XT (PBXT) 和 FederatedX存储引擎。
Mysql 和 MariaDB 的区别:
与 MySQL 相比较,MariaDB 更强的地方在于:
Maria 存储引擎
PBXT 存储引擎
XtraDB 存储引擎
FederatedX 存储引擎
更快的复制查询处理
线程池
更少的警告和bug (这个我喜欢)
运行速度更快
更多的 Extensions (More index parts, new startup options etc)
更好的功能测试
数据表消除
慢查询日志的扩展统计
支持对 Unicode 的排序
redo和undo日志
浅析MySQL事务中的redo与undo
redo log主要作用是用于数据库的崩溃恢复, 用来保证事务的持久性,即事务ACID中的D。实际上它可以分为以下两种类型:物理Redo日志 和 逻辑Redo日志
在InnoDB存储引擎中,大部分情况下 Redo是物理日志,记录的是数据页的物理变化。而逻辑Redo日志,不是记录页面的实际修改,而是记录修改页面的一类操作,比如新建数据页时,需要记录逻辑日志。关于逻辑Redo日志涉及更加底层的内容,这里我们只需要记住绝大数情况下,Redo是物理日志即可,DML对页的修改操作,均需要记录Redo.
Redo log的组成
一是内存中重做日志缓冲 (redo log buffer),是易失的,在内存中
二是重做日志文件 (redo log file),是持久的,保存在磁盘中
undo log日志用于存放数据修改被修改前的值,假设修改 tba 表中 id=2的行数据,把Name=’B’ 修改为Name = ‘B2’ ,那么undo日志就会用来存放Name=’B’的记录,如果这个修改出现异常,可以使用undo日志来实现回滚操作,保证事务的一致性。
系统的吞吐量瓶颈往往出现在数据库的访问速度上
随着应用程序的运行,数据库的中的数据会越来越多,处理时间会相应变慢
数据是存放在磁盘上的,读写速度无法和内存相比
优化原则:减少系统瓶颈,减少资源占用,增加系统的反应速度。
一个好的数据库设计方案对于数据库的性能往往会起到事半功倍的效果。
需要考虑数据冗余、查询和更新的速度、字段的数据类型是否合理等多方面的内容。
将字段很多的表分解成多个表
增加中间表
增加冗余字段
注意:
冗余字段的值在一个表中修改了,就要想办法在其他表中更新,否则就会导致数据不一致的问题。
当 cpu 飙升到 500%时,先用操作系统命令 top 命令观察是不是 mysqld 占用导致的,如果不是,找出占用高的进程,并进行相关处理。
如果是 mysqld 造成的, show processlist,看看里面跑的 session 情况,是不是有消耗资源的 sql 在运行。找出消耗高的 sql,看看执行计划是否准确, index 是否缺失,或者实在是数据量太大造成。
一般来说,肯定要 kill 掉这些线程(同时观察 cpu 使用率是否下降),等进行相应的调整(比如说加索引、改 sql、改内存参数)之后,再重新跑这些 SQL。
也有可能是每个 sql 消耗资源并不多,但是突然之间,有大量的 session 连进来导致 cpu 飙升,这种情况就需要跟应用一起来分析为何连接数会激增,再做出相应的调整,比如说限制连接数等
读写分离解决的是数据库读写操作的压力,但是没有分散数据库的存储压力,利用分库分表可以解决数据库的储存瓶颈,并提升数据库的查询效率。
1、垂直拆分:
(1)垂直分表
:将一个表按照字段分成多个表,每个表存储其中一部分字段。一般会将常用的字段放到一个表中,将不常用的字段放到另一个表中。
优点:
(1)避免IO竞争减少锁表的概率。因为大的字段效率更低,第一,大字段占用的空间更大,单页内存储的行数变少,会使得IO操作增多;第二数据量大,需要的读取时间长。
(2)可以更好地提升热门数据的查询效率。
(2)垂直分库
:按照业务模块的不同,将表拆分到不同的数据库中,适合业务之间的耦合度非常低、业务逻辑清晰的系统。
优点:
降低业务中的耦合,方便对不同的业务进行分级管理
可以提升IO、数据库连接数、解决单机硬件存储资源的瓶颈问题
(3)垂直拆分(分库、分表)的缺点:
主键出现冗余,需要管理冗余列
事务的处理变得复杂
仍然存在单表数据量过大的问题
2、水平拆分:
(1)水平分表
:在同一个数据库内,把同一个表的数据按照一定规则拆分到多个表中。
优点:
解决了单表数据量过大的问题
避免IO竞争并减少锁表的概率
(2)水平分库
:把同一个表的数据按照一定规则拆分到不同的数据库中,不同的数据库可以放到不同的服务器上。
优点:
解决了单库大数据量的瓶颈问题
IO冲突减少,锁的竞争减少,某个数据库出现问题不影响其他数据库,提高了系统的稳定性和可用性
(3)水平拆分(分表、分库)的缺点:
分片事务一致性难以解决
跨节点JOIN性能差,逻辑会变得复杂
数据扩展难度大,不易维护
3、分库分表存在的问题的解决:
(1)事务的问题:
① 方案一:使用分布式事务:
优点:由数据库管理,简单有效。
缺点:性能代价高,特别是shard越来越多。② 方案二:程序与数据库共同控制实现,原理就是将一个跨多个数据库的分布式事务分解成多个仅存在于单一数据库上面的小事务,并交由应用程序来总体控制各个小事务。
优点:性能上有优势;
缺点:需要在应用程序在事务上做灵活控制。如果使用了spring的事务管理,改动起来会面临一定的困难。
(2)跨节点 Join 的问题:
解决该问题的普遍做法是分两次查询实现:在第一次查询的结果集中找出关联数据的id,根据这些id发起第二次请求得到关联数据。
(3)跨节点count,order by,group by,分页和聚合函数问题:
由于这类问题都需要基于全部数据集合进行计算。多数的代理都不会自动处理合并工作,解决方案:
与解决跨节点join问题的类似,分别在各个节点上得到结果后在应用程序端进行合并。和 join 不同的是每个结点的查询可以并行执行,因此速度要比单一大表快很多。但如果结果集很大,对应用程序内存的消耗是一个问题。
4、分库分表后,ID键如何处理?
分库分表后不能每个表的ID都是从1开始,所以需要一个全局ID,设置全局ID主要有以下几种方法:
(1)UUID:
优点:本地生成ID,不需要远程调用,全局唯一不重复。
缺点:占用空间大,不适合作为索引。
(2)数据库自增ID:在分库分表表后使用数据库自增ID,需要一个专门用于生成主键的库,每次服务接收到请求,先向这个库中插入一条没有意义的数据,获取一个数据库自增的ID,利用这个ID去分库分表中写数据。
优点:简单易实现。
缺点:在高并发下存在瓶颈。
(3)Redis生成ID:
优点:不依赖数据库,性能比较好。
缺点:引入新的组件会使得系统复杂度增加
(4)Twitter的snowflake算法:是一个64位的long型的ID,其中有1bit是不用的,41bit作为毫秒数,10bit作为工作机器ID,12bit作为序列号。
1bit:第一个bit默认为0,因为二进制中第一个bit为1的话为负数,但是ID不能为负数.
41bit:表示的是时间戳,单位是毫秒。
10bit:记录工作机器ID,其中5个bit表示机房ID,5个bit表示机器ID。
12bit:用来记录同一毫秒内产生的不同ID。
(5)美团的Leaf分布式ID生成系统,美团点评分布式ID生成系统:
当MySQL单表记录数过大时,数据库的CRUD性能会明显下降,一些常见的优化措施如下:
限定数据的范围: 务必禁止不带任何限制数据范围条件的查询语句。比如:我们当用户在查询订单历史的时候,我们可以控制在一个月的范围内。
读/写分离: 经典的数据库拆分方案,主库负责写,从库负责读;
缓存: 使用MySQL的缓存,另外对重量级、更新少的数据可以考虑使用应用级别的缓存;
还有就是通过分库分表的方式进行优化,主要有垂直分表和水平分表
垂直分区:
根据数据库里面数据表的相关性进行拆分。 例如,用户表中既有用户的登录信息又有用户的基本信息,可以将用户表拆分成两个单独的表,甚至放到单独的库做分库。
简单来说垂直拆分是指数据表列的拆分,把一张列比较多的表拆分为多张表。 如下图所示,这样来说大家应该就更容易理解了。
垂直拆分的优点: 可以使得行数据变小,在查询时减少读取的Block数,减少I/O次数。此外,垂直分区可以简化表的结构,易于维护。
垂直拆分的缺点:主键会出现冗余,需要管理冗余列,并会引起Join操作,可以通过在应用层进行Join来解决。此外,垂直分区会让事务变得更加复杂;
垂直分表
把主键和一些列放在一个表,然后把主键和另外的列放在另一个表中
适用场景
1、如果一个表中某些列常用,另外一些列不常用
2、可以使数据行变小,一个数据页能存储更多数据,查询时减少I/O次数
缺点
有些分表的策略基于应用层的逻辑算法,一旦逻辑算法改变,整个分表逻辑都会改变,扩展性较差
对于应用层来说,逻辑算法增加开发成本
管理冗余列,查询所有数据需要join操作
水平分区:
保持数据表结构不变,通过某种策略存储数据分片。这样每一片数据分散到不同的表或者库中,达到了分布式的目的。 水平拆分可以支撑非常大的数据量。
水平拆分是指数据表行的拆分,表的行数超过200万行时,就会变慢,这时可以把一张的表的数据拆成多张表来存放。举个例子:我们可以将用户信息表拆分成多个用户信息表,这样就可以避免单一表数据量过大对性能造成影响。
水品拆分可以支持非常大的数据量。需要注意的一点是:分表仅仅是解决了单一表数据过大的问题,但由于表的数据还是在同一台机器上,其实对于提升MySQL并发能力没有什么意义,所以 水平拆分最好分库 。
水平拆分能够 支持非常大的数据量存储,应用端改造也少,但分片事务难以解决 ,跨界点Join性能较差,逻辑复杂。
《Java工程师修炼之道》的作者推荐 尽量不要对数据进行分片,因为拆分会带来逻辑、部署、运维的各种复杂度 ,一般的数据表在优化得当的情况下支撑千万以下的数据量是没有太大问题的。如果实在要分片,尽量选择客户端分片架构,这样可以减少一次和中间件的网络I/O。
水平分表:
表很大,分割后可以降低在查询时需要读的数据和索引的页数,同时也降低了索引的层数,提高查询次数
适用场景
1、表中的数据本身就有独立性,例如表中分表记录各个地区的数据或者不同时期的数据,特别是有些数据常用,有些不常用。
2、需要把数据存放在多个介质上。
水平切分的缺点
1、给应用增加复杂度,通常查询时需要多个表名,查询所有数据都需UNION操作
2、在许多数据库应用中,这种复杂度会超过它带来的优点,查询时会增加读一个索引层的磁盘次数 下面补充一下数据库分片的两种常见方案:客户端代理: 分片逻辑在应用端,封装在jar包中,通过修改或者封装JDBC层来实现。 当当网的 Sharding-JDBC 阿里的TDDL是两种比较常用的实现。
中间件代理: 在应用和数据中间加了一个代理层。分片逻辑统一维护在中间件服务中。 我们现在谈的 Mycat 、360的Atlas、网易的DDB等等都是这种架构的实现。
分库分表后面临的问题
事务支持 分库分表后,就成了分布式事务了。如果依赖数据库本身的分布式事务管理功能去执行事务,将付出高昂的性能代价; 如果由应用程序去协助控制,形成程序逻辑上的事务,又会造成编程方面的负担。
跨库join
只要是进行切分,跨节点Join的问题是不可避免的。但是良好的设计和切分却可以减少此类情况的发生。解决这一问题的普遍做法是分两次查询实现。在第一次查询的结果集中找出关联数据的id,根据这些id发起第二次请求得到关联数据。 分库分表方案产品
跨节点的count,order by,group by以及聚合函数问题 这些是一类问题,因为它们都需要基于全部数据集合进行计算。多数的代理都不会自动处理合并工作。解决方案:与解决跨节点join问题的类似,分别在各个节点上得到结果后在应用程序端进行合并。和join不同的是每个结点的查询可以并行执行,因此很多时候它的速度要比单一大表快很多。但如果结果集很大,对应用程序内存的消耗是一个问题。
数据迁移,容量规划,扩容等问题 来自淘宝综合业务平台团队,它利用对2的倍数取余具有向前兼容的特性(如对4取余得1的数对2取余也是1)来分配数据,避免了行级别的数据迁移,但是依然需要进行表级别的迁移,同时对扩容规模和分表数量都有限制。总得来说,这些方案都不是十分的理想,多多少少都存在一些缺点,这也从一个侧面反映出了Sharding扩容的难度。
ID问题
一旦数据库被切分到多个物理结点上,我们将不能再依赖数据库自身的主键生成机制。一方面,某个分区数据库自生成的ID无法保证在全局上是唯一的;另一方面,应用程序在插入数据之前需要先获得ID,以便进行SQL路由. 一些常见的主键生成策略
UUID 使用UUID作主键是最简单的方案,但是缺点也是非常明显的。由于UUID非常的长,除占用大量存储空间外,最主要的问题是在索引上,在建立索引和基于索引进行查询时都存在性能问题。 Twitter的分布式自增ID算法Snowflake 在分布式系统中,需要生成全局UID的场合还是比较多的,twitter的snowflake解决了这种需求,实现也还是很简单的,除去配置信息,核心代码就是毫秒级时间41位 机器ID 10位 毫秒内序列12位。
跨分片的排序分页
般来讲,分页时需要按照指定字段进行排序。当排序字段就是分片字段的时候,我们通过分片规则可以比较容易定位到指定的分片,而当排序字段非分片字段的时候,情况就会变得比较复杂了。为了最终结果的准确性,我们需要在不同的分片节点中将数据进行排序并返回,并将不同分片返回的结果集进行汇总和再次排序,最后再返回给用户。如下图所示:
分区就是将表的数据按照特定规则存放在不同的区域,也就是将表的数据文件分割成多个小块,从逻辑上看,只有一张表,但是底层却是由多个物理分区组成。在查询数据的时候,只要知道数据数据存储在哪些区域,然后直接在对应的区域进行查询,不需要对表数据进行全部的查询,提高查询的性能。同时,如果表数据特别大,一个磁盘磁盘放不下时,我们也可以将数据分配到不同的磁盘去,解决存储瓶颈的问题,利用多个磁盘,也能够提高磁盘的IO效率,提高数据库的性能。在使用分区表时,需要注意分区字段必须放在主键或者唯一索引中、每个表最大分区数为1024;常见的分区类型有:Range分区、List分区、Hash分区、Key分区,
(1)Range分区:按照连续的区间范围进行分区
(2)List分区:按照给定的集合中的值进行选择分区。
(3)Hash分区:基于用户定义的表达式的返回值进行分区,该表达式使用将要插入到表中的这些行的列值进行计算。这个函数可以包含MySQL中有效的、产生非负整数值的任何表达式。
(4)Key分区:类似于按照HASH分区,区别在于Key分区只支持计算一列或多列,且key分区的哈希函数是由 MySQL 服务器提供。
(1)表分区的优点:
① 可伸缩性:
将分区分在不同磁盘,可以解决单磁盘容量瓶颈问题,存储更多的数据,也能解决单磁盘的IO瓶颈问题。
② 提升数据库的性能:
减少数据库检索时需要遍历的数据量,在查询时只需要在数据对应的分区进行查询。
避免Innodb的单个索引的互斥访问限制
对于聚合函数,例如sum()和count(),可以在每个分区进行并行处理,最终只需要统计所有分区得到的结果
③ 方便对数据进行运维管理:
方便管理,对于失去保存意义的数据,通过删除对应的分区,达到快速删除的作用。比如删除某一时间的历史数据,直接执行truncate,或者直接drop整个分区,这比detele删除效率更高;
在某些场景下,单个分区表的备份很恢复会更有效率。
表分区 | 分表 | |
---|---|---|
概念 | 将同一张表分别储存到不同的分区 | 将一张大数据表分成多个小表 |
相同点 | 提高效率 | 提高效率 |
不同点 | 描述的存储方式 | 描述表的逻辑关系 |
分区就是把一张表的数据分成N多个区块,这些区块可以在同一个磁盘上,也可以在不同的磁盘上, 从逻辑上看,只有一张表,但是底层却是由多个物理分区组成。
分表就是把一张表分成N多个小表
主从复制:将主数据库中的DDL和DML操作通过二进制日志(binlog)传输到从数据库上,然后将这些日志重新执行(重做);Slave从Master获取binlog二进制日志文件,然后再将日志文件解析成相应的SQL语句在从服务器上重新执行一遍主服务器的操作,通过这种方式来保证数据的一致性。由于主从复制的过程是异步复制的
,因此Slave和Master之间的数据有可能存在延迟的现象,只能保证数据最终的一致性。
在master和slave之间实现整个复制过程主要由三个线程来完成:
(1)binlog线程——master记录下所有DDL和DML语句,写进binlog中,位于master端
(2)io线程——在使用start slave 之后,负责从master上读取 binlog 内容,写进自己的relay log中,位于slave端
(3)sql执行线程——读取relay log中的内容然后重放,位于slave端
主从复制的作用:
(1)读写分离
,通过动态增加从服务器来提高数据库的性能,在主服务器上执行写入和更新,在从服务器上执行读功能。
(2)提高数据安全
,因为数据已复制到从服务器,从服务器可以终止复制进程,所以,可以在从服务器上备份而不破坏主服务器相应数据。
(3)在主服务器上生成实时数据,而在从服务器上分析这些数据,从而提高主服务器的性能。
MySQL主从复制解决的问题
数据分布
:随意开始或停止复制,并在不同地理位置分布数据备份
负载均衡
:降低单个服务器的压力
高可用和故障切换
:帮助应用程序避免单点失败
升级测试
:可以用更高版本的MySQL作为从库
主从复制流程:
(1)master服务器在执行SQL语句之后,记录在binlog
二进制文件中;
(2)slave端的IO线程连接上master端,并请求从指定bin log日志文件
的指定pos节点位置
(或者从最开始的日志)开始复制之后的日志内容。
(3)master端在接收到来自slave端的IO线程请求后,通知负责复制进程的IO线程,根据slave端IO线程的请求信息,读取指定binlog日志指定pos节点位置之后的日志信息,然后返回给slave端的IO线程。该返回信息
中除了binlog日志
所包含的信息之外,还包括本次返回的信息在master端的binlog文件名
以及在该binlog日志中的pos节点位置
。
(4)slave端的IO线程在接收到master端IO返回的信息后,将接收到的binlog日志内容依次写入到slave端的relay log文件的最末端
,并将读取到的master端的binlog文件名和pos节点位置记录到 master-info
文件中(该文件存slave端),以便在下一次同步的候能够告诉master从哪个位置开始进行数据同步;
(5)slave端的SQL线程在检测到relay log文件中新增内容后,就马上解析该relay log文件中的内容
,然后还原
成在master端真实执行的那些SQL语句,再按顺序依次执行
这些SQL语句,从而到达master端和slave端的数据一致性;
MySQL支持的复制类型及其优缺点:
binlog日志文件有两种格式:
- Statement-Based(基于语句的复制)
- Row-Based(基于行的复制)
默认格式为Statement-Based,如果想改变其格式在开启服务的时候使用 -binlog-format 选项,其具体命令如下:
mysqld_safe –user=msyql –binlog-format=格式 &
(1)基于语句的复制(Statement-Based)
:在主服务器上执行的SQL语句,在从服务器上执行同样的语句。效率比较高。 一旦发现没法精确复制时,会自动选着基于行的复制。
优点:
- ① 因为记录的SQL语句,所以占用更少的存储空间。binlog日志包含了描述数据库操作的事件,但这些事件包含的情况只是对数据库进行改变的操作,例如 insert、update、create、delete等操作。相反对于select、desc等类似的操作并不会去记录。
- ② binlog日志文件记录了所有的改变数据库的语句,所以此文件可以作为数据库的审核依据。
缺点:
- ① 不安全,不是所有的改变数据的语句都会被记录。对于非确定性的行为不会被记录。例如:对于 delete 或者 update 语句,如果使用了 limit 但是并没有 order by ,这就属于非确定性的语句,就不会被记录。
- ② 对于没有索引条件的update,insert……select 语句,必须锁定更多的数据,降低了数据库的性能。
(2)基于行的复制(Row-Based)
:把改变的内容复制过去,而不是把命令在从服务器上执行一遍,从mysql5.0开始支持;
优点:
① 所有的改变都会被复制,这是最安全的复制方式;
② 对于 update、insert……select等语句锁定更少的行;
缺点:
① 不能通过binlog日志文件查看什么语句执行了,也无从知道在从服务器上接收到什么语句,我们只能看到什么数据改变。
② 因为记录的是数据,所以说binlog日志文件占用的存储空间要比Statement-based大。
③ 对于数据量大的操作其花费的时间有更长。
(3)混合类型的复制(mixed)
:默认采用基于语句的复制,一旦发现基于语句的无法精确的复制时,就会采用基于行的复制。
有关主从复制更详细的内容,请阅读这篇文章:https://blog.csdn.net/a745233700/article/details/85256818
读写分离是依赖于主从复制,而主从复制又是为读写分离服务的。因为主从复制要求slave不能写只能读(如果对slave执行写操作,那么show slave status将会呈现Slave_SQL_Running=NO,此时你需要按照前面提到的手动同步一下slave)。
(1)基于程序代码内部实现:在代码中根据select、insert进行路由分类。优点是性能较好,因为程序在代码中实现,不需要增加额外的硬件开支,缺点是需要开发人员来实现,运维人员无从下手。
(2)基于中间代理层实现:代理一般介于应用服务器和数据库服务器之间,代理数据库服务器接收到应用服务器的请求后根据判断后转发到后端数据库。
方案一
使用mysql-proxy代理
优点:直接实现读写分离和负载均衡,不用修改代码,master和slave用一样的帐号,mysql官方不建议实际生产中使用
缺点:降低性能, 不支持事务
方案二
使用 AbstractRoutingDataSource + aop + annotation 在dao层决定数据源。
如果采用了mybatis, 可以将读写分离放在ORM层,比如mybatis可以通过mybatis plugin拦截sql语句,所有的insert/update/delete都访问master库,所有的select 都访问salve库,这样对于dao层都是透明。 plugin实现时可以通过注解或者分析语句是读写方法来选定主从库。
不过这样依然有一个问题, 也就是不支持事务
, 解决办法:
重写一下DataSourceTransactionManager,将read-only的事务扔进读库, 其余的有读有写的扔进写库
。
方案三
使用AbstractRoutingDataSource + aop + annotation 在 service 层决定数据源,可以支持事务.
缺点:类内部方法通过this.xx()方式相互调用时,aop不会进行拦截,需进行特殊处理。
读写分离提高性能的原因:
(1)增加物理服务器,负荷分摊;
(2)主从只负责各自的写和读,极大程度的缓解X锁和S锁争用;
(3)从库可配置MyISAM引擎,提升查询性能以及节约系统开销;
(4)主从复制另外一大功能是增加冗余,提高可用性,当一台数据库服务器宕机后能通过调整另外一台从库来以最快的速度恢复服务。
(1)备份计划
视库的大小来定,一般来说 100G 内的库,可以考虑使用 mysqldump 来做,因为 mysqldump更加轻巧灵活,备份时间选在业务低峰期,可以每天进行都进行全量备份(mysqldump 备份出来的文件比较小,压缩之后更小)。
100G 以上的库,可以考虑用 xtranbackup 来做,备份速度明显要比 mysqldump 要快。一般是选择一周一个全备,其余每天进行增量备份,备份时间为业务低峰期。
(2)备份恢复时间
物理备份恢复快,逻辑备份恢复慢
这里跟机器,尤其是硬盘的速率有关系,以下列举几个仅供参考
20G的2分钟(mysqldump)
80G的30分钟(mysqldump)
111G的30分钟(mysqldump)
288G的3小时(xtra)
3T的4小时(xtra)
逻辑导入时间一般是备份时间的5倍以上
(3)备份恢复失败如何处理
首先在恢复之前就应该做足准备工作,避免恢复的时候出错。比如说备份之后的有效性检查、权限检查、空间检查等。如果万一报错,再根据报错的提示来进行相应的调整。
(4)mysqldump 和 xtrabackup 实现原理
mysqldump
mysqldump 属于逻辑备份。加入–single-transaction 选项可以进行一致性备份。后台进程会先设置 session 的事务隔离级别为 RR(SET SESSION TRANSACTION ISOLATION LEVELREPEATABLE READ),之后显式开启一个事务(START TRANSACTION /*!40100 WITH CONSISTENTSNAPSHOT */),这样就保证了该事务里读到的数据都是事务事务时候的快照。之后再把表的数据读取出来。如果加上–master-data=1 的话,在刚开始的时候还会加一个数据库的读锁(FLUSH TABLES WITH READ LOCK),等开启事务后,再记录下数据库此时 binlog 的位置(showmaster status),马上解锁,再读取表的数据。等所有的数据都已经导完,就可以结束事务
Xtrabackup:
xtrabackup 属于物理备份,直接拷贝表空间文件,同时不断扫描产生的 redo 日志并保存下来。最后完成 innodb 的备份后,会做一个 flush engine logs 的操作(老版本在有 bug,在5.6 上不做此操作会丢数据),确保所有的 redo log 都已经落盘(涉及到事务的两阶段提交
概念,因为 xtrabackup 并不拷贝 binlog,所以必须保证所有的 redo log 都落盘,否则可能会丢最后一组提交事务的数据)。这个时间点就是 innodb 完成备份的时间点,数据文件虽然不是一致性的,但是有这段时间的 redo 就可以让数据文件达到一致性(恢复的时候做的事
情)。然后还需要 flush tables with read lock,把 myisam 等其他引擎的表给备份出来,备份完后解锁。这样就做到了完美的热备。
使用 myisamchk 来修复,具体步骤:
使用REPAIR TABLE 或者 OPTIMIZE TABLE 命令来修复,REPAIR TABLE table_name 修复表 OPTIMIZE TABLE table_name 优化表 REPAIR TABLE 用于修复被破坏的表。
OPTIMIZE TABLE 用于回收闲置的数据库空间,当表上的数据行被删除时,所占据的磁盘空间并没有立即被回收,使用了OPTIMIZE TABLE命令后这些空间将被回收,并且对磁盘上的数据行进行重排(注意:是磁盘上,而非数据库)
MySQL服务器通过权限表来控制用户对数据库的访问,权限表存放在mysql数据库里,由mysql_install_db脚本初始化。这些权限表分别user,db,table_priv,columns_priv和host。下面分别介绍一下这些表的结构和内容:
user权限表
:记录允许连接到服务器的用户帐号信息,里面的权限是全局级的。
db权限表
:记录各个帐号在各个数据库上的操作权限。
table_priv权限表
:记录数据表级的操作权限。
columns_priv权限表
:记录数据列级的操作权限。
host权限表
:配合db权限表对给定主机上数据库级操作权限作更细致的控制。这个权限表不受GRANT和REVOKE语句的影响。
有三种格式,statement,row 和 mixed。
statement
模式下,每一条会修改数据的sql都会记录在binlog中。不需要记录每一行的变化,减少了binlog日志量,节约了IO,提高性能。由于sql的执行是有上下文的,因此在保存的时候需要保存相关的信息,同时还有一些使用了函数之类的语句无法被记录复制。
row
级别下,不记录sql语句上下文相关信息,仅保存哪条记录被修改。记录单元为每一行的改动,基本是可以全部记下来但是由于很多操作,会导致大量行的改动(比如alter table),因此这种模式的文件保存的信息太多,日志量太大。
mixed
,一种折中的方案,普通操作使用statement记录,当无法使用statement的时候使用row。
此外,新版的MySQL中对row级别也做了一些优化,当表结构发生变化的时候,会记录语句而不是逐行记录。
为了提高复杂SQL语句的复用性和表操作的安全性
,MySQL数据库管理系统提供了视图特性。所谓视图,本质上是一种虚拟表
,在物理上是不存在的,其内容与真实的表相似,包含一系列带有名称的列和行数据。但是,视图并不在数据库中以储存的数据值形式存在。行和列数据来自定义视图的查询所引用基本表,并且在具体引用视图时动态生成。
视图使开发者只关心感兴趣的某些特定数据和所负责的特定任务,只能看到视图中所定义的数据,而不是视图所引用表中的数据,从而提高了数据库中数据的安全性。
视图是从一个或者多个表(或视图)导出的表,其内容由查询定义。视图是一个虚拟表,数据库中只存储视图的定义,不存储视图对应的数据,在对视图的数据进行操作时,系统根据视图的定义去操作相应的基本表。可以说,视图是在基本表之上建立的表,它的结构和内容都来自基本表,依据基本表存在而存在。一个视图可以对应一个基本表,也可以对应多个基本表。视图是基本表的抽象和在逻辑意义上建立的新关系。
(1)视图的优点:
简化了操作,把经常使用的数据定义为视图
安全性,用户只能查询和修改能看到的数据
逻辑上的独立性,屏蔽了真实表的结构带来的影响
(2)视图的缺点:
性能差,数据库必须把对视图的查询转化成对基本表的查询,如果这个视图是由一个复杂的多表查询所定义,那么,即使是视图的一个简单查询,数据库也要把它变成一个复杂的结合体,需要花费一定的时间。
视图的特点如下:
视图的列可以来自不同的表,是表的抽象和在逻辑意义上建立的新关系。
视图是由基本表(实表)产生的表(虚表)。
视图的建立和删除不影响基本表。
对视图内容的更新(添加,删除和修改)直接影响基本表。
当视图来自多个基本表时,不允许添加和删除数据。
视图的操作包括创建视图,查看视图,删除视图和修改视图。
视图根本用途:简化sql查询,提高开发效率
。如果说还有另外一个用途那就是兼容老的表结构
。
下面是视图的常见使用场景:
重用SQL语句;
简化复杂的SQL操作。在编写查询后,可以方便的重用它而不必知道它的基本查询细节;
使用表的组成部分而不是整个表;
保护数据。可以给用户授予表的特定部分的访问权限而不是整个表的访问权限;
更改数据格式和表示。视图可返回与底层表的表示和格式不同的数据。
查询简单化。视图能简化用户的操作
数据安全性。视图使用户能以多种角度看待同一数据,能够对机密数据提供安全保护
逻辑数据独立性。视图对重构数据库提供了一定程度的逻辑独立性
性能。数据库必须把视图的查询转化成对基本表的查询,如果这个视图是由一个复杂的多表查询所定义,那么,即使是视图的一个简单查询,数据库也把它变成一个复杂的结合体,需要花费一定的时间。
修改限制。当用户试图修改视图的某些行时,数据库必须把它转化为对基本表的某些行的修改。事实上,当从视图中插入或者删除时,情况也是这样。对于简单视图来说,这是很方便的,但是,对于比较复杂的视图,可能是不可修改的
这些视图有如下特征:
1.有UNIQUE等集合操作符的视图。
2.有GROUP BY子句的视图。
3.有诸如AVG\SUM\MAX等聚合函数的视图。
4.使用DISTINCT关键字的视图。
5.连接表的视图(其中有些例外)
游标,就是游动的标识,可以充当指针的作用,使用游标可以遍历查询数据库返回的结果集中的所有记录,是系统为用户开设的一个数据缓冲区
,存放SQL语句的执行结果, 但是每次只能提取一条记录,即每次只能指向并取出一行的数据
,以便进行相应的操作。当你没有使用游标的时候,相当于别人一下给你所有的东西让你拿走;用了游标之后,相当于别人一件一件的给你,这时你可以先看看这个东西好不好,再自己进行选择。
存储过程是一个预编译的SQL语句
,优点是允许模块化的设计,就是说只需要创建一次,以后在该程序中就可以调用多次。如果某次操作需要执行多次SQL,使用存储过程比单纯SQL语句执行要快。
优点
1)存储过程是预编译过的,执行效率高。
2)存储过程的代码直接存放于数据库中,通过存储过程名直接调用,减少网络通讯。
3)安全性高,执行存储过程需要有一定权限的用户。
4)存储过程可以重复使用,减少数据库开发人员的工作量。
缺点
1)调试麻烦,但是用 PL/SQL Developer 调试很方便!弥补这个缺点。
2)移植问题,数据库端代码当然是与数据库相关的。但是如果是做工程型项目,基本不存在移植问题。
3)重新编译问题,因为后端代码是运行前编译的,如果带有引用关系的对象发生改变时,受影响的存储过程、包将需要重新编译(不过也可以设置成运行时刻自动编译)。
4)如果在一个程序系统中大量的使用存储过程,到程序交付使用的时候随着用户需求的增加会导致数据结构的变化,接着就是系统的相关问题了,最后如果用户想维护该系统可以说是很难很难、而且代价是空前的,维护起来更麻烦。
触发器是用户定义在关系表上的一类由事件驱动
的特殊的存储过程
。触发器是指一段代码,当触发某个事件时,自动执行这些代码
。
使用场景
可以通过数据库中的相关表实现级联更改
。
实时监控某张表中的某个字段
的更改而需要做出相应的处理。例如可以生成某些业务的编号。
注意不要滥用,否则会造成数据库及应用程序的维护困难。
在MySQL数据库中有如下六种触发器:
Before Insert
After Insert
Before Update
After Update
Before Delete
After Delete
数据定义语言DDL
(Data Ddefinition Language)CREATE,DROP,ALTER, 主要为对表逻辑结构等操作的,其中包括表结构,视图和索引。
数据查询语言DQL
(Data Query Language)SELECT, 这个较为好理解 即查询操作,以select关键字。各种简单查询,连接查询等 都属于DQL。
数据操纵语言DML
(Data Manipulation Language)INSERT,UPDATE,DELETE, 主要为以上操作 即对数据进行操作的,对应上面所说的查询操作 DQL与DML共同构建了多数初级程序员常用的增删改查操作。而查询是较为特殊的一种 被划分到DQL中。
数据控制功能DCL
(Data Control Language)GRANT,REVOKE,COMMIT,ROLLBACK, 主要为以上操作 即对数据库安全性完整性等有操作的,可以简单的理解为权限控制等。
NOT NULL
: 用于控制字段的内容一定不能为空(NULL)。
UNIQUE
: 控件字段内容不能重复,一个表允许有多个 Unique 约束。
PRIMARY KEY
: 也是用于控件字段内容不能重复,但它在一个表只允许出现一个。
FOREIGN KEY
: 用于预防破坏表之间连接的动作,也能防止非法数据插入外键列,因为它必须是它指向的那个表中的值之一。
CHECK
: 用于控制字段的值范围。
交叉连接(CROSS JOIN)
内连接(INNER JOIN)
外连接(LEFT JOIN / RIGHT JOIN)
全连接(FULL JOIN)
联合查询(UNION 与 UNION ALL)
SELECT * FROM A,B(,C)
或者
SELECT * FROM A CROSS JOIN B (CROSS JOIN C)
#没有任何关联条件,结果是笛卡尔积,结果集会很大,没有意义,很少使用内连接(INNER JOIN)
SELECT * FROM A,B WHERE A.id=B.id
或者
SELECT * FROM A INNER JOIN B ON A.id=B.id
多表中同时符合某种条件的数据记录的集合,INNER JOIN可以缩写为JOIN
内连接分为三类
外连接(LEFT JOIN / RIGHT JOIN)
SELECT * FROM A UNION SELECT * FROM B UNION ...
就是把多个结果集集中在一起,UNION前的结果为基准,需要注意的是联合查询的列数要相等,相同的记录行会合并
如果使用UNION ALL,不会合并重复的记录行
效率 UNION 高于 UNION ALL
全连接(FULL JOIN)
MySQL不支持全连接
可以使用LEFT JOIN 和UNION和RIGHT JOIN联合使用
SELECT * FROM A LEFT JOIN B ON A.id=B.id UNIONSELECT * FROM A RIGHT JOIN B ON A.id=B.id
表连接
有2张表,1张R、1张S,R表有ABC三列,S表有CD两列,表中各有三条记录。
R表
A | B | C |
---|---|---|
a1 | b1 | c1 |
a2 | b2 | c2 |
a3 | b3 | c3 |
S表 |
C | D |
---|---|
c1 | d1 |
c2 | d2 |
c4 | d3 |
交叉连接(笛卡尔积): | |
select r.,s. from r,s |
A | B | C | C | D |
---|---|---|---|---|
a1 | b1 | c1 | c1 | d1 |
a2 | b2 | c2 | c1 | d1 |
a3 | b3 | c3 | c1 | d1 |
a1 | b1 | c1 | c2 | d2 |
a2 | b2 | c2 | c2 | d2 |
a3 | b3 | c3 | c2 | d2 |
a1 | b1 | c1 | c4 | d3 |
a2 | b2 | c2 | c4 | d3 |
a3 | b3 | c3 | c4 | d3 |
内连接结果: |
select r.,s. from r inner join s on r.c=s.c
A | B | C | C | D |
---|---|---|---|---|
a1 | b1 | c1 | c1 | d1 |
a2 | b2 | c2 | c2 | d2 |
左连接结果: |
select r.,s. from r left join s on r.c=s.c
A | B | C | C | D |
---|---|---|---|---|
a1 | b1 | c1 | c1 | d1 |
a2 | b2 | c2 | c2 | d2 |
a3 | b3 | c3 | ||
右连接结果: |
select r.,s. from r right join s on r.c=s.c
A | B | C | C | D |
---|---|---|---|---|
a1 | b1 | c1 | c1 | d1 |
a2 | b2 | c2 | c2 | d2 |
c4 | d3 | |||
全表连接的结果(MySql不支持,Oracle支持): |
select r.,s. from r full join s on r.c=s.c
A | B | C | C | D |
---|---|---|---|---|
a1 | b1 | c1 | c1 | d1 |
a2 | b2 | c2 | c2 | d2 |
a3 | b3 | c3 | ||
c4 | d3 |
条件:一条SQL语句的查询结果做为另一条查询语句的条件或查询结果
嵌套:多条SQL语句嵌套使用,内部的SQL查询语句称为子查询。
子查询是单行单列的情况:结果集是一个值,父查询使用:=、 <、 > 等运算符
select * from employee where salary=(select max(salary) from employee);
子查询是多行单列的情况:结果集类似于一个数组,父查询使用:in 运算符
select * from employee where salary=(select max(salary) from employee);
子查询是多行多列的情况:结果集类似于一张虚拟表,不能用于where条件,用于select子句中做为子表
-- 1) 查询出2011年以后入职的员工信息
-- 2) 查询所有的部门信息,与上面的虚拟表中的信息比对,找出所有部门ID相等的员工。
select * from dept d, (select * from employee where join_date > '2011-1-1') e where e.dept_id = d.id;
-- 使用表连接:
select d.*, e.* from dept d inner join employee e on d.id = e.dept_id where e.join_date > '2011-1-1'
https://blog.csdn.net/enmotech/article/details/100111441