《Mysql技术内幕》札记(中)

                      第四章  

一、innodb逻辑结构

Innodb存储引擎表,每张表都有个主键,如果没有显示的主键,则innodb存储引擎会按如下的方式选择或创建主键。

  1. 是否有非空的唯一索引,如果有即为主键

  2. 不符合上述条件,innodb自动创建一个6个字节大小的指针

表空间由段,区,页组成。

Innodb_file_per_table选项,每张表的表空间内存放的只是数据,索引和插入缓冲。其他类型undo信息,系统事务信息,二次写缓冲等还是存放在原来的共享表空间内。即使在Innodb_file_per_tableOn的情况下,共享文件依然会增大。在执行事务时会产生大量的undo操作,当rollback后,共享表空间也不会缩减至原来大小。当发现不需要时将此空间标记为可用空间,供下次undo使用,这时你会发现下次再执行update语句时,共享表空间没有再增大

表空间由分散的页和段组成。段常见的有数据段,索引段和回滚段。区有连续的64个页构成,每个区大小为1MB,大于大的数据段,最多可以申请4个区。然而在每个段开始,先申请32页大小的碎片页来存储,当这些页使用完之后才是申请连续的64个页。

行:每个页最多7992行记录。

行格式:默认为compact格式

mysql> show table status like'user_ship'\G

*************************** 1. row***************************

          Name: user_ship

        Engine: InnoDB

       Version: 10

    Row_format: Compact

          Rows: 277974

 Avg_row_length: 115

   Data_length: 32047104

Max_data_length: 0

  Index_length: 13156352

     Data_free: 5242880

 Auto_increment: NULL

   Create_time: 2014-12-22 01:30:10

   Update_time: NULL

    Check_time: NULL

     Collation: utf8_unicode_ci

      Checksum: NULL

 Create_options:

       Comment: 我的舰船

1 row in set (0.14 sec)

 

Compact行记录格式

变长字段长度列表 NULL标识位 记录头信息
1数据 2数据 ………

变长字段:不能超过2个字节,2个字节一共16位,即spacer.gif=65535个字节,因此varchar最大长度不超过65535个字节。

NULL表示位:标识该数据中是否有NULL值,有则显示1

记录头信息,占5个字节。

NULL占用标识位不占用任何空间。

还有两列为事务id列和回滚指针列,若Innodb没有定义primarykey 每行会增加一个6字节的rowid列。

记录头信息:

该行是否被删除 该行是否为最小记录 该记录拥有记录数 索引堆中该条记录额排序记录

记录类型

(指针/普通节点)
页中下一条记录的相对位置

二、分区表

水平分区:同一个表中不同行的记录分配到不同的物理文件中
垂直分区:同一表中不同列分配到不同的物理文件
分区中即存了数据又存了索引
分区类型:
range分区:给定连续区域区间存入不同分区(year(),todays(), tosecond(),unixtimestamp()只能包含这些函数的一个,不然将全表扫描) 
List分区 离散的值存入不同分区

Column分区  不需要整形数据 是升级版(时间列用了比较多)
Hash分区 自定义表达式返回值进行分区 
Key分区 根据哈希函数分区
分区的列如果表中有唯一索引或主键索引,那必须他是其中的一部分,不然会报A PRIMARY KEY must include all columns in the table's partitioningfunction如果没有这种唯一索引,他可以任何个列。
Hash分区根据给定的列除以分区个数决定分区在哪 

Colunmn分区

create table div_colum(id int ,d datetime )partition by rangecolumns (d) (partition p0 values less than ('2015-10-01'),partition p1 valuesless than ('2015-11-01'),partition p2 values less than maxvalue);

根据d分区 d小于2015-10-01或者为Null的存在p0分区里,同理。。。

insert into div_colum values (1,'2015-09-01');  

insert into div_colum values (2,'2015-12-01'); 

insert into div_colum values (3,'null');

插入时如果不在定义的分区范围将会报错

mysql> select * from information_schema.PARTITIONS whereTABLE_SCHEMA='freedom' and TABLE_NAME='div_colum'\G

*************************** 1. row ***************************

                TABLE_CATALOG:def

                 TABLE_SCHEMA:freedom

                   TABLE_NAME:div_colum

               PARTITION_NAME:p0

            SUBPARTITION_NAME:NULL

   PARTITION_ORDINAL_POSITION:1

