select * from A where id in(select id from B)
in 和 exists的查询方式
结论:
not in 和 not exists:
not in 为了证明数据确实不存在,需要把表全部查完,没有用到索引
not exists 的子查询依然能用表上的索引,所有无论哪个表大,not exists 都比not in 要快
例如:
系及系主任,明显还可以拆分,不是一个原子数据。第一范式需要把每一列拆成一个原子数据
满足第一范式后,仍存在的问题:
姓名、系主任、系名可以依赖于的学号以消除数据冗余
课程名称、分数也可以依赖于学号
第二范式,在满足第一范式的基础上,所有非主属性都得依赖于主属性
课程名称、分数
可以发现经过第二范式拆分,数据冗余没有了,但是上述2、3问题仍然存在
第三范式,在满足第二范式的基础上,非主属性之间不能有依赖关系
很明显系主任依赖于系名
MySQL数据库将默认引擎从MyISAM变成了InnoDB
索引的区别:
InnoDB:
InnoDB的主键索引是聚簇索引,其主键索引的叶子节点存储着行数据,因此效率非常高。
对于辅助索引(非主键索引),InnoDB采用的是在叶子节点中保存主键值,通过主键值再回表查询到一条完整记录,实际进行了二次查询,效率没有主键索引高。
MyISAM索引是非聚簇索引,其索引文件和数据文件是分离的,索引文件仅保存记录再所在页的指针(物理地址),需要在寻址依次才能找到数据,因此查询较慢
MySQL在查询数据时,对于InnoDB存储引擎而言,会先将磁盘上的数据以页为单位加载进内存,以缓存页的形式存放在Buffer Pool中。
Buffer Pool是InnoDB的一块内存缓冲区,在MySQL启动时,会按照配置的缓存页的大小,将Buffer Pool缓存区初始化为许多个缓存页,默认情况下,缓存页大小为16KB。
刚开始Buffer Pool 中这些缓存页都是处于空闲状态。随着运行时间越来越长,Buffer Pool 满了。就得淘汰掉之前的缓存页。
LRU是Least Recently Used的简写, 用来淘汰最久未使用的数据。
它通过维护一个链表,每当访问了某个数据时,就将这个数据加入到链表头部,如果数据本身存在于链表中,九江数据冲链表中间移动到链表头部,这样链表尾部的数据就一定是最久未被使用的数据了,在缓存不足时将其淘汰。
InnoDB存储引擎层内部维护了一个链表,链表中的元素就算指向缓存页的指针。
全表扫描
当出现全表扫描的时候,InnoDB会将该表中的数据页全部从磁盘文件加载进缓存中。如果刚好这个表很大且是一个不常用的表,那可能把常用的缓存页给淘汰了。最终导致,Buffer Pool缓存的命中率明显下降,SQL性能也明显下降,由于之前的缓存页要去磁盘读,磁盘IO性能也下降。
所有,简单的LRU算法,碰到全表扫描时,就会存在性能下降问题,这在高并发场景下可能成为性能瓶颈
InnoDB 预读
预读是InnoDB引擎的一个优化机制,当你从磁盘上读取某个数据也,InnoDB可能会将与这个数据页相邻的其他数据页页读取到Buffer Pool中。
(从磁盘读取数据是随机IO,性能差,InnoDB猜测你可能需要下一页的数据,就会连着读取多个数据页,这是顺序读取,性能高)
在这两种情况下会触发预读机制:
MySQL的优化思路是,对数据进行冷热分离,将LRU链表分成两个部分,一部分用来存冷数据,也就是刚从磁盘读进来的数据,另一部分用来存热点数据,也就是经常被访问到的数据。冷数据默认占LRU链表的37%
优化过的LRU链表工作流程:
从磁盘读取数据页后先放在冷数据区的头部,如果这些缓存页在1秒之后被访问,就将缓存页移动到热数据区的头部,如果一秒之后没访问,就不动它。1秒这个数值,由参数innodb_old_blocks_time控制。
由预读和全表扫描带来的缓存页1秒后没人访问,它们就会一直呆在冷数据区,再需要淘汰的时候,首先淘汰它们
MySQL还做了优化,就是如果缓存页处在热数据区的前 1/4区域,就不用把它移动到头部了,因为移动链表需要加锁,会存在锁竞争
即 读读共享, 读写互斥,写写互斥。
对于 UPDATE、 DELETE 和 INSERT 语句, InnoDB 会自动给涉及数据集加排他锁(X)
于普通 SELECT 语句, InnoDB 不会加任何锁。 事务可以通过以下语句显示的加锁
共享锁(S) : SELECT * FROM table name WHERE … LOCKIN SHARE MODE。
排他锁(X) : SELECT * FROM table name WHERE … FOR UPDATE。
事务尝试给整张表加排他锁的时候,得保证表中没有行锁或表锁,如果有任意一种锁,就得阻塞,怎么判断有没有行锁呢?得遍历表中的每一行,性能很差,所有每次加行锁的时候都会自动给表加上意向锁,之后如果有锁表的业务,只需要判断表中有没有意向锁。意向锁分为以下两种
InnoDB 行锁是通过给索引上的索引项加锁来实现的, 如果没有索引, InnoDB 将通过隐藏的聚簇索引来对记录加锁。
InnoDB行锁分为3种情形:
记录锁: 对索引项加锁
间隙锁:对索引项之间的间隙, 第一条记录前的“间隙”和最后一条记录后的“间隙”加锁。
Next-key lock:前两种的组合,对记录及其前面的间隙加锁。主要目的是解决幻读的问题。
【当我们用范围条件而不是相等条件检索数据,并请求共享或排他锁的时候,InnoDB会给符合条件的已有数据记录加记录锁。
对于键值在条件范围内,但是并不存在的记录,叫做间隙,InnoDB也会对间隙加锁。】
InnoDB这种行锁实现特点意味着,如果不通过索引条件检索数据,InnoDB将对表中所有记录加锁,实际效果和表锁一样
MyISAM
为了提高复杂sql语句的复用性和表操作的安全性,MySQL提供了视图。
视图:存储的查询语句,当调用的时候,产生结果集,视图充当的是虚拟表的角色.,本身并不包含任何数据,它只包含映射到基表的一个查询语句
视图本质上就是一条SELECT语句,所以当访问视图时,只能访问到所对应的SELECT语句中涉及到的列,对基表中的其它列起到安全和保密的作用,可以限制数据访问。
存储过程就是一个预编译好的SQL代码块。执行SQL的时候会先编译后执行,而存储过程是编译过的,所以执行效率比sql语句高。
通过存储过程能够使没有权限的用户在控制之下间接的存取数据库,从而确保数据的安全。
触发器是用户定义在关系表上的一类由时间驱动的特殊的存储过程。
触发器是指一段代码,当触发某个事件的时候,自动执行这些代码。
内连接: inner
左外连接:left【outer】
右外连接:right【outer】
全外连接: full【outer】
交叉连接:cross 笛卡尔积
事务是一个不可分割的数据库操作序列,也是数据库并发控制的基本单位,其执行结果必须使数据库从一种一致性状态变到另一种一致性状态。
原子性:一个事务是不可再分割的整体,要么都执行,要么都不执行
一致性:一个事务执行前后都必须处于一致性状态
隔离性:一个事务不受其他事务的干扰,多个事务互相隔离
持久性:一个事务一旦提交了,则永久的持久化到本地。
指的是一个事务读取到其他事务没有提交的数据。一个事务正在修改数据,还没提交,另一个事务也访问了数据库,读取到了没有提交的数据
解决方式:读的时候加共享锁、读完就立即释放锁(不用等事务提交),更新修改的时候加排他锁,事务提交完才释放锁
指的是一个事务对同一行记录的两次读取结果不同。(两次读取之间,别人修改了数据,读完了还没提交就释放了锁,可能会出现不可重复读)
解决方式:通过读取时加共享锁,更新时加排它锁,都是在事务提交后释放锁
指的是一个事务对同一范围的两次查询结果不同。 例如:一个事务对表全部数据行进行修改,同时第二个事务向表中插入一行新数据。就会出现操作第一个事务的用户发现表中还有没有修改的数据,就像发生了幻觉。需要通过范围锁来解决该问题
隔离级别对应可能出现的问题,级别增加问题变少,但同时效率逐渐下降。
隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
READ-UNCOMMITTED(读未提交) | √ | √ | √ |
READ-COMMITTED(读已提交) | × | √ | √ |
REPEATABLE-READ(可重复读) | × | × | √ |
SERIALIZABLE(可串行化) | × | × | × |
读未提交: 最低隔离级别,并发性能最高,不加锁,允许读取未提交的数据变更
读已提交:允许读取并发事务已经提交的数据,可以阻止脏读,但是仍有幻读和不可重复读发生的可能。
底层使用MVCC总是读取最新一份的快照数据。Oracle的默认隔离级别
可重复读:同一事务,对于同一字段的多次读取结果一致,除非是事务自己修改的。
InnoDB依靠范围锁在该级别下也可以避免幻读 。底层使用MVCC总是读取事务开始时的快照数据。 Mysql默认采用此隔离级别。
可串行化:最高的隔离级别。所有事务依次逐个执行,事务之间完全不可能产生干扰。
MVCC即多版本并发控制,MVCC 在 MySQL InnoDB 中实现主要是为了提高数据库并发性能,做到读可不加锁,读写不冲突。并发性能很高。
MVCC 中默认的读是非锁定的一致性读, 也称快照读。读取的是记录的可见版本, 当读取的的记录正在被别的事务并发修改时, 会读取记录的历史版本。
当前读和快照读:
当前读:共享锁和排他锁这些操作都是当前读,读取的是记录的最新版本,读取时要保证其他并发事务不能修改当前记录,会对读取的记录加锁
快照读:不加锁的select操作就是快照读,串行级别下快照读会退化成当前读。快照读读到的不一定是数据的最新版本,可能是历史版本。
隐式字段
每行记录除了我们自定义的字段外,还要数据库隐式定义的DB_TRX_ID,DB_ROLL_PRY,DB_ROW_ID
DB_TRX_ID : 6byte,最近修改事务ID,记录创建/最后依次修改 该记录的事务ID
DB_ROLL_PRY: 7byte,回滚指针,指向这条记录的上一个版本 (存储于rollback segment里)
DB_ROW_ID: 6byte, 隐含的自增ID,如果没有主键,InnoDB会自动以DB_ROW_ID产生一个簇族索引
实际还有一个删除flag隐藏字段
undo日志
insert undo log: 代表事务在insert 新纪录时产生的undo log, 只在事务回滚时需要,并且事务提交后可以立即被丢弃。
update undo log: 事务进行修改删除时产生的undo log, 不仅在事务回滚的时候需要,在快照读的时候也需要,所以不能随便删除,只要在快速读或事务回滚不涉及该日志时,对应日志才会被purge线程统一清除。
MVCC更新过程
开始是这样,假设 隐含ID = 1, 事务ID和回滚ID为空
当事务1更改该行的值时:
①加排他锁
②记录redo log
③ 记录undo log(把修改前的值copy的undo log, 即下图下行)
④修改当前行的值,填写事务编号,使回滚指针指向undo log中修改前的行
当再来一个事务2来更改改行的值时, 与事务1相同,但此时undo log 中有两行记录,并通过回归指针连在一起。 但InnoDB中存在 purge线程,它会查询那些比现在最老的活动事务还早的undo log 并删除它们
Read View 读视图
Read View 就是事务进行快照读操作的时候生产的读视图,主要用来做可见性判断,在该事务执行的快照读的那一刻,会生成数据库系统当前的一个快照,记录并维护系统当前活跃事务的ID。
索引是对数据库表中一列或多列的数据进行排序的一种数据结构, 通过它可以加速数据的检索。 索引的作用相当于图书的目录, 可以根据目录中的页码快速找到所需的内容。
索引的数据结构和具体存储引擎的实现有关。常用的InnoDB和MyISAM的默认索引是B+树。对于Memony,底层数据结构是哈希表。
同时, 在MySQL中存在普通索引、唯一索引、主键索引、组合索引、全文索引。平时用的较多的是主键索引和组合索引。在索引使用的过程总,会出现一系列的回表、覆盖索引、最左匹配、索引下推等问题。
索引可以达达加快数据的检索速度,但是创建索引和维护索引要耗费时间,并且对表中数据增删改的时候,索引也要动态维护,会降低增删改的执行效率。索引还需要占用额外的空间。
存储需要的一般是K-V格式存储。可以用哈希表,也可以用二叉树、红黑树、B树、B+树。二进制树的整棵树会比较高,树高越高,访问数据就会进行更多的磁盘IO。 而B树、B+树可以在一个节点种存更多的元素,让整棵树变低,减少IO次数,提高访问效率。B+树是B树的变种,有着更好的查询效率,所以选用B+树。
普通索引: 没有什么限制,仅加速查询
唯一索引:加速查询 + 列值唯一 (可以有null)
主键索引:加速查询+列值唯一(没有null)
组合索引:多列值组成一个索引,用于组合搜索,效率大于索引合并
全文索引:对文本的内容进行分词,进行搜索
InnoDB聚集索引的叶子节点存储了整个行数据,非主键索引叶子节点存的是主键索引的索引值,需要回表查询才能查到整行数据。
索引覆盖是指通过查询条件获取到需要返回字段的过程不需要进行回表查询,(查询条件的字段是索引,返回的字段是主键或联合索引)
最左匹配原则就是指在联合索引中,如果你的 SQL 语句中用到了联合索引中的最左边的索引,那么这条 SQL 语句就可以利用这个联合索引去进行匹配
(有联合索引, 然后查联合索引最左边的索引字段的时候可以索引查)
没有索引下推之前,使用非主键索引进行查询的时候,存储引擎通过索引检索到数据,返回给MySQL服务器,然后服务器判断数据是否符合条件。
有了ICP,MySQL服务器将判断条件传递给存储引擎,然后存储引擎只返回符合条件的数据给MySQL服务器,减少存储引擎查询基础表的次数,减少MySQL服务器从存储引擎接收数据的次数。
hash索引顺序与元数据顺序无法保持一致,等值查询块,不支持范围查询,而B+树天然支持范围
Hash索引任何时候都避免不了回表查询,B+树使用聚簇索引、覆盖索引时,只通过索引就能完成查询
Hash索引某个键值存在大量重复会导致hash碰撞,效率很差,B+树查询效率稳定,且树高较低,减少磁盘IO
在创建多列索引时,要根据业务需求,where子句中使用最频繁的一列放在最左边。
当创建(a,b,c)复合索引时,想要索引生效的话,只能使用 a和a,b和a,b,c三种组合
https://mp.weixin.qq.com/s/ynGUGSbTdXbvEneFDVK88A
MySQL中有六种日志,重做日志、回滚日志、二进制日志、错误日志、慢查询日志、一般查询日志
存储引擎层生成的日志,为了保证数据可靠性。
根据事务的ACID特性,为了持久化,最简单的做法是每次事务提交的时候把涉及修改的数据页全部刷新到磁盘中。但会有严重的效率问题:
redo log 记录的是 “物理级别”上的页修改操作,比如页号xxx、偏移量xxx写入xxx数据。当MySQL写入数据时,先写redo log 然后redo log 根据某种方式持久化到磁盘,变成redo log file,用户数据则在buffer中,避免频繁IO操作。
回滚日志,注意为了保证数据的原子性,保存了事务发生之前的一个版本,可以用于回滚,INSERT语句对应一条DELETE的undo log, UPDATE对应一个相反的UPDATE 的 undo log ,这样发生错误时就能回滚到事务之前的数据状态。同时undo log 也是MVCC(多版本并发控制)实现的关键
记录了所有的数据DDL(数据定义语言)语句和DML(数据操纵语言)语句,不包括数据查询语句。语句以事件的形式保存,描述了数据的更改过程。用于灾难时的数据恢复,主从复制。
记录了当mysqld启动和停止时,以及服务器在运行过程中发生任何严重错误时的相关信息。
是MySQL中最重要的日志之一,放数据库出现任何故障导致无法使用时,可以首先查看此日志
记录客户端所有语句,而二进制日志不包含查询数据的语句
记录了所有执行时间超过参数设置值,并且扫描记录数不小于设置值的所有SQL语句的日志,默认情况下管理语句和不使用索引进行查询的语句并不会记录到慢查询日志中。
redo logo: 在MySQL中数据是先缓存在缓冲池,然后再以某种方式刷新到磁盘,如果宕机导致缓冲池中数据丢失,则读取磁盘上的redo log file 进行数据恢复。
由于文件系统对一次大数据页大多数情况下不是原子操作,如果服务器宕机了,可能值做了部分写入,如果突然断电,就是部分写问题。由于redo log 记录的是对页的物理修改,这个页已经发生损坏了,所以没法重做。
为了解决这个问题, InnoDB实现了double write buffer,简单来说就是写数据页之前,先把这个数据页写到一块独立的物理文件位置,然后再写到磁盘。这样如果数据页出现损坏,可以通过该页的副本还原该页,然后再redo log 重做。