NOSQL和SQL的区别
关系型数据库:以行列二维表格的形式存在,每一列代表数据的一种属性,每一行代表一个数据实体。
NOSQL是非关系型数据库。数据库逻辑上提供了不同于二维表的存储方式,存储方式可以是JSON文档、哈希表或者其他方式。
- 对于需要保证ACID的应用,优先考虑SQL,反之优先考虑NoSQL。
- NoSQL数据之间无关系,非常容易扩展。
数据库的三大范式
- 第一范式(1NF):要求数据库的每一行都是不可分割的原子数据项。
- 第二范式(2NF):在1NF的基础上,非码属性必须完全依赖于候选码(在1NF基础上消除非主属性对主码的部分函数依赖);第二范式需要确保数据库表中的每一行都和主键相关,而不是只与主键的某个部分相关(主要针对联合主键而言)。
- 第三范式(3NF):在2NF基础上,任何非主属性不依赖于其它非主属性(在2NF基础上消除传递依赖);第三范式需要确保数据表格中的每一列数据都和主键直接相关,而不是间接相关。
MySQL怎么联表查询?
- 内连接(INNER JOIN):返回两个表中有匹配关系的行。
- 左外连接(LEFT JOIN):返回左表中的所有行,即使在右表中没有匹配的行,未匹配的行会包含NULL。
- 右外连接(RIGHT JOIN):返回右表的所有行。
- 全外连接(FULL JOIN):返回所有表中的行。MySQL不直接支持FULL JOIN,需要使用UNION来实现。
MySQL如何避免重复插入数据?
- 使用UNIQUE约束:在表中的相关列上添加UNIQUE约束,确保每个值在该列中唯一。
- 使用INSERT…ON DUPLICATE KEY UPDATE:如果插入的记录与现有记录冲突,可以选择更新现有记录。
- 使用INSERT IGNORE:该语句会在插入记录时忽略那些因重复键而导致的插入错误。
如果需要保证全局唯一性,使用UNIQUE约束是最佳做法。
如果需要插入和更新结合可以使用ON DUPLICATE KEY UPDATE。
对于快速忽略重复插入,INSERT IGNORE是合适的选择。
CHAR 和 VARCHAR 有什么区别?
- CHAR是固定长度的字符串类型,定义时需要指定固定长度,存储时会在末尾补足空格。
- VARCHAR是可变长度的字符串类型,定义时需要指定最长长度,实际存储时根据实际长度占用存储空间。
int(1) int(10) 在mysql中有什么区别?
区别主要在于显示宽度,而不是存储范围或数据类型本身的大小。
外键约束
外键约束的作用是维护表与表之间的关系,确保数据的完整性和一致性。
SQL查询语句的执行顺序是怎么样的?
所有的查询语句都是从FROM开始执行的,在执行过程中,每个步骤都会生成一个虚拟表,这个虚拟表将作为下一个执行步骤的输入,最后一个步骤产生的虚拟表即为输出结果。
(9) SELECT
(10) DISTINCT <column>,
(6) AGG_FUNC(<column> or <expression>, ...
(1) FROM <left_table>
(3) <join_type>JOIN<right_table>
(2) ON<join_condition>
(4) WHERE <where_condition>
(5) GROUP BY <group_by_list>
(7) WITH <CUBE|ROLLUP>
(8) HAVING <having_condition>
(11) ORDER BY <order_by_list>
(12) LIMIT <limit_number>;
执行一条SQL请求的过程是什么?
- 连接器:建立连接,管理连接、检验用户身份。
- 查询缓存:查询语句如果命中查询缓存则直接返回,否则继续往下执行。MySQL8.0已删除该模块。
- 解析SQL:通过解析器对SQL查询语句进行词法分析、语法分析,然后构建语法树,方便后续模块读取表名、字段、语句类型。
- 执行SQL:执行SQL共有三个阶段:
- 预处理阶段:检查表或字段是否存在;将 select * 中的 * 符号扩展为表上的所有列。
- 优化阶段:基于查询成本的考虑,选择查询成本最小的执行计划。
- 执行阶段:根据执行计划执行SQL查询语句,从存储引擎读取记录,返回给客户端。
mysql的引擎
存储引擎 | 特性 | 适用场景 | 不支持特性 |
---|---|---|---|
InnoDB | MySQL默认存储引擎,具备ACID事务支持、行级锁、外键约束 | 高并发的读写操作,对数据完整性和并发控制要求高的场景 | 无(相对MyISAM、Memory而言,它支持事务等关键特性 ) |
MyISAM | 存储空间和内存消耗较低 | 大量读操作的场景 | 事务、行级锁、外键约束 |
Memory | 数据存储在内存中,读操作性能高 | 对性能要求较高的读操作场景 | 事务、行级锁、外键约束,服务器重启或崩溃时数据丢失 |
MySQL为什么InnoDB是默认引擎?
- 事务支持:InnoDB引擎提供了对事务的支持,可以进行ACID(原子性、一致性、隔离性、持久性)属性的操作。
- 并发性能:InnoDB引擎采用了行级锁定的机制,可以提供更好的并发性能。
- 崩溃恢复:InnoDB引擎通过redolog日志实现了崩溃恢复,可以在数据库发生异常情况时,通过日志文件进行恢复,保证数据的持久性和一致性。
mysql中的innodb和MyISAM的区别
对比维度 | InnoDB | MyISAM |
---|---|---|
事务支持 | 支持 | 不支持 |
索引结构 | 聚簇索引,文件存于主键索引叶子节点,必须有主键,辅助索引需两次查询 | 非聚簇索引,数据文件分离,索引存数据文件指针,主、辅索引独立 |
锁粒度 | 行锁 | 表锁,更新语句锁整张表,并发访问受限 |
count(*) 效率 | 不保存表行数,执行需全表扫描 | 用变量保存表行数,执行语句直接读变量,速度快 |
索引有什么好处?
索引类似书籍的目录,可以减少扫描的数据量,提高查询效率。
- 如果查询的时候,没有用到索引就会全表扫描,这个时候查询的事件复杂度是On。
- 如果用到了索引,那么查询的时候,可以基于二分查找算法,通过索引快速定位到目标数据,mysql索引的数据结构一般是b+树,其搜索复杂度为O(logdN),其中d表示节点允许的最大节点个数为d个。
索引的分类
分类维度 | 具体分类 |
---|---|
按数据结构 | B+tree索引、Hash索引、Full - text索引 |
按物理存储 | 聚簇索引(主键索引)、二级索引(辅助索引) |
按字段特性 | 主键索引、唯一索引、普通索引、前缀索引 |
按字段个数 | 单列索引、联合索引 |
MySQL聚簇和非聚簇索引的区别
对比维度 | 聚簇索引 | 非聚簇索引 |
---|---|---|
数据存储 | 数据行按索引键值顺序存储,叶子节点包含实际数据行,索引结构是数据物理存储结构 | 叶子节点不包含完整数据行,包含指向数据行的指针或主键值,数据行存储在聚簇索引中 |
索引与数据关系 | 通过聚簇索引查找数据,可直接从索引中获得数据行 | 通过非聚簇索引查找数据时,先在非聚簇索引中找到对应主键值,再通过主键值回溯到聚簇索引查找实际数据行(回表过程 ) |
唯一性 | 基于主键构建,每个表只能有一个聚簇索引(数据只有一种物理排序方式 ) | 一个表可以有多个非聚簇索引(不直接影响数据物理存储位置 ) |
效率 | 对于范围查询和排序查询更有效率,避免额外寻址开销 | 使用覆盖索引查询时效率更高(无需读取完整数据行 );需回表时效率较低(有额外回表操作 ) |
MySQL主键是聚簇索引吗?
InnoDB 在创建聚簇索引的时候,会根据不同的场景选择不同的列作为索引:
- 如果有主键,默认会使用主键作为聚簇索引键。
- 如果没有主键,选择第一个不包含NULL值的唯一列作为聚簇索引的索引键。
- 在上面两个都没有的情况下,InnoDB将自动生成一个隐式自增的id列作为聚簇索引键。
什么字段适合当做主键?
要点 | 内容 |
---|---|
字段特性 | 字段具有唯一性,且不能为空 |
字段趋势建议 | 最好有递增趋势,值随机无序可能引发页分裂,影响性能 |
主键选用禁忌 | 不建议用业务数据(如会员卡号、订单号等 ),因业务可能导致字段重复或重用 |
主键常规方案及局限 | 常选用自增字段,单机适用;多服务器录入数据需合并时,可能主键重复,需考虑分布式 id 方案 |
B+树的特点
- 所有叶子节点都在同一层:这是B+树的一个重要特性,确保了所有数据项的检索都具有相同的I/O延迟,提高了搜索效率。每个叶子节点都包含指向相邻叶子节点的指针,形成一个链表,由于叶子节点之间的链接,B+树非常适合进行范围查询和排序扫描。可以沿着叶子节点的链表顺序访问数据,而无需进行多次随机访问。
- 非叶子节点存储键值:非叶子节点仅存储键值和指向子节点的指针,不包含数据记录。这些键值用于指导搜索路径,帮助快速定位到正确的叶子节点。并且,由于非叶子节点只存放键值,当数据量比较大时,相对于B树,B+树的层高更少,查找效率也就更高。
- 叶子节点存储数据记录:与B树不同,B+树的叶子节点存储实际的数据记录或指向数据记录的指针。这意味着每次搜索都会到达叶子节点,才能找到所需数据。
- 自平衡:B+树在插入、删除和更新操作后会自动重新平衡,确保树的高度保持相对稳定,从而保持良好的搜索性能。每个节点最多可以有M个子节点,最少可以有ceil(M/2)个子节点(除了根节点),这里的M是树的阶数。
B+树和B树的区别
对比维度 | B+树 | B树 |
---|---|---|
数据存储位置 | 数据仅存储在叶子节点,非叶子节点只存索引信息 | 非叶子节点既存索引信息,也存部分数据 |
叶子节点连接方式 | 叶子节点用链表相连 | 叶子节点无链表连接 |
查找性能特点 | 查找需到叶子节点,性能稳定 | 可能在非叶子节点找到数据,性能相对不稳定 |
B+树的好处
- B+树的非叶子节点不存放实际的记录数据,仅存索引,因此数据量相同的情况下,相比存储即存索引又存记录的B树,B+树的非叶子节点可以存放更多的索引,因此B+树可以比B树更「矮胖」,查询底层节点的磁盘I/O次数会更少。
- B+树有大量的冗余节点(所有非叶子节点都是冗余索引),这些冗余索引让B+树在插入、删除的效率都更高,比如删除根节点的时候,不会像B树那样会发生复杂的树的变化;
- B+树叶子节点之间用链表连接了起来,有利于范围查询,而B树要实现范围查询,因此只能通过树的遍历来完成范围查询,这会涉及多个节点的磁盘I/O操作,范围查询效率不如B+树。
联合索引的实现原理
联合索引:将多个字段组合成一个索引。
- 联合索引查询的B+Tree是先按product_no进行排序,然后再按name排序。
- 使用联合索引的时候,存在最左匹配原则,也就是按照最左优先的方式进行索引的匹配。
创建联合索引时需要注意什么?
建立联合索引时,要把区分度大的字段排在前面,这样区分度大的字段越有可能被更多的SQL使用到。
索引失效有哪些?
- 当使用左或者左右模糊匹配的时候(like %xx或者like %xx%)。
- 查询条件中对索引使用函数。
- 查询条件中对索引进行计算。
- 在字符串和数字进行比较的时候,会进行隐式类型转换,会用到函数,索引失效。
- 联合索引不遵循最左匹配原则,索引会失效。
- 在WHERE子句中,如果在OR前的条件列是索引列,而在OR后的条件列不是索引列,那么索引会失效。
什么情况下会回表查询?
如果查询的数据不在二级索引里,就会先检索二级索引,找到对应的叶子节点,获取到主键值后,然后再检索主键索引,就能查询到数据了,这个过程就是回表查询。
什么是覆盖索引
覆盖索引是指一个索引包含了查询所需的所有列,因此不需要访问表中的数据行就能完成查询(不需要回表)。
如果一个列即是单列索引又是联合索引,单独查它的话走哪个?
mysql优化器会分析每个索引的查询成本,然后选择成本最低的方案来执行sql。
索引已经建好了,再插入一条数据,索引会有哪些变化?
插入新数据可能导致B+数结构的调整和索引信息的更新,以保持B+树的平衡性和正确性,这些变化通常由数据库系统自动处理,确保数据的一致性和索引的有效性。
如果插入的数据导致叶子节点已满,可能会触发叶子节点的分裂操作,以保持B+树的平衡性。
索引字段是不是建的越多越好?
不是,建的越多会占用越多的空间,而且再写入频繁的场景下,对于B+树的维护所付出的性能消耗也会越大。
索引的优缺点?
分类 | 索引相关要点 | 具体内容 |
---|---|---|
好处 | 索引的好处 | 提高查询速度 |
缺点 | 索引的缺点1 | 需要占用物理空间,数量越大,占用空间越大 |
缺点 | 索引的缺点2 | 创建索引和维护索引要耗费时间,这种时间随着数据量的增加而增大 |
缺点 | 索引的缺点3 | 会降低表的增删改的效率,因为每次增删改索引,B + 树为了维护索引有序性,都需要进行动态维护 |
使用原则 | 索引的使用原则 | 不是万能钥匙,根据场景来使用 |
怎么决定建立索引?
分类 | 具体情况 | 说明 |
---|---|---|
适用索引场景 | 字段有唯一性限制 | 如商品编码 |
适用索引场景 | 常用于 WHERE 查询条件的字段 | 可提高查询速度,多字段条件可建联合索引 |
适用索引场景 | 常用于 GROUP BY 和 ORDER BY 的字段 | 查询时无需再排序,因 B + Tree 中记录已排好序 |
不适用索引场景 | WHERE、GROUP BY、ORDER BY 用不到的字段 | 索引价值是快速定位,起不到定位则无需创建,且索引会占物理空间 |
不适用索引场景 | 字段存在大量重复数据 | 如性别字段,记录分布均匀时,查询优化器可能忽略索引进行全表扫描 |
不适用索引场景 | 表数据太少 | 无需创建索引 |
不适用索引场景 | 经常更新的字段 | 如电商项目用户余额,索引字段频繁修改,维护成本高 |
索引优化
优化/注意事项分类 | 具体内容 |
---|---|
前缀索引优化 | 使用前缀索引减小索引字段大小,增加索引页存储索引值数量,提升查询速度;大字符串字段作索引时,可帮减小索引项大小 |
覆盖索引优化 | SQL 中 query 所有字段能在索引 B + Tree 叶子节点找到的二级索引,查询得记录无需通过聚簇索引,可避免回表操作 |
主键索引建议 | 最好自增,插入新数据按顺序添加到当前索引节点位置,无需移动已有数据,页面写满自动开辟新页面,插入效率高;非自增主键插入时可能插入到现有数据页中间,需移动数据,造成“页分裂”,产生内存碎片,影响查询效率 |
防止索引失效情况1 | 使用左或左右模糊匹配(like %xx、like %xx% )会造成索引失效 |
防止索引失效情况2 | 查询条件中对索引列做计算、函数、类型转换操作,会造成索引失效 |
防止索引失效情况3 | 联合索引需遵循最左匹配原则,否则导致索引失效 |
防止索引失效情况4 | WHERE 子句中,OR 前条件列是索引列,OR 后条件列不是索引列,索引失效 |
前缀索引
使用前缀索引是为了减小索引字段大小,可以增加一个索引页中存储的索引值,有效提高索引的查询速度。在一些大字符串的字段作为索引时,使用前缀索引可以帮助减小索引项的大小。
事务的特性是什么?
- 原子性:一个事务中的所有操作,要么全部完成,要么全部不完成。通过 undo log实现。
- 一致性:事务操作前和操作后,数据满足完整性约束,数据库保持一致性状态。通过 持久性+原子性+隔离性来保证
- 隔离性:数据库允许多个并发事务同时对其数据进行读写和修改的能力。MVCC(多版本并发控制) 或锁机制来保证的
- 持久性:事务结束后,对数据的修改是永久的。通过redo log保证。
mysql可能出现什么和并发相关的问题?
- 脏读:如果一个事务读到了另一个未提交事务修改过的数据,就意味者发生了脏读现象。
- 不可重复读:在一个事务内多次读取同一个数据,如果出现前后两次读到的数据不一样的情况,就意味着发生了不可重复读现象。
- 幻读:在一个事务内多次查询某个符合查询条件的记录数量,如果出现前后两次查询到的记录数量不一样的情况,就意味着发生了幻读现象。
mysql是怎么解决并发问题的?
- ** 锁机制**:通过锁机制,可以在读写操作时进行加锁,确保同时只有一个操作能够访问或修改数据。
- 事务隔离级别:Mysql提供了多个事务隔离级别,包括读未提交、读已提交、可重复读和串行化。通过设置合适的事务隔离级别,可以在多个事务并发执行时,控制事物之间的隔离程度,以避免数据不一致的问题。
- MVCC:通过在数据库中保存不同版本的数据来实现不同事务之间的隔离。在读数据时,Mysql会根据事务的隔离级别来选择合适的数据版本,从而保证数据的一致性。
事务的隔离级别
- 读未提交:一个事务还没提交时,它做的变更就能被其他事务看到。
- 读提交:一个事务提交之后,它做的变更才能被其他事务看到。
- 可重复读:一个事务执行过程中看到的数据,一直跟这个事务启动时看到的数据是一致的,Mysql InnoDB 引擎的默认隔离级别。
- 串行化:会对记录加上读写锁,在多个事务对这条记录进行读写操作时,如果发生了读写冲突的时候,后访问的事务必须等前一个事务执行完成,才能继续执行。
隔离级别 | 可能发生的现象 | 不可能发生的现象 |
---|---|---|
读未提交 | 脏读、不可重复读、幻读 | - |
读提交 | 不可重复读、幻读 | 脏读 |
可重复读 | 幻读 | 脏读、不可重复读 |
串行化 | - | 脏读、不可重复读、幻读 |
这四个隔离级别是如何实现的
隔离级别 | 实现方式/特点 |
---|---|
读未提交 | 可读取未提交事务修改的数据,直接取最新数据 |
串行化 | 通过加读写锁避免并行访问 |
读提交 | 通过 Read View 实现,每个语句执行前重新生成 Read View |
可重复读 | 通过 Read View 实现,启动事务时生成 Read View,事务期间复用 |
滥用事务的弊端
- 如果一个事务特别多 sql,锁定的数据太多,容易造成大量的死锁和锁超时。
- 回滚记录会占用大量存储空间,事务回滚时间长。在 MySQL 中,实际上每条记录在更新的时候都会同时记录一条回滚操作。记录上的最新值,通过回滚操作,都可以得到前一个状态的值,sql 越多,所需要保存的回滚数据就越多。
- 执行时间长,容易造成主从延迟,主库上必须等事务执行完成才会写入 binlog,再传给备库。所以,如果一个主库上的语句执行 10 分钟,那个事务很可能就会导致从库延迟 10 分钟 。
mysql里的锁
锁类型 | 具体说明 |
---|---|
全局锁 | 通过flush tables with read lock 语句使整个数据库只读,其他线程增删改、表结构修改会阻塞,用于全库逻辑备份,避免备份期间数据/表结构更新致备份文件数据不符 |
表级锁 - 表锁 | 通过lock tables 语句对表加锁,限制其他线程读写,也限制本线程后续读写操作 |
表级锁 - 元数据锁(MDL) | 对表操作时自动加锁,CRUD 加 MDL 读锁,结构变更加 MDL 写锁,保证用户 CRUD 时表结构不被其他线程变更 |
表级锁 - 意向锁 | 执行插入、更新、删除操作前,先对表加“意向独占锁”,再对记录加独占锁,用于快速判断表里是否有记录被加锁 |
行级锁 | InnoDB 引擎支持,MyISAM 引擎不支持 |
记录锁 | 锁住一条记录,分 S 锁和 X 锁,满足读写互斥、写写互斥 |
间隙锁 | 仅存在于可重复读隔离级别,解决该级别下幻读问题 |
Next - Key Lock(临键锁) | 是 Record Lock + Gap Lock 的组合,锁定一个范围且锁定记录本身 |
数据库的表锁和行锁有什么作用
表锁的作用:
- 整体控制:表锁可以用来控制整个表的并发访问,当一个事务获取了表锁时,其他事务无法对该表进行任何读写操作,从而确保数据的完整性和一致性。
- 粒度大:表锁的力度比较大,在锁定表的情况下,可能会影响到整个表的其他操作,可能会引起锁竞争和性能问题。
- 适用于大批量操作:表锁适用于需要大批量操作表中数据的场景,例如表的重建、大量数据的加载等。
行锁的作用:
- 细粒度控制:行锁可以精确控制对表中某行数据的访问,使得其他事务可以同时访问表中的其他行数据,在并发量大的系统中能够提高并发性能。
- 减少锁冲突:行锁不会像表锁那样造成整个表的锁冲突,减少了锁竞争的可能性,提高了并发访问的效率。
- 适用于频繁单行操作:行锁适用于需要频繁对表中单独进行操作的场景,例如订单系统中的订单修改、删除等操作。
日志的文件分为哪几种?
- redo log 重做日志:Innodb存储引擎层生成的日志,实现了事务的持久性,主要用于掉电等故障恢复。
- undo log 回滚日志:Innodb存储引擎层生成的日志,实现了事务的原子性,主要用于事务回滚和MVCC。
- bin log 二进制日志:Server层生成的日志,主要用于数据备份和主从复制。
- relay log中继日志:用于主从复制场景下,slave通过io线程拷贝master的bin log后生成的日志。
- 慢查询日志:用于记录执行时间过长的sql,需要设置阈值后手动开启。
以下是简化提取的文字内容,保留核心要点:
MySQL 的 binlog
- 生成与写入:更新操作后 Server 层生成 binlog,事务提交时统一写入文件;属于 Server 层日志,所有存储引擎可用。
- 写入与用途:追加写模式,写满建新文件,不覆盖旧日志;用于备份恢复、主从复制,记录全量变更。
- 记录内容:仅记库表结构变更、数据修改(如增删改),不记查询(SELECT、SHOW 等)。
- 格式类型:
- STATEMENT(默认):记修改数据的 SQL 逻辑操作,主从复制用 SQL 重现;但含
uuid
/now
等动态函数时,主从数据可能不一致。 - ROW:记每行数据最终变更结果,解决动态函数问题;但批量更新会因逐行记录,导致 binlog 体积大。
- MIXED:自动结合前两种模式,按场景选 STATEMENT 或 ROW 记录。
- STATEMENT(默认):记修改数据的 SQL 逻辑操作,主从复制用 SQL 重现;但含
UndoLog日志的作用是什么
undo log是一种用于撤销回退的日志,它保证了事务的ACID特性中的原子性。
在事务没提交之前,Mysql会先记录更新前的数据到undo log日志文件里面,当事务回滚时,可以利用undo log来进行回滚。
有了undolog为什么还要redolog?
WAL技术:MySQL的写操作并不是立刻写到磁盘上,而是先写日志,然后在合适的时间再写到磁盘上。
redo log是物理日志,记录了某个数据页做了什么修改,每当执行一个事务就会产生这样的一条或者多条物理日志。
redo log 记录了此次事务完成后的数据状态,记录的是更新之后的值 |
---|
undo log记录了此次事务开始前的数据状态,记录的是更新之前的值 |
事务提交之前发生了崩溃,重启后会通过undo log回滚事务,事务提交后发生了崩溃,重启后会通过redo log恢复事务
redo log保证了事务的持久性。
- 实现事务的持久性,让MySQL有crash-safe(崩溃恢复)的能力,能够保证MySQL在任何时间段突然崩溃,重启之后已提交的记录都不会丢失。
- 将写操作从随机读变成了顺序写,提升MySQL写入磁盘的性能。
redo log怎么保证持久化的
- Write-ahead logging(WAL):在事务提交之前,将事务所做的修改操作记录到redo log中,然后再将数据写入磁盘。这样即使在数据写入磁盘之前发生了宕机,系统可以通过redo log中的记录来恢复数据。
- Redo log的顺序写入:redo log采用追加写入的方式,将redo日志记录追加到文件末尾,而不是随机写入。这样可以减少磁盘的随机I/O操作,提高写入性能。
- Checkpoint机制:MySQL会定期将内存中的数据刷新到磁盘,同时将最新的LSN(Log Sequence Number)记录到磁盘中,这个LSN可以确保redo log中的操作是按顺序执行的。在恢复数据时,系统会根据LSN来确定从哪个位置开始应用redo log。
binlog两阶段提交过程是怎么样的?
当客户端执行commit语句或者在自动提交的情况下,MySQL内部开启一个XA事务,分两个阶段来完成XA事务的提交。
redo log的写入拆分成了两个步骤:prepare和commit,中间再穿插写入binlog。
prepare 阶段:将 XID(内部 XA 事务的 ID) 写入到 redo log,同时将 redo log 对应的事务状态设置为
prepare,然后将 redo log 持久化到磁盘(innodb_flush_log_at_trx_commit = 1 的作用);commit 阶段:把 XID 写入到 binlog,然后将 binlog 持久化到磁盘(sync_binlog = 1
的作用),接着调用引擎的提交事务接口,将 redo log 状态设置为 commit,此时该状态并不需要持久化到磁盘,只需要 write 到文件系统的 page cache 中就够了,因为只要 binlog 写磁盘成功,就算 redo log 的状态还是 prepare也没有关系,一样会被认为事务已经执行成功;
如果 binlog 中没有当前内部 XA 事务的 XID,说明 redolog 完成刷盘,但是 binlog 还没有刷盘,则回滚事务。对应时刻 A 崩溃恢复的情况。
如果 binlog 中有当前内部 XA 事务的 XID,说明 redolog 和 binlog 都已经完成了刷盘,则提交事务。对应时刻 B 崩溃恢复的情况。
处于prepare阶段的redo log,既可以提交事务,也可以回滚事务,这取决于是否能够在binlog中查找到与redo log相同的XID。
update语句的具体执行过程是怎样的?
具体更新一条记录 UPDATE t_user SET name = 'xiaolin' WHERE id = 1;
的流程如下:
- 执行器负责具体执行,会调用存储引擎的接口,通过主键索引树搜索获取
id = 1
这一行记录:- 如果
id=1
这一行所在的数据页本来就在 buffer pool 中,就直接返回给执行器更新; - 如果记录不在 buffer pool,将数据页从磁盘读入到 buffer pool,返回记录给执行器。
- 如果
- 执行器得到聚簇索引记录后,会看一下更新前的记录和更新后的记录是否一样:
- 如果一样的话就不进行后续更新流程;
- 如果不一样的话就把更新前的记录和更新后的记录都当作参数传给 InnoDB 层,让 InnoDB 真正的执行更新记录的操作;
- 开启事务,InnoDB 层更新记录前,首先要记录相应的 undo log,因为这是更新操作,需要把被更新的列的旧值记下来,也就是要生成一条 undo log,undo log 会写入 Buffer Pool 中的 Undo 页面,不过在内存修改该 Undo 页面后,需要记录对应的 redo log。
- InnoDB 层开始更新记录,会先更新内存(同时标记为脏页),然后将记录写到 redo log 里面,这个时候更新就算完成了。为了减少磁盘 I/O,不会立即将脏页写入磁盘,后续由后台线程选择一个合适的时机将脏页写入到磁盘。这就是 WAL 技术,MySQL 的写操作并不是立刻写到磁盘上,而是先写 redo 日志,然后在合适的时间再将修改的行数据写到磁盘上。
- 至此,一条记录更新完了。
- 在一条更新语句执行完成后,然后开始记录该语句对应的 binlog,此时记录的 binlog 会被保存到 binlog cache,并没有刷新到硬盘上的 binlog 文件,在事务提交时才会统一将该事务运行过程中的所有 binlog 刷新到硬盘。
- 事务提交(为了方便说明,这里不说组提交的过程,只说两阶段提交):
- prepare 阶段:将 redo log 对应的事务状态设置为 prepare,然后将 redo log 刷新到硬盘;
- commit 阶段:将 binlog 刷新到磁盘,接着调用引擎的提交事务接口,将 redo log 状态设置为 commit(将事务设置为 commit 状态后,刷入到磁盘 redo log 文件);
- 至此,一条更新语句执行完成。
为什么要写redo log,而不是直接写到B+树里面
- Redo Log 可提升事务提交效率,因它顺序写磁盘,比 B + 树数据页随机写性能好。
- 具备故障恢复能力,记录物理级别修改(如数据页地址、修改内容 )。
- 数据库重启时,依据 Redo Log 检查并重做已提交但数据页未完全写入磁盘的事务,保障事务修改生效 。
MySQL两次写
Doublewrite的出现是解决页数据损坏的问题。
Doublewrite Buffer 作用是,在把页写到数据文件之前,InnoDB先把它们写到一个叫doublewrite buffer(双写缓冲区)的共享表空间内,在写doublewrite buffer完成后,InnoDB才会把页写到数据文件的适当的位置。如果在写页的过程中发生意外崩溃,InnoDB在稍后的恢复过程中在doublewrite buffer中找到完好的page副本用于恢复,所以本质上是一个最近写回的页面的备份拷贝。
- 页数据先通过
memcpy
函数拷贝至内存中的Doublewrite Buffer
(大小为约 2MB)中,Doublewrite Buffer
分为两个区域,每次写入一个区域(最多 1MB 的数据)。 Doublewrite Buffer
的内存里的数据页,会fsync
到Doublewrite Buffer
的磁盘上,写两次到共享表空间中(连续存储,顺序写,性能很高),每次写 1MB;- 写入完成后,再将脏页刷到数据磁盘存储
.ibd
文件上(随机写);
给你张表,发现查询速度很慢,你有那些解决方案
- 分析查询语句:使用EXPLAIN命令分析SQL执行计划,找出慢查询的原因,比如是否使用了全表扫描,是否存在索引未被利用的情况等,并根据相应情况对索引进行适当修改。
- 创建或优化索引:根据查询条件创建合适的索引,特别是经常用于WHERE子句的字段、Orderby排序的字段、Join连表查询的字典、group by的字段,并且如果查询中经常涉及多个字段,考虑创建联合索引,使用联合索引要符合最左匹配原则,不然会索引失效
- 避免索引失效:比如不要用左模糊匹配、函数计算、表达式计算等等。
- 查询优化:避免使用SELECT *,只查询真正需要的列;使用覆盖索引,即索引包含所有查询的字段;联表查询最好要以小表驱动大表,并且被驱动表的字段要有索引,当然最好通过冗余字段的设计,避免联表查询。
- 分页优化:针对 limit n,y 深分页的查询优化,可以把Limit查询转换成某个位置的查询:select * from tb_sku where id>20000 limit 10,该方案适用于主键自增的表,
- 优化数据库表:如果单表的数据超过了千万级别,考虑是否需要将大表拆分为小表,减轻单个表的查询压力。也可以将字段多的表分解成多个表,有些字段使用频率高,有些低,数据量大时,会由于使用频率低的存在而变慢,可以考虑分开。
- 使用缓存技术:引入缓存层,如Redis,存储热点数据和频繁查询的结果,但是要考虑缓存一致性的问题,对于读请求会选择旁路缓存策略,对于写请求会选择先更新db,再删除缓存的策略。
MySQL的主从复制
- MySQL 主库在收到客户端提交事务的请求之后,会先写入 binlog,再提交事务,更新存储引擎中的数据,事务提交完成后,返回给客户端“操作成功”的响应。
- 从库会创建一个专门的 I/O 线程,连接主库的 log dump 线程,来接收主库的 binlog 日志,再把 binlog 信息写入 relay log 的中继日志里,再返回给主库“复制成功”的响应。
- 从库会创建一个用于回放 binlog 的线程,去读 relay log 中继日志,然后回放 binlog 更新存储引擎中的数据,最终实现主从的数据一致性。
分库和分表有什么区别
- 分库是一种水平扩展数据库的技术,将数据根据一定规则划分到多个独立的数据库中。每个数据库只负责存储部分数据,实现了数据的拆分和分布式存储。分库主要是为了解决并发连接过多,单机 mysql扛不住的问题。
- 分表指的是将单个数据库中的表拆分成多个表,每个表只负责存储一部分数据。这种数据的垂直划分能够提高查询效率,减轻单个表的压力。分表主要是为了解决单表数据量太大,导致查询性能下降的问题。
- 垂直分库:一般来说按照业务和功能的维度进行拆分,将不同业务数据分别放到不同的数据库中,核心理念 专库专用。按业务类型对数据分离,剥离为多个数据库,像订单、支付、会员、积分相关等表放在对应的订单库、支付库、会员库、积分库。垂直分库把一个库的压力分摊到多个库,提升了一些数据库性能,但并没有解决由于单表数据量过大导致的性能问题,所以就需要配合后边的分表来解决。
- 垂直分表:针对业务上字段比较多的大表进行的,一般是把业务宽表中比较独立的字段,或者不常用的字段拆分到单独的数据表中,是一种大表拆小表的模式。数据库它是以行为单位将数据加载到内存中,这样拆分以后核心表大多是访问频率较高的字段,而且字段长度也都较短,因而可以加载更多数据到内存中,减少磁盘IO,增加索引查询的命中率,进一步提升数据库性能。
- 水平分库:是把同一个表按一定规则拆分到不同的数据库中,每个库可以位于不同的服务器上,以此实现水平扩展,是一种常见的提升数据库性能的方式。这种方案往往能解决单库存储量及性能瓶颈问题,但由于同一个表被分配在不同的数据库中,数据的访问需要额外的路由工作,因此系统的复杂度也被提升了。
- 水平分表:是在同一个数据库内,把一张大数据量的表按一定规则,切分成多个结构完全相同表,而每个表只存原表的一部分数据。水平分表尽管拆分了表,但子表都还是在同一个数据库实例中,只是解决了单一表数据量过大的问题,并没有将拆分后的表分散到不同的机器上,还在竞争同一个物理机的CPU、内存、网络IO等。要想进一步提升性能,就需要将拆分后的表分散到不同的数据库中,达到分布式的效果。