Oracle MySQL NoSQL DBA|数据管理,架构,监控与性能优化 --- NinGoo.net
NinGoo's blog
Ad. 招聘信息:猛击获得淘宝DBA工作机会 | 域名空间:DreamHost | 团购:拉手

InnoDB的多版本一致性读的实现

InnoDB是支持MVCC多版本一致性读的,因此和其他实现了MVCC的系统如Oracle,PostgreSQL一样,读不会阻塞写,写也不会阻塞读。虽然同样是MVCC,各家的实现是不太一样的。Oracle通过在block头部的事务列表,和记录中的锁标志位,加上回滚段,个人认为实现上是最优雅的方式。 而PostgreSQL则更是将多个版本的数据都放在表中,而没有单独的回滚段,导致的一个结果是回滚非常快,却付出了查询性能降低的代价。

InnoDB的实现更像Oracle,同样是将数据的旧版本存放在单独的回滚段中,但是也有不同。之前还以为整体实现都会跟Oracle不会有太大的出入,也一直没有太在意去看具体实现。今晚晚上下班准备回家时,刚好路过几个同事在交流分享这个问题,遇到一个疑问:

我们知道,InnoDB表会有三个隐藏字段,6字节的DB_ROW_ID,6字节的DB_TX_ID,7字节的DB_ROLL_PTR(指向对应回滚段的地址),这个可以通过innodb monitor看到,当然如果你熟悉innodb文件结构,也可以直接od ibd文件来验证。一致性读主要跟后两者有关系。InnoDB内部维护了一个递增的tx id counter,其当前值可以通过show engine innodb status获得

echo "show engine innodb status\G" | mysql -uroot | grep "Trx id counter"

假设有一个表,当前已经有两条记录。这时候我们开始一个实验,开启两个session,A和B,都设置autocommit=0

MySQL>set global autocommit=0;

T1时间:
A开始一个事务,执行一条select,可以看到已有的两条记录,show engine innodb status可以知道A的tx id,假设为7430
T2时间:
B开始一个事务,执行一条select,可以看到已有的两条记录,可以知道B的tx id,为7431
T3时间:
A中insert一条记录,此时A再select能看到,所以返回三条记录,而B无法看到,还是返回两条记录。
T4时间:
A中执行commit提交事务,分别在A和B中select,得到的结果和T3时间相同。

Ok,假设一致性读是根据事务先后,也就是tx id来比较的话,如果B事务的一致性读是通过B的tx id即7431来和A事务中insert的这条记录的tx id即7430来比较的话,由于A.tx_id < B.tx_id,那么B应该能都到A的记录(tx id是递增的,所以越小说明事务越早开始),但如果能读到,则显然不符合多版本一致性。

因此结果是正确的,那么就是InnoDB的一致性读的实现方式不是像我们按照经验来测试的那样了。通过google和察看代码,原来InnoDB还真是有一个感觉上很山寨的设计,由于tx id是事务一开始就分配的,事务中的变化也没有记录一个类似于Oracle的SCN的逻辑时钟,于是由了如下的实现:

InnoDB每个事务在开始的时候,会将当前系统中的活跃事务列表(trx_sys->trx_list)创建一个副本(read view),然后一致性读去比较记录的tx id的时候,并不是根据当前事务的tx id,而是根据read view最早一个事务的tx id(read view->up_limit_id)来做比较的,这样就能确保在事务B之前没有提交的所有事务的变更,B事务都是看不到的。当然,这里还有个小问题要处理一下,就是当前事务自身的变更还是需要看到的。

有兴趣的可以去仔细看看代码的实现,在storage/innobase/read/read0read.c中实现了创建read view的函数read_view_open_now,在storage/innobase/include/read0read.ic中实现了判断一致性读是否可见的read_view_sees_trx_id。以下代码摘自5.5.8:

