读书笔记

书名 :Mysql技术内幕 第二版

第一章

  1. mysql整体架构: 连接池,管理服务及工具组件,sql接口(dml,ddl等),解析器,优化器,缓冲(cache & buffer),存储引擎,物理文件。
  2. innodb 特性:mvcc,next key lock,二次写,插入缓冲,自动哈希索引等特性。
  3. innodb 属于单进程多线程架构。

第二章

  1.   mysql 线程类型: IO thread(write、read、insert buffer、log io) ,master thread, 锁监控,错误监控,purge thread(5.6.5 开始默认使用,从master中剥离,用于undo资源回收),page cleaner thread(将刷脏页的任务委托至此)。
  2. 缓冲池内容
  •    缓冲池中管理的数据页包括:索引页,数据页,undo页,insert buffer, adaptive hash index, 锁信息,数据字典信息等。innodb_buffer_pool_size控制使用内存大小,通过 innodb... pool_instances进行内存缓冲池实例配置(多个实例可以提高性能,存在缓冲池级别的互斥等待时)。

   instances 讨论见https://dba.stackexchange.com/questions/115139/optimal->number-of-mysql-innodb-buffer-pool-instances
   判定instances方法如下(照抄上面回答)

为了确定缓冲池是否存在争用,互斥锁SHOW ENGINE INNODB STATUS在高峰时间收集了十几个样本。
然后使用shell片段聚合它:
#!/bin/sh
cat $1.innodb | grep "Mutex at " | cut -d"," -f1 | sort | uniq -c > /tmp/tmp1.txt
cat $1.innodb | grep "lock on " | cut -d"-"
-f2- | sort | uniq -c > /tmp/tmp2.txt
cat /tmp/tmp1.txt /tmp/tmp2.txt | sort -n > $1.contention rm /tmp/tmp1.txt /tmp/tmp2.txt
这给出了这样的输出:
.....
4 lock on RW-latch at 0x7fb86b2c9138 created in file dict/dict0dict.c line 1356
6 lock on RW-latch at 0x7fb86b2c4138 created in file dict/dict0dict.c line 1356
12 lock on RW-latch at 0x7fb86b2d9538 created in file dict/dict0dict.c line 1356
20 lock on RW-latch at 0x7fb86b2db138 created in file dict/dict0dict.c line 1356
22 Mutex at 0x7fb86b28f0e0 created file btr/btr0sea.c line 139
30 lock on RW-latch at 0x7fb86b2ba938 created in file dict/dict0dict.c line 1356
36 lock on RW-latch at 0x7fb86b2bad38 created in file dict/dict0dict.c line 1356
71 Mutex at 0x7fb86b28ecb8 created file buf/buf0buf.c line 597
164 lock on RW-latch at 0x7fb86b28f0b8 created in file btr/btr0sea.c line 139
如果您看到缓冲池互斥等待的计数很高,那么就应该考虑多个缓冲池实例。在小于~48G的缓冲池上不太可能发生争用。
  1. 缓存池中默认最小的内存管理粒度为16k(页),以段、区、页不同粒度进行管理。

  2. 缓冲池页面淘汰通过LRU进行管理,不同于一般的LRU,其具体实现如下:
      新读取的页先插入到LRU中的midpoint中(innodb_old_blocks_pct控制),即插入到队列的尾部37%处。midpoint后的页称为old,前称为new。利用innodb_old_blocks_time控制数据页从midpoint到new.page made young 为old到new的过程。 page not made young innodb_old_blocks_time导致页存放于old中,未能存放于new中的状态。

    优势: 避免热点页被意外清除出缓存。
    ps: show engine innodb status 查看到的buffer pool 中database pages表示LRU管理的页面数量。其总量不等于BUffer pool size,因为不是所有的缓冲池页都使用LRU管理。

  3. 页压缩功能,分配方式如下(假如请求4k页面):

    1. 检查列表是否存在可用4k页,有则使用。
    2. 没有则检查8k页,将8k页划分为两个4k页面,将两个4k页面放入4k页列表中。
    3. 如果2失败,则请求一个16k页,将16k页划分为2个4k页,1一个8k页面,并且放入对应的列表中。
  4. checkpoint机制

   作用:通过lsn进行标识,刷新内存中脏页。使得redo log大小可控(无需记录checkpoint前的redo log)。

