作者:SparkOnly
InnoDB架构 内存架构 Buffer Poolinnodb_buffer_pool_size,正常推荐设置50%-75%的系统内存
Change Buffer
在内存中,change buffer占用部分的buffer pool。在磁盘上,change buffer是系统表空间的一部分,当数据库服务器关闭的时候,索引的更改将在其中进行缓存
Adaptive Hash Index
Log Buffer
磁盘结构 表 Tables
索引 Indexes
表空间 Tablespaces
system表空间
File-Per-Table表空间
General表空间
Undo表空间
Temporary表空间
Doublewrite Buffer
Redo Log
Undo Logs
一个事务最多分配4个undo logs,每个都是下面的操作类型之一:
INSERT操作,用户定义的表 
UPDATE和DELETE操作,用户定义的表 
INSERT操作,用户定义的临时表 
UPDATE和DELETE操作,用户定义的临时表 
InnoDB锁和事务模型 InnoDB锁 共享锁和排他锁 意向锁 Intention Locks 表级锁,用于指示事务稍后在表里需要哪种类型的锁(共享或排他)
SELECT … FOR SHARE设置IS锁,SELECT … FOR UPDATE设置IX锁
事务在能获取表里行的共享锁之前,必须先获取表的IS锁或更强的锁
记录锁 Record Locks 在索引记录上的锁
间隙锁 Gap Locks 索引记录间隙之间的锁,或者第一个索引记录之前或最后一个索引记录之后的间隙的锁
对于使用唯一索引搜索唯一行的语句,不需要间隙锁。e.g.列id有唯一索引,下面这语句只对id=100的行使用索引记录锁,其他会话是否在前面的间隙中插入行并不重要
SELECT  * FROM  child  WHERE  id  = 100 ;如果id没有被索引,或者没有唯一索引,则语句会锁定前面的间隙
InnoDB的间隙锁是“纯抑制性的”,它的唯一目的是阻止其他事务插入到这个间隙,间隙锁可以共存,共享和排他间隙锁没有差别
间隙锁是可以被禁用的,如果被禁用可能会导致幻读问题,因为其他session可以将新行插入间隙
Next-Key锁 是索引记录上的记录锁和索引记录之前间隙上的间隙锁的组合
(negative infinity, 10] 默认InnoDB运行在REPEATABLE READ事务隔离级别。这种情况下,InnoDB使用next-key锁来进行搜索和索引扫描
插入意向锁 Insert Intention Locks 行插入之前通过INSERT操作设置的一种间隙锁。此锁表示插入的意图,如果插入到同一个索引间隙的多个事务不在间隙的同个位置插入,则它们无需互相等待
mysql> CREATE TABLE child (id  int (11 ) NOT NULL , PRIMARY KEY(id )) ENGINE=InnoDB;id ) values (90 ),(102 );id  > 100  FOR UPDATE;id   |102  |客户端B开始一个插入记录到间隙的事务。这个事务在它等待获取排它锁时接受插入意向锁
mysql>  START TRANSACTION; mysql>  INSERT INTO child (id) VALUES (101); AUTO-INC 锁 是一种特殊的表锁,事务插入有AUTO_INCREMENT列的表的时候使用。如果一个事务正在向表插入值,任何其他事务在插入这个表的时候都必须等待,以便第一个事务接收连续的主键值
InnoDB事务模型 事务隔离级别 REPEATABLE READ (默认级别)
READ COMMITTED
e.g. 可重复读 VS. 读已提交 
CREATE  TABLE 
INT  NOT  NULL , b INT ) ENGINE  = InnoDB ;INSERT  INTO  t VALUES  (1 ,2 ),(2 ,3 ),(3 ,2 ),(4 ,3 ),(5 ,2 );COMMIT ;这种情况下,表没有索引,因此搜索和索引扫描时使用隐式的聚簇索引来进行记录锁定
### Session A 执行更新语句 START  TRANSACTION ;UPDATE  t SET  b = 5  WHERE  b = 3 ;### Session B 在sessionA之后执行 UPDATE  t SET  b = 4  WHERE  b = 2 ;在InnoDB执行每个更新的时候,它先为每一行获取一个排它锁,然后决定是否修改它。如果InnoDB不修改这行,它就会释放这个锁。否则InnoDB持有锁,直到事务结束。
当使用默认的可重复读 级别时,第一个UPDATE在它读取的每行上获取x锁,不释放它们中的任何一个:
x-lock(1,2); retain x-lock
第二个UPDATE在尝试获取任何锁的时候会立即阻塞(因为第一个更新在所有行上保留了锁),并且在第一次UPDATE提交或回滚之前不会继续
x-lock(1,2); block and wait for first UPDATE to commit or roll back
如果使用READ COMMITTED,第一个UPDATE会在每行上获取一个x锁,并释放这些不修改的行
x-lock(1,2); unlock(1,2)
对于第二个UPDATE,InnoDB执行“半一致”读,将读取的每一行的最新版本返回给MySQL,以便MySQL可以确定该行是否匹配UPDATE的WHERE条件
x-lock(1,2); update(1,2) to (1,4); retain x-lock
然后如果WHERE条件包含索引列,并且InnoDB使用这个索引,那么在获取和保留记录锁的时候只考虑索引列。
CREATE  TABLE  t (a INT  NOT  NULL , b INT , c INT , INDEX  (b)) ENGINE  = InnoDB ;INSERT  INTO  t VALUES  (1 ,2 ,3 ),(2 ,2 ,4 );COMMIT ;### Session A START  TRANSACTION ;UPDATE  t SET  b = 3  WHERE  b = 2  AND  c = 3 ;### Session B UPDATE  t SET  b = 4  WHERE  b = 2  AND  c = 4 ;READ UNCOMMITTED 读未提交
SERIALIZABLE 串行化
autocommit, Commit, Rollback InnoDB中,所有用户活动都是在事务里。如果autocommit模式启用,每个SQL语句都会形成它自己的事务。默认MySQL为每个新连接启动session的时候,会设置autocommit为启用,所以MySQL会在每个SQL语句没有返回错误之后进行一次提交。如果语句返回错误,会依赖具体的错误进行提交或回滚
一致非阻塞读 一致读意味着InnoDB使用多版本控制呈现数据库在某个时间点的一个快照。查询可以看到在这个时间点之前其他事务已提交的更改,不能看得到之后或未提交的事务。
例子(其他session提交记录,原session查询不到,可以删除):
#sessionA  select  count (name ) from  child  where  name  = 'hello100' ;### 返回0行 #sessionB插入两行并提交 insert  into  child (id ,name ) values (100 , 'hello100' );insert  into  child (id ,name ) values (101 , 'hello100' );#sessionA统计,返回0行 select  count (name ) from  child  where  name  = 'hello100' ;#sessionA删除,尽管查询不到,但可以删除2行 delete  from  child  where  name  = 'hello100' ;### Query OK, 2 rows affected (0.00 sec) 可以通过提交事务,然后执行SELECT或START TRANSACTION WITH CONSISTENT SNAPSHOT来更新时间点。这就是多版本并发控制 
            Session A              Session BSET  autocommit=0 ;      SET  autocommit=0 ;SELECT  * FROM  t;set INSERT  INTO 
