我理解的myisam引擎之一 myisam表特征

MySQL的MyISAM引擎是基于旧有的isam引擎,并在其上添加了一些有用的扩展形成的。MyISM引擎有以下特征:

我理解的myisam引擎之一 myisam表特征_第1张图片这里来逐个分析这个YES or NO背后的逻辑。首先,MyISAM表存在两个文件(最新的8.0版本多一个sdi文件)MYI存放索引数据、MYD存放表数据。并且MYI使用的是B-tree结构,因此就支持了B-tree索引。接着备份和时间点恢复,这个是由mysql server提供的支持,因为所有的修改都已按照时间点顺序持久化到binlog中,这是server的机制,与MyISAM引擎不相干,属于搭便车行为。再下来,聚簇索引,innodb表的主键索引为clustered index,innodb表又称为index organized table索引组织表,表数据是按照主键索引的顺序有序存储的,整个一个innodb表就是一个索引。但是MyISAM表不是,它更像是oracle中的heap堆表,它的索引就是普通的B+树索引。并且myisam不强制要求必须存在索引,

虽然在创建表的同时创建了MYI文件,但如果没有创建索引的话,这个文件是空的(没有数据)。因此不支持clusterd index。接下来,Encrypted data已注明了是server实现的;外键约束不支持,这个就是不支持而已。值得一说的index caches,对应在内存中的结构是key buffer cache,内存大小由key_buffer_size参数控制。这个buffer区域是专门用来存储MyISAM表的索引数据以及update、insert、delete、select以及当myisam重建索引的时候(repair table、alter table、load data),如果myisam_max_sort_file_size的大小不够的话,也会使用。

再下来,lock granularity,个人理解与myisam对于表数据的访问方式有关。myisam表数据的访问完全基于操作系统的文件缓存,各个并发客户端都直接通过各自保留的MYD文件的描述符来操作数据,mysql并不干预这个过程。如果不使用表锁来互斥修改的话,很难保证不产生并发问题。想象一下两个客户端同时修改同一个文件块。

MVCC全拼是multiple version concurrent control,与transaction是相关的。多版本并发控制,为了满足某一个时刻开始的事务的读一致性(这里要区分两个概念,read和read for write是不同的。一个是consistent read,一个是lock read。),同一条记录同时存在多个版本。MVCC本质上是一条记录的多个不同时刻的快照,而存在这些快照的目的是为了满足transaction也即事务的一致性(这里回想下事务的ACID4个特性)。MyISAM虽然只有表锁,阻塞了写-写的并发,但是还是存在写-读并发,那么为什么不支持MVCC呢?这个问题要这样想,如果MyISAM支持了MVCC,那和innodb比起来,两者还有多少区别呢?MyISAM相比innodb最大的优势就是快(大吞吐量读写,至少在本人测试的场景下,相同资源条件下,MyISAM还是会比innodb较快)费这么大劲实现MyISAM版的MVCC,而且即使实现了,大概率也会丢掉MyISAM本身的优势,这是何苦。

最后值得说的是storage limit,这里列出了256TB。这是在默认myisam_data_pointer_size=6条件下的限制。这个参数的含义The default pointer size in bytes。字节为单位的默认的指针大小。6表示6个字节,256TB = 2的48次方 。这是对于默认的dynamic存储格式的表,myisam_data_pointer_size的值在MYI中以key_value:myisam_data_pointer_size形式使用,表示key_value键值对应的行在MYD文件的偏移myisam_data_pointer_size处。需要注意的是,对于fixed存储格式的表,由于每行所占空间固定,在MYI中是使用4字节的指针指向MYD中的行号,对应的行记录开始地址可以根据行号*行size可得出。更进一步,由于4字节的指针能表示的最大行号为2的32次方,所以得出myisam能存储的最大记录数为4294967296,但是文档中给出了最大记录数为2的32次方再平方,也即2的64次方,这就不知道用了什么奇技淫巧了,需要继续探索。根据文档描述myisam_data_pointer_size取值范围为不小于2,同时不大于7。那么当myisam_data_pointer_size=7的时候,storage limit就变成了2的56次方大小了。

除了上面表格中列出的这些指标,MyISAM还有其他一些特征。

mysql8.0的MyISAM不支持分区表。

1,所有数据(注意是数据,不是索引)以小端方式存储,以使数据与操作系统独立。

2,数值类型的key(mysql术语,在mysql的语境中是索引的意思) 值以大端方式存储,以使用压缩。

3,支持的文件大小最大可到63bit,也就是2的63次方减1。MYI和MYD文件的长度都是以8字节来表示。