checkpoint类型如下:

  • sharp checkpoint(数据库关闭时回写脏页)
  • fuzzy checkpoint包括:
  1. master thread checkpoint
    master线程主动刷脏
  2. flush_lru_list checkpoint
    为保证LRU缓存中存在一定的可用空闲页,当可用空闲页不足时,强制将LRU尾端刷脏.5.6后page clean线程中执行以免导致用户查询(读)线程堵塞.
  3. async/sync checkpint(5.6后page clean线程中执行)
    作用:redo日志不可用时使用(比如说批量更改,更改的内容远远大于redo log的容量时触发):
    判定公式如下(此部分mysql文档中没有,类似于自适应刷写机制。):
    checkpoint_age = redo_lsn - checkpoint_lsn
    async water mark = 75%redo log file size
    sync water mark = 90%
    redo log file size
    1)大于75触发异步写(将一次写拆分为多次提交写,减少瞬间系统压力)。
    2)大于90出发同步写(减少redo占用)dirty page too much
  4. 脏页在缓冲池中的占比超过阈值进行checkpoint(阈值用innodb_max_dirty_pages_pct控制,默认为75%)
  1. Master thread 工作方式
    工作状态如下:
  •    loop
  •    backgroud(书中有错字)
  •    flush
  •    suspend

   loop:
   包含两种模式:每秒,10秒。

  1. 每秒操作:
    1 redo日志缓冲刷写到日志,包括尚未提交事务(大事务提交快的原理)。
    2 合并插入缓冲(可能)。
    3 最多刷新100个缓存池脏页(可能)。
       ps:5.6及以后将master中刷脏操作移到page cleaner thread中。并且刷页数量innodb_io_capacity控制并判断(不再是100个硬编码,而是5%innodb_io_capacity,小于则写入),并且通过innodb_max_dirty_pages_pct控制刷新界限为75%。5.6以以后版本通过自适应刷写,通过特定算法低于75%脏页时也动态刷脏。
  • 当前没有任何活动,线程切换到background。
  1. 每10秒操作:
    1 刷新100个缓存池脏页。(可能)
       ps:5.6以后刷脏操作移到page cleaner thread中。并且刷页数量innodb_io_capacity控制并判断(100%innodb_io_capacity,小于则写入)。
    2 合并最多5个脏页
       ps:5.6以后通过innodb_io_capacity控制,5%为阈值。不同于每秒,10秒循环中必定执行。
    3 日志缓冲刷写到日志。
    4 删除无用的undo页(full purge操作)。
       ps:5.6后通过onnodb_purge_batch_size控制最大回收undo页数。
    刷新100个缓存池脏页。5.6后超过70%脏页比,刷新所有脏页。低于则刷新10%innodb_io_capacity。

background:
1 删除undo页
2 合并插入缓冲
3 跳到主循环
4 跳转到flush loop中。

flush loop:
判定脏页比是否大于70%,并执行相关刷脏机制。

  1. Innodb特性:
  • 插入缓冲

参考资料:
insert buffer FAQ
About change buffer from mysql website.
The analysis of source code for mysql 5.6 change buffer .
Ps: Insert buffer也是一个持久化的对象,虽然它也存在于缓冲池中。利用刷脏机制持久化于磁盘中。
   早期利用insert buffer作为非聚簇索引在索引不唯一的情况下,缓存非聚簇索引的插入更新和插入操作。索引不唯一保证了,数据库无需先查找索引页在进行插入,减少了随机读写。5.6及以后利用了chage buffer代替insert buffer,并且支持delete操作。
  inserte buffer实际还是一颗b+树,并且是一个全局唯一树。非叶子节点有search key(4字节space表空间id(每一个表id都唯一),marker1字节,offset表示页偏移量。),叶子节除search key后接metadata和插入记录。配合使用insert buffer bitmap实现merge管理。
触发insert buff merge的情况
1 辅助索引页被读取到缓冲池(select 时先查insert buffer bitmap,若查找的数据页存在于insert buffer树中触发合并)。

