MyISAM是MySQL的默认数据库引擎(5.5版之前)。虽然性能极佳,⽽且提供了⼤量的特性,包括全⽂索引、压缩、空间函数等,但MyISAM不⽀持事务和⾏级锁,⽽且最⼤的缺陷就是崩溃后⽆法安全恢复。不过,5.5版本之后,MySQL引⼊了InnoDB(事务性数据库引擎),MySQL 5.5版本后默认的存储引擎为InnoDB。
⼤多数时候我们使⽤的都是 InnoDB 存储引擎,但是在某些情况下使⽤ MyISAM 也是合适的⽐如读密集的情况下。(如果你不介意 MyISAM 崩溃恢复问题的话)。
两者的对比:
①是否支持行级锁:MyISAM只有表级锁(table-level locking),而InnoDB支持行级锁(row-level locking)和表级锁,默认为行级锁。
②是否支持事务和崩溃后的安全恢复:MyISAM强调的是性能,每次查询具有原子性,其执行速度比InnoDB类型更快,但是不提供事务支持。但是InnoDB提供事务支持,外部键等高级数据库功能。具有事务(commit)、回滚(rollback)和崩溃修复能力(crash recovery capabilities)的事务安全(transaction-safe(ACID compliant))型表。
③是否支持外键:MyISAM不⽀持,⽽InnoDB⽀持。
④是否支持MVCC:仅InnoDB支持。应对高并发事务,MVCC比单纯的加锁更高效。MVCC只在READ COMMITTED 和 REPEATABLE READ两个隔离级别下工作。MVCC可以使用乐观锁(optimistic)和悲观锁(pessimistic)来实现。各数据库中MVCC实现并不统一。
(MVCC:多版本并发控制)
InnoDB存储引擎支持B+树索引、全文索引、哈希索引。
MySQL索引使⽤的数据结构主要有**B+树**索引 和 哈希索引 。对于哈希索引来说,底层的数据结构就是[哈希表](),因此在绝⼤多数需求为单条记录查询的时候,可以选择哈希索引,查询性能最快;其余⼤部分场景,建议选择B+树索引。
**两者区别:**B+树索引查询效率稳定,且支持范围查询。而哈希索引的查询一次只能查询一行数据,且不支持范围查询,但查询速度要优于B+树索引。
**MySQL的****BTree****索引使⽤的是****B****树中的B+Tree,但对于主要的两种存储引擎的实现⽅式是不同的。**
**MyISAM:** B+树叶节点的data域存放的是数据记录的地址。在索引检索的时候,⾸先按照B+树搜索[算法]()搜索索引,如果指定的Key存在,则取出其 data 域的值,然后以 data 域的值为地址读取相应的数据记录。这被称为“⾮聚簇索引”。
**InnoDB:**其数据⽂件本身就是索引⽂件。相⽐MyISAM,索引⽂件和数据⽂件是分离的,其表数据⽂件本身就是按B+Tree组织的⼀个索引结构,树的叶节点data域保存了完整的数据记录。这个索引的key是数据表的主键,因此InnoDB表数据⽂件本身就是主索引。这被称为“聚簇索引(或聚集索引)”。⽽其余的索引都作为辅助索引,辅助索引的data域存储相应记录主键的值⽽不是地址,这也是和MyISAM不同的地⽅。在根据主索引搜索时,直接找到 **key**所在的节点即可取出数据;在根据辅助索引查找时,则需要先取出主键的值,再⾛⼀遍主索引。 因此,在设计表的时候,不建议使⽤过⻓的字段作为主键,也不建议使⽤⾮单调的字段作为主键,这样会造成主索引频繁分裂。
一个单列索引只包含一列,其又具体分为如下三个索引。
CREATE INDEX index_name
ON table_name (column_name)
CREATE UNIQUE INDEX index_name
ON table_name (column_name)
组合索引
一个组合索引包含多列值,其创建的SQL语句如下:
CREATE INDEX index_name
ON table_name (column_name1, column_name2)
组合索引的匹配服从“最左匹配”原则。如果你建立了组合索引(nickname,account,createdTime_Index),那么他实际包含的是3个索引 (nickname)、 (nickname,account) 、(nickname,account,created_time)。
聚簇索引/非聚簇索引:
1、聚簇索引就是按照每张表的主键构造一颗B+树,同时叶子节点中存放的是整张表的行记录数据,也将聚集索引的叶子节点称为数据页。
2、在聚簇索引之上创建的索引称之为辅助索引,辅助索引访问数据总是需要二次查找。辅助索引叶子节点存储的不再是行的物理位置,而是主键值。通过辅助索引首先找到的是主键值,再通过主键值找到数据行的数据页,再通过数据页中的Page Directory找到数据行。
索引失效
1、最好全值匹配;
2、最左前缀法则:如果索引了多列,查询从索引的最左前列开始,且不能跳过索引中的列;
3、不在索引列上做任何操作(计算,函数,类型转换),会导致索引时校而转向全表扫描;
4、存储引擎不能使用索引中范围条件右边的列,即范围之后全失效;
5、尽量使用覆盖索引,只访问索引的查询(索引列和查询列一致),减少selec *;
6、MySQL在使用不等于的时候无法使用索引会导致全表扫描;
7、is null,is not null 也无法使用索引;
8、like 以通配符开头(’%aa‘)索引会失效,变成全表扫描;
9、字符串不加单引号,索引失效;
10、少用 or,用它来连接时候会索引失效
**B树:**
B树是一棵多路平衡查找树,B树的每个节点都存储索引和数据(key和data)
对于一个m阶的B树:
1.每个节点最多有m-1个关键字(可以存有的键值对),根节点最少可以只有1个关键字,非根节点最少有m/2个关键字
关键字范围:**根节点[1,m-1] 非根节点[m/2,m-1]**
2.每个节点中的关键字都按照从小到大的顺序排列
3.所有叶子节点都位于同一层,或者说根节点到每个叶子节点的长度都相同。
4.每个节点都存有索引和数据,也就是对应的key和value
**B+树:**查询的时间复杂度O(log n)
只有叶子节点存储data,叶子节点包含了这棵树的所有数据,所有的叶子节点使用[链表]()相连,便于区间查找和遍历,所有的非叶子节点起到了索引作用。
B+树也是一棵多路平衡查找树
对于一个m阶的B+树:
1.关键字范围:根节点[1,m-1] 非根节点[m/2,m-1]
2.B+树中非叶子节点不存储数据,只存储索引,数据都存储在叶子节点中。
3.对于非叶子节点中key都按照从小到大的顺序排列, 非叶子节点中的每一个key,都会出现在子节点中,是子节点中最大或最小元素。
叶子节点中的记录也按照key从小到大排列。
4.叶子节点依据关键字的大小从小到大顺序链接,形成一个有序[链表]()。
5.每个结点至多有m个子女;除根结点外,每个结点至少有[m/2]个子女,根结点至少有两个子女;有k个子女的结点必有k个关键字。
6.所有叶子节点都位于同一层,或者说根节点到每个叶子节点的长度都相同。
**红黑树:**
![img](https://uploadfiles.nowcoder.com/files/20211209/261242890_1639032488666/1616726301976-aec1bbc2-c420-4280-affd-0be9fea8284a.png)
① **B+树的磁盘读写代价更低**:B+树的内部节点并没有指向关键字具体信息的指针,因此其内部节点相对B树更小,如果把所有同一内部节点的关键字存放在同一盘块中,那么盘块所能容纳的关键字数量也越多,一次性读入内存的需要查找的关键字也就越多,相对IO读写次数就降低了。
②**B+树的查询效率更加稳定:**由于非终结点并不是最终指向文件内容的结点,而只是叶子结点中关键字的索引。所以任何关键字的查找必须走一条从根结点到叶子结点的路。所有关键字查询的路径长度相同,导致每一个数据的查询效率相当。
③**B+树更便于遍历**:由于B+树的数据都存储在叶子结点中,分支结点均为索引,方便扫库,只需要扫一遍叶子结点即可,但是B树因为其分支结点同样存储着数据,我们要找到具体的数据,需要进行一次中序遍历按序来扫,所以B+树更加适合在区间查询的情况,所以通常B+树用于数据库索引。
B+叶子节点依据关键字的大小从小到大顺序链接,形成一个有序[链表]()。
B+树更适合基于范围的查询:B树在提高了IO性能的同时并没有解决元素遍历的我效率低下的问题,正是为了解决这个问题,B+树应用而生。B+树只需要去遍历叶子节点就可以实现整棵树的遍历。而且在数据库中基于范围的查询是非常频繁的,而B树不支持这样的操作或者说效率太低。
![img](https://uploadfiles.nowcoder.com/files/20211209/261242890_1639032488639/1627177372597-9f68dcfb-d7b6-4699-8677-d33be2d857c4.png)
![img](https://uploadfiles.nowcoder.com/files/20211209/261242890_1639032488654/1627177480310-a857252a-9f34-4089-b1fd-a6dea88420e2.png)
![img](https://uploadfiles.nowcoder.com/files/20211209/261242890_1639032488660/1628129249007-cd842f43-9c88-441c-abe1-3d86bbb5283a.png)
数据库事务(transaction)是数据库中的一组操作,这些操作要么全部执行,要么全部不执行,是一个不可分割的工作单位。
**①原子性(Atomicity):**事务是最小的执行单位,不允许分割。事务的原子性确保动作要么全部完成,要么完全不起作用。
实现:mysql采用undo log实现,对没有完成的事务进行回滚。undo log 叫做回滚日志,用于记录数据被修改前的信息。他正好跟前面所说的重做日志所记录的相反,重做日志记录数据被修改后的信息。
**②一致性(Consistency):**事务将数据库从一种状态转变为下一种一致性状态。在事务开始之前和事务结束之后,数据库的完整性约束没有被破坏。
实现:一致性由其余三大属性一起形成。
**③隔离性(Isolation):**并发访问数据库时,一个用户的事务不被其他事务所干扰,各并发事务之间数据库是独立的。
实现: 锁+MVCC多版本并发控制。
**④持久性(Durability):**一个事务被提交之后。它对数据库中数据的改变是永久性的,即使数据库发生故障也不应该对其又任何影响。
实现:mysql提供了一个缓冲池buffer用以加快数据的检索,当从数据库读取数据时,会首先从Buffer Pool中读取,如果Buffer Pool中没有,则从磁盘读取后放入Buffer Pool;当向数据库写入数据时,会首先写入Buffer Pool,Buffer Pool中修改的数据会定期刷新到磁盘中(这一过程称为刷脏)。
①**脏读**(Dirty read):当一个事务正在访问数据并且对数据进行了修改,而这种修改还没有提交到数据库中,这时另外一个事务也访问了这个数据,然后使用了这个数据。因为这个数据是还没有提交的数据,那么另外⼀个事务读到的这个数据是“脏数据”,依据“脏数据”所做的操作可能是不正确的。
②**不可重复读**(Unrepeatableread)**:** 指在⼀个事务内多次读同⼀数据。在这个事务还没有结束时,另⼀个事务也访问该数据。那么,在第⼀个事务中的两次读数据之间,由于第⼆个事务的修改导致第⼀个事务两次读取的数据可能不太⼀样。这就发⽣了在⼀个事务内两次读到的数据是不⼀样的情况,因此称为不可重复读。
**③幻读** (Phantom read):幻读与不可重复读类似。它发⽣在⼀个事务(T1)读取了⼏⾏数据,接着另⼀个并发事务(T2)插⼊了⼀些数据时。在随后的查询中,第⼀个事务(T1)就会发现多了⼀些原本不存在的记录,就好像发⽣了幻觉⼀样,所以称为幻读。
①**READ-UNCOMMITTED(读取未提交)**:最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读。
②**READ-COMMITTED(读取已提交)**:允许读取并发事务已经提交的数据,**可以阻止脏读**,但是幻读或不可重复读仍有可能发生。
实现:已提交读通过对数据的版本进行校验解决了脏读问题,但是已提交读存在不可重复读。
**③REPEATABLE-READ(可重复读):(默认)**对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,**可以阻止脏读和不可重复读**,但幻读仍有可能发生。
实现:可重复读的主要实现方式是采用MVCC+Next-Key-Lock。
④**SERIALIZABLE(可串行化)**:最高的隔离级别,完全服从ACID的隔离级别。所有的事务依此逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。
实现:可序列化的主要实现方式是将所有的查询语句修改成对应的select in share mode,来使得事务串型执行。
![img](https://uploadfiles.nowcoder.com/files/20211209/261242890_1639032488604/1607771450811-145847fe-e098-4419-acf5-db80b02e1b9a.png)
MySQL InnoDB 存储引擎的默认⽀持的隔离级别是 **REPEATABLE-READ**(可重复读)。
当前读:读取的是记录数据的最新版本,并且当前读返回的记录都会加上锁,保证其他事务不会再并发的修改这条记录。
例如,假设要update一条记录,但是另一个事务已经delete这条数据并且commit了,如果不加锁就会产生冲突。
所以update的时候肯定要是当前读,得到最新的信息并且锁定相应的记录。
快照读:读取的是记录数据的可见版本(可能是过期的数据),不用加锁。
快照读:普通的select
当前读:update、insert、delete及加锁的select
快照读是基于 MVCC 和 undo log 来实现的,适用于简单 select 语句。
当前读是基于Next-key Lock(行锁 + 间歇锁)来实现的,适用于 insert,update,delete, select ... for update, select ... lock in share mode 语句,以及加锁了的 select 语句。
对于更新数据不能使用快照读,因为更新数据时如果使用快照读会可能会覆盖其他事务的更改。
**行级锁**
**共享锁(S锁)**:又称读锁,若事务T对数据对象A加上S锁,则事务T可以读A但不能修改A,其他事务只能再对A加S锁,而不能加X锁,直到T释放A上的S锁。这保证了其他事务可以读A,但在T释放A上的S锁之前不能对A做任何修改。
**排它锁(X锁)**:又称写锁。若事务T对数据对象A加上X锁,事务T可以读A也可以修改A,其他事务不能再对A加任何锁,直到T释放A上的锁。这保证了其他事务在T释放A上的锁之前不能再读取和修改A。
MySql InnoDB行锁的加锁机制
1.INNODB表是索引组织的表,主键是聚集索引,非主键索引都包含主键信息。
2.INNODB默认是行锁。
3.INNODB行锁是通过给索引项加锁来实现的,即只有通过索引条件检索数据,InnoDB才使用行级锁,否则将使用表锁。
**表级锁**
意向锁设计目的主要是为了在一个事务中揭示下一行将被请求的锁的类型。
**意向共享锁:(IS锁)**表明事务准备给数据行添加一个共享锁,一个数据行在被添加共享锁之前首先需要获取当前表的意向共享锁。
**意向排它锁:(IX锁)**表明事务准备给数据行添加一个排它锁,一个数据行在被添加排它锁之前首先需要获取当前表的意向排它锁。
意向共享锁和意向排它锁是数据库主动加的,不需要我们手动处理。
行锁
select * from xxx where xxx=xxx LOCK IN SHARE MODE#加共享锁S
select * from xxx where xxx=xxx for update加独占锁X
表锁
LOCK TABLE t READ 对表t加表级别的S锁
LOCK TABLE t WRITE 对表t加表级别的X锁
**行锁、间隙锁、Next-Key Lock**
InnoDB有三种行锁的[算法]():
1.行锁(Record Lock):单个行记录上的锁。
2.间隙锁(Gap Lock):锁定一个范围,防止新增行插入。GAP锁的目的,是为了防止同一事务的两次当前读,出现幻读的情况。
3.临键锁(Next-Key Lock):行锁+间隙锁,锁定一个范围,并且锁定记录本身。对于行的查询,都是采用该方法,主要目的是解决幻读的问题。
**①bin log**
binlog:二进制日志文件。二进制日志文件记录了MySQL所有修改数据库的操作,然后以二进制的形式记录在日志文件中,其中还包括每条语句所执行的时间和所消耗的资源,以及相关的事务信息。
binlog有以下三种模式:
**②redo log**
redo log:重做日志。**redo log用****来实现事务的持久性**,即事务的ACID中的D。它由两部分组成:一是内存中的**重做日志缓冲**(redo log buffer),其中易失的;二是**重做日志文件**(redo log file),它是持久的。
mysql每执行一条DML语句,先将记录写入redo log buffer,后续某个时间点再一次性将多个操作记录写到redo log file。这种先写日志,再写磁盘的技术就是MySQL里经常说到的WAL(Write-Ahead Logging) 技术。
InnoDB是事务的存储引擎,它通过Force Log at Commit机制实现事务的持久性,即当事务提交(COMMIT)时,必须先将该事务的所有日志写入到重做日志文件进行持久化,待事务的COMMIT操作完成才算完成。这里的日志是指重做日志,在InnoDB存储引擎中,由两部分组成,即redo log和undo log。
redo log用来保证事务的持久性,undo log用来帮助事务回滚及MVCC的功能。redo log基本上都是顺序写的,在数据库运行时不需要对redo log的文件进行读取操作。而undo log是需要进行随机读写的。
**③undo log**
**undo log用来帮助事务回滚及MVCC的功能**
原子性底层就是通过undo log实现的。
重做日志记录了事务的行为,可以很好地通过其对页进行“重做”操作。但是事务有时还需要进行回滚操作,这时就需要undo。因此在对数据库进行修改时,InnoDB存储引擎不但会产生redo,还会产生一定量的undo。这样如果用户执行的事务或语句由于某种原因失败了,又或者用户用一条ROLLBACK语句请求回滚,就可以利用这些undo信息将数据回滚到修改之前的样子。
redo存放在重做日志文件中,与redo不同,undo存放在数据库内部的一个特殊段(segment)中,这个段称为undo段(undo segment),undo段位于共享表空间内。
除了“回滚”操作,undo的另一个作用是MVCC,即在InnnoDB存储引擎中MVCC的实现是通过undo来完成。当用户读取一行记录时,若该记录已经被其他事务占用,当前事务可以通过undo读取之前的行版本信息,以此实现非锁定读取。
undo log会产生redo log,也就是undo log的产生会伴随着redo log的产生,这是因为undo log也需要持久性的保护。
1.第一范式(1NF):列不可再分
2.第二范式(2NF)非主属性完全依赖于主键
3.第三范式(3NF)属性不依赖于其它非主属性 属性直接依赖于主键,不是间接。
1.char的长度是不可变的,而varchar的长度可变。
2.定义char长度为10,存入‘qwe’也会占用10的长度并使用空格补齐,而varchar不会。
3.char的存取速度更快,因为其长度固定,方便程序的存储与查找;但是char也为此付出的是空间的代价,因为其长度固 定,所以难免会有多余的空格占位符占据空间,可谓是以空间换取时间效率,而varchar是以空间效 率为首位的。
4.char的存储方式是,对英文字符(ASCII)占用1个字节,对一个汉字占用两个字节;而varchar的存 储方式是,对每个英文字符占用2个字节,汉字也占用2个字节,两者的存储数据都非unicode的字 符数据
MVCC(多版本并发控制),是一种用来解决读-写冲突的无锁并发控制,也就是为事务分配单向增长的时间戳,为每个修改保存一个版本,版本与事务时间戳关联,读操作只读该事务开始前的数据库的快照。 它的实现原理主要是依赖记录中的 隐式字段,undo日志 ,Read View 来实现的。
一、隐式字段:
每行记录除了我们自定义的字段外,还有数据库隐式定义的DB_TRX_ID,DB_ROLL_PTR,DB_ROW_ID等字段
查询一条记录的流程大致如下:
1. 如果被访问版本的trx_id=creator_id,意味着当前事务在访问它自己修改过的记录,所以该版本可以被当前事务访问。
2. 如果被访问版本的trx_id=max_trx_id,表明生成该版本的事务在当前事务生成ReadView后才开启,该版本不可以被当前事务访问。如果数据不可以被访问,则会按照roll_pointer查找上一个版本的记录。
4. 被访问版本的trx_id是否在m_ids列表中。
4.1 是,创建ReadView时,该版本还是活跃的,该版本不可以被访问。顺着版本链找下一个版本的数据,继续执行上面的步骤判断可见性,如果最后一个版本还不可见,意味着记录对当前事务完全不可见
4.2 否,创建ReadView时,生成该版本的事务已经被提交,该版本可以被访问