UNIV_INTERN
read_view_t*
read_view_open_now(
/*===============*/
	trx_id_t	cr_trx_id,	/*!< in: trx_id of creating
					transaction, or 0 used in purge */
	mem_heap_t*	heap)		/*!< in: memory heap from which
					allocated */
{
	read_view_t*	view;
	trx_t*		trx;
	ulint		n;
	ut_ad(mutex_own(&kernel_mutex));
	view = read_view_create_low(UT_LIST_GET_LEN(trx_sys->trx_list), heap);
	view->creator_trx_id = cr_trx_id;
	view->type = VIEW_NORMAL;
	view->undo_no = 0;
	/* No future transactions should be visible in the view */
	view->low_limit_no = trx_sys->max_trx_id;
	view->low_limit_id = view->low_limit_no;
	n = 0;
	trx = UT_LIST_GET_FIRST(trx_sys->trx_list);
	/* No active transaction should be visible, except cr_trx */
	while (trx) {
		if (trx->id != cr_trx_id
		    && (trx->conc_state == TRX_ACTIVE
			|| trx->conc_state == TRX_PREPARED)) {
			read_view_set_nth_trx_id(view, n, trx->id);
			n++;
			/* NOTE that a transaction whose trx number is <
			trx_sys->max_trx_id can still be active, if it is
			in the middle of its commit! Note that when a
			transaction starts, we initialize trx->no to
			IB_ULONGLONG_MAX. */
			if (view->low_limit_no > trx->no) {
				view->low_limit_no = trx->no;
			}
		}
		trx = UT_LIST_GET_NEXT(trx_list, trx);
	}
	view->n_trx_ids = n;
	if (n > 0) {
		/* The last active transaction has the smallest id: */
		view->up_limit_id = read_view_get_nth_trx_id(view, n - 1);
	} else {
		view->up_limit_id = view->low_limit_id;
	}
	UT_LIST_ADD_FIRST(view_list, trx_sys->view_list, view);
	return(view);
}

UNIV_INLINE
ibool
read_view_sees_trx_id(
/*==================*/
	const read_view_t*	view,	/*!< in: read view */
	trx_id_t		trx_id)	/*!< in: trx id */
{
	ulint	n_ids;
	ulint	i;
	if (trx_id < view->up_limit_id) {
		return(TRUE);
	}
	if (trx_id >= view->low_limit_id) {
		return(FALSE);
	}
	/* We go through the trx ids in the array smallest first: this order
	may save CPU time, because if there was a very long running
	transaction in the trx id array, its trx id is looked at first, and
	the first two comparisons may well decide the visibility of trx_id. */
	n_ids = view->n_trx_ids;
	for (i = 0; i < n_ids; i++) {
		trx_id_t	view_trx_id
			= read_view_get_nth_trx_id(view, n_ids - i - 1);
		if (trx_id <= view_trx_id) {
			return(trx_id != view_trx_id);
		}
	}
	return(TRUE);
}

参考:
http://dev.mysql.com/doc/refman/5.1/en/innodb-multi-versioning.html
http://wangyuanzju.blog.163.com/blog/static/130292009107101544125/
http://bbs.chinaunix.net/thread-1773206-1-1.html

使用smartmontools监控磁盘状况

现代的磁盘基本上都支持S.M.A.R.T.技术,关于S.M.A.R.T., 维基百科的条文如下:

S.M.A.R.T. (Self-Monitoring, Analysis, and Reporting Technology; sometimes written as SMART) is a monitoring system for computer hard disk drives to detect and report on various indicators of reliability, in the hope of anticipating failures.

When a failure is anticipated by S.M.A.R.T., the drive should be replaced, and can sometimes be returned to the manufacturer, who can use these failed drives to discover where faults lie and how to prevent them from recurring on the next generation of hard disk drives.

Smartmontool是sourceforge上的一个开源项目,可以对磁盘的S.M.A.R.T.进行提取和定期监控。Smartmontool包含两个工具: smartctl和smartd。

$sudo apt-get install smartmontools