2 master thread循环执行merge,merge的页选定为随机选定。
3 insert buffer中辅助索引插入后,该页可用空间小于 1/32,执行merge。

  • 两次写(保证页写入部分失效下正常恢复)
  • 自适应哈希
  • 异步io配合刷新临近页

第三章文件

  1. Mysql中的参数类型:
  • 动态参数
  • 静态参数

  静态参数一般需要停机更改,实例运行时为只读状态。

  1. Mysql中的日志:

日志文件:

  • 错误日志(error log)

  • 二进制日志(binlog)
       记录除select、show以外的语句对数据库执行的操作。每一个事务都拥有单独的缓存,当事务过大内存无法放入,将放入临时文件。事务提交时,将缓存内容写入二进制文件中。

  作用如下:

  1. 数据恢复。
  2. 复制。
  3. 审计

  binlog格式:

  1. statement(语句)
  2. row(行更改)
  3. mixed(混合)
  • 慢查询日志(slow query log)

   5.6.5后除慢查询外,增加未使用索引语句记录。5.1后可使用slow_log表(需要更改log_output为table)进行检索。慢查询中还引入了逻辑读取和物理读取,还能指定捕获超过一定逻辑读取次数的语句。

  • 查询日志(log)

   记录了所有请求信息,包括语句是否执行。

  1. 套接字文件:
    用于socket连接。

  2. pid文件:
      保证只能启动唯一一个mysql主程序,后缀为 .pid

  3. 表结构定义文件:
      后缀为 .frm文件。

  4. 表空间文件:
       可以由多个文件组成一个表空间,通过不同文件放入不同的磁盘中,以提高性能。针对不同的表可以设置独立的表空间,独立表空间文件后缀为 .ibd。独立表空间仅仅存储表的数据,索引,插入缓冲bitmap等信息。其余信息如:undo(5.5后可以调整为独立空间)log、插入缓冲、系统事务、二次写缓冲等仍然存放于共享表空间中。

  5. 重做日志(redo log):
       一般文件名字类似为ib_logfileXXX。会有多个文件,以类似循环写入的方式进行记录数据。重做日志过小会导致频繁的磁盘读写,过大导致异常恢复时间过长。不同于binlog,binlog仅仅在事务提交时执行一次写入,redo无论事务是否提交,都会不断写入,这也是事务能够快速提交的原因。注意,redo log先写入 redo log buff 然后再写入redo log file。

redo log是持久性的保证。为了实现redo log 的同步刷新,还必须将innodb_flush_log_at_trx_commit调整为1。(0代表1秒一次同步写;1代表事务commit时同步写;2代表异步写;)

第四章 表

  1.   innodb引擎中表都具有主键,并且数据根据主键组织。假如建表时没有显示指定索引,mysql将会按照以下方式选定主键:假如有非空唯一索引,则利用该索引;不符合上述情况,则自动创建一个6字节大小的指针作为主键。
  2. 表空间结构
    表空间(共享表空间)物理文件为ibdataXXX。一般情况下,表空间会随着undo log的使用而增大。当undo空间被full purge时(GC),文件不会缩小,回收的空间会标记为可用空间以供下次使用。
    表空间组成如下:
    • 段(segment)

      段一般有数据段,索引段、回滚断等。数据段是b+树的叶子节点,索引端为b+树的非叶子节点。

  • 区(extent)

      区是由页组成的,每一个区大小为1M,按照默认页16k计算,一共由64个页组成一个区。

  • 页(亦称块 page/block)

      页默认大小为16k,可以调整。除了一般页外,还有压缩页。每一个段初始使用时,先使用32个碎片页,然后再申请64个连续页。
    页类型如下:

    • 数据页
    • undo页
    • 系统页
    • 事务数据页
    • 插入缓冲位图页
    • 插入缓冲空闲列表页
    • 未压缩的二进制大对象页
    • 压缩的二进制大对象页
  1. innodb是一个以行的方式进行存储数据。
  2. innodb行存储格式
    Antelope文件格式如下:
  • compact

compact行记录格式如下:

变长字段长度 NULL标志位 记录头信息 数据

1) 变长字段长度
  变长列长度小于255使用一个字节表示,否则使用两个字节表示。单行变长最大长度为65535(实际刨去头开销等,小于该值。这也是为什么单表所有varchar字段类型总字节不能超过65535的原因,会引起报错,需要使用blob,text等字段类型。)