SUBPARTITION_ORDINAL_POSITION: NULL

             PARTITION_METHOD:RANGE COLUMNS

          SUBPARTITION_METHOD:NULL

         PARTITION_EXPRESSION:`d`

      SUBPARTITION_EXPRESSION:NULL

        PARTITION_DESCRIPTION:'2015-10-01'

                   TABLE_ROWS:2

               AVG_ROW_LENGTH:8192

*************************** 2. row ***************************

                TABLE_CATALOG:def

                 TABLE_SCHEMA:freedom

                   TABLE_NAME:div_colum

               PARTITION_NAME:p1

            SUBPARTITION_NAME:NULL

   PARTITION_ORDINAL_POSITION:2

SUBPARTITION_ORDINAL_POSITION: NULL

             PARTITION_METHOD:RANGE COLUMNS

          SUBPARTITION_METHOD:NULL

         PARTITION_EXPRESSION:`d`

      SUBPARTITION_EXPRESSION:NULL

        PARTITION_DESCRIPTION:'2015-11-01'

                   TABLE_ROWS:0

               AVG_ROW_LENGTH:0

 

*************************** 3. row ***************************

                TABLE_CATALOG:def

                 TABLE_SCHEMA:freedom

                   TABLE_NAME:div_colum

               PARTITION_NAME: p2

            SUBPARTITION_NAME:NULL

   PARTITION_ORDINAL_POSITION:3

SUBPARTITION_ORDINAL_POSITION: NULL

             PARTITION_METHOD:RANGE COLUMNS

          SUBPARTITION_METHOD:NULL

         PARTITION_EXPRESSION:`d`

      SUBPARTITION_EXPRESSION:NULL

        PARTITION_DESCRIPTION:MAXVALUE

                   TABLE_ROWS:1

               AVG_ROW_LENGTH:16384

  可以看到表PARTITIONS,知道分区情况 ,p0有两个数据,p2有1个

 

-rw-rw----. 1 mysql mysql 8.4K Nov 24 10:05 div_colum.frm

-rw-rw----. 1 mysql mysql   32 Nov 24 10:05 div_colum.par

-rw-rw----. 1 mysql mysql 96K Nov 24 10:13 div_colum#P#p0.ibd

-rw-rw----. 1 mysql mysql 96K Nov 24 10:05 div_colum#P#p1.ibd

-rw-rw----. 1 mysql mysql 96K Nov 24 10:06 div_colum#P#p2.ibd

可以看到分区表的存储是均等分配的,每个分区有独立的文件。

删除数据直接drop这个分区

mysql> alter table div_colum drop partition p0;

Query OK, 0 rows affected (0.04 sec)

Records: 0 Duplicates: 0  Warnings: 0

 

mysql> select * from div_colum;

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

| id   | d                   |

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

|    2 | 2015-12-0100:00:00 |

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


子分区
在分区上再分区
每个子分区数量必须相同
子分区要么全显示定义要么都隐式定义
分区可以规定分区放在哪 索引放在哪

create table div_colum_key(id int ,d datetime )ENGINE=innodb partition by range(month(d)) SUBPARTITION BY HASH(month(d))(partition p0 values less than (10)(SUBPARTITION s0 data directory='/opt/data0' index directory ='/opt/index0', SUBPARTITION s1 data directory='/opt/data1'  index directory='/opt/index1'));

只能运用range list分区法,并且字分区只能用hash 和key,存储地址直接加 data directory,index directory进行存储

 

-rw-rw----. 1 mysql mysql 8.4K Nov 24 11:09div_colum_key.frm

-rw-rw----. 1 mysql mysql   40 Nov 24 11:09 div_colum_key.par

-rw-rw----. 1 mysql mysql   47 Nov 24 11:09 div_colum_key#P#p0#SP#s0.isl

-rw-rw----. 1 mysql mysql   47 Nov 24 11:09 div_colum_key#P#p0#SP#s1.isl

文件中包含isl,真正的数据在指定目录里

/opt/data1/freedom/ div_colum_key#P#p0#SP#s1.ibd 子分区文件在指定文件夹中再加入了库名

分区表在获取大量数据而且一定要走这个列速度提快
分区列不作为搜索条件时会进行n个分区倍次数的io执行


第五章  索引与算法

一、索引简介