Smartctl用于提取和修改硬盘的某些属性和统计信息。Smartd则可以作为守护进程运行,定期收集信息,监控硬盘状态。在我的Thinkpad X200上运行smartctl的结果如下:


ningoo@ning:/sys/block/sda/queue$ sudo smartctl --all /dev/sda
smartctl 5.40 2010-03-16 r3077 [i686-pc-linux-gnu] (local build)
Copyright (C) 2002-10 by Bruce Allen, http://smartmontools.sourceforge.net

=== START OF INFORMATION SECTION ===
Device Model:     FUJITSU MJA2320BH G2
Serial Number:    K95DTA52FWAA
Firmware Version: 0084001C
User Capacity:    320,072,933,376 bytes
Device is:        Not in smartctl database [for details use: -P showall]
ATA Version is:   8
ATA Standard is:  ATA-8-ACS revision 3f
Local Time is:    Tue Mar 22 19:14:04 2011 CST
SMART support is: Available - device has SMART capability.
SMART support is: Enabled

=== START OF READ SMART DATA SECTION ===
SMART overall-health self-assessment test result: PASSED

General SMART Values:
Offline data collection status:  (0x00)	Offline data collection activity
					was never started.
					Auto Offline Data Collection: Disabled.
Self-test execution status:      (   0)	The previous self-test routine completed
					without error or no self-test has ever
					been run.
Total time to complete Offline
data collection: 		 (1009) seconds.
Offline data collection
capabilities: 			 (0x7b) SMART execute Offline immediate.
					Auto Offline data collection on/off support.
					Suspend Offline collection upon new
					command.
					Offline surface scan supported.
					Self-test supported.
					Conveyance Self-test supported.
					Selective Self-test supported.
SMART capabilities:            (0x0003)	Saves SMART data before entering
					power-saving mode.
					Supports SMART auto save timer.
Error logging capability:        (0x01)	Error logging supported.
					General Purpose Logging supported.
Short self-test routine
recommended polling time: 	 (   2) minutes.
Extended self-test routine
recommended polling time: 	 ( 143) minutes.
Conveyance self-test routine
recommended polling time: 	 (   2) minutes.
SCT capabilities: 	       (0x003d)	SCT Status supported.
					SCT Error Recovery Control supported.
					SCT Feature Control supported.
					SCT Data Table supported.
198 Offline_Uncorrectable   0x0010   100   100   000    Old_age   Offline      -       0
199 UDMA_CRC_Error_Count    0x003e   200   253   000    Old_age   Always       -       2
200 Multi_Zone_Error_Rate   0x000f   100   100   060    Pre-fail  Always       -       4364
203 Run_Out_Cancel          0x0002   100   100   000    Old_age   Always       -       1533219504675
240 Head_Flying_Hours       0x003e   200   200   000    Old_age   Always       -       0

SMART Error Log Version: 1
ATA Error Count: 1
	CR = Command Register [HEX]
	FR = Features Register [HEX]
	SC = Sector Count Register [HEX]
	SN = Sector Number Register [HEX]
	CL = Cylinder Low Register [HEX]
	CH = Cylinder High Register [HEX]
	DH = Device/Head Register [HEX]
	DC = Device Command Register [HEX]
	ER = Error register [HEX]
	ST = Status register [HEX]
Powered_Up_Time is measured from power on, and printed as
DDd+hh:mm:SS.sss where DD=days, hh=hours, mm=minutes,
SS=sec, and sss=millisec. It "wraps" after 49.710 days.

Error 1 occurred at disk power-on lifetime: 1222 hours (50 days + 22 hours)
  When the command that caused the error occurred, the device was active or idle.

  After command completion occurred, registers were:
  ER ST SC SN CL CH DH
  -- -- -- -- -- -- --
  84 51 03 97 95 a6 00  Error: ABRT

  Commands leading to the command that caused the error were:
  CR FR SC SN CL CH DH DC   Powered_Up_Time  Command/Feature_Name
  -- -- -- -- -- -- -- --  ----------------  --------------------
  00 08 01 01 00 00 00 ff      02:19:49.864  NOP [Reserved subcommand]
  60 08 00 90 95 a6 40 08      02:19:49.862  READ FPDMA QUEUED
  60 10 00 c8 3c a6 40 08      02:19:49.850  READ FPDMA QUEUED
  60 20 00 a8 3c a6 40 08      02:19:49.841  READ FPDMA QUEUED
  60 08 00 88 95 a6 40 08      02:19:49.833  READ FPDMA QUEUED

