在NUMA处理器绑定多实例到固定核心

7 月 1st, 2011 | Posted by | Filed under 未分类

另发在:http://www.mysqlops.com/2011/07/01/mysql_multi_using_numactl.html

关于NUMA的介绍我这里就不多说了,网上太多资料了,我在这篇文章要介绍的是如何在MySQL多实例场景下使用numactl来绑定各个实例到具体的物理节点上,避免跨节点分配内存和跨节点访问寄存器。

至于为何使用多实例,因为MySQL对于多处理机和大内存的利用效率不佳,采用多实例可以很大程度提高MySQL对资源的利用,详情可以看Percona的白皮书中对多实例的测试:Scaling MySQL With Virident Flash Drives and Multiple Instances of Percona Server .

numactl这个程序的用法可以参照man手册:http://linux.die.net/man/8/numactl

基本用法是“numactl  [option] 程序路径”,例如我希望用numactl启动mysqld则是numactl  [option] /usr/local/mysql/bin/mysqld。曾经我误以为numactl是控制某一个程序名,汗……亲手做过才明白是程序路径。

我只介绍几个重要参数
–interleave=all 这是使用交叉分配模式启动一个程序,也就是说程序可以随意跨节点用其他节点的内存,传说中这是效率最高的关闭NUMA特性的方法,只是传说。
–cpunodebind=node 这是把程序绑定在指定的node节点上运行,即使另一个物理节点是idle的,也不会去使用。
–localalloc 严格控制只在节点内分配内存,禁止分配其他节点下的内存到当前节点运行的程序。

我们启动MySQL希望的参数是 numactl –cpunodebind=node –localalloc mysqld_path
为了运维方便,我不可能每次mysql启动都这么执行,我依然希望通过/etc/init.d/mysql和mysqld_multi来管理mysql启动和关闭,于是我采用自定义启动脚本的方式。

首先编写自定义启动脚本如下:

#!/bin/sh

# Program Path
NUMACTL=`which numactl`
MYSQLD=/usr/alibaba/mysql/libexec/mysqld
PS=`which ps`
GREP=`which grep`
CUT=`which cut`
WC=`which wc`
EXPR=`which expr`

