Monthly Archives: September 2014

基于比较的分页机制 V.S. 页码式分页

基于比较的分页机制中,输入是一个被比较值。 页码式分页机制中,输入则是页码。

用户体验
对于数据不断增长的功能,页码式分页机制在用户体验方面有个缺点:你翻到下一页时可能会看到刚刚已经看到过的记录。

以贴吧为例,你进到第一页时,你看到的是06,05,04这三条记录;翻到第二页时,本应看到的是03、02、01。 然而在你翻页之前,另一个用户插进了记录07记录; 这时再看第二页时,系统认为此时第一页是07、06、05,于是把第二页04、03、02返回给你,而你刚刚已经看了记录04.

tps越高,这种现象就越严重。

而基于比较的分页机制就没有这个问题。第一页看到06, 05, 04; 翻到第二页时,客户端 “告诉系统我要比04更小的三条记录”, 不管这时有没有新增记录07,系统收到的指令都是“比04更小的三条记录”,所以总是返回03、02、01.

性能
如果要对分页查看列表的功能进行性能优化,一个常见的策略就是对前几页进行缓存。用缓存有一个问题:缓存的key是哪些?

对于页码式分页机制,缓存的key就是页码;要缓存前N页,只需缓存N个key对应的数据。

而对于比较式分页机制,缓存的key是什么? 它可能是你系统中的任何值,而且随着数据的增减,这个值可能原来是页尾,马上就又不是了。 要缓存多少个key?  只能把所有值都缓存一遍。除了首页由于被比较值固定为0可以缓存之外,其他页都无法缓存。

用户足迹追踪
比较式分页机制对用户足迹追踪不利。你很难根据访问记录(如access log),决定大部分用户会下翻多少页;而页码式分页机制就没这个问题。

mysql: 了解数据库状态时常用的sql语句

show table status like ‘some_table’

它会给出本表的storage engine, 行数,表占的空间和索引所占的空间。还有一个有意思的信息:每行平均所占的空间。这个信息不仅可用于DB管理,也可以帮助在整个信息系统中估计各种存储空间、网络带宽之类的。

show variables like ‘%slow%’

show variables  like ‘%long%’;  

了解慢查询相关的设置

show variables like ‘storage_engine’;

查看当前默认的storage engine

show full processlist

当前连到本库的客户端进程

show variables like ‘query_cache_type’

查看数据库的query cache设置

innodb的record lock, row lock, gap lock, next-key lock

record lock – 对索引上某个记录的锁

row lock -对某条数据记录的锁。然而innodb总是通过施加record lock来施加row lock, 也就是说innodb本身没有row lock的概念,record lock就是row lock

gap lock – 索引record之间的锁,用来防止phantom read

next-key lock – 本身不是一种锁。它是a combination of a record lock on the index record and a gap lock on the gap before the index record. 

用next-key lock可以防止phantom read.

对于这样一条语句,

SELECT * FROM child WHERE id > 100 FOR UPDATE;  — 对id=100这条记录上了锁

假设id = {…  90, 100, 105…}

那么当执行这条语句时,其他语句不得插入id=101, id=102…id=103的语句。

可以显式地关掉gap lock.  另外如果isolation level是READ COMMITTED, 则gap lock已经关闭。

poi – 安全地把cell当作string读出来

如果cell并非string类型,调用cell.getStringCellValue()会出错。

下面这样可以保证安全:

	
	private String readString(Cell cell) {
		if (cell == null) {
			return null;
		}
		int cellType = cell.getCellType();

		if (cellType == Cell.CELL_TYPE_BLANK) {
			return null;
		}

		if (cellType == Cell.CELL_TYPE_BOOLEAN) {
			return String.valueOf(cell.getBooleanCellValue());
		}

		if (cellType == Cell.CELL_TYPE_ERROR) {
			return null;
		}

		if (cellType == Cell.CELL_TYPE_FORMULA) {
			return cell.getCellFormula();
		}

		if (cellType == Cell.CELL_TYPE_NUMERIC) {
			return String.valueOf(cell.getNumericCellValue());
		}

		if (cellType == Cell.CELL_TYPE_STRING) {
			return cell.getStringCellValue();
		}

		return null;
	}

例示Innodb中的read commited和repeatable read

本文读者须已经了解mysql的mvcc机制。

假定某行某字段值为100; 以下所有的查询和修改都针对这个字段。

事务甲                                                                   事务乙

——————————————————————————-

set commit = 0                                                  set commit = 0

select …

(值为100)

update … = 200;

selet …

(值为200, 自已事务下的修改是立即可见的)

                                                                          update … = 300;

select …

(仍为200, 右边事务未提交,不关我的事)

                            

                                                                          commit;

select …

(

如果level=read commited, 值为300, 可以读到其它事务的已提交修改

如果level=read repeatable, 值仍为200, 其他事务即使提交了也不关我的事

)

commit;

—-

select …

(值为300,这个不用解释)

innodb中,普通读不加读锁

http://dev.mysql.com/doc/refman/5.5/en/innodb-consistent-read.html

"Consistent read(即普通select) is the default mode in which InnoDB processes SELECT statements in READ COMMITTED and REPEATABLE READ isolation levels. "

"
A consistent read does not set any locks on the tables it accesses"

因此另一个事务可以随便改:

" and therefore other sessions are free to modify those tables at the same time a consistent read is being performed on the table."

不用读锁了,那怎么保证repeatable read?  这就要归功于mysql的MVCC机制了。在MVCC机制中,我们可以理解每个事务对同一份数据有自己的数据空间,你的改动跟我无关(类似于java中的threadlocal)

innodb中,每条语句都运行在事务中

autocommit下,每条语句组成一个事务:

"In InnoDB, all user activity occurs inside a transaction. If autocommit mode is enabled, each SQL statement forms a single transaction on its own"

"By default, MySQL starts the session for each new connection with autocommit enabled, so MySQL does a commit after each SQL statement if that statement did not return an error."

http://dev.mysql.com/doc/refman/5.5/en/innodb-transaction-model.html

two-phase locking

two-phase locking(2pl)是指在一个事务分成两个阶段:

1. 第1阶段只能加锁

2. 第2阶段只能释放锁

它的反面就是: 对A加锁,然后释放; 再加B锁,再释放。。。

后面这种方式保证不了serializability.   比如,

事务甲               事务乙

———————-

对A加写锁

set A=100

对A释放写锁

                      对A加写锁

                      set A=200

                      对A释放写锁

对A加读锁

对B加写锁

set B=A*2=…

select B

对A释放读锁    

对B释放写锁

             

———————-

事务甲结束后,B的值变成了400; 而我们本来预期这个值应该是200的 (没有满足repeatable read这个级别)

要解决这个问题,可以干脆等事务甲全部结束再执行事务乙;既然事务之间是通过锁来协调的,可以让事务甲的锁全部释放了,再让事务乙获得锁,以保证一致性。

事务甲               事务乙

———————-

对A加写锁

set A=100

                      对A加写锁

                      等待。。。。

对B加写锁

set B=A*2=…                 

select B

对A释放读锁

                      set A=200

                      对A释放写锁

对B释放写锁 

———————-

事务甲结束后,B的值变成了200,符合预期。这时事务甲用的就是2pl, 释放第一个锁后不再加任何锁。

2pl有个缺点:事务有多久,一个锁就会持续多久,即使你加的是读锁。结合 MVCC机制(Multi-Version Concurrency Control)情况可以好一点,MVCC并没有否定2pl,只不过在读时它并不加锁。