MySQL 5.1时,InnoDB存储引擎提供了Compact和Redundant两种格式来存放行记录数据,Redundant是为兼容之前版本而保留的 。
下面用一个具体事例来分析Compact行记录的内部结构:
create table mytest (
t1 varchar(10),
t2 varchar(10),
t3 char(10),
t4 varchar(10)
) engine=innodb charset=latin1 row_format=compact;
insert into mytest values('a','bb','bb','ccc');
03 02 01/*变长字段长度列表,逆序*/ 第一个varchar 1byte 第二个2byte,第三个3byte
00/*NULL标志位,第一行没有NULL值*/ 转为2进制是0000 0000,因为只有4个字段,只有后面0000起作用
00 00 10 00 2c/*记录头信息,固定5字节长度*/
00 00 00 2b 68 00/*RowID我们建的表没有主键,因此会有RowID*/
00 00 00 00 06 05/*TransactionID*/
80 00 00 00 32 01 10/*Roll Pointer*/
61/*列1数据'a' 长度01 */
62 62/*列2'bb' 长度02*/
62 62 20 20 20 20 20 20 20 20/*列3数据'bb'*/
63 63 63/*列4数据'ccc' 长度03*/
insert into mytest values('d',NULL,NULL,'fff');
03 01/*变长字段长度列表,逆序*/第一个varchar(‘d’)1byte 第二个('fff')3byte
06/*NULL标志位,第三行有NULL值*/转为2进制是0000 0110,只有后面0110起作用,表示第二,三为NULL
00 00 20 ff 98/*记录头信息*/
00 00 00 2b 68 02/*RowID*/
00 00 00 00 06 07/*TransactionID*/
80 00 00 00 32 01 10/*Roll Pointer*/
64/*列1数据'd'*/
66 66 66/*列4数据'fff'*/
是指显示字符的长度,不影响内部存储。
针对1,2,3,4,5,6,7,8,9的记录,如果插入10 将拆分成page1[1,2,3,4] , page2[5,6,7,8,9,10],由于是顺序(逆序)插入,page1将造成极大的浪费,
聚集索引
按照每张表的主键构造一颗B+树,并且叶节点存放着整张表的行记录数据
辅助索引
也称非聚集索引,叶节点不包含行的全部数据,只是存着(Key+主键)。
覆盖索引
就是从辅助索引中查询的记录,而不需要查询聚集索引中的记录。 好处就是辅助索引不包含整个行记录的所有信息,页面大小远小于聚集索引。因此可以减少大量的IO操作。
例设表t,(OrderID,ProductID)的联合索引
现在对表查询 select OrderID,ProductID from t
直接在我们的(OrderID,ProductID)的联合索引中可以拿的到,不用去聚集索引再拿。这种情况称为覆盖索引。
需要用的锁的时候自动加载,例 select for update, update ,delete;
当运行delete t where id =1;的时候,获取了 t 表的 IX, 获取了记录的 X;
针对表z,a为聚集索引,b是普通索引
create table z( a INT, b INT, PRIMARY KEY(a), KEY(b));
INSERT INTO z SELECT 1,1;
INSERT INTO z SELECT 3,1;
INSERT INTO z SELECT 5,3;
INSERT INTO z SELECT 7,6;
INSERT INTO z SELECT 10,8;
当运行select * from z where b = 3 for update;
聚集索引上id为5的会被上Record Lock,普通索引(1,3)和(3,6)被加上Gap Lock
Undo log:回滚而记录的这些东西称之为撤销日志。
- RR级别下,事务中的第一个SELECT请求才开始创建read view;
1. try_id < up_try_id , 数据在事务开始前已经提交改可见
2. up_try_id <= try_id <= low_try_id ,数据在事务开启前末提交,不可见
3. try_id>low_try_id, 数据在事务启动后被提交,不可见(后面的事务提交速度超过当前事务)
事务1.5开始的时候,事务列表为(1,1.5)。即计算得列表中最早末提交的事务 up_try_id = 1, 最晚提交事务low_try_id = 1.5
第一条记录事物try_id=null < low_try_id =1,可见
第二条记录事物 low_try_id =1 <= try_id= 1 < up_try_id = 1.5,不可见,因为还没提交.
第三条记录事物try_id= 2 > up_try_id = 1.5,不可见,当前事务之后被修改
log buffer根据一定的规则将log block刷新到磁盘:
- 事务提交时
- 当log buffer中一半的空间已经被使用
- log checkpoint时 (page页被刷新到磁盘的时候)
InnoDB的redo log是固定大小的,比如可以配置为一组4个文件,每个文件的大小是1GB,那么这块“粉板”总共就可以记录4GB的操作。从头开始写,写到末尾就又回到开头循环写,如下面这个图所示。
write pos是当前记录的位置,一边写一边后移,写到第3号文件末尾后就回到0号文件开头。当page页被刷新到磁盘的时候,redo log 会记录checkpoint,这样checkpoint之前的数据就可以擦除。当发生死机需要恢复时,只要拿磁盘上page数据+checkpoint到write_post的数据
ROW(行模式):记录那条数据修改了,注意:记录的是这条记录的全部数据,即使只更新了一个字段,binlog里也会记录所有字段的数据
Statement(语句模式): 每一条会修改数据的sql都会记录在binlog中。
Mixed(混合模式):在Mixed模式下,一般的语句修改使用statment格式保存binlog,如一些函数,statement无法完成主从复制的操作,则采用row格式保存binlog,MySQL会根据执行的每一条具体的sql语句来区分对待记录的日志形式,也就是在Statement和Row之间选择一种。
因为他们之间都是事务的保证,当事务成功这2都必须全部成功。 会存在一个内部XA的问题:事务在存储引擎层(redo)commit的顺序和在binlog中提交的顺序不一致的问题。2pc流程如下
第一阶段:InnoDB prepare,持有prepare_commit_mutex,并写入到redo log中。将回滚段(undo)设置为Prepared状态,binlog不做任何操作。
第二阶段:将事务写入Binlog中,将redo log中的对应事务打上commit标记,并释放prepare_commit_mutex。
MySQL以binlog的写入与否作为事务是否成功的标记,innodb引擎的redo commit标记并不是这个事务成功与否的标记。
崩溃时:
1. 扫描最后一个Binlog文件,提取其中所有的xid。
2. InnoDB维持了状态为Prepare的事务链表,将这些事务的xid与刚刚提取的xid做比较,若存在,则提交prepare的事务,若不存在,回滚。