2) NULL标志位
  NULL标志位表明该行数据中是否含有NULL值,占用一个字节,有则用1表示那一列数据存在null值(011代表第一,第二列)。
3)记录头信息
  占用5字节,包括标识该行是否删除、记录类型(叶子节点等)、页中的下一条记录的相对位置等。

  1. 数据部分除了正常的列数据以外,还包括了两列隐藏列:事务ID列、回滚指针列,放在真实数据前。 当使用定长char数据类型时,字段未能占用所有空间时,利用0X20占用空间。
  • redundant
    Barracuda文件格式:

Barracuda不同与Antelope,存放的blob数据类型全部采用行溢出的形式存放,数据页中存放的是指针。

  • Compressed
    利用zlib进行压缩。
  • Dynamic
  1. 行溢出数据
    为什么会存在行溢出?
      页大小为16k即16384b大小,而行最大为65536,所以varchar,blob,test等数据类型都可能会导致行溢出。blob,text虽然是大对象。但是当他们小于一页范围内,不存在行溢出的问题。行溢出通过利用多个页进行解决。详细见官网
      注意,页大小65536但是不意味着能够放入65536个字符串,能够放入的字符需要根据编码转换为对应的字节数占比计算得出。从MySQL5.x开始,char中表示的字符串数,不是字节数。

  2. Innodb数据页结构
    Innodb数据页由以下部分组成:

    • File header (文件头,大小固定,38个字节)
        组成部分按顺序如下:checksum,表空间中页偏移值,前向页,后向页,最后一次修改LSN号,存储页类型,flush_lsn(特殊用途),表空间ID。
    • Page header(页头,大小固定,56字节)
        记录了数据页的状态信息。包括:Slot(槽数),堆中第一个数据的指针,堆中记录数,指向可重用空间的首指针,已删除记录数,最后插入记录位置,最后掺入方向,修改当前页的最大事务ID,当前页在索引树的位置,索引ID标识所属索引等。
    • Infimun 和Supermum Records
      虚拟行记录,Inf比任意键值都小,Sup比所有键值大。
    • User Records(用户行记录)
    • Free Space(空闲位置)
    • Page Directory(页目录)
      存放了记录的相对位置(不是偏移量),是一个稀疏目录(一个目录对应多条记录,又被称为slot)。通过二叉查找,找到对应记录。
    • File Trailer(页尾信息,大小固定,8字节)
      由checksum和Lsn组成,并且与Fileheader中的checksum、Lsn对应。当两者不一致时,说明页出现残损。该办法保证了页的完整性。
  3. 数据完整性

  ps:关系模型中三大完整性为:1实体完整性(非空主键)。2参照完整性(外码)。3 用户自定义完整性。

完整性实现方式如下:

  • Primary key(主键)
  • Unique key
  • Foreign key (外键)
  • Default(列的数据类型,包括使用enum,set类型。)
  • 触发器
  • Not null
  1. 视图(view)
      一般而言(mysql不支持物化视图,可以用真实表实现物化视图。),视图是一个虚表(视图中的数据并不真实存放,数据只是存放在构成视图的实表中)。通过更改视图,可以改变实表的值。

9.分区

以下言论未经证实,不负责。只是从理论猜想,未有能力进行测试。有机会会验证。
  ps:对于读的影响: 分区后可能存在当一次查询需要多树查询结果合并时,本来一次树查询复杂度为log2xxN提高到 Mxlog2xxN(M为分区数))。更为致命的是本来是存放在相邻位置的数据再次被变为散列存储,引发随机读取。整体可能导致查询耗时上升。 因此,要想尽可能的提高单库,单磁盘下的性能,应该将单表分区,并且分区部署在不同磁盘上。(抛开坏处而言。分区不一定提高速度,但是可以提高系统上限。将读负载划分到不同磁盘上。)
  对于写的影响:多盘分区后从理论上讲在redo log写上限到达前,能够提高页刷脏性能(将写负载划分到不同盘上)。分区后假如存在Null值,将会导致数据倾斜在左边。
  Notice Notice注意,书上说Mysql innodb不能使用单表多磁盘。官网描述是Innodb会出现安全bug(通过指定directory越权访问其他表格)。(实测Winodws、Linux下5.7可以实现单表多磁盘。)