索引的多少需要找到合适的平衡点。如果索引太多,磁盘的使用率会增加(索引字段的更新需要随机读)。索引太少,查询性能会被影响(查询速度和行锁的个数)。

Innodb支持两种索引:B+树索引和哈希索引。Innodb的哈希索引时自适应式哈希索引,系统自动为表建,不需要认为干涉。B+树索引就是数据库常见的索引方式,b+树是平衡树,b+树索引不能直接找到记录,而是找到页,将页读入内存,在内存中innodb根据page diretory中的槽二分查找定位页中的记录。

平衡二叉树查询速度快,最快的是最优二叉树,但维护成本高,一般性平衡二叉树运用在内存里。

B+树维护时避免更多拆分采用旋转将数据放到兄弟节点。B+树索引有其高扇出性,b+树一般都在2-3层,意味着查某个值记录最多需要2,3io,一般的磁盘每秒至少可以做100Io

B+树索引可以分为聚集索引和辅助索引。聚集索引和辅助索引是否叶子节点存储一整行记录。

Innodb存储引擎是索引组织表,即逻辑上表中数据都根据主键存放,物理存储堆方式顺序存储,一个表只能拥有一个聚集索引。定义了逻辑顺序,通过集聚索引很容易找到记录。

   辅助索引一般包括键值和书签,innodb是索引组织表,所以他的书签就是他的主键值。搜索数据时遍历索引并通过页节点指针指向主建 然后通过主件索引找到完整记录行。Sqlsever存储堆方式,书签就是用文件号,页号,槽号。这样的方式在查询时不需要通过主键虽然快了,预读避免了这其中离散读的情况,但是在插入,更新,删除等更新时,需要不断的更改书签。这样会更慢。然而在排序和范围查询时,索引组织表可以通过访问中间节点就可以查找读取数据是一种顺序读。

mysql> alter table a add indexa(name(5));

可以对列的前几个字符建索引。

事务一

mysql> start transaction ;

mysql> delete from a;

 

事务二

mysql> alter table a add index ab(name);

^CCtrl-C -- sending "KILL QUERY2069" to server ...

Ctrl-C -- query aborted.

可以看到在加索引时会有排斥锁的存在。

在建主键索引时,他会新建一张临时表,然后将数据导入该表,删除原表,再把临时表重命名

索引详细的查看

mysql> show index from a\G

*************************** 1. row***************************

       Table: a

  Non_unique: 1 是否为非唯一索引

    Key_name: a 索引名

 Seq_in_index: 1 联合索引时,次序列号将一致

 Column_name: name 列名

   Collation: A 列的存放方式

 Cardinality: 1   唯一值的数量

     Sub_part: 5   如果是部分索引,表示索引前几个字符

      Packed: NULL   如何被压缩 没有压缩则为Null

        Null: YES     是否索引含有Null

  Index_type: BTREE    

     Comment:

Index_comment:

Collation/行数是否接近1,优化器以Collationexplain rows是否小于一个阀值来确定是否走索引。但是有时会不准,需要analyze table t来优化。

强制走索引select * from c force index(PRIMARY);

预读:就是通过一次Io,将多个页预读到缓冲池,并且多个页马上会被访问,

预读分为随机预读和线性预读。随机预读(效果不好),当一个区(64个页)中13个页在缓冲区中,并且在lru列表前端(页的频繁访问)。则将这个区的剩余所有页预读到缓冲区。线性预读:基于页的访问模式,如果一个区的24个页都被顺序的访问,则innodb会读取下一个区的所有页。

mysql> show variables like'%read_ahead%';

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

| Variable_name               | Value |

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

| innodb_random_read_ahead    | OFF  |

| innodb_read_ahead_threshold |56    |

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

innodb_random_read_ahead显示随机预读已关

innodb_read_ahead_threshold 表明一个区多少页在缓冲池里就将下一个区的所有页读入缓冲池

二、辅助索引的利用

mysql> select * from id_var;

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

| id | name |

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

| 2 | a    |

| 1 | b    |

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

可以看到利用辅助索引来得到结果,那为什么不利用主键呢?因为查出来数据包括主键数据和索引数据,都可以在辅助索引页中找到,并且辅助索引页能存放数据多,所以优化器就选择直接利用附注索引来查找得到数据

select * from id_var order by iddesc;