SMART Self-test log structure revision number 1
No self-tests have been logged.  [To run self-tests, use: smartctl -t]

SMART Selective self-test log data structure revision number 1
 SPAN  MIN_LBA  MAX_LBA  CURRENT_TEST_STATUS
    1        0        0  Not_testing
    2        0        0  Not_testing
    3        0        0  Not_testing
    4        0        0  Not_testing
    5        0        0  Not_testing
Selective self-test flags (0x0):
  After scanning selected spans, do NOT read-scan remainder of disk.
If Selective self-test is pending on power-up, resume after 0 minute delay.

对于使用了RAID卡的硬盘,还需要RAID卡的支持才能读取到磁盘的S.M.A.R.T.信息,同时intel的SSD也将自身的一些健康指标如磨损率等写入了S.M.A.R.T.信息,于是我们就能通过smartctl来获取安装了RAID卡的SSD的寿命等关键指标了。有了这些指标,可以提前预知到SSD的健康状况,对于SSD单点有写入极限这个担忧,就可以稍微降低那么一点,在生产环境中使用也就更有谱了。具体的用法,之前褚霸B2B的DBA都有写过blog介绍,这里就不展开了,通过MegeCli可以获取LSI的Raid卡的信息(其他Raid卡也有类似的工具):

sudo  /opt/MegaRAID/MegaCli/MegaCli64 -LdPdInfo -aALL