VALUES  (1 , 2 );SELECT  * FROM  t;set COMMIT ;SELECT  * FROM  t;set COMMIT ;SELECT  * FROM  t;--------------------- --------------------- 如果想看到数据库的最新状态,那么要使用READ COMMITTED隔离级别,或者锁定读:
SELECT * FROM t FOR SHARE;
InnoDB中不同SQL语句设置的锁 锁定读,UPDATE或者DELETE通常会在SQL语句处理的过程中对每个被扫描到的索引记录设置记录锁。它不管语句中的WHERE条件是否会排除掉行。InnoDB不会记得准确的WHERE条件,只知道哪部分索引范围被扫描。如果没有索引适合执行语句,那么MySQL必须扫描整个表来处理语句,表的每一行都会被锁定,从而也会阻塞其他用户插入到这个表中。所以创建好的索引,让查询不会扫描超过需要的行是很重要的 。
InnoDB中的死锁 因为不同的事务持有对方需要的锁,导致这些事务不能执行下去。由于每个事务都在等待资源变成可用,都不会释放它持有的锁。
为了减少死锁的可能,
使用事务而不是LOCK TABLES语句; 
保持insert或update的事务足够小,让他们不会长时间打开; 
当不同的事务更新多个表或者大范围的行,在每个事务里使用相同顺序的操作(例如SELECT … FOR UPDATE); 
在SELECT … FOR UPDATE和UPDATE … WHERE 语句中使用到的列上创建好索引 
如果使用锁定读(SELECT … FOR UPDATE或者SELECT … FOR SHARE),尝试使用例如READ COMMITTED这样的低隔离级别 
添加选择度高的索引到表中,这样查询只需要扫描更少的索引记录,相应的也就设置更少的锁。使用EXPLAIN SELECT来查看 
死锁的可能不会被隔离级别影响,因为隔离级别只改变了读操作的行为,而死锁是因为写操作。
InnoDB多版本控制 MVCC InnoDB是一个多版本的存储引擎。它持有被更改过的行的旧版本信息,以支持例如并发和回滚这样的事务特性。这个信息存储在undo表空间里面一个叫rollback segment的数据结构中。InnoDB使用回滚段里的信息来执行事务回滚里面的undo操作。它还使用这信息来构建更早版本的行用于实现一致性读。
6-byte DB_TRX_ID,表示最近一个插入或更新行的事务的事务标识符。删除在内部也是被视为更新,行里面一个特定的bit会设置以标识它为已删除 
7-byte DB_ROLL_PTR,滚动指针。它指向回滚段里的一条undo日志记录。如果行被更新了,这条undo日志记录会包含更新前重建行所需要的必要信息 
6-byte DB_ROW_ID,包含了随着新行插入而单调递增的行ID。如果InnoDB自动生成了一个聚簇索引,索引会包含行ID的值。否则DB_ROW_ID列不会出现在任何索引里 
回滚段里的Undo日志被分成了插入和更新的undo日志。插入的undo日志只在事务回滚的时候需要,可以在事务提交的时候立即丢弃掉。更新的undo日志也用于一致性读,但只有在不存在InnoDB已为其分配快照的事务时才能丢弃。在一致性读中,快照需要更新的undo日志中的信息用于构建数据库行的早期版本。
多版本和二级索引 InnoDB 多版本并发控制(MVCC)对待二级索引不同于聚簇索引。聚簇索引中的记录会就地更新,其隐藏的系统列指向undo日志项,从中可以重构早期版本的记录。二级索引不包含隐藏的系统列,也不进行就地更新。
MVCC和幻读 幻读 ,同一个事务里连续执行两次同样的SQL,可能导致不同结果的问题。第二次sql语句可能会返回之前不存在的行。
select  ... lock  in  share  mode select  ... for  update insert update delete 在RR级别:
MySQL事务的实现原理 binlog binlog包含描述数据库更改(如表创建操作或表数据变更)的“事件”。除非使用基于行的日志记录,否则它还包含可能已进行更改的语句的事件(例如不匹配任何行的删除)。binlog还包含更新数据时每个语句花费了多长的时间。
MySQL默认隔离级别为什么是可重复读 数据库默认隔离级别
为什么mysql用可重复读(RR)而不是读已提交(RC)
为什么默认隔离级别很多选用RC