目录
- 数据库的三大范式是什么
- Mysql的存储引擎都有哪些,有什么区别
- Mysql数据库的隔离级别,以及不同隔离级别下存在的并发事务问题
- 索引
- Sql优化
- 简单说一说drop、delete和truncate的区别
- 什么是视图(View)
- 左连接和右连接和内连接
- Mysql大表(千万级数据量)如何处理?(难)
- 分布式中的分库分表之后,ID 主键如何处理?
- 在mysql中一条sql语句是如何执行的
- Mysql中的binlog、redolog、undolog(重要)
- mysql中varchar和char的区别,varchar(5)和varchar(200)的区别
- MYSQL中的int(11)到底代表什么意思?
- 为什么 select count(*) from t,在 InnoDB 引擎中比 MyISAM 慢?
- 什么是MVCC(非常重要)
- Mysql的锁
- 锁升级的原因
- 死锁相关
- MySQL如何实现高可用(难)
数据库的三大范式是什么
-
第一范式(确保每列保持原子性)
所有字段值都是不可分解的原子值,比如说code和name是两个字段,不能合并成一个 -
第二范式(确保表中的每列都和主键相关)
意思就是不要冗余字段,所属单位就和订单编号无关(主键)
-
第三范式(确保每列都和主键列直接相关,而不是间接相关)
Mysql的存储引擎都有哪些,有什么区别
有InnoDB和MyISAM,具体区别可以看我的这篇文章
【Mysql】存储引擎
Mysql数据库的隔离级别,以及不同隔离级别下存在的并发事务问题
数据库在并发情况下的常见问题
- 读-读:不存在任何问题,也不需要并发控制
- 读-写:有线程安全问题,可能会造成事务隔离性问题,可能遇到脏读、不可重复读、幻读
- 写-写:有线程安全问题,可能会存在更新丢失问题
Mysql的隔离级别以及解决的问题
- READ-UNCOMMITTED(读未提交)
可能导致的问题:脏读
解决:隔离级别升级,读已提交或以上 - READ-COMMITTED(读已提交)
可能导致的问题:不可重复读
解决:隔离级别升级,升级到可重复读或以上 - REPEATABLE-READ(可重复读)
可能导致的问题:幻读
解决:隔离级别升级,升级到串行化 - SERIALIZABLE(串行化)
可避免幻读。读加共享锁,写加排他锁。这样读取事务可以并发,但是读写,写写事务之间都是互斥的
索引
索引是什么
索引是一种优化查询的数据结构,是表的一部分,记录这表里所有记录的引用指针,可以理解为一本书的目录
索引的优缺点
- 优点
- 查询效率变快
- 唯一索引可以保证数据行的唯一性
- 加速表与表之间的连接
- 在使用分组和排序进行检索的时候,可以减少查询中分组和排序的时间
- 缺点
- 创建索引和维护索引需要时间,随数据量的增加而增加
- 索引占用空间
- 更新数据的同时会更新索引,降低了更新的速度
索引的类型
- FULLTEXT 全文索引:目前只有MyISAM引擎支持
- HASH 哈希:HASH的唯一(几乎100%的唯一)及类似键值对的形式,特点:“=”和“in”条件下高效,对于范围查询、排序效率不高
- BTree B树(Mysql默认和最常用的索引类型):一种将索引值按一定的算法,存入一个树形的数据结构中(二叉树),每次查询都是从树的入口root开始,依次遍历node,获取leaf(索引存在叶子节点吗?todo)
索引的种类
- 聚集索引(主键索引):在数据库里面,所有行数都会按照主键索引进行排序,一张表就只有一个聚集索引。
- 非聚集索引:就是给普通字段加上索引。
- 联合索引:就是好几个字段组成的索引,称为联合索引。
联合索引遵从最左前缀原则
什么时候需要创建索引
-
主键自动建立唯一索引(ID)
-
频繁作为查询条件的字段应该创建索引、一般创建联合索引(Where)
-
查询中排序的字段创建索引将大大提高排序的速度(Order By)
-
查询中统计或者分组的字段(Group By)
什么时候不需要创建索引
- 频繁更新的字段不适合创建索引
- where条件里用不到的字段,不创建索引
- 表记录太少,不需要创建索引
- 数据离散度小的字段,不需要创建索引,例如性别
哪些情况会不走索引
- Like:%在前面的话不走索引,%在后面的话还是会走索引的
- 索引列有进行计算操作,不走索引
- 索引列使用了函数,不走索引
- 索引列使用了不等(!=)符号,不走索引
- 不满足联合索引的最左前缀原则,不走索引
- or操作,不走索引(可以用union替代)
- 索引列的字段具体值为null,不走索引,进行全表扫描(很关键)
- 字符串列与数字直接比较,字段属性是字符,但是你用数据类型去比较,不走索引(因为隐式类型转换)
- order by操作的索引列如果也在where条件里,则不走索引
- 对于联合索引,范围查询如between、>、< 这些条件,会造成后面的索引字段失效
主键和索引的区别
-
主键一定是唯一索引,唯一索引并不一定就是主键。
-
一个表中可以有多个唯一性索引,但只能有一个主键。
-
主键列不允许为null,唯一性索引列允许为null
Sql优化
Explain执行计划:
- type列,连接类型。一个好的SQL语句至少要达到range级别。杜绝出现all级别。
从坏到好:All、index、range。。。。。。 - key列,使用到的索引名。如果没有选择索引,值是NULL。可以采取强制索引方式。
- key_len列,索引长度。
- rows列,扫描行数。该值是个预估值。
- extra列,详细说明。注意,常见的不太友好的值,如下:Using filesort,Using temporary。
Sql优化的一般手段
- 不用select *:SELECT语句务必指明字段名称
- 小表去驱动大表:表关联查询(left join)尽量使用数据量小的表作为主表(驱动表)
- 尽可能走索引(这里好多操作,不举例了)
- 使用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来分页,每页数量也不要太大
摘自:https://www.cnblogs.com/adolfmc/p/11497261.html
简单说一说drop、delete和truncate的区别
- delete、truncate只删除表的数据而不删除表的结构
- drop会删除表的结构和数据,不支持回滚
- delete操作会触发delete触发器,支持回滚
- truncate不会触发如何触发器,不支持回滚
什么是视图(View)
视图其实就是一张虚表,本质就是一个Select语句,结果集被命名为视图名称
当原表发生改变的时候,视图的数据也会发生改变
左连接和右连接和内连接
- 左连接Left join,左表为驱动表,查询出左表的所有数据,以及右表关联数据(没有的用null表示)
- 右连接Right join,右表为驱动表,查询出右表的所有数据,以及左表关联数据(没有的用null表示)
- 内连接Inner jion,查出两表都存在的数据(交集)
Mysql大表(千万级数据量)如何处理?(难)
限定数据的范围
禁止不带任何查询条件的查询语句,避免查询全表数据,尽量多的走索引。
读/写分离
主库负责写,从库负责读(待扩充。。。)
垂直拆分
定义:把一张列比较多的表拆分为多张表
例如:将更新频繁的字段(如用户最近一次登录时间)或大字段(text类型,如个人介绍)垂直拆分,变成一个单独的表,
- 优点:
- 使得列数据变小,在查询时减少锁占用时间,减少I/O次数。此外,垂直分区可以简化表的结构,易于维护
- 缺点:
- 主键会出现冗余(拆完后的两个表主键必须保持一致,这个需要代码去控制)
- 垂直分区会让事务变得更加复杂;
水平拆分
定义:保持数据表结构不变,通过某种策略存储数据分片。这样每一片数据分散到不同的表(表1、表2、表3.。。。。。)或者库(不同库就可以用同样的表名了)中,达到了分布式的目的
例如:对于一些无穷增长的表(如操作日志表),提升机器性能、增加存储的方式解决不了,这时候就要对表进行水平拆分,拆层多个相同的表
注意:水平拆分建议分库水平拆分,放到不同的服务器上,这样才能有效缓解单台服务器的压力
- 优点
- 解决了无穷增长的表的问题
- 缺点
- 分片事务难以解决 ,跨节点Join性能较差,逻辑复杂
实现方案(以下的实现可以去了解以下):
- 客户端代理: 分片逻辑在应用端,封装在jar包中,通过修改或者封装JDBC层来实现。 当当网的Sharding-JDBC 、阿里的TDDL是两种比较常用的实现。
- 中间件代理: 在应用和数据中间加了一个代理层。分片逻辑统一维护在中间件服务中。 我们现在谈的 Mycat 、360的Atlas、网易的DDB等等都是这种架构的实现。
更多具体的实现方式可以参考文章:
MySQL 对于千万级的大表要怎么优化?
分布式中的分库分表之后,ID 主键如何处理?
分库分表的原因:1.单库并发太高 2.数据量太大
ID主键如何获取,自增
- 基于数据库的实现方案
- 建一张表,单独存放生成的唯一ID,这张表没有任何业务意义(缺点:并发低)
- 使用数据库的序列Sequence,每次ID自增的步长和服务节点有关(如:表被分到了8个服务节点,那么ID每次自增的长度是8,可以防止ID重复,缺点:服务节点一旦确认,就不好修改)
- 基于代码的实现方案
- 使用UUID(作为主键性能太差了,不可取)
- snowflake 雪花算法:分布式 id 生成算法
生成的ID组成:一个 64 位的 long 型的 id
- 使用Redis生成分布式ID:Redis中的incr命令来实现原子性的自增与返回
- 百度(uid-generator):uid-generator使用的就是snowflake,只是在生产机器id,也叫作workId时有所不一样。
- 美团(Leaf):Leaf中workId是基于ZooKeeper的顺序Id来生成的
在mysql中一条sql语句是如何执行的
执行的流程图如下:
- 连接器
连接器负责跟客户端建立连接、获取权限、维持和管理连接
mysql -h$ip -P$port -u$user -p
连接命令中的 mysql 是客户端工具,用来跟服务端建立连接。在完成经典的 TCP 握手后,连接器就要开始认证你的身份
-
查询缓存
Mysql拿到一个select请求后,会去缓存中查看是否存在(以key-value的形式,key:select语句,value:查询的结果)缓存不好:更新频繁的数据,缓存经常没有命中,查缓存浪费时间
-
分析器
分析语法,看看sql语句有没有问题 -
优化器
生成执行计划Explain,还有比如,选择哪个索引,表关联查询的时候决定表的连接顺序 -
执行器
判断权限、选择存储引擎、执行sql语句
Mysql中的binlog、redolog、undolog(重要)
Mysql执行器在执行更新相关的语句时候,会记录日志
binlog 二进制日志
binlog是Mysql自带的日志模块(叫什么归档日志),所有引擎都可以使用
MySQL数据库的数据备份、主备、主主、主从都离不开binlog,需要依靠binlog来同步数据,保证数据一致性
redolog 事务日志
redolog是InnoDB引擎自带的日志模块(重做日志),InnoDB就是通过redolog来保证事务操作的
比如 MySQL 实例挂了或宕机了,重启时,InnoDB存储引擎会使用redo log恢复数据,保证数据的持久性与完整性。
undolog 回滚日志
在异常发生时,对已经执行的操作进行回滚
JavaGuide链接 MySQL三大日志(binlog、redo log和undo log)
InnoDB模式下的更新的sql执行流程:
- 先查询到张三这一条数据,如果有缓存就用缓存。
- 然后拿到查询的语句,把 age 改为19,然后调用引擎API接口,写入这一行数据,InnoDB引擎把数据保存在内存中,同时记录redo log,此时redo log进入prepare状态,然后告诉执行器,执行完成了,随时可以提交。
- 执行器收到通知后记录binlog,然后调用引擎接口,提交redo log 为commit状态。
- 更新完成。
原文链接:https://www.cnblogs.com/lfri/p/12598339.html
mysql中varchar和char的区别,varchar(5)和varchar(200)的区别
mysql中varchar和char的区别
存储引擎存储CHAR或者VARCHAR值的方式在内存中和在磁盘上可能不一样
VARCHAR类型用于存储可变长字符串,是最常见的字符串数据类型
CHAR类型是定长的:MySQL总是根据定义的字符串长度分配足够的空间(CHAR会把末尾空格取消掉
)
选取:该字段数据集的平均长度与最大长度是否相差很小,选择CHAR,其他情况选择VARCHAR
varchar(5)和varchar(200)的区别
- 相同点:存同样的数据,在硬盘上的空间开销是一样的
- 不同点:分配的内存不一样,长度大的分配更多内存
MYSQL中的int(11)到底代表什么意思?
int(11)中,11代表的并不是长度,而是字符的显示宽度
- 长度不足11,左边自动补零
- 字段超过了11,仍然可存储,这是显示宽度就不起作用了
为什么 select count(*) from t,在 InnoDB 引擎中比 MyISAM 慢?
- 在 MyISAM 存储引擎中,把表的总行数存储在磁盘上,当执行 select count(*) from t 时,直接返回总数据。
- 在 InnoDB 存储引擎中,跟 MyISAM 不一样,没有将总行数存储在磁盘上,当执行 select count(*) from t 时,会先把数据读出来,一行一行的累加,最后返回总数量。
什么是MVCC(非常重要)
定义(Mutli Version Concurreny Control)
MVCC其实就是一个多版本并发控制,即多个不同版本的数据实现并发控制的技术,其基本思想是为每次事务生成一个新版本的数据,在读数据时选择不同版本的数据即可以实现对事务结果的完整性读取。
MVCC主要有什么作用
提高并发读写性能,操作时会生成事务id,能够更好地解决读-写冲突,不加锁、非阻塞的并发读
什么是MySQL InnoDB下的当前读和快照读
当前读
就是读取当前数据的最新记录,会加锁,为了保证其他事务不能修改记录(如select lock in share mode(共享锁), select for update ; update, insert ,delete(排他锁))
快照读
不加锁的select操作就是快照读(隔离级别不能是串行化,如果是串行化快照读就变成了当前读)
MVCC实现原理
- 三个隐藏列:每条记录都会保存三个隐藏列:
- DB_TRX_ID(事务id):记录创建这条记录/最后一次修改该记录的事务ID
- DB_ROLL_PTR(回滚指针):指向这条记录的上一个版本
- DB_ROW_ID(隐藏主键):隐含的自增ID。现在不知道干嘛用的
-
undo log:每次操作都会生成一条undo_log日志
分为insert undo log和update undo log -
Read View(读视图,又叫一致性视图?)
查询的时候会读取出【read-view】:[未提交的事务id]数组+最大事务id,并根据read-view从undo_log日志中最新的记录一次往下找,查找规则如下:
①从最新记录开始查找(重要):
下面的事务id:就是一次更新操作的这个快照所处的事务id
-
如果当前记录:事务id < 未提交事务的最小id,则可读
-
如果当前记录:最小id <= 事务id <= 事务的最小id,则判断事务id是否在未提交事务id的数组中,若在则不可读(如果只有自己还是可读)
-
如果当前记录:事务id > 事务的最大id,则不可读
②可重复读返回的是第一次查询生成的【read-view】,读已提交每次都会重新生成一个新的【read-view】
有点晦涩难懂:具体的操作看这篇文章,写的非常棒!
https://www.cnblogs.com/boluopabo/p/13111961.html
- MVCC如何实现读已提交
每次查询的时候生成一个新的【read-view】,然后去【undo】日志中寻找符合结果的一条数据 - MVCC如何实现可重复读
第一次查询的时候生成一个新的【read-view】,后面的查询会继续沿用第一次的【read-view】,然后找出一个可读的数据
Mysql的锁
锁的种类
- 共享锁:对数据就行读操作是加的锁。也叫读锁
- 排他锁:对数据就行写操作是加的锁。也叫写锁
- 意向锁:为了在一个事务中揭示下一行将要被请求锁的类型,意向锁是表锁,分为意向共享锁和意向排他锁
锁的级别
- 表级锁: MySQL 中锁定 粒度最大 的一种锁,锁表,加锁快,不会出现死锁、并发度低
- 行级锁: MySQL 中锁定 粒度最小 的一种锁,锁行,加锁慢,会出现死锁、并发度高
- 页面锁:介于表级锁和行级锁之间,不怎么用
InnoDB 存储引擎的锁的算法有三种:
- Record lock:记录锁,单个行记录上的锁
- Gap lock:间隙锁,锁定一个范围,不包括记录本身(这个要记)
- Next-key lock:record+gap 临键锁,锁定一个范围,包含记录本身
悲观锁和乐观锁
- 悲观锁:很悲观,查询或者更新数据的时候,总认为有人改动,所以会加锁
- 乐观锁:很乐观,查询数据的时候,默认没人改动,所以不加锁;更新的时候会对数据
锁升级
锁升级:行锁→页面锁→表锁的过程
锁升级的原因
- 一条SQL对同一对象上持有锁的数量超过了阈值
- 内存不过用,锁资源占用内存太多
死锁相关
什么是死锁
多线程因为争抢资源而出现的互相等待的状态
死锁产生的四个必要条件
- 互斥:当资源被一个线程使用(占有)时,别的线程不能使用(如:张三拿到了苹果,李四就不能拿苹果了)
- 不可抢占:资源请求者不能强制从资源占有者手中夺取资源,资源只能由资源占有者主动释放(如:李四不能从张三手里抢苹果,只能等张三主动放弃苹果)
- 请求和保持:当资源请求者在请求其他的资源的同时保持对原有资源的占有(如:张三拿到了苹果,他还可以去那香蕉,并不冲突)
- 循环等待:存在一个等待队列(如张三等李四的香蕉,李四在等张三的苹果,僵住了)
如何避免死锁
打破上诉四个必要条件其中一个(除了互斥),就可以有效避免死锁
-
破坏“请求和保持”条件:所有的进程在开始运行之前,必须一次性的申请其在整个运行过程各种所需要的全部资源
-
破坏“不可抢占”条件:当一个已经持有了资源的进程在提出新的资源请求没有得到满足时,它必须释放已经保持的所有资源,待以后需要使用的时候再重新申请
-
破坏“循环等待”条件:可以通过定义资源类型的线性顺序来预防,可以将每个资源编号,当一个进程占有编号为i 的资源时,那么它下一次申请资源只能申请编号大于i 的资源。
MySQL如何实现高可用(难)
再说