# Variables
CPU_BIND=(`$NUMACTL --show | $GREP nodebind | $CUT -d: -f2 `)   # CPU bins list
CPU_BIND_NUM=${#CPU_BIND[@]}    # How many CPU binds
MYSQLD_NUM=`$PS aux | $GREP mysqld | $GREP -v grep | $GREP '\' | $WC -l`
MYSQLD_NUM=`$EXPR $MYSQLD_NUM + 1`
BIND_NO=`$EXPR $MYSQLD_NUM % $CPU_BIND_NUM ` # Calc Which CPU to Bind

# echo CMD
echo "$NUMACTL --cpunodebind=$BIND_NO --localalloc $MYSQLD" > /tmp/mysqld.$MYSQLD_NUM

# use exec to avoid having an extra shell around.
exec $NUMACTL --cpubind=$BIND_NO --localalloc $MYSQLD "$@"

方法是查看当前有多少个mysqld进程已经存在,并且通过numactl –show判断有多少个物理节点,从而判断当前的进程应该分配给哪个节点,例如有2个物理节点,没有mysqld进程,则分配当前进程到0节点,再启动一个实例,当前已经有1个mysqld进程,则分配到1节点,再启动一个实例到0节点……依次循环。

然后在my.cnf文件中配置使用我们自己的脚本启动:

[mysqld_safe]
......
ledir=/usr/local/mysql/bin/ # 放自定义脚本的目录
mysqld=mysqld_using_numactl # 自定义脚本的名称

然后再用/etc/init.d/mysql或mysqld_multi启动mysqld进程就可以实现绑定了。
你可以先启动一个实例,然后在MySQL里做一些消耗CPU的操作,可以观察到只有一个物理节点上的core有活动,哪怕这个节点的core全是100%的利用率,另一个节点的core也全部都是闲的~

有兴趣的话赶紧尝试一下吧~

如果可以,我们一起留在宜春

6 月 6th, 2011 | Posted by | Filed under 心灵感触

如果可以,我们一起留在宜春

不要那些所谓的理想      不要那些所谓的奋斗

不想去英国、美国,读书留学      不想去上海、北京,打拼奋斗

就一起留在我们熟悉的城市

每一条街道都能叫出名字    每一个邻居都认识

想打个麻将,唱个歌

一个电话,半个小时

人就聚齐了

 

如果可以,我们一起留在宜春

无聊了一起去南昌玩一趟

开个车三四个小时就到江西首府了

过年过节几个朋友窜窜门吃顿饭

谁要是不来,打个车几分钟就到他家门口

直接拖出来

 

如果可以,我们一起留在宜春

嘴馋的时候

满大街吃美味的小吃

或者到鼓楼、到麻辣大王,吃个烧烤

水果出来的季节

到白马农庄摘草莓

都是一箱一箱的买

因为便宜又好吃

 

如果可以,我们一起留在宜春

周末的时候还能骑车满世界转悠

心血来潮就去明月山

不好就去袁山

找个野山,带着烧烤架

美滋美滋的自助烧烤

 

如果可以,我们一起留在宜春

冬天的时候一起堆雪人

夏天去秀江游个泳

累了就随便找个KTV、棋牌室、桌游社呼朋唤友

打打麻将,斗斗地主    输赢都在这个圈子

每个人我们都熟悉    知根知底

 

如果可以,我们一起留在宜春

等我们工作了

没有那么大的压力

不用天天加班到10点

不用没有节假日

不用周周出差

只要8点上班,5点下班

不想做饭了就找个哥们家蹭顿饭

饭后可以不用洗碗

还可以一起散散步

 

如果可以,我们一起留在宜春

看着朋友结婚,每个人的婚礼都能参加

等我们有了孩子

我们要让他们也天天在一起玩

让我们成为世交他们也成为世交

礼拜天领着他们去森林公园

他们看植物,我们看他们

让他干爹干妈一大堆

过年压岁钱多的拿不了

让他一出生就学普通话

而不是一出生周围就是不知道哪个地方的方言或者英语

 

如果可以,我们一起留在宜春

等我们老了可以天天有人陪着

走不动了还可以打麻将

商量着什么时候再去趟宜春中学

什么时候再爬趟明月山

什么时候再去鼓楼吃小吃

什么时候再……

把年轻的事情都再做一遍

 

 

如果……如果……如果可以……

标签:

MySQL中创建及优化索引组织结构的思路

6 月 2nd, 2011 | Posted by | Filed under 未分类

原文链接:http://www.mysqlops.com/2011/05/23/mysql%E4%B8%AD%E5%88%9B%E5%BB%BA%E5%8F%8A%E4%BC%98%E5%8C%96%E7%B4%A2%E5%BC%95%E7%BB%84%E7%BB%87%E7%BB%93%E6%9E%84%E7%9A%84%E6%80%9D%E8%B7%AF.html

【导读】
通过一个实际生产环境中的数据存取需求,分析如何设计此存储结构,如何操纵存储的数据,以及如何使操作的成本或代价更低,系统开销最小。同时,让更多初学者明白数据存储的表上索引是如何一个思路组织起来的,希望起到一个参考模板的价值作用。

测试用例描述
测试用例为B2C领域,一张用于存储用户选购物品而生成的产品订单信息表,不过去掉一些其他字段,以便用于测试,其表中的数据项也不特别描述,字段意思见表

USE `test`;
DROP TABLE IF EXISTS `test`.`goods_order`;
CREATE TABLE `goods_order`(
`order_id`        INT UNSIGNED      NOT NULL             COMMENT ‘订单单号’,
`goods_id`        INT UNSIGNED      NOT NULL DEFAULT ’0′ COMMENT ‘商品款号’,
`order_type`      TINYINT UNSIGNED  NOT NULL DEFAULT ’0′ COMMENT ‘订单类型’,
`order_status`    TINYINT UNSIGNED  NOT NULL DEFAULT ’0′ COMMENT ‘订单状态’,
`color_id`        SMALLINT  UNSIGNED NOT NULL DEFAULT ’0′ COMMENT ‘颜色id’,
`size_id`         SMALLINT  UNSIGNED NOT NULL DEFAULT ’0′ COMMENT ‘尺寸id’,
`goods_number`    MEDIUMINT  UNSIGNED NOT NULL DEFAULT ’0′ COMMENT ‘数量’,
`depot_id`        INT UNSIGNED  NOT NULL DEFAULT ’0′ COMMENT ‘仓库id’,
`packet_id`       INT UNSIGNED  NOT NULL DEFAULT ’0′ COMMENT ‘储位code’,
`gmt_create`      TIMESTAMP     NOT NULL DEFAULT ’0000-00-00 00:00:00′ COMMENT ‘添加时间’,
`gmt_modify`      TIMESTAMP     NOT NULL DEFAULT ’0000-00-00 00:00:00′ COMMENT ‘更新时间’,
PRIMARY KEY(order_id,`goods_id`)
)ENGINE=InnoDB AUTO_INCREMENT=1 CHARACTER SET ‘utf8′ COLLATE ‘utf8_general_ci’;

其中,主键信息:PRIMARY KEY(order_id,`goods_id`),为何主键索引索引字段的顺序为:order_id,`goods_id`,而不是: `goods_id`, order_id呢?原因很简单,goods_id在订单信息表中的重复率会比order_id高,也即order_id的筛选率更高,可以减少扫描索引记录个数,从而达到更高的效率,同时,下面即将会列出的SQL也告诉我们,有部分SQL语句的WHERE字句中只出现order_id字段,为此更加坚定我们必须把字段:order_id作为联合主键索引的头部,`goods_id`为联合主键索引的尾部。

数据存储表设计的小结:
设计用于存储数据的表结构,首先要知道有哪些数据项,也即行内常说的数据流,以及各个数据项的属性,比如存储的数据类型、值域范围及长度、数据完整性等要求,从而确定数据项的属性定义。存储的数据项信息确定之后,至少进行如下三步分析:
l 首先,确定哪些数据项或组合,可以作为记录的唯一性标志;
l 其次,要确定对数据记录有哪些操作,每个操作的频率如何,对网站等类型应用,还需要区分前台操作和后台操作,也即分外部用户的操作,还是内部用户的操作;
l 最后,对作为数据记录操作的条件部分的数据项,分析其数据项的筛选率如何,也即数据项不同值占总数据记录数的比例关心,比例越接近1则是筛选率越好,以及各个值得分布率;
综上所述,再让数据修改性操作优先级别高于只读性操作,就可以创建一个满足要求且性能较好的索引组织结构。
数据的存取设计,就涉及一块非常重要的知识: 关系数据库的基础知识和关系数据理论的范式。对于范式的知识点,特别解释下,建议学到BCNF范式为止,1NF、2NF、3NF和BCNF之间的差别,各自规避的问题、存在的缺陷都要一清二楚,但是在真实的工作环境中,不要任何存取设计都想向范式靠,用一句佛语准确点表达:空即是色,色即是空。

用于生成测试数据的存储过程代码
创建索引,就离不开表存储的真实数据,为此编写一个存储过程近可能模拟真实生产环境中的数据,同时也方便大家使用此存储过程,在自己的测试环境中,真实感受验证,
存储过程代码:

DELIMITER $$
DROP PROCEDURE IF EXISTS `usp_make_data` $$
CREATE PROCEDURE `usp_make_data`()
BEGIN
DECLARE iv_goods_id INT UNSIGNED DEFAULT 0;
DECLARE iv_depot_id INT UNSIGNED DEFAULT 0;
DECLARE iv_packet_id INT UNSIGNED DEFAULT 0;

SET iv_goods_id=5000;
SET iv_depot_id=10;
SET iv_packet_id=20;

WHILE iv_goods_id>0
DO
START  TRANSACTION;
WHILE iv_depot_id>0
DO
WHILE iv_packet_id>0
DO
INSERT INTO goods_order(order_id,goods_id,order_type,order_status,color_id,size_id,goods_number,depot_id,packet_id,gmt_create,gmt_modify)
VALUES(SUBSTRING(RAND(),3,8),iv_goods_id,SUBSTRING(RAND(),3,1),SUBSTRING(RAND(),5,1)%2,SUBSTRING(RAND(),3,3),SUBSTRING(RAND(),4,3),SUBSTRING(RAND(),5,2),
iv_depot_id,SUBSTRING(RAND(),4,2)*iv_packet_id,DATE_ADD(NOW(),INTERVAL -SUBSTRING(RAND(),2,3) DAY),DATE_ADD(NOW(),INTERVAL -SUBSTRING(RAND(),3,2) DAY)
);
SET iv_packet_id=iv_packet_id-1;
END WHILE;
SET iv_packet_id=20;
SET iv_depot_id=iv_depot_id-1;
END WHILE ;

COMMIT;
SET iv_depot_id=10;
SET iv_goods_id=iv_goods_id-1;
END WHILE ;
END $$
DELIMITER ;

业务逻辑描述
l 非注册用户,或网站的注册用户不登陆,都能可选购买物品,生成订单号对应的用户UID为系统默认的;
l 订单与用户UID关联、描述等信息,存储其它的表中,通过订单号的模式关联;
l 用户的订单信息,在未付款之前都可以再修改,付款之后则无法修改;
l 已经付费的订单信息,自动发送到物流部门,进行后续工序的操作。处理完毕之后,会更新订单中涉及物品的存储位置信息;
l 定期读取部分数据到数据仓库分析系统,用于统计分析;
l 个人订单查询,前后台都有;
l 购物记录查询显示;

根据业务规则描述需要使用操纵数据的SQL语句
(1). EXPLAIN SELECT * FROM goods_order WHERE `order_id`=40918986;
(2). SELECT * FROM goods_order WHERE `order_id` IN (40918986,40717328,30923040…) ORDER BY gmt_modify DESC;
(3). UPDATE goods_order SET gmt_modify=NOW(),…. WHERE `order_id`=40717328 AND goods_id=4248;
(4). SELECT COUNT(*) FROM goods_order WHERE depot_id=0 ORDER BY gmt_modify DESC LIMIT 0,50;
(5). SELECT * FROM goods_order WHERE depot_id=6 AND packet_id=0 ORDER BY gmt_modify DESC LIMIT 0,50;
(6). SELECT COUNT(*) FROM goods_order WHERE goods_id=4248 AND order_status=0 AND order_type=1
(7). SELECT * FROM goods_order WHERE goods_id=4248 AND order_status=0 AND order_type=1 ORDER BY gmt_modify DESC LIMIT 0,50;
(8). SELECT * FROM goods_order WHERE gmt_modify>=’ 2011-04-06’;
8条SQL语句按触发其执行的用户分类:
l 前台用户点击触发的操作而会执行的SQL语句为:(1)、(2)、(3);
l 后台内部用户点击触发的操作而会执行的SQL语句为:(1)、(2)、(3)、(4)、(5)、(6)、(7);
l 后台系统自动定期执行:(4)、(5)、(6)、(7),工作时间正常情况每隔15分钟执行一次,以检查是否有已付款而没有准备货物的订单、是否有收款而未发货的订单等;
l 统计分析系统定期导出数据而执行的SQL语句为:(8),频率为每24小时一次;
我们再分析上述列出来的SQL,分为2类,一类是读操作的SQL(备注:SELECT操作),另外一类为修改性操作(备注:UPDATE、DELETE操作),分别如下:
SELECT 的WHERE子句、GROUP BY子、ORDER BY 子句和HAVING 子句中,出现的字段:
(1). order_id
(2). order_id+gmt_modify
(3). depot_id+gmt_modify
(4). depot_id+packet_id+gmt_modify
(5). goods_id+order_status+order_type
(6). goods_id+order_status+order_type+gmt_modify
(7). gmt_modify
修改性操作的WHERE子句中出现的条件字段:
(8). order_id+ goods_id

我们已经存在主键索引:PRIMARY KEY(order_id,`goods_id`),另外考虑到此表数据的操作以SELECT和INSERT为主,UPDATE的SQL量其次,再根据上述SQL语句,为此我们可以初步确定需要创建的索引:
ALTER TABLE goods_order
ADD INDEX idx_goodsID_orderType_orderStatus_gmtmodify(goods_id,order_type,order_status,gmt_modify),
ADD INDEX idx_depotID_packetID_gmtmodify(depot_id,packet_id,gmt_modify);

总结:
文章中也分析了为何联合主键索引的顺序为:order_id,`goods_id`,再补充下作为主键的联合索引的字段属性的其他特性:字段值写入之后不变化、字段值长度短且最好为数值类型;
对于编号SQL:(8),每天按更新日期读取一次数据的操作,以采用全表扫描的方式实现,牺牲其数据读取的性能,以减少更新字段修改日期的值而带来的索引维护开销;
对于编号SQL:(4)、(5),考虑到每次都是读取最新的50条记录,以及读取的数据基本上可肯定为热数据,为此不得不牺牲其中一条SQL的数据读取性能,而少创建一个联合索引,从而减少维护索引字段的IO量;
对于编号SQL:(6)、(7),创建的联合索引,需要特别注意联合索引:idx_goodsID_orderType_orderStatus_gmtmodify(goods_id,order_type,order_status,gmt_modify)中的字段顺序,其中:
l goods_id字段的筛选率高于order_type,order_status,另外gmt_modify字段只出现在ORDER BY子句中,为此只有让goods_id字段作为联合索引的头部,以提高索引的筛选率,从而提高索引的效率,减少逻辑或物理的读。
l order_status字段只有0或1两种值,而order_type有多种,以及根据SQL语句,必须order_type出现在联合中的位置要比order_status靠近头部;
l gmt_modify字段出现在ORDER BY子句中,为此必须放到联合索引字段的最后;

最后,再梳理一下从需求到设计存储结构,再到编写SQL和创建索引结构,我们应该做的步骤:
l 整理业务产生的数据流,读取数据的方式;
l 整理清楚数据流中的每个数据项属性信息;
l 分析业务指标,推测需要存储数据的规模(备注:一定要以多少GB作为容量单位);
l 选择可能用于支持业务的硬件设备和数据库架构;
l 把所有可能操纵数据的条件和操作类型,都整理清楚;
l 分析操纵数据条件字段各自的数据筛选率;
l 权衡各个SQL的性能和IO量,也即类似于哪个操作权重高一些,那些操作权重适当低一些;
l 创建索引组织结构;
l 收集测试和生产环境的反馈信息,优化索引组织结构;

备注:
本想再用测试环境结合业务的方式,跑一套模拟测试脚本程序,让大家更加直观地看到不同索引组织情况下,相同的SQL操作及频率,数据库服务器的处理能力和负载变化及对比信息,可惜唯一的服务器无法使用了,只好放弃。对于分析相同的SQL,走不通索引,其需要的逻辑IO和物理IO量也是一个办法,此次就不分析了,有需要的朋友可以去玩玩,另外建议初学者一定要好好阅读下mysql 手册上的相关章节内容:7.2.6. Index Merge Optimization。

MySQL删除大表更快的DROP TABLE办法

6 月 2nd, 2011 | Posted by | Filed under 未分类

原文地址:http://www.mysqlops.com/2011/05/18/mysql%E5%88%A0%E9%99%A4%E5%A4%A7%E8%A1%A8%E6%9B%B4%E5%BF%AB%E7%9A%84drop-table%E5%8A%9E%E6%B3%95.html

 

曾经发文介绍过,DROP table XXX ,特别是碰到大表时,
http://www.mysqlops.com/2011/02/18/mysql-drop-table-%e5%a4%84%e7%90%86%e8%bf%87%e7%a8%8b.html
在DROP TABLE 过程中,所有操作都会被HANG住。
这是因为INNODB会维护一个全局独占锁(在table cache上面),直到DROP TABLE完成才释放。
在我们常用的ext3,ext4,ntfs文件系统,要删除一个大文件(几十G,甚至几百G)还是需要点时间的。
下面我们介绍一个快速DROP table 的方法; 不管多大的表,INNODB 都可以很快返回,表删除完成;
实现:巧用LINK(硬链接)

实测:

root@127.0.0.1 : test 21:38:00> show table status like ‘tt’ \G
*************************** 1. row ***************************
Name: tt
Engine: InnoDB
Version: 10
Row_format: Compact
Rows: 151789128
Avg_row_length: 72
Data_length: 11011096576
Max_data_length: 0
Index_length: 5206179840
Data_free: 7340032
Auto_increment: NULL
Create_time: 2011-05-18 14:55:08
Update_time: NULL
Check_time: NULL
Collation: utf8_general_ci
Checksum: NULL
Create_options:
Comment:
1 row in set (0.22 sec)

root@127.0.0.1 : test 21:39:34> drop table tt ;
Query OK, 0 rows affected (25.01 sec)

删除一个11G的表用时25秒左右(硬件不同,时间不同);

下面我们来对另一个更大的表进行删除;
但之前,我们需要对这个表的数据文件做一个硬连接:

root@ # ln stock.ibd stock.id.hdlk
root@ # ls stock.* -l
-rw-rw—- 1 mysql mysql        9196 Apr 14 23:03 stock.frm
-rw-r–r– 2 mysql mysql 19096666112 Apr 15 09:55 stock.ibd
-rw-r–r– 2 mysql mysql 19096666112 Apr 15 09:55 stock.id.hdlk

你会发现stock.ibd的INODES属性变成了2;

下面我们继续来删表。

root@127.0.0.1 : test 21:44:37> show table status like ‘stock’ \G
*************************** 1. row ***************************
Name: stock
Engine: InnoDB
Version: 10
Row_format: Compact
Rows: 49916863
Avg_row_length: 356
Data_length: 17799577600
Max_data_length: 0
Index_length: 1025507328
Data_free: 4194304
Auto_increment: NULL
Create_time: 2011-05-18 14:55:08
Update_time: NULL
Check_time: NULL
Collation: utf8_general_ci
Checksum: NULL
Create_options:
Comment:
1 row in set (0.23 sec)

root@127.0.0.1 : test 21:39:34> drop table stock ;
Query OK, 0 rows affected (0.99 sec)

1秒不到就删除完成; 也就是DROP TABLE不用再HANG这么久了。
但table是删除了,数据文件还在,所以你还需要最后数据文件给删除。

root # ll
total 19096666112
-rw-r–r– 2 mysql mysql 19096666112 Apr 15 09:55 stock.id.hdlk
root # rm stock.id.hdlk
虽然DROP TABLE 多绕了几步。(如果你有一个比较可靠的自运行程序(自动为大表建立硬链接,并会自动删除过期的硬链接文件),就会显得不那么繁琐。)
这样做能大大减少MYSQL HANG住的时间; 相信还是值得的。

至于原理: 就是利用OS HARD LINK的原理,
当多个文件名同时指向同一个INODE时,这个INODE的引用数N>1, 删除其中任何一个文件名都会很快.
因为其直接的物理文件块没有被删除.只是删除了一个指针而已;
当INODE的引用数N=1时, 删除文件需要去把这个文件相关的所有数据块清除,所以会比较耗时;

好了. 大家试试吧.

标签: , ,

自编译MySQL指南 2.0

4 月 13th, 2011 | Posted by | Filed under 未分类

原文:http://www.mysqlops.com/2011/03/06/mysql_compile_reference.html

一般情况下,用户选择的MySQL安装方式为 RPM包 或 二进制压缩包,但是,通用安装包为了适应不同的软硬件平台,都会采用保守的编译方式,功能上也是选择最常用最稳定的功能编译入二进制版本。
虽然这满足了大部分用户的需求,但是有时我们仅仅需要一部分功能(例如我们不需要Query Cache,但这个模块编译时不去掉的话,运行时依然会触发其代码清理Query Cache内存池,并引发过Bug),或者有性能更好的商业编译器(例如ICC),或者对源码做了修改时,就必须采用编译的方式来安装了。

下面我们就来介绍下如何从源码编译安装MySQL。
阅读全文…