PS: 分区前需要确保Mysql有对应文件夹的linux权限。
CREATE TABLE users (
       uid INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
       name VARCHAR(30) NOT NULL DEFAULT '',
       email VARCHAR(30) NOT NULL DEFAULT ''
)
engine='InnoDB'
PARTITION BY RANGE (uid) (
       PARTITION p4 VALUES LESS THAN (3000000)
       DATA DIRECTORY = '/data/data',
       PARTITION p6 VALUES LESS THAN (6000000)
       DATA DIRECTORY = '/data/data2'
);

Mysql中分区类型:

  •   Range分区
      连续区间分区。
  •   List分区
      离散区间分区。
  •   Hash分区
      自需要指定分区的值和分区数,并且利用hash函数进行划分分区。
  •   Key分区
      类似Hash分区,不同的是利用数据库内置划分函数进行分区。
  • Columns分区
      以上四种分区都需要指定分区类型为整型(非整型需要转换为整型),Columns就是可以直接利用非整型数据进行分区。
  1. 子分区
      分区的基础再分区,mysql可以在Range和List基础上,进行Hash和Key分区。

第五章 索引与算法

  1. 索引类型
  • B+树(聚簇索引,非聚簇索引)
    聚簇索引物理上页并不是一定是连续分布的。非聚簇索引在innodb都需要回表查找。

  B+树是一种平衡树,叶子节点都是有序数据,通过指针连接数据页(叶子节点)。非叶子节点为索引页。
  为了提高空闲数页的利用率,内部一般不会马上进行数据页的拆分。一般会通过移动数据页内的数据,以提高利用数据页内的空闲空间。实际上中间节点的选择会考虑页的page header的的过去参入情况进行选择中间节点,以进一步合理的使用页。
  B+树插入方式(考虑插入时,各种页面是否以满,对应不同的插入方式。)

叶子节点(数据页) 非叶子节点(索引页) 插入方式
未满 未满 直接将数据插入到数据页中
以满 未满 1) 拆分叶子节点,将叶子节点中间节点放入到索引页中。 2)小于中间节点的放入左边叶子节点,大于等于的记录放入右边叶子节点。
已满 已满 1) 拆分叶子节点,将叶子节点中间节点放入到索引页中。 2)小于中间节点的放入左边叶子节点,大于等于的记录放入右边叶子节点。3)类似数据页拆分的方式进行拆分。唯一不同的是,将索引页的中间节点建立为新的节点,然后中间节点的左右数据作为该节点的左右孩子。

B+树删除操作
填充率=页占用数/页总空间数

叶子节点小于填充率 非叶子节点小于填充率 删除方式
No No 直接删除该条记录,假如改记录为index中的节点,将改节点的右节点作为索引节点代替。
Yes No 合并叶子节点和它兄弟节点,同时更新index中的节点。
Yes Yes 1)合并叶子节点和它兄弟节点。2)更新index中的节点。3)合并index和它兄弟节点。
  • 全文索引
  • 哈希索引
  1. 索引选择参照值Cardinality
    Cardinality = 索引值条数/总记录数(当索引值唯一时候该值为1,否者小于1。越解决1,越能说明索引的区分度,一定程度上索引值建立比较合理。)
  2. 索引的特性
  • 联合索引(前缀覆盖原则)
  • 索引结果合并(假如有a,b列索引。当数据扫描时,将会利用a,b列结果合取交集取出对应的回表主键值。)
  • 范围扫描容易导致解析器不使用索引(哪怕是索引范围覆盖在非聚簇索引中,可以通过FORCE INDEX 强制使用索引。USE index命令并不会强制使用index)。
  • MRR优化(开启后会自动将上述范围问题转化为键值查找问题,以使用索引。但是经过实测,MRR在一定范围内有效,当扫描范围过大,MRR会失效。)
  • ICP(查找条件下放到索引中,减少无关数据页的调入到内存中。)
  • 自适应哈希算法(数据库内部实现,无法控制。将多次访问键值语句以哈希的形式进行查找。)
  1. MRR

  MRR能够优化range,ref,eq_ref类型的查询。
  MRR通过将主键排序以提高性能。减少了缓冲池页面的替换,批量处理对键值的查询(将范围查询变为键值查询)。