4,支持的最大行数为2的64次方。原文是这样描述的:There is a limit of (232)2 (1.844E+19) rows in a MyISAM table。个人猜测 是使用了2个四字节的指针。

5,单表最大索引数量为64,单索引包含最大16列。索引block大小默认1024,最大16k。这些都没啥,只是mysql源码中使用了对应长度的字节数来表示长度而已。需要注意的是有两个索引block的概念需要区分,一个是内存中的key cache buffer使用的索引block大小,这个由参数key_cache_block_size控制,一个是索引文件MYI中的keys使用的块大小,这个在创建表或者索引时可以单独指定KEY_BLOCK_SIZE参数。默认是1024。这个仅仅是个hint,mysql会根据自身需要修改这个值。

6,当索引单调增的时候,例如使用auto_increment列,索引块的分裂使用类似于2/8分裂,包含较大数据的节点仅包含一个key。

与MyISAM特定的变量有下面这些:

我理解的myisam引擎之一 myisam表特征_第2张图片

bulk_insert_buffer_size:myisam表批量插入优化使用的内存,适用于insert..select...、insert into ..values..、load data等批量操作。默认是8M大小。

concurrent_insert:控制MyISAM是否写-读并发。取值范围

NEVER、AUTO、ALWAYS也可设置为对应的0、1、2值。NEVEL,禁用写-读并发;AUTO,如果MYD文件没有碎片的话,那么insert默认是在文件末尾,这个时候是可以并发的,但是如果MYD文件中间有碎片,那么insert会往数据文件中间的空闲空间插入,此时不能并发。碎片是由于delete或者将较长值update为较短值产生的;ALWAYS,无论是有有碎片,插入都发生在文件末尾,此时可以并发。任何看起来美好的东西都在不经意的地方有其代价,concurrent_insert也一样。https://bugs.mysql.com/bug.php?id=36618描述了一个问题,insert大体上会经历锁表、插入、释放锁三个阶段。mysql server会在insert过程中的第三个阶段完成前就会给客户端返回成功。问题在于,客户端收到了insert成功的返回,但此时释放锁并没有完成,并且在负载较高的情况下,这个完成时间是不确定的。根据https://dev.mysql.com/doc/internals/en/myisam-concurrent-insert.html这里的描述,select是通过读取一个state.state副本的方式实现与insert的并发,当insert  session锁未释放,select使用的state.state未更新,因此即使在客户端得到insert成功的返回,此时并发的select仍然读取不到此insert的成功的记录。

delay_key_write:这个是很多myisam插入优化文章都会提到的,但是也是误解较多的参数。

这个变量有两个层面的意思,一个是系统变量,一个是myisam表级别的设置项。
关于这个变量,原文解释如下:
This variable specifies how to use delayed key writes. It applies only to MyISAM tables. Delayed key writing causes key buffers not to be flushed between writes.If DELAY_KEY_WRITE is enabled for a table, the key buffer is not flushed for the table on every index update, but only when the table is closed. 
系统变量层面的意思如下:
This variable can have one of the following values to affect handling of the DELAY_KEY_WRITE table option that can be used in CREATE TABLE statements.

Option    Description
OFF    DELAY_KEY_WRITE is ignored.
ON    MySQL honors any DELAY_KEY_WRITE option specified in CREATE TABLE statements. This is the default value.
ALL    All new opened tables are treated as if they were created with the DELAY_KEY_WRITE option enabled.

注意这里的第一句话"to affect handling of the DELAY_KEY_WRITE table option",系统变量层面的DELAY_KEY_WRITE的作用并不是直接作用于key_buffer_cache上,而是通过影响表级别上的DELAY_KEY_WRITE选项,进而影响key_buffer_cache。是“系统变量DELAY_KEY_WRITE-->表选项DELAY_KEY_WRITE-->key_buffer_cache”这样一个作用链。如果系统变量DELAY_KEY_WRITE设置为OFF,则忽略表级别上的设置,效果等同于表级别DELAY_KEY_WRITE=0。如果系统变量DELAY_KEY_WRITE设置为ON(这是默认值),则尊重表级别DELAY_KEY_WRITE的设置,表级别的DELAY_KEY_WRITE可以在创建表时指定,也可以通过alter table DELAY_KEY_WRITE = {0|1}的方式修改。这里值得注意的是,DELAY_KEY_WRITE设置为大于1的值,效果等于1。并且如果你的表已经很大了,那么需要慎重修改,因为会重建表。虽然采用的是online的方式,但是在重建过程中,ddl还是会阻塞。如果系统变量DELAY_KEY_WRITE设置为ALL,那么无论表级别DELAY_KEY_WRITE设置为何值,新open的表都会视为DELAY_KEY_WRITE=1。有两种方式可以查看表级别DELAY_KEY_WRITE的设置,一种是select CREATE_OPTIONS from information_schema.tables t where t.TABLE_NAME = '';如果为空,则是0。如果不为空,会显示delay_key_write=1。另外一种是查看sdi文件,sdi文件全称是Serialized Dictionary Information。这是一个文本文件,作用是做一个相对于数据字典表空间的冗余,如果字典表空间损坏,可以使用这个恢复。innodb的sdi内容存放在表头,myisam表有一个单独的sdi文件,里面记录表的元数据信息就包括DELAY_KEY_WRITE的值。

