美文网首页Java技术问答
15 | 答疑(一):日志和索引相关问题

15 | 答疑(一):日志和索引相关问题

作者: hedgehog1112 | 来源:发表于2019-08-27 09:41 被阅读0次

    日志相关问题

    两阶段提交不同瞬间,MySQL异常重启,怎么保证数据完整?

    图 1 两阶段提交示意图

    不是update 语句执行流程吗,怎么调用 commit ?两个“commit”的概念混淆了:

    1)上述“commit 语句”,MySQL 语法中,提交事务。跟 begin/start transaction 配对用。执行时,包含“commit 步骤(2)”。

    2)图中“commit ”,事务提交小步骤(最后一步),执行完,事务提交。

    没显式开启事务,update 语句自己就是一个事务,执行完成,用到“commit 步骤“。

    A :binlog 还没写,redo log 也还没提交,崩溃恢复,事务回滚。binlog 没写,不会传备库。

    B, binlog 写完,redo log 没 commit , crash,崩溃恢复判断规则

    1.  redo log 事务完整,有commit 标识,提交

    2.  redo log 事务完整 prepare, 对应binlog 完整提交.否则回滚:

    追问 1怎么知道 binlog 是否完整?

    有完整格式:

    statement 格式的 binlog,最后有 COMMIT

    row 格式的 binlog,最后有XID event

    binlog-checksum 验证 binlog 正确性。磁盘原因,中间出错的情况。

    追问 2redo log binlog 怎么关联?

    共同数据字段XID。崩溃恢复顺序扫描 redo log:

    只有 parepare、没有 commit 的 redo logXID 去 binlog 找对应事务

    追问 3prepare 阶段的 redo log 加上完整 binlog, 重启就能恢复,为什么这么设计?

    保证主、备库数据一致性。 

    B, binlog 已写,被从库(用 binlog 恢复的库)用,主库也要提交这个事务。

    追问 4:如这,为什么还要两阶段提交呢?redo log写完,再写 binlog。崩溃恢复时,必须得两个日志都完整才可以。是不是一样的逻辑?

    场景,事务持久性问题。每人都“ok”,一起提交。

    redo log 提交完成了,事务不能回滚(如回滚,覆盖别的事务更新)。

    如 redo log 直接提交,binlog 写入失败,InnoDB 回滚不了,数据和 binlog 日志不一致了。

    追问 5:不引入两个日志,也就没有两阶段提交的必要了。只用 binlog 来支持崩溃恢复,又能支持归档,不就可以了?

    只保留 binlog, “更新到内存” -> “写 binlog”-> “提交事务”不可以。

    历史原因,InnoDB 是 MySQL 插件,binlog 没有崩溃恢复能力,用 InnoDB 原有 redo log 。

    实现上原因:只用 binlog 来实现崩溃恢复的流程,没有 redo log 

    图 2 只用 binlog 支持崩溃恢复

    binlog 不能恢复“数据页”。binlog2 写完,没commit 的时候,crash。

    事务 2 回滚, binlog2 补回来;事务 1 来说,认为提交完了,不会再用 binlog1。

    InnoDB 用 WAL,依赖日志恢复数据页。事务 1 可能丢失数据页。binlog 没记录数据页更新细节,补不回来

    追问 6:只用 redo log,不要 binlog

    只从崩溃恢复角度可以。正式生产库,binlog 开。有着 redo log 无法替代的功能。

    1)归档。redo log 循环写,历史日志没法保留。

    2)高可用的基础,binlog 复制。

    2)数据分析系统,靠消费 MySQL 的 binlog 更新数据。关掉下游没法输入。

    追问 7redo log 一般设置多大?

    redo log 太小的话,很快写满,强行刷 redo log, WAL 能力发挥不出来。

    redo log  4 个文件、每个 1GB 

    追问 8:数据写入后落盘,redo log更新过来还是从buffer pool

    redo log 没有记录数据页完整数据,不能更新磁盘数据页

    1.  不一致,称为脏页。内存中数据页写盘。与 redo log 毫无关系

    2.  崩溃恢复场景,InnoDB 判断数据页丢失更新,将它读到内存,让 redo log 更新内存。更新完成,内存页变成脏页,回到第一种情况状态。

    追问 9redo log buffer 是什么?是先修改内存,还是先写 redo log文件?

    更新中,日志写多次:不能没 commit 时写到 redo log 文件里。

    begin;

    insert into t1  ...    //内存被修改,redo log buffer 写入日志

    insert into t2  ...

    commit;    //日志写到 redo log 文件(文件名是 ib_logfile+ 数字)

    redo log buffer 块内存,先存 redo 日志。

    业务设计问题

    业A、B 两个用户,互相关注成好友。like 表,friend 表,like 表有 user_id、liker_id 两个字段,设置为复合唯一uk_user_id_liker_id。:

    以 A 关注 B 为例:第一步,先查询对方有没有关注自己(B 有没有关注 A)

    select * from like where user_id = B and liker_id = A;

    有,则成为好友 insert into friend;

    没有,单向关注关系insert into like;

    A、B 同时关注对方不会成好友。第 1 步双方没互关,用了排他锁也不行,记录不存在,行锁不生效锁层面如何处理

    图 3 并发“喜欢”逻辑操作顺序

    select 都空。session 1 插入一个单向关注关系”。session 2 也同样

    解决办法:给“like”表增加relation_ship,取值 1、2、3。

    1 ,user_id 关注liker_id;

    2,liker_id 关注user_id;

    3,互相关注。

    然后,当 A 关注 B 的时候,逻辑改成如下所示的样子:

    比较 A 和 B 的大小,A<B:

    如果 A>B,则执行下面的逻辑

    让“like”表里的数据保证 user_id < liker_id,反向关系已存在,出现行锁冲突

    insert … on duplicate 确保事务内部,强行占行锁,select 判断relation_ship 逻辑时确保行锁保护下读操作

    操作符 “|” 是按位或,连同 insert  ignore保证重复调用时幂等性

    like 表relation_ship = 3,  friend 表里也有 A 和 B 记录。

    之前说尽量不要使用唯一索引,这个例子创建两个。例子业务根本,“一定插入重复数据,数据库要有唯一性约束”。

    在“业务开发保证不会插入重复记录”的情况下,着重要解决性能问题时,尽量用普通索引

    思考题

    创建表 t,插入一行,修改。

    mysql> CREATE  TABLE `t` (

    `id` int(11) NOT  NULL primary key auto_increment,

    `a` int(11)  DEFAULT NULL

    ) ENGINE=InnoDB;

    insert into t  values(1,2);   表 t 唯一的一行数据 (1,2)。假设,我现在要执行:

    mysql> update  t set a=2 where id=1;  结果:

    MySQL 处理命令三种选择:你觉得实际哪种呢?为什么?

    1.  更新都是先读后写的,MySQL 读出数据,发现 a 的值本来就是 2,不更新,直接返回

    2.  MySQL 调用了 InnoDB 引擎提供的“修改为 (1,2)”这个接口,但是引擎发现值与原来相同,不更新,直接返回

    3.  InnoDB 认真执行了“把这个值修改成 (1,2)"这个操作,该加锁的加锁,该更新的更新

    图 12 锁验证方式

    B 的 update 被 blocked ,InnoDB 才能加锁,排除 1

    图 13 可见性验证方式

    A 第二个 select 是一致性读(快照读),看不见 B 更新。返回 (1,3)表示看见新版本,session A update 时生成。排除 2

    答案 3,InnoDB ,该加锁的加锁,该更新更新

    更新前判断一下,不用浪费 InnoDB 操作?其实 MySQL 是确认的。MySQL读的,确定 (id=1), 要写的 (a=3),看不出来“不需要修改”。验证:

    图 14 可见性验证方式 -- 对照

    补充说明:

    上面都是在binlog_format=statement 下进行。如果是binlog_format=row且 binlog_row_image=FULL ,需在 binlog 记录所有字段,读数据时读出。

    “既然读了就会判断”, 此时“返回 (1,2)”。

    binlog_row_image=NOBLOB, 会读除 blob 外所有字段,结果还是(1,2)

    图 15 binlog_row_image=FULL 读字段逻辑  

    新加update_time on update current_timestamp,发现会加锁,提交update_time不变,binlog没生成,加锁实际没更新?

     timestamp 设置自动更新,更新“别的字段”时,MySQL 会读入所有涉及字段,不需要修改。

    @郭江伟 同学提到了两个点,都非常好,有去实际验证。结论是这样的:第一,hexdump 看出来没改应该是 WAL 机制生效了,要过一会儿,或者把库 shutdown 看看。第二,binlog 没写是 MySQL Server 层知道行的值没变,所以故意不写的,这个是在 row 格式下的策略。你可以把binlog_format 改成 statement 再验证下。

    评论1

    酒馆生意好,老板把孔乙己的欠账记录记小黑板上并记己点菜单。吹牛,忘了叫几两酒了。又给老板说,老板把酒改成二两。老板也不确定孔乙己叫没叫酒,就去查菜单,发现孔乙己确实点了酒,但是本来就二两,也就难得麻烦了,又要修改小黑板,又要改菜单。直接就给孔乙己说已经改好了。😄

    小二连忙过来拦住,“老板,又赊账一碟茴香豆。”

    老板大惊,“我怎不知?”

    小二道,“老板你方才看板时没拿记账笔,记账笔没人用,自然可用”

    于是把店规“变账须用记账笔。” 改为

    改帐均须动笔。纵为不变之帐,仍需覆写之

    评论2

    创建测试数据:

    mysql> create table t(id int primary key auto_increment,a int );

    mysql> insert into t values(1,2);

    mysql> begin;

    Query OK, 0 rows affected (0.00 sec)

    mysql> update t set a=2 where id=1;

    Query OK, 0 rows affected (0.00 sec)

    查看系统锁情况:

    show engine innodb status

    ---TRANSACTION 958998, ACTIVE 51 sec

    2 lock struct(s), heap size 1136, 1 row lock(s)

    MySQL thread id 2, OS thread handle 139663691581184, query id 22 localhost root

    mysql> show processlist;

    +----+------+-----------+--------------------+---------+------+----------+------------------+

    | Id | User | Host | db | Command | Time | State | Info |

    +----+------+-----------+--------------------+---------+------+----------+------------------+

    | 2 | root | localhost | sysbench | Sleep | 352 | | NULL |

    | 3 | root | localhost | NULL | Sleep | 301 | | NULL |

    +----+------+-----------+--------------------+---------+------+----------+------------------+

    其中Thread id=2 为update会话,说明系统有锁

    另一会话执行 update t set a=2 where id=1;

    ERROR 1205 (HY000): Unknown error 1205 MySQL error code 1205 (ER_LOCK_WAIT_TIMEOUT): Lock wait timeout exceeded; try restarting transaction

    提交第一个会话查看生成的binlog

    ### INSERT INTO `sysbench`.`t`

    ### SET

    ### @1=1 /* INT meta=0 nullable=0 is_null=0 */

    ### @2=2 /* INT meta=0 nullable=1 is_null=0 */

    # at 858

    #181217 14:28:21 server id 9012 end_log_pos 889 CRC32 0xf96f7fcb Xid = 20

    COMMIT/*!*/;

    # at 889

    #181217 14:42:14 server id 9012 end_log_pos 930 CRC32 0x3de034ba Rotate to bin.000089 pos: 4

    SET @@SESSION.GTID_NEXT= 'AUTOMATIC' /* added by mysqlbinlog */ /*!*/;

    DELIMITER ;

    # End of log file

    /*!50003 SET COMPLETION_TYPE=@OLD_COMPLETION_TYPE*/;

    /*!50530 SET @@SESSION.PSEUDO_SLAVE_MODE=0*/;

    发现没有update的binlog产生,也就是说该语句在server层没有实际执行

    用hexdump对比update前后的数据行,事务id和回滚id也没变,说明innodb没有实际更新行。

    鉴于该语句产生行锁,有事务信息,没有实际修改,可判断innodb在更新前后值一样时不会实际更新数据

    评论3

    1.事务执行过程中,binlog像redo log一样记录到binlog_cache里,单独的内存,redo log buffer

    2.redo log buffer设置成全局参数,Binlog cache size也是global 的,5.5~5.7

    相关文章

      网友评论

        本文标题:15 | 答疑(一):日志和索引相关问题

        本文链接:https://www.haomeiwen.com/subject/cuazsctx.html