第六章 锁

  1. lock与latch区别
lock latch
对象 事务 线程
保护 数据库内容 内存数据结构
持续时间 整个事务过程 临界资源
模式 行锁、表锁、意向锁 读写锁、互斥量
死锁 wait for graph、time out机制进行死锁检测与处理 无死锁检测与处理机制。仅通过加锁顺序保证无死锁现象
存在于 Lock Manager的哈希表 每个数据结构的对象
  1. innodb锁类型
  • 表级

  意向共享锁
  意向排他锁
实际实现中,一般情况下意向锁不会阻塞除全表扫描以外的任何请求。

  • 行级

  共享锁
  排他锁

  1. 查看锁信息

  通过information_schema下的表innodb_trx、innodb_locks、innodb_lock_waits查询可以得知锁相关信息

  1. MVCC
  2. 一般情况下(rr等级及以下隔离等级),Select没有显示声明则没有任何锁,锁也不会影响select的读。如果需要select中加锁,S锁则用select lock in share mode;X锁select for update。
  3. 自增长与锁

  Innodb内部实现的自增长,原始是通过表锁实现,通过语句粒度进行申请,而不是事务粒度进行申请以保证性能。由于原始实现并发性能差,5.1版本后通过互斥量实现更轻量级自增实现,并且按照插入类型不同提供不同方式的自增支持(innodb_autoinc_lock_mode调整自增策略)。
  对于自增列,必须是索引且是索引第一列。

  1. 外键与锁

  外键父表的插入、删除、更改会导致子表的相关外键插入、删除、更改被阻塞(锁引起)。原因是每一次子表的相关操作会以S锁的形式对父表进行Select以实现外键约束,后续才进行相关的插入、删除、更改操作。

  1. innodb 行锁实现方式
  • Record Lock

锁定记录本身。

  • Gap Lock

锁定一个范围,不包括记录本身。

  • Next-key Lock

锁定一个范围,并包含记录本身。

PS PS PS以下讨论环境为REPEATABLE READ, READ COMMITTED锁的范围会远小于REPEATABLE READ.
  只会对update,select for update ,select for lock in share mode, delete产生next key lock 和gap lock;insert 是record lock ;
  对于被范围锁定的语句来说,锁定的对象为where后的条件。Update会锁定扫描过的列,因此当条件中没有索引列,可能会引起锁表(可以通过explain 大致查看锁定范围,锁定对象)。当where条件中中包含非索引列和索引列,锁定只考虑索引列。
  除了扫描的键值是索引是唯一索引(或者是主键),才能将Gap lock 和Next key lock变为Record Lock。多个index可选时候,可以通过explain查看sql选定的index。


官网关于锁定详解


测试案例如下