值得一提的是,SSD的磨损率一般通过E9(233)来获取,也就是Media_Wearout_Indicator指标,是0~100的值,精度还是比较粗的,B2B这边线上用了很久,写入了40TB的测试数据,这个值也还是比较高(99,只少了一滴血,这个boss很难打啊^_^)。如果需要更细粒度的数据,上次Intel的人过来交流的时候,说可以取E2(226 Timed Workload Indicator),E3(227 Timed Workload Read Ratio),E4((228 Timed Workload Timer)的值来做更高精度的计算,在开始压力测试前,先将对应的E2/E3/E4的值清零

smartctl -t vendor, 0x40 -a /dev/hdx

当然,如果你使用了Riad卡,还是需要配合Raid卡的相应工具才行

smartctl -t vendor,0x40 -d megaraid,30 /dev/sdx

然后执行一次测试(至少60分钟),重启系统,再次执行smartctl得到E2 = 22, 则该次测试的磨损率为 22 / 1024 / 100= 0.021%(E9的一点血是E2的100点血,也就是说E2的精度比E9高100倍,所以可以在较短时间的测试中看到其变化)。E3 =99 说明读写比例为 99%。该次测试的时间则为E4 = 981分钟,通过计算可以得到该SSD在这样的工作压力下的寿命为E4*1024*100/E2/1440/365 = 8.68年(一年按365天计算)。当然,这个结果是假设这些E2/E3/E4/E9的值都是线性变化的前提下得出的,如果像某些手机的电池指示一样,第一格电可以用一天,最后一格电只能用一个小时,那么这些估算就都是浮云。欲知后事如何,只能等某块SSD用到寿命正常终止,再回头来分析这些指标是否靠谱了。

2011,快乐生活

2010过去,随之而去的,是我二十几岁的青春年华。从此再也没有借口的,变成了三十好几的人了。不过稍感欣慰的是,昨晚一起跨过新年钟声的四个老男人里,我是最年轻的一个,而且年轻了一个时代,好歹我还是八零后。

工作已经过七年多,不知道是不是所谓的七年之痒,今年在一些事情上摇摆不定了很久。好在团队给力,今年数据库整体稳定性上了一个台阶,技术也开始顺畅的华丽转身。也有了更多的时间在工作和生活之间平衡,不再是下班后还是对着电脑敲代码,虽然每天对着电脑的时间依然漫长,却半推半就欲拒还迎的在上下半年有两次的长途旅行。上半年的泰国,下半年的欧洲,有人一路随行,人生不再寂寞。

翻了翻去年元旦,当时雄心满满的To-Do-List,得失参半:

在学习方面,看的技术书很少只是偶尔看看blog,英语除了旅游中多记住了一些常见词依然无起色,杂七杂八的看了基本书也属于过目就忘。至于写书,年头虽然写了几万字,却意兴阑珊的没有坚持下去。

驾照终于到手,报名了一年多,却拖到四月份才去练车,然后又去了泰国旅行,回来考试排到了六月份,好在一气呵成的完成了桩考场考路考,七月份顺利过关,第二周开始开着二手车上路,第三周闯了红灯撞了墙,然后过户,保险,年检,一个多月所有流程打通一遍,囧。

上半年去了一趟泰国,路过一趟吉隆坡。看过安达曼海,从此什么东海南海黄海成浮云。谢谢亲爱的@tb无忧,还有@小LV绿敏小两口,4个人的假期短暂而悠长,逝去的时间短暂,留下的记忆悠长。

下半年领了人生重要一证,去了一趟欧洲。拥挤的香港岛见证,绿色的莫斯科见证,蔚蓝的爱琴海见证,中世纪的布拉格见证,浪漫的巴黎见证,路过的米兰大教堂见证,罢工的雅典见证,人生的里程碑,和@tb无忧 一起完成。人生的道路短暂而悠长,过去的漂泊短暂,未来的依靠悠长。

11月18号,在竹海水韵的小房子交付,后面两个月基本上为装修奔忙,跑遍杭城新时代,第六空间,佳好佳,杭陶,温州村,宏丰,秒遍淘宝,京东,苏宁易购。一个温馨的小家,从无到有开始在慢慢成形。

总体来说,2010收获很多,不管工作还是生活,都完成了180度的大转身。对于2011,其实我已经奢望不多,只是希望,快乐生活。希望我的团队,快乐生活。希望我的朋友们,快乐生活。希望我的家人,快乐生活。希望我自己,快乐生活。

MySQL 5.5.8 GA版本发布

在这几天的OOW上没怎么听到MySQL的消息,session少而且很多讲师是sales,忽悠成份居多。倒是今天一大早在Google Reader上看到MySQL5.5.8 GA版本发布了,真是期待了很久的消息。当然,官方网站上宣传的在windows性能提升540%,在Linux上性能提升370%,未必有那么靠谱,尤其是已经使用了innodb plugin的,那这么高的性能提升就更是浮云了。

但是,MySQL5.5还是有很多值得期待的特性,其中比较吸引人的,像semi-replication,replication heartbeat,以及partition的可用性提升等。MySQL5.5.8已经将Innodb Plugin1.1.4版本做为内建的Innodb引擎。

MySQL5.5.8源代码编译工具在Linux平台放弃了autotool,而是采用了跨平台的cmake。因此从源代码编译的过程有些不一样。如果系统中没有cmake,还需要先安装:

sudo apt-get install cmake
tar zxvf mysql-5.5.8.tar.gz
cd mysql-5.5.8

CFLAGS="-O3 -g"
CXX=gcc
CXXFLAGS="-O3 -g -felide-constructors -fno-exceptions -fno-rtti"
export CFLAGS CXX CXXFLAGS
cmake -DCMAKE_INSTALL_PREFIX=/opt/mysql -DSYSCONFDIR=/opt/mysql

make -j 2
make install

关于MySQL5.5.8更多cmake选项,请参考文档


常用标签: oracle MySQL Oracle11g dba blog 新特性 oow oow2009 wordpress ASM

最新评论 | Recent comments