InnoDB和MyISAM存储引擎的区别
https://edgar615.github.io/mysql-myisam-lock.html
MyISAM只支持表锁,InnoDB支持表锁和行锁(行锁实际上是锁索引)
MyISAM不支持事务,InnoDB支持事务
MyISAM的索引叶子节点存储的行记录的地址,InnoDB辅助索引的叶子节点存储的主键,聚集索引的叶子节点存放的行数据
MyISAM存储引擎的读锁和写锁是互斥的,读写操作是串行的,那么如果读写两个进程同时请求同一张表,Mysql将会使写进程先获得锁。不仅仅如此,即使读请求先到达锁等待队列,写锁后到达,写锁也会先执行。因为mysql认为写请求比读请求更加重要。这也正是MyISAM不适合含有大量更新操作和查询操作应用的原因。因为大量的更新操作会造成查询操作很难获得读锁,从而可能永远阻塞。
在一定的条件下,MyISAM表支持查询和插入并发执行。MyISAM有一个系统变量concurrent_insert
,用来专门控制其并发行为的。
- concurrent_insert设置为0时,不允许并发插入
- 当concurrent_insert设置为1时,如果MyISAM表中没有空洞(即表的中间没有被删除的行),MyISAM允许在一个线程读表的同时,另一个线程从表尾插入记录。这也是MySQL的默认设置
- 当concurrent_insert设置为2时,无论MyISAM表中有没有空洞,都允许在表尾并发插入记录。
为什么MySQL的索引要使用B+树而不是B树?
https://edgar615.github.io/mysql-index-b+tree.html
因为B树不管叶子节点还是非叶子节点,都会保存数据,这样导致在非叶子节点中能保存的指针数量变少,指针少的情况下要保存大量数据,只能增加树的高度,导致IO操作变多,查询性能变低;
对于范围查询,B+树定位最小值和最大值后,中间的叶子节点就是结果集,而不需要回溯查询
为什么用自增列作为主键
https://edgar615.github.io/mysql-index-b+tree.html
如果没有显式定义主键,则InnoDB会选择第一个不包含NULL值的唯一索引作为主键索引。如果也没有这样的唯一索引,则InnoDB会选择内置6字节长的ROWID作为隐含的聚集索引。
每当有一条新的记录插入时,MySQL会根据其主键将其插入适当的节点和位置,为了保证B+树的平衡,会引起节点的分裂,这就导致了空间利用率不高、分裂频率较大。
InnoDb针对分裂做了优化,为每个索引页面维护了一个上次插入的位置,以及上次的插入是递增/递减的标识。根据这些信息,InnoDB能够判断出新插入到页面中的记录,是否仍旧满足递增/递减的约束,若满足约束,则采用优化后的分裂策略
- 不移动原有页面的任何记录,只是将新插入的记录写到新页面之中,分裂的代价小
- 原有页面的利用率,仍旧是100%;
但是这个优化仅对递增有效:如果新的插入,不再满足递增插入的条件,而是插入到原有页面,那么就会导致原有页面再次分裂,增加了分裂的概率
InnoDB 一棵 B+ 树可以存放多少行数据
一页大小是16K,我们假设主键 ID 为 bigint 类型,长度为 8 字节,而指针大小在 InnoDB 源码中设置为 6 字节,这样一共 14 字节。那么非叶子节点的记录数=16384字节/14=1170。
如果叶子节点每行数据占1K,那么高度为2的B+树就可以存储1170*16=18720
行数据,高度为3的B+树可以存储1170*1170*16=21902400
行数据
唯一索引和普通索引怎么选择
https://edgar615.github.io/mysql-index-unique-key.html
在业务能保证唯一性的前提下可以使用普通索引,如果 业务不能保证唯一性就使用唯一索引
对于查询两者性能相差不大:
唯一索引定义了唯一性,查找到第一个满足条件的记录后就会停止继续减少,只需要一次搜索树操作
普通索引,查找到第一个满足条件的记录后,还需要查找下一个记录,直到碰到第一个不满足条件的记录(读取下一个索引值,并比较),但不需要重新执行搜索树操作(局部性原理)。如果这个记录刚好是这个数据页的最后一个记录,要取下一页还需要读取下一个数据页(一页可以存储大量数据,这个事件发生的改了很低)。所以普通索引查找的操作只需要一次指针寻找和一次计算
对于insert、update:
如果数据页不在内存页中,innodb都需要从磁盘读取数据到内存,会触发一次随机读IO的操作。为了提升写操作的性能更,innodb引入了change buffer。如果数据页不在缓冲池,并不会立即将数据页加载到缓冲池,而是先将修改记录保存到 Change Buffer。等到未来Change Buffer数据对应的数据页被读取到缓冲区时合并到真正的数据页中。(如果数据页已经在缓冲池中了,直接修改缓冲页中的数据)
change buffer会缓存对二级索引的数据操作(update、insert、delete)。主要是减少磁盘的随机I/O.
唯一索引在进行插入或修改操作时,InnoDB必须进行唯一性检查。如果不读取索引页到缓冲池中,无法校验索引是否唯一(如果将写操作记录在change buffer当中,则无法检测到多次插入操作之间是否有冲突)
- 如果目标页在缓冲池中,只需要找到对应的位置,判断没有冲突,插入新值
- 如果目标页不在缓冲池中,需要将数据页读取到缓冲中,判断没有冲突,插入新值
因为将数据从磁盘读取到缓冲中涉及随机IO的访问,是数据库里面成本最高的操作之一。change buffer因为减少了随机IO,对更新性能会有显著提示。所以对于写多读少的情况,建议尽量选择普通索引。
InnoDB有没有哈希索引
https://edgar615.github.io/mysql-index-adaptive-hash-index.html
Innodb存储引擎会监控对表上二级索引的查找,如果发现某二级索引被频繁访问,二级索引成为热数据,如果认为建立哈希索引可以提高查询效率,则自动在内存中的 自适应哈希索引缓冲区
建立哈希索引。
InnoDB会丢数据吗
InnoDB 在写事务日志的时候,为了提高性能,先将信息写入 Log Buffer 中。当满足条件时才写入到日志文件
innodb_flush_log_at_trx_commit
控制如何将缓冲区的内容写入到日志文件。innodb_flush_log_at_timeout
控制缓存写到redo log文件的频率。
innodb_flush_log_at_trx_commit
:
- 0:在事务提交时,innodb 不会立即触发将缓存日志写到磁盘文件的操作,而是每秒触发一次缓存日志回写磁盘操作,并调用系统函数 fsync 刷新 IO 缓存。这种方式效率最高,也最不安全。在这种策略下,如果数据库崩溃,有一秒的数据丢失。
- 1:在每个事务提交时,innodb 立即将缓存中的 redo 日志回写到日志文件,并调用 fsync 刷新 IO 缓存。
- 2:在每个事务提交时,innodb 立即将缓存中的 redo 日志回写到日志文件,但并不马上调用 fsync 来刷新 IO 缓存,而是每秒只做一次磁盘IO 缓存刷新操作。只要操作系统不发生崩溃,数据就不会丢失,如果操作系统崩溃,最多有一秒的数据丢失,这种方式是对性能和数据安全的折中,其性能和数据安全性介于其他两种方式之间。
高并发的业务,一般设置innodb_flush_log_at_trx_commit=2,原因如下:
- 配置为2和0,性能差异并不大,因为将数据从log buffer拷贝到os cache虽然跨越了用户态和内核态,但仍然只是内存的数据拷贝,速度很快
- 配置为2和0,安全差异很大,操作系统崩溃的概率相比MySQL崩溃的概率要小得多,设置为2,只要操作系统不崩溃,就不会丢失数据
InnoDB 支持的四种事务隔离级别名称
- Serializable (串行化):可避免脏读、不可重复读、幻读的发生。
- Repeatable read (可重复读):可避免脏读、不可重复读的发生。
- Read committed (读已提交):可避免脏读的发生。
- Read uncommitted (读未提交):最低级别,任何情况都无法保证。
事务隔离级别通过MVCC(多版本并发控制协议)实现,对于insert、update、delete操作都需要写入undo log,undo log是一个链表,
RR在第一次查询时生成事务快照,RC在每次查询时都生成事务快照,在访问某条记录时,只需要按照下边的步骤判断记录的某个版本是否可见:
- 如果被访问版本的
trx_id
属性值小于m_ids
列表中最小的事务id,表明生成该版本的事务在生成ReadView
前已经提交,所以该版本可以被当前事务访问。 - 如果被访问版本的
trx_id
属性值大于m_ids
列表中最大的事务id,表明生成该版本的事务在生成ReadView
后才生成,所以该版本不可以被当前事务访问。 - 如果被访问版本的
trx_id
属性值在m_ids
列表中最大的事务id和最小事务id之间,那就需要判断一下trx_id
属性值是不是在m_ids
列表中,如果在,说明创建ReadView
时生成该版本的事务还是活跃的,该版本不可以被访问;如果不在,说明创建ReadView
时生成该版本的事务已经被提交,该版本可以被访问。