这个变量的主要的作用就是延迟key buffer 从内存刷新flush到disk。按照文档描述,如果不使用lock table ,unlock table包裹的insert,那么在每次insert完成后都会flush key buffer 到磁盘,按照我的测试结果来看,可以通过lock或者批量插入的方式并且适当加大一次insert的记录数(当然,这会带来另外一个问题,一次insert包裹的数据变多了,那么网络传输的package也变大了,从客户端到mysql服务器之前的网络延迟会加大,网络延迟会成为阻碍tps增高的主要因素,这个必须要考虑。)来做优化。相比于这个选项带来的风险来说,不应过分看重这个选项带来的性能提升。另外,在多次测试过程中,发现既使没使用lock,也没有设置DELAY_KEY_WRITE=all,同时表级别DELAY_KEY_WRITE=0的情况下,在Key_blocks_unused变为0之前,Key_writes也没有增长。

have_rtree_keys:控制是否允许rtree索引。与空间索引有关。

key_buffer_size:存放MyISAM表索引数据的内存大小。 MyISAM引擎表的索引数据是以block的形式存放在内存中供所有线程共享访问的,就这一点来看与innodb的buffer pool使用方式基本雷同。对于存在索引的myisam表的dml来说,这是一个对性能影响至关重要的参数。对于key cache的优化,放在另外一篇里面写。

log_isam:指定日志文件路径。将所有myisam的修改都记录到这个日志文件中。

myisam-block-size:看到这里的中划线了吗?对,这是一个启动选项,只能在启动时设置并且不能动态修改,或者设置在配置文件中,或者在启动命令行上。围绕这个参数应该是一个系统变量还是一个启动选项产生了一些争议。https://bugs.mysql.com/bug.php?id=34363  这个参数的意在设置MYI文件的block大小。默认值为1k,最大16k,最小1K。通常来说应该与操作系统block相同或者倍数关系,才能取得较好性能,如果小于系统block大小,就存在写读的问题:假如系统block大小4k,这里使用默认值1k,那么如果要写入一个1k大小的block,需要先将整个4k读出来,更新,然后再写进去。如果都是4k的话,就直接写。修改myisam-block-size不影响已存在的MYI文件。如果要修改已存在的MYI,需要重建索引。意义接近的有另外两个参数key_cache_block_size、key_block_size。key_cache_block_size是系统变量,可在参数文件中设置,也可动态修改。用于设置内存中存放索引的块大小,最大值16k,最小值512字节,默认1k;key_block_size作为参数项出现在create、alter ddl语句中,与myisam-block-size意义相同,都是控制MYI文件块大小。区别在于如果修改myisam-block-size默认值,并且ddl中没有指定key_block_size,则key_block_size默认等于myisam_block_size的值;如果即设置了myisam-block-size也指定了key_block_size,则MYI文件块使用key_block_size块大小。MYI的块大小有两种意义,一种是其中各个key包含的索引块的大小,一种是MYI文件头大小。举个栗子,myisam-block-size=4096,那么MYI中文件头占用4k大小,接下来就是一个个4k大小的块,组成各个key内容。假如一张表包含2个key,那么MYI初始大小包含一个4k文件头块,和2个分别属于两个key的4k块。随着插入数据越来越多,文件头块大小不变,2个key分别以4k大小的块进行扩展。具体可以通过解析MYI文件观察。按照https://dev.mysql.com/doc/internals/en/the-myi-file.html的描述,可以发现,mysql使用2字节来表示key_block_size的大小,不仅可以看到单块大小,还可以看到单块的最大大小为64k。

myisam_data_pointer_size:字节为单位的myisam索引指针长度。默认6个字节,取值范围为不小于2不大于7。myisam索引指针类似oracle中的rowid,在myisam表行格式为固定长度时,索引指针值包括了有序的行号;如果行格式为dynamic的,那么指针表示在数据文件中偏移地址,因此myisam table最小64k,最大大小为65535TB。那么问题来了,当包括有序行号的时候,如果从中间删除了记录,那么其余的索引指针应该不变,下一次在删除的地址插入新记录的时候,这个指针值会赋值给新记录。

