🚀最近也打算整理一波已经学过的知识,名字已经想好了就叫《CheckList》系列,以后需要用到的时候也可以拿出来看。问题来源于网上常见的面试题,问题的答案多以官网为主,每个问题我都贴了链接,觉得我写的不清楚的地方可以点击链接查看原文。
另外如果有什么问题这上面没有,可以评论,私信,提交issue等等的各种方式,只要我看到都会观看的,满一定程度会出续集。
点赞👍收藏🌟支持一波呗~~~~~~
PS:提交issue地址:https://github.com/Nortyr/monk_note/issues
MySQL是开源的关系型数据库,以下是MySQL的一些关键特性
范式的目的是减少冗余,会造成一些问题
精简版本:
官网版本:
官网链接:https://dev.mysql.com/doc/refman/8.0/en/pluggable-storage-overview.html
大致情况如上图
官网链接:https://dev.mysql.com/doc/refman/8.0/en/char.html
官网链接:https://dev.mysql.com/doc/refman/8.0/en/blob.html
官网链接:https://dev.mysql.com/doc/refman/8.0/en/datetime.html
官网链接:https://dev.mysql.com/doc/refman/8.0/en/aggregate-functions.html#function_count
InnoDB | MyISAM | |
---|---|---|
事务 | Yes | NO |
MVCC | Yes | NO |
外键 | Yes | No |
聚簇索引 | Yes | no |
锁最小粒度 | 行锁 | 表锁 |
清空方式 | 逐行 | 重建 |
官网链接:https://dev.mysql.com/doc/refman/8.0/en/myisam-storage-engine.html
当AUTO_INCREMENT整数列用完值时,得到的值就是最大的值,后续INSERT操作将返回重复键错误。
官网链接:https://dev.mysql.com/doc/refman/8.0/en/innodb-auto-increment-handling.html
官网链接:https://dev.mysql.com/doc/refman/8.0/en/union.html
官网链接:
官网链接:https://zh.wikipedia.org/wiki/%E6%95%B0%E6%8D%AE%E5%BA%93%E4%BA%8B%E5%8A%A1
ACID 模型是一组数据库设计原则。上面提到了事务的需要解决2个问题。而ACID模型是是解决这个问题的基本条件。InnoDB遵循ACID模型,因此数据不会被损坏,结果也不会因软件崩溃和硬件故障等异常情况而失真。
官网链接:https://dev.mysql.com/doc/refman/8.0/en/mysql-acid.html
官网链接:https://dev.mysql.com/doc/refman/8.0/en/innodb-transaction-isolation-levels.html#isolevel_serializable
例子:
参考链接:https://stackoverflow.com/questions/11043712/non-repeatable-read-vs-phantom-read
隔离级别 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
读未提交(READ UNCOMMITTED) | ? | ? | ? |
读已提交(READ COMMITTED) | ? | ? | ? |
可重复度(REPEATABLE READ) | ? | ? | ? |
串行化(SERIALIZABLE) | ? | ? | ? |
RR级别下解决了大部分的幻读的问题。但是还是会有幻读问题,下面是一点例子
事务A | 事务B |
---|---|
通过加锁阻塞B事务,这样就肯定不会有幻读了
事务A | 事务B |
---|---|
事务A | 事务B |
---|---|
PS:update test set name='kak' where id >120
替换成锁也有一样的效果
快照适用于事务内的select语句,不一定适用于DML语句。
其他事务修改或删除并提交,其他事务的修改或删除可能影响到那些行,这些行对于该事务可见
上面
update test set name='kak' where id >120
和
update test set name='kak' where id <120
查询到的结果不同证明了这一点
锁和cud操作会生成最新的ReadView快照
SELECT ... LOCK IN SHARE MODE
SELECT ... FOR UPDATE
UPDATE
DELETE
INSERT
官网链接:https://dev.mysql.com/doc/refman/8.0/en/innodb-consistent-read.html
MVCC(Multi Version Concurrency Control),多版本并发控制。由多个模块共同实现。
DB_TRX_ID(事务id)
,DB_ROLL_PTR(回滚指针)
,DB_ROW_ID(记录id)
大致逻辑:
先简单的概述下这四个
接下来就是比较事务id了
下面画图举个栗子
这个是ReadView源码
private:
// Disable copying
ReadView(const ReadView&);
ReadView& operator=(const ReadView&);
private:
/** The read should not see any transaction with trx id >= this
value. In other words, this is the "high water mark". */
/** 读取不应看到任何 trx id >= m_low_limit_id 的值。换句话说,这就是“高水位线”。*/
trx_id_t m_low_limit_id;
/** The read should see all trx ids which are strictly
smaller (<) than this value. In other words, this is the
low water mark". */
/** 读取应该看到 trx id <= m_up_limit_id 的值。换句话说,这就是低水位线”。*/
trx_id_t m_up_limit_id;
/** trx id of creating transaction, set to TRX_ID_MAX for free
views. */
/** 创建事务的事务id,TRX_ID_MAX是空闲的视图 */
trx_id_t m_creator_trx_id;
/** Set of RW transactions that was active when this snapshot
was taken */
/** 当前快照还活跃的读写事务 */
ids_t m_ids;
/** The view does not need to see the undo logs for transactions
whose transaction number is strictly smaller (<) than this value:
they can be removed in purge if not needed by other views */
/** 小于这个值的undolog 可以个栗子不用再看了,他们已经可以被回收了*/
trx_id_t m_low_limit_no;
/** AC-NL-RO transaction view that has been "closed". */
bool m_closed;
typedef UT_LIST_NODE_T(ReadView) node_t;
/** List of read views in trx_sys */
byte pad1[64 - sizeof(node_t)];
node_t m_view_list;
};
官网链接:https://dev.mysql.com/doc/refman/8.0/en/innodb-multi-versioning.html
官网链接:https://dev.mysql.com/doc/refman/8.0/en/create-index.html
聚簇索引:将数据存储与索引放到了一块,找到索引也就找到了数据行。
非聚簇索引:将数据存储与索引分开,叶子结点包指向数据行。
聚簇索引:
数据结构大致如图所示(实际还有槽(Solt),不影响理解就没画)
数据结构大致如图所示
上面应该看到了非举出索引的结构比如说
select id,name,work,create_time from fanhua where work= '至真园'
select id,work from fanhua where work= '至真园'
这两个字段,work索引中都涵盖了,就不会回表了,直接返回。
索引下推是避免全表遍历的一种查询优化,由MySQL判断这些where条件,交给存储引擎判断,如果满足,直接筛选部分条件
explain 的时候 Extra列如果有Using index condition
就代表使用了索引下推
索引下推的开关控制
SET optimizer_switch = 'index_condition_pushdown=off';
SET optimizer_switch = 'index_condition_pushdown=on';
官网链接:https://dev.mysql.com/doc/refman/8.0/en/index-condition-pushdown-optimization.html
假如有这个联合索引
ALTER TABLE `test`.`test`
ADD INDEX `union_idx`(`work`, `create_time`);
那么他的结构如下
还记得前面说到b+树实际上是二分查找的一种吗
二分查找,最最最基本要求,是要能确定答案在你的左半边,还是在右半边,这类,连续的记录行,那么有序是一种相对简单高效的方式,这类多列索引,怎么确定顺序呢,就是按照你创建的方式进行排序(从左到右)
select work,create_time from test order by work asc,create_time asc
后面的列只是辅助最左列的排名,如果查询不带最左列,就无法使用这个联合索引。
一般生产中,捆绑了这两个条件查询的我们才创建联合索引
我感觉用一下的提问方式比较好一点
什么是B+树?为什么使用B+树而不使用其他数据结构?或者B+树相对于其他数据结构的优点是什么
受到索引覆盖和命中数据量的影响
测试数据脚本
CREATE TABLE `monk` (
`id` int NOT NULL AUTO_INCREMENT,
`name` varchar(255) DEFAULT NULL,
`age` int DEFAULT NULL,
`email` varchar(255) DEFAULT NULL,
`city` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `name_idx` (`name`) USING BTREE,
KEY `age_idx` (`age`) USING BTREE
) ENGINE=InnoDB CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
DELIMITER //
CREATE PROCEDURE insert_data()
BEGIN
DECLARE i INT DEFAULT 1;
WHILE i <= 10000 DO
INSERT INTO monk (name, age, email, city)
VALUES (
CONCAT('User', i),
FLOOR(RAND() * 100) + 1,
CONCAT('user', i, '@example.com'),
'City'
);
SET i = i + 1;
END WHILE;
END //
DELIMITER ;
CALL insert_data();
不是,索引也有很多弊端
SELECT * FROM ref_table
WHERE key_column=expr OR key_column IS NULL;
value IN (SELECT primary_key FROM single_table WHERE some_expr)
value IN (SELECT key_column FROM single_table WHERE some_expr)
SELECT * FROM tbl_name
WHERE key_column = 10;
SELECT * FROM tbl_name
WHERE key_column BETWEEN 10 and 20;
SELECT * FROM tbl_name
WHERE key_column IN (10,20,30);
SELECT * FROM tbl_name
WHERE key_part1 = 10 AND key_part2 IN (10,20,30);
官网链接:https://dev.mysql.com/doc/refman/8.0/en/explain-output.html
我个人日常大致是按照以下九个步骤
降多个范围扫描的行合并,适用于单表而不是多表,合并可以产生交集,并集或者交集并集
extra中出现以下信息表示使用了索引合并,
Using intersect(…)Using union(…)Using sort_union(…)
注意:
以下是索引合并的案例
SELECT * FROM tbl_name WHERE key1 = 10 OR key2 = 20;
SELECT * FROM tbl_name
WHERE (key1 = 10 OR key2 = 20) AND non_key = 30;
SELECT * FROM t1, t2
WHERE (t1.key1 IN (1,2) OR t1.key2 LIKE 'value%')
AND t2.key1 = t1.some_col;
SELECT * FROM t1, t2
WHERE t1.key1 = 1
AND (t2.key1 = t1.some_col OR t2.key2 = t1.some_col2);
官网链接:https://dev.mysql.com/doc/refman/8.0/en/index-merge-optimization.html
前文中已经提到了。这里更详细的说明下
单纯优化查询速度的角度
limit 10000,10
id> 111111111 limit 10
select id ,name,age,email from monk where age = 10 limit 50, 10
--修改为这个
SELECT
m.id,NAME,age,email
FROM
monk m
INNER JOIN ( SELECT id FROM monk WHERE age = 10 LIMIT 50, 10 ) AS t1 ON m.id = t1.id
正常如果mybatis插件,一般会有2次查询,我在如何优雅的实现一个Mybatis插件里面提到了,可以走自定义的查询
如果不需要分页信息,也可以PageHelper.startPage(pageNum, pageSize, false);
换成其他数据库
如果不需要优化查询速度,单纯降低负载来说
这三个日志各自承担的职责是不同的,解决不同的问题
binlog
undolog
redolog
checkpoint_lsn
全局变量,表示已经刷入到磁盘的脏页的最大lsn。oldest_modification
字段。PS:flush链表也是根据这个字段排序newest_modification
字段innodb_flush_log_at_trx_commit
控制刷入磁盘的策略。
前文中提到了binlog和redolog都是用于崩溃恢复,那么为什么要两阶段提交呢?
是为了保证redolog和binlog的一致性
binlog记录缺失代表从库缺失。
redolog记录缺失代表主库缺失
单次提交,无论先后顺序怎么样,在极端情况下,都有可能后写入的造成缺失,最终都是主从不同步。
binlog 先写就会主库存在丢失数据的情况
binlog后写就会造成从库丢失数据的情况
提交阶段如果发生崩溃
也就如下两种情况种极端情况
redolog和binlog状态不一致,直接回滚
比较binlog和redolog中的xid,不一致,回滚,一致,提交
参考博客:https://zhuanlan.zhihu.com/p/343449447
SELECT ... FOR SHARE
SELECT ... FOR UPDATE
X | IX | S | IS | |
---|---|---|---|---|
X | 互斥 | 互斥 | 互斥 | 互斥 |
IX | 互斥 | 兼容 | 互斥 | 兼容 |
S | 互斥 | 互斥 | 兼容 | 兼容 |
IS | 互斥 | 兼容 | 兼容 | 兼容 |
比如下面一张表,主键id分别是 99,101,109
那间隙锁可能存在的地方是(-∞,99)(99,101)(101,109)(109,+∞)
那间隙锁可能存在的地方是(-∞,99](99,101](101,109](109,+∞)
官网链接:https://dev.mysql.com/doc/refman/8.0/en/innodb-locking.html#innodb-record-locks
### Error updating database. Cause: com.mysql.jdbc.exceptions.jdbc4.MySQLTransactionRollbackException: Deadlock found when trying to get lock; try restarting transaction ### The error may involve com.iflytek.hmreader.order.dao.UBookShelfMapper.batchInsertOrUpdate-Inline
### The error occurred while setting parameters
类似于发现以上日志
导致死锁的原因:
多个事务同事访问相同的资源。但是访问顺序不同,出现了需要的锁在对方的手上,形成了互斥。和java之类的死锁产生条件一直
解决方案:
官网链接:https://dev.mysql.com/doc/refman/8.0/en/innodb-deadlocks.html
普通查询没有加锁的情况
此种情况下会添加【意向排它锁,行锁】
< 最小值 【间隙锁】
最大值 【行键锁】由于后面为”正无穷“所以LOCK_DATA为
supremum pseudo-record
范围查询—for update
会加【行键锁】并且范围为(15,16](16,17](17,18](19,20](19,20)
lock in share mode
和上面一致,就不重复写了,排它锁换成共享锁即可
for update
都执行了,但是只会添加行锁
lock in share mode
脚本
set global transaction isolation level read committed
set session transaction isolation level read committed;
set global transaction isolation level Repeatable Read
set session transaction isolation level Repeatable Read;
select @@transaction_isolation;
begin
select id from monk where id = 100;
select id from monk where id = 100 for UPDATE;
select id from monk where id = 9999999 for UPDATE ; -- >最大id
select id from monk where id = 0 for update; -- <最小id
select id from monk where id >15 and id<20 for update;
select id from monk where id = 100;
select id from monk where id = 100 LOCK IN SHARE MODE;
select id from monk where id = 9999999 LOCK IN SHARE MODE; -- >最大id
select id from monk where id = 0 LOCK IN SHARE MODE; -- <最小id
select id from monk where id >15 and id<20 LOCK IN SHARE MODE;
SELECT ENGINE_TRANSACTION_ID as Trx_Id,
OBJECT_NAME as `Table`,
INDEX_NAME as `Index`,
LOCK_DATA as Data,
LOCK_MODE as Mode,
LOCK_STATUS as Status,
LOCK_TYPE as Type
FROM performance_schema.data_locks;
ROLLBACK
commit
上面一个问题中就是锁退化很好的例子
几乎所有的悲观锁都是依靠排它锁实现的,不光在mysql中
下面就是一个悲观锁的实现方式
begin ;
select id,name ,age from monk where id =100 for update;
update monk set age=27 where id =100;
commit;
乐观锁基本都是cas的实现方式,compare and set的值一般是某个状态
下面是乐观锁的实现方式
update monk set age=27 where id =100 and age=39;
大致流程如下图所示
IO线程接收到变动保存到relaylog中
sql线程读取relaylog中的内容,写入本地(sql线程受到replica_parallel_workers控制)
参见官网:https://dev.mysql.com/doc/refman/8.0/en/replication-threads.html
分库主要解决如下几个问题
垂直分表
由于业务拆分,一个表几十个字段了,可能把一些不活跃的字段拆分到别的表去,这样数据页能存放更多的数据,提升查询效率。
又或者业务就是庞杂,拆分的。
水平分表
主要是解决数据量大的问题,数据量大导致的最直接就是一堆慢sql,理论上要根据慢sql来优化,这个要设计到多个指标。
像我们主表1亿+,还能坚挺,每一天都提心吊胆。
服务端处理
数据库代理
这个是重中之重,多个库如何保证事务的一致性就是个问题
如果分的不多,可以再内存中进行数据处理。
如果业务场景比较复杂,上面这种就不大合适了
一般是下面两种思路
按照卖家和买家冗余两份>_>
走es等三方数据库
统计也是开发中比较常见的场景,如果分库分表影响了统计。
我能想到比较简单的方法,同步到clickhouse之类的列式数据库里面
常见的解决算法是以下2种算法,都可以保证生成的id不重复
时间+机器id+序号 理论上一台机器的极限是1秒4096个,多布置机器可以防止重复问题
参考资料
《MySQL 是怎样运行的:从根儿上理解 MySQL》
https://dev.mysql.com/doc/refman/8.0/en/preface.html
https://stackoverflow.com/questions/11043712/non-repeatable-read-vs-phantom-read
https://zhuanlan.zhihu.com/p/343449447