但涉及到主键排序,将走聚集索引。 因为聚集索引已经排好序 

联合索引

CREATE TABLE `c` (

 `id` int(11) DEFAULT NULL,

 `dd` datetime DEFAULT NULL,

 KEY `id` (`id`,`dd`)

)

mysql> insert into c values (1,'2016-08-01');

mysql> insert into c values(2,'2015-08-01');

联合索引第一:加快了联合键值查询的速度

mysql> explain select * from cwhere id=2 and dd='2015-08-01';

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

| id | select_type | table | type| possible_keys | key  | key_len |ref         | rows | Extra       |

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

| 1 | SIMPLE      | c     | ref (根据索引查找值) | id            | id  | 11      | const,const |    1 | Using index |

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

1 row in set (0.03 sec)

 

mysql> explain select * from cwhere dd='2015-08-01';

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

| id | select_type | table |type  | possible_keys | key  | key_len | ref  | rows | Extra                    |

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

| 1 | SIMPLE      | c     | index |NULL          | id   | 11     | NULL |    2 | Using where; Usingindex |

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

可以看到是索引遍历。对第二个索引列进行查找时,虽然他在索引页中出现,但是这课联合索引的B+树不是根据第二列所排序组成的(即无序),所以需要全索引遍历

联合索引第二:可以对后一个键值进行排序

mysql> explain select * from cwhere id=1 order by dd desc;

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

| id | select_type | table | type| possible_keys | key  | key_len |ref   | rows | Extra                    |

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

| 1 | SIMPLE      | c     | ref | id            | id   | 5      | const |    1 | Using where;Using index |

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

 

mysql> explain select *  from c  order by dd desc;

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

| id | select_type | table |type  | possible_keys | key  | key_len | ref  | rows | Extra                       |

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

| 1 | SIMPLE      | c     | index | NULL          | id  | 11      | NULL |   3 | Using index; Using filesort |

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

mysql> show status like'sort_rows';#共排序了几个数据

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

| Variable_name | Value |

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

| Sort_rows     | 3    |

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

对比发现第二个语句就是没有用文件中完成好的排序。sort_rows统计也上升了。

 

 

第六章 锁

一、一致性非锁定读操作

Innodb读默认非锁定一致性读,即读取正在执行的dml行的操作时,不需要等待锁的释放
Innodb用行多版本来读取当前行,读取快照是指该行之前版本的数据,历史数据,因此历史数据不需要加锁
READ-COMMITTED和REPEATABLE-READ,都是采用快照,但快照的定义不一样,READ-COMMITTED是被锁定行最新版本,REPEATABLE-READ是事务一开始的版本。

二、容易忽视的锁
外建约束与自增也会锁表,(详细介绍:
你容易忽视的mysql外键锁和自增锁

#外建约束必须加索引才能建立。
脏页在缓存中的页与磁盘不同 在刷新到磁盘前日志被写到重做日志里?
脏数据读到未提交的数据

三、阻塞

innodb_lock_wait_timeout用来控制等待时间默认50秒,此参数可以动态修改,但是是在新的链接里才能生效。
innodb_rollback_on_timeout超时是否回滚,默认不回滚
死锁:a锁住一行,b锁住一行,b需要访问a的那一行被锁住,a需要访问b的那一行,也锁住,死锁后报1213错误,系统自动回滚a的事务

mysql> select * from q;

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

| id | b    |

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

|  1 |    1 |

|  2 |    2 |

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

 

        事务a         事务b

mysql> start transaction;

Query OK, 0 rows affected (0.00 sec)



mysql> delete from q where id=1;

Query OK, 1 row affected (0.00 sec)




mysql> delete from q where id=2;

ERROR 1213 (40001): Deadlock found when trying to get lock;try restarting transaction

 

mysql> delete from q where id=2;

ERROR 1213 (40001): Deadlock found when trying to getlock; try restarting transaction





mysql> select * from q;

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

| id | b    |

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

|  1 |    1 |

|  2 |    2 |

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

2 rows in set (0.00 sec)




 delete from qwhere id=2;

 Query OK, 0rows affected(0.00 sec)



 mysql> deletefrom q where id=1

Query OK, 1 row affected (4.98 sec)










mysql> select * from q

Empty set (0.00 sec)


死锁发生,可以看到事务1发生了回滚。


你可能感兴趣的:(mysql,MySQL技术内幕)