myisam_max_sort_file_size:设置重建索引时使用的最大临时文件大小。如果索引文件大于这个值的话,那么会用到key cache,会降低性能,此时增大设置会提高性能(这点不理解)。If the file size would be larger than this value, the index is created using the key cache instead, which is slower. The value is given in bytes.If MyISAM index files exceed this size and disk space is available, increasing the value may help performance. 

myisam_mmap_size:映射到压缩myisam表MYD文件的内存的最大大小。如果有太多压缩表的话,可以降低这个值以避免内存交换到swap分区。

myisam_recover_options:设置myisam存储引擎的recover mode。取值为OFF、DEFAULT、BACKUP、FORCE、QUICK。可以同时设置多个值例如:myisam_recover_options=BACKUP,FORCE,QUICK如果未设置值,默认为DEFAULT,如果设置为空值“”,则等于OFF。如果不为OFF的话,那么就启用recover,因此在mysql启动的时候,会检查MYI文件以确定表是否已损坏或者未关闭。如果设置为BACKUP,那么在recover过程中如果有数据变化,会先备份MYD文件,save a backup of the tbl_name.MYD file as tbl_name-datetime.BAK;如果设置为FORCE,那么即使在recover过程中需要删除超过1条以上记录,也会继续进行恢复操作;如果设置为QUICK,那么如果没有删除的block的话,就不检查表行记录;如果设置为DEFAULT,相当于非BACKUP、非FORCE、非QUICK。如果设置为OFF,那么在启动时不进行recover过程。

myisam_repair_threads:并行恢复的线程数。是在索引级别进行并行,一个索引一个thread。并不是多个thread一个索引。

myisam_sort_buffer_size:设置在进行repair table、create index、alter table等动作需要进行索引排序时可以分配的内存大小。

myisam_stats_method:进行索引统计信息收集时,如何处理null值。取值有nulls_equal、nulls_unequal、nulls_ignored。字面意思。

myisam_use_mmap:控制是否使用mmap函数读写myisam表。mmap主要的通过将文件内容映射到共享内存的方式加快文件读写。通常当select、insert、update、delete 发生在inside MYD时会提高myisam的性能,不包括在文件末尾insert数。但这个参数需要仔细测试,如果机器内存不足,可能造成mysql crash。与myisam_mmap_size结合使用。https://www.percona.com/blog/2006/05/26/myisam-mmap-feature-51/

tmp_table_size:mysql内部内存中临时表大小。对用户创建的memory表不起作用。在实际使用中通常与 max_heap_table_size 是min的关系,即实际生效的值为min(tmp_table_size,max_heap_table_size )。实际使用中,当有较大数据量的group by、order by操作时,如果tmp_table_size太小,mysql会将内存临时表转为磁盘临时表,会降低性能。可以通过Created_tmp_disk_tables 和Created_tmp_tables两个status 变量观察Created_tmp_disk_tables/Created_tmp_tables比值,越小越好。

另外一个有较大影响的参数,PACK_KEYS,这个参数在建表时指定或者通过alter table修改。取值为0或者1或者DEFAULT。如果不设置的话,则为DEFAULT。取值为0则禁用所有类型的key压缩;取值为1则启用包括 CHARVARCHARBINARY, or VARBINARY 以及numbers(int、biging、long等数值类型)类型的列压缩,取值为DEFAULT则仅启用 CHARVARCHARBINARY, or VARBINARY类型的列压缩。对于通常的索引结构为storage_size_for_key + pointer_size,如果启用压缩,那么相邻的完全相同的key值和pointer,仅需要两个字节。大大减少了索引大小。启用压缩,对于读性能有较大提高,因为相同的block包含的的记录数更多,但是会削弱写性能。
MYI文件大小可以近似按照(key_length+4)/0.67*records的公式计算(这只能作为近似值,因为8.0最新版本格式有更新)。key_length为所有索引key的列长度,4是指指针长度(最新的版本默认指针长度6)。这种是没有任何key压缩的最坏情况。
You can roughly calculate the size for the index file as (key_length+4)/0.67, summed over all keys. This is for the worst case when all keys are inserted in sorted order and the table doesn't have any compressed keys.

参考:

https://dev.mysql.com/doc/internals/en/the-myi-file.html

https://dev.mysql.com/doc/refman/8.0/en/myisam-storage-engine.html

你可能感兴趣的:(mysql)