#测试数据如下
 ![6470c763acb04507ec05f54f19da989d.jpeg](en-resource://database/784:0)
 # a, b
10, 0
20, 2
30, 5
40, 7
50, 10

#建表语句
CREATE TABLE `lock_t` (
   `a` int(11) DEFAULT NULL,
   `b` int(11) DEFAULT NULL,
   KEY `l_1` (`a`)
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
 
 #session A
 begin;
select * from mytest.lock_t;
update mytest.lock_t set a=21 where a=20;

#session B
begin;
select * from mytest.lock_t;
insert into mytest.lock_t values(16,1);
#fail
insert into mytest.lock_t values(9,1);
#successful 
insert into mytest.lock_t values(10,1);
# fail
insert into mytest.lock_t values(30,1);
# successful 

==============================
#建表语句
CREATE TABLE `lock_t` (
   `a` int(11) DEFAULT NULL,
   `b` int(11) DEFAULT NULL,
   KEY `l_1` (`a`)
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
 #session A
 update mytest.lock_t set a=23 where b= 2;
#session B
insert into mytest.lock_t values(999,11);
fail
insert into mytest.lock_t values(46,6);
fail
insert into mytest.lock_t values(26,6);
fail

--------------------------------------------
rollback all session
---------------------------------------------
#session A
begin;
select * from mytest.lock_t;
update mytest.lock_t set a=25 where a= 20;

#session B
begin;
select * from mytest.lock_t;
insert into mytest.lock_t values(30,2)
successful
insert into mytest.lock_t values(29,2);
fail
insert into mytest.lock_t values(10,2);
fail
insert into mytest.lock_t values(9,2);
successful

锁定的对象为where 后所接的条件,例子如下

#建表语句
CREATE TABLE `lock_t` (
  `a` int(11) DEFAULT NULL,
  `b` int(11) DEFAULT NULL,
  KEY `l_1` (`a`),
  KEY `l_2` (`b`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8

#数据同上一例子
#session A
begin;
select * from mytest.lock_t;
update mytest.lock_t set b=1 where a= 30;

#session B
begin;
select * from mytest.lock_t;
insert into mytest.lock_t values(35,2);
fail
insert into mytest.lock_t values(35,1);
fail
insert into mytest.lock_t values(45,1);
successful

利用以上例子还能发现mysql锁定方式为[xxx,bbb)的锁定方式,比如update set a=30 ...则锁定[20,30)的范围内的a值。

  1. sql 并发问题
  • 脏读
  • 不可重复读
  • 丢失更新
  • 幻读
  1. 锁问题
  • 阻塞
  • 死锁

第七章 事务

  1. 数据库特性
  • A 原子性
  • C 一致性
  • I 隔离性
  • D 持久性
  1. 事务的类型
  • 扁平事务
  • 带保存点的扁平事务
  • 链事务
  • 嵌套事务
  • 分布式事务
  1. 事务的实现
  • redo log

  redo log由redo log buffer和redo log file组成。由于redo log最小单位为512个字节,因此无需double write保证同步写入的正确性。每一个redo log file都是通过循环顺序写的方式写入,每一次写入除了顺序写部分,还需要写入log file header 、checkpoint1 、null(占位)、checkpoint2(同checkpoint1,保证物理介质异常下,依然能够正确读取出checkpoint信息。) Redo log记录的是页的物理日志,因此其在ROW日志格式下具有幂等性。

  • undo log

  MVCC的具体实现方式,通过undo log记录数据的历史版本实现了MVCC和Rollback功能。undo log是逻辑日志,因此其需要double write 技术保证写入的数据完整。undo log也需要redo log同步写入,以保证数据不丢失。undo log决定了事务并发的理论上限。5.6开始可以通过指定独立空间以提升性能。
  undo log的资源回收依靠purge 线程进行处理。为了提供空间利用率,undo 页可以重用。undo log只有insert 和update(负责update和delete操作)两种类型。

  1. LSN
      log sequence number,其具有checkpoint位置,页版本和重做日志写入总量的作用。通过写入字节的累加,实现LSN号的变化。

  数据库恢复时,通过对比重做日志的LSN号和对应页的LSN号,得知页面是否需要从左,假如某一页P的LSN号大于等于该页redo log中的LSN号,则无需重做。

  1. Group commit
      为了进一步提供性能,innodb引入了redo log 的group commit功能,将多次同步写合并为一次同步写。
  2. 事务执行步骤
  • 没有binlog情况下:

  1) 修改事务的内存信息,并且将信息写入Redo log buffer(包括redo信息,undo信息),写入undo log等。
  2) 调用同步写,确保redo log从缓冲区写入到磁盘中。
  3) 事务提交,完成一个事务。

  • 具有binlog的情况下:

  binlog引入必须考虑存储引擎层写入和binlog写入顺序一致。
  1) 事务提交到innnodb引擎,进行prepare工作。
  2) Mysql上层写入binlog。
  3) innodb存储引擎层将日志写入重做日志(重复没有binlog 的步骤)。


    为了保证binlog和redo log顺序一致,引入prepare_commit_mutex锁。但是引入后导致group commit失效。

  • 5.6及以后解决方案
      BLGC

  BLGC的核心是引入有序队列。每一个事务从flush到内存,Sync阶段(binlog写入),commit阶段(存储引擎写入)。事务的写入顺序按照第一阶段的写入顺序写入。

  1. 事务提交的方式
  • 显式提交
  • 隐式提交
  1. 分布式事务
  2. 长事务(大事务)

核心思想就是锁定插入或者更改范围,然后通过切糕的方式小部分写入。

你可能感兴趣的:(读书笔记)