Author Archive
Tuesday, November 18th, 2008
Innodb 作为 MySQL 中使用最为广泛的 事务型存储引擎,不仅在事务实现数据版本控制方面和其他存储引擎有一定的区别,其数据结构也是以非常有特点的方式存储的。
每个Innodb表的数据其实可以说就是以一个树型(B-Tree)结构存储的,表的数据和主键(Primary Key)共同组成了一个索引结构,也就是我们常说的Innodb的Clustered Primary Key。在这个Clustered Primary Key中,Leaf Nodes其实就是实际的表记录,我们常规理解上的索引信息全部在Branch Nodes上面。
除了Clustered Primary Key之外的其他所有索引在Innodb中被称为Secondary Index。Secondary Index就和普通的B-Tree索引差不多了,只不过在Secondary Index的所有Leaf Nodes上面同时包含了所指向数据记录的主键信息,而不是直接指向数据记录的位置信息。
所以,在 Innodb 中,如果主键值占用存储空间较大的话,会直接影响整个存储 Innodb 表所需要的物理空间,同时也会直接影响到 Innodb 的查询性能。
下面是画的一张 Innodb 索引基本结构图,包括 Primary Key 和 Secondary Index 两种索引的比较。
原文出自: Innodb 索引结构了解 - Innodb Index Structure
Posted in 大话技术 | No Comments »
Friday, November 14th, 2008
随着 BI (DW) 在各个企业中重要性的不断提升,各个数据库厂家都希望能搭上这辆班车。这不,MySQL 也联合 Infobright 一起推出了开源的 数据仓库解决方案,而且是开源的。
其实现的各种DW该有的功能就不多说了,但是 Infobright 有一点非常吸引人的技术特点不能不提,那就是以列为导向的架构设计。
以列为导向的架构设计是非常适合于DW应用场景的,对于大多数DW的分析场景中,实际关注的数据很多时候都只有那么一列或者少数几列的数据。所以在以列为导向的设计中,大部分的分析查询都只需要读取某一个(或者几个)表的几列,而不需要像传统以行为导向的数据库(或者存储引擎)那样需要扫描整个表的数据,这两者IO量的差距是非常大的。除了以列为导向的架构设计之外,Infobright 和很多其他的DW解决方案一样,也会进行数据压缩,而且由于其以列为导向的存储方式,压缩比率在很多情况下都会比以行为导向的存储方式更高,效果更理想。有人通过测试比较,常规的以行为导向的存储数据压缩比率较高的时候也就 3:1 左右,但是 Infobright 的却很容易就做到 10:1 的压缩比率。
此外,从MySQL 以及 Infobright 的官方报道中除了上述技术特点(或者说优势)之外,还有很多其他的被描绘的非常神奇的功能,如被称为 “知识网格” (Knowledge Grid) 的自我管理功能,完全不需要索引或者分区,神奇的自我查询优化器等等。
这里是官方给出的一张 Infobright 的架构图:
感兴趣的朋友可以通过自行阅读其 技术白皮书 获取更多的细节
原文链接:MySQL 的 DW 解决方案(MySQL + Infobright)
Posted in 大话技术 | No Comments »
Thursday, October 23rd, 2008
开始着手写这本书也有40来天了,期间有不少热心网友不断的送来非常好的建议,也得到了身边同事和朋友的支持,目前进展基本还算顺利。
由于多方面原因的考虑,和出版社商量之后决定暂停正在前面两“基础”和“维护”这两篇的写作,而先转向第三篇“优化”的写作,完成第三篇之后也会继续往后直接进行“架构设计”部分的写作,然后再转回完成第一篇和第二篇的内容完善。
谢谢各位朋友的支持!
原文首发: Sky.Jian 朝阳的天空
原文链接:MySQL数据库优化与架构设计一书写作计划调整
Posted in 大话技术, 生活八卦 | 7 Comments »
Wednesday, September 24th, 2008
1、复制进程
Mysql的复制(replication)是一个异步的复制,从一个Mysql instace(称之为Master)复制到另一个Mysql instance(称之Slave)。实现整个复制操作主要由三个进程完成的,其中两个进程在Slave(Sql进程和IO进程),另外一个进程在 Master(IO进程)上。
要实施复制,首先必须打开Master端的binary log(bin-log)功能,否则无法实现。因为整个复制过程实际上就是Slave从Master端获取该日志然后再在自己身上完全顺序的执行日志中所记录的各种操作。
复制的基本过程如下:
1)、Slave上面的IO进程连接上Master,并请求从指定日志文件的指定位置(或者从最开始的日志)之后的日志内容;
2)、Master接收到来自Slave的IO进程的请求后,通过负责复制的IO进程根据请求信息读取制定日志指定位置之后的日志信息,返回给Slave 的IO进程。返回信息中除了日志所包含的信息之外,还包括本次返回的信息已经到Master端的bin-log文件的名称以及bin-log的位置;
3)、Slave的IO进程接收到信息后,将接收到的日志内容依次添加到Slave端的relay-log文件的最末端,并将读取到的Master端的 bin-log的文件名和位置记录到master-info文件中,以便在下一次读取的时候能够清楚的高速Master“我需要从某个bin-log的哪 个位置开始往后的日志内容,请发给我”;
4)、Slave的Sql进程检测到relay-log中新增加了内容后,会马上解析relay-log的内容成为在Master端真实执行时候的那些可执行的内容,并在自身执行。
实际上在老版本的Mysql的复制实现在Slave端并不是两个进程完成的,而是由一个进程完成。但是后来发现这样做存在较大的风险和性能问题,主要如下:
首先,一个进程就使复制bin-log日志和解析日志并在自身执行的过程成为一个串行的过程,性能受到了一定的限制,异步复制的延迟也会比较长。
另外,Slave端从Master端获取bin-log过来之后,需要接着解析日志内容,然后在自身执行。在这个过程中,Master端可能又产生了大量 变化并声称了大量的日志。如果在这个阶段Master端的存储出现了无法修复的错误,那么在这个阶段所产生的所有变更都将永远无法找回。如果在Slave 端的压力比较大的时候,这个过程的时间可能会比较长。
所以,后面版本的Mysql为了解决这个风险并提高复制的性能,将Slave端的复制改为两个进程来完成。提出这个改进方案的人是Yahoo!的一位工程 师“Jeremy Zawodny”。这样既解决了性能问题,又缩短了异步的延时时间,同时也减少了可能存在的数据丢失量。当然,即使是换成了现在这样两个线程处理以后,同 样也还是存在slave数据延时以及数据丢失的可能性的,毕竟这个复制是异步的。只要数据的更改不是在一个事物中,这些问题都是会存在的。如果要完全避免 这些问题,就只能用mysql的cluster来解决了。不过mysql的cluster是内存数据库的解决方案,需要将所有数据都load到内存中,这 样就对内存的要求就非常大了,对于一般的应用来说可实施性不是太大。
2、复制实现级别
Mysql的复制可以是基于一条语句(Statement level),也可以是基于一条记录(Row level),可以在Mysql的配置参数中设定这个复制级别,不同复制级别的设置会影响到Master端的bin-log记录成不同的形式。
Row Level:日志中会记录成每一行数据被修改的形式,然后在slave端再对相同的数据进行修改。
优点:在row level模式下,bin-log中可以不记录执行的sql语句的上下文相关的信息,仅仅只需要记录那一条记录被修改了,修改成什么样了。所以row level的日志内容会非常清楚的记录下每一行数据修改的细节,非常容易理解。而且不会出现某些特定情况下的存储过程,或function,以及 trigger的调用和触发无法被正确复制的问题。
缺点:row level下,所有的执行的语句当记录到日志中的时候,都将以每行记录的修改来记录,这样可能会产生大量的日志内容,比如有这样一条update语 句:update product set owner_member_id = 'b' where owner_member_id = 'a',执行之后,日志中记录的不是这条update语句所对应额事件(mysql以事件的形式来记录bin-log日志),而是这条语句所更新的每一条 记录的变化情况,这样就记录成很多条记录被更新的很多个事件。自然,bin-log日志的量就会很大。尤其是当执行alter table之类的语句的时候,产生的日志量是惊人的。因为Mysql对于alter table之类的表结构变更语句的处理方式是整个表的每一条记录都需要变动,实际上就是重建了整个表。那么该表的每一条记录都会被记录到日志中。
Statement Level:每一条会修改数据的sql都会记录到 master的bin-log中。slave在复制的时候sql进程会解析成和原来master端执行过的相同的sql来再次执行。
优点:statement level下的优点首先就是解决了row level下的缺点,不需要记录每一行数据的变化,减少bin-log日志量,节约IO,提高性能。因为他只需要记录在Master上所执行的语句的细节,以及执行语句时候的上下文的信息。
缺点:由于他是记录的执行语句,所以,为了让这些语句在slave端也能正确执行,那么他还必须记录每条语句在执行的时候的一些相关信息,也就是上下文信 息,以保证所有语句在slave端杯执行的时候能够得到和在master端执行时候相同的结果。另外就是,由于Mysql现在发展比较快,很多的新功能不 断的加入,使mysql得复制遇到了不小的挑战,自然复制的时候涉及到越复杂的内容,bug也就越容易出现。在statement level下,目前已经发现的就有不少情况会造成mysql的复制出现问题,主要是修改数据的时候使用了某些特定的函数或者功能的时候会出现,比 如:sleep()函数在有些版本中就不能真确复制,在存储过程中使用了last_insert_id()函数,可能会使slave和master上得到 不一致的id等等。由于row level是基于每一行来记录的变化,所以不会出现类似的问题。
从官方文档中看到,之前的Mysql一直都只有基于statement的复制模式,直到5.1.5版本的Mysql才开始支持row level的复制。从5.0开始,Mysql的复制已经解决了大量老版本中出现的无法正确复制的问题。但是由于存储过程的出现,给Mysql的复制又带来 了更大的新挑战。另外,看到官方文档说,从5.1.8版本开始,Mysql提供了除Statement Level和Row Level之外的第三种复制模式:Mixed,实际上就是前两种模式的结合。在Mixed模式下,Mysql会根据执行的每一条具体的sql语句来区分对 待记录的日志形式,也就是在Statement和Row之间选择一种。新版本中的Statment level还是和以前一样,仅仅记录执行的语句。而新版本的Mysql中队row ...
Posted in 大话技术 | 1 Comment »
Thursday, September 18th, 2008
前天突然收到Monitor Center发出来的报警:
PROBLEM:CN_DSL_***4/MAXFILE is CRITICAL,SNMP CRITICAL - *These File size is larger than 18932735283: /abc/def/ghi/***/***.MYD
刚看到这个报警的时候,还吓了一跳。数据文件超过最大文件限制?不会吧?好像才18G嘛,怎么会就开始报警了?
于是开始查找报警来源,经过一番查找,总算找到监控脚本。这台机器之前是SA管理维护的,所以相关规范也是按照SA管理的web服务器来设计的。所以他们 在监控系统中添加了某些特定的目录下文件大小的限制,防止某些意外出现大文件的情况。最后我只得在监控脚本中过滤掉了MySQL的“.MYD”和 “.MYI”文件。
说到文件大小,刚好常有人问我说MySQL是否有单个表的大小限制?限制多大呢?这里做一个简单的介绍吧。
在老版本的MySQL 3.22中,MySQL的单表限大小为4GB,当时的MySQL的存储引擎还是ISAM存储引擎。但是,当出现MyISAM存储引擎之后,也就是从 MySQL 3.23开始,MySQL单表最大限制就已经扩大到了64PB了(官方文档显示)。也就是说,从目前的技术环境来看,MySQL数据库的MyISAM存储 引擎单表大小限制已经不是有MySQL数据库本身来决定,而是由所在主机的OS上面的文件系统来决定了。
而MySQL另外一个最流行的存储引擎之一Innodb存储数据的策略是分为两种的,一种是共享表空间存储方式,还有一种是独享表空间存储方式。
当使用共享表空间存储方式的时候,Innodb的所有数据保存在一个单独的表空间里面,而这个表空间可以由很多个文件组成,一个表可以跨多个文件存在,所 以其大小限制不再是文件大小的限制,而是其自身的限制。从Innodb的官方文档中可以看到,其表空间的最大限制为64TB,也就是说,Innodb的单 表限制基本上也在64TB左右了,当然这个大小是包括这个表的所有索引等其他相关数据。
而当使用独享表空间来存放Innodb的表的时候,每个表的数据以一个单独的文件来存放,这个时候的单表限制,又变成文件系统的大小限制了。
以下是从收集到的一点信息,不一定全部准确:
操作系统 大小限制
win32 w/ FAT/FAT32 2GB/4GB
win32 w/ NTFS 2TB(可能更大)
Linux 2.2-Intel 32-bit 2GB (LFS: 4GB)
Linux 2.4+ 4TB(ext3)
Solaris 9/10 16TB
NetWare w/NSS filesystem 8TB
MacOS X w/ HFS+ 2TB
以下是MySQL文档中的内容:
Windows用户请注意: FAT和VFAT (FAT32)不适合MySQL的生产使用。应使用NTFS。
在默认情况下,MySQL创建的MyISAM表允许的最大尺寸为4GB。你可以使用SHOW TABLE STATUS语句或myisamchk -dv ...
Posted in 大话技术 | No Comments »
Sunday, August 17th, 2008
MySQL MyIsam 存储引擎在创建索引的时候,索引键长度是有一个较为严格的长度限制的,所有索引键最大长度总和不能超过1000,而且不是实际数据长度的总和,而是索引键字段定义长度的总和。下面做个简单的测试,记录一下。
root@sky:~# mysql -u sky -p -h127.0.0.1
Enter password:
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 44
Server version: 5.0.51a-log MySQL Community Server (GPL)
Type 'help;' or '\h' for help. Type '\c' to clear the buffer.
sky@127.0.0.1 : (none) 05:23:08> use test;
Database changed
sky@127.0.0.1 : test 05:23:11>
sky@127.0.0.1 ...
Posted in 大话技术 | 3 Comments »
Saturday, May 24th, 2008
前段时间一位同事收到某开发部门一位同事求助,希望帮忙优化一条Mysql的sql语句,大体如下:
select n.id ,nc.content
from news n force index (category1_status,category2_status,category3_status),news_content nc
where n.id=nc.id
and n.status=2 and (n.category_id_1 in (5003107,5003108)
or n.category_id_2 in (5003107,5003108)
or n.category_id_3 in (5003107,5003108)
调试的时候发现怎么都不能走index_merge的执行计划(我们所期望的),后来临时给他们一个union的解决方案。后来下班吃完晚饭后一起找问题,发现即使只有单个表,也没办法走到index_merge的执行计划,不管是加提示还是不加提示,调试过程如下:
mysql> explain select n.id ,nc.content
-> from news n force index (category1_status,category2_status,category3_status),news_content nc
-> where n.id=nc.id
...
Posted in 大话技术 | 2 Comments »
Saturday, March 29th, 2008
在V2的Heartbeat中,为了将资源的监控和切换结合起来,同时支持多节点集群,Heartbeat提供了一种积分策略来控制各个资源在集群中各节点之间的切换策略。通过该积分机制,计算出各节点的的总分数,得分最高者将成为active状态来管理某个(或某组)资源。
如果在CIB的配置文件中不做出任何配置的话,那么每一个资源的初始分数(resource-stickiness)都会是默认的0,而且每一个资源在每次失败之后所减掉的分数(resource-failure-stickiness)也是0。如此的话,一个资源不论他失败多少次,heartbeat都只是执行restart操作,不会进行节点切换。一般来说,resource-stickiness的值都是正数,resource-failure-stickiness的值都是负数。另外还有一个特殊值那就是正无穷大(INFINITY)和负无穷大(-INFINITY)。如果节点的分数为负分,那么不管什么情况发生,该节点都不会接管资源(冷备节点)。随着资源的各种状态的发生,在各节点上面的分数就会发生变化,随着分数的变化,一旦某节点的分数大于当前运行该资源的节点的分数之后,heartbeat就会做出切换动作,现在运行该资源的节点将释放资源,分数高出的节点将接管该资源。
在CIB的配置中,可以给每个资源定义一个分数,通过resource-stickiness来设置,同样也可以设置一个失败后丢失的分数,通过resource-failure-stickiness来设置。如下:
<primitive id="mysql_db" class="ocf" type="mysql" provider="heartbeat">
<meta_attributes id="mysql_db_meta_attr">
<attributes>
<nvpair name="resource_stickiness" id="mysql_db_meta_attr_1" value="100"/>
<nvpair name="resource_failure_stickiness" id="mysql_db_meta_attr_2" value="-100"/>
</attributes>
</meta_attributes>
...
<primitive />
上面的配置就是给mysql_db这个resource配置了两个分数,成功运行的时候所得到的分数(resource_stickiness)和运行失败会丢失的分数(resource_failure_stickiness),两项分数值一样多,成功则得100分,失败则-100分。
除了可以通过给每个资源单独设置两项的分数之外,也可以将所有的resource设置成相同的分数,如下:
<configuration>
<crm_config>
<cluster_property_set id="cib-bootstrap-options">
<attributes>
...
<nvpair id="default-resource-failure-stickiness" name="default-resource-failure-stickiness" value="-100"/>
<nvpair id="default-resource-stickiness" name="default-resource-stickiness" value="100"/>
...
</attributes>
</cluster_property_set>
</crm_config>
...
在这个配置中,就是给所有资源设置了两个默认的分数,省去单独每个资源都设置的麻烦。当然,如果在设置了这个default分数之后,同时也给部分或者全部资源也设置了这两个分数的话,将取单独设置的各个资源设置的分数而不取默认分数。
除了资源的分数之外,节点自身同样也有分数。节点分数可以如下设置:
...
<constraints>
<rsc_location id="rsc_location_group_mysql" rsc="group_mysql">
<rule id="mysql1_group_mysql" score="200">
<expression id="mysql1_group_mysql_expr" attribute="#uname" operation="eq" value="mysql1"/>
</rule>
<rule id="mysql2_group_mysql" score="150">
<expression id="mysql2_group_mysql_expr" attribute="#uname" operation="eq" value="mysql2"/>
</rule>
</rsc_location>
</constraints>
...
注意这里节点分数的设置是放在configuration配置项里面的constraints配置项下的,通过rule来设置。这里是通过节点主机名来匹配的(实际上heartbeat的很多配置中对主机名都是很敏感的)。这里的value值就是节点的主机名,rule里面的score就是一个节点的分数。
通过上面的配置,我们可以作出如下计算:
a、在最开始,两边同时启动heartbeat的话,两边都没有开始运行这个resource,resource本身没有分数,那么仅仅计算节点的分数:
mysql1的分数:node+resource+failcount*failure=200+0+(0*(-100))=200
mysql2的分数:node+resource+failcount*failure=150+0+(0*(-100))=150
heartbeat会做出选择在mysql1上面运行mysql_db这个资源,然后mysql1的分数发生变化了,因为有资源自身的分数加入了:
mysql1的分数:node+resource+failcount*failure=200+100+(0*(-100))=300
mysql2的分数:node+resource+failcount*failure=150+0+(0*(-100))=150
b、过了一段时间,heartbeat的monitor发现mysql_db这个资源crash(或者其他问题)了,分数马上会发生变化,如下:
mysql1的分数:node+resource+failcount*failure=200+100+(1*(-100))=200
mysql2的分数:node+resource+failcount*failure=150+0+(0*(-100))=150
heartbeat发现mysql1节点的分数还是比mysql2的高,那么资源不发生迁移,将执行restart类操作。
c、继续运行一段时间发现又有问题(或者是b后面restart没有起来)了,分数又发生变化了:
mysql1的分数:node+resource+failcount*failure=200+100+(2*(-100))=100
mysql2的分数:node+resource+failcount*failure=150+0+(0*(-100))=150
这时候heartbeat发现mysql2节点比mysql1节点的分数高了,资源将发生迁移切换,mysql1释mysql_db相关资源,mysql2接管相关资源,并在mysql2上运行mysql_db这个资源。这时候,节点的分数又会发生变化如下:
mysql1的分数:node+resource+failcount*failure=200+0+(2*(-100))=0
mysql2的分数:node+resource+failcount*failure=150+100+(0*(-100))=250
这时候如果在mysql2上面三次出现问题,那么mysql2的分数将变成-50,又比mysql1少了,资源将迁移回mysql1,mysql1的分数将变成100,而mysql2的分数将变成-150,因为又少了资源所有者的那100分。到这里,mysql2节点的分数已经是负数了。heartbeat还有一个规则,就是资源永远都不会迁移到一个分数分数是负数的节点上面去。也就是说从这以后,mysql1节点上面不管mysql_db这个资源失败多少次,不管这个资源出现什么问题,都不会迁移回mysql2节点了。一个节点的分数会在该节点的heartbeat重启之后被重置为初始状态。或者通过相关命令来对集群中某个节点的某个资源或者资源组来重置或者查看其failcount,如下:
crm_failcount -G -U mysql1 -r mysql_db #将查看mysql1节点上面的mysql_db这个资源的failcount
crm_failcount -D ...
Posted in 大话技术 | 5 Comments »