事务操作的统计,TPS的计算,隔离级别的读提交
对于事务操作的统计
因为InnoDB存储引擎是支持事务的,因此对于InnoDB存储引擎的应用,在考虑每秒请求数(Question Per Second,QPS)的同时,也许更应该关注每秒事务处理的能力(Transaction Per Second,TPS)。
计算TPS的方法是(com_commit+com_rollback)/time。但是用这种方法计算的前提是:所有的事务必须都是显式提交的,如果存在隐式的提交和回滚(默认autocommit=1),不会计算到com_commit和com_rollback变量中。如:
show global status like 'com_commit'\G 5
insert into t select 3;
select * from t\G
show global status like'com_commit'\G 5
MySQL另外还有2个参数handler_commit和handler_rollback。但是我注意到,这两个参数在MySQL 5.1中可以很好地用来统计InnoDB存储引擎显式和隐式的事务提交操作,而在InnoDB Plugin中该参数的表现有些“怪异”,并不能很好地统计事务的次数。所以,如果你的程序都是显式控制事务的提交和回滚,那么可以通过com_commit和com_rollback进行统计。如果不是,那么情况就显得复杂了。
事务的隔离级别
令人惊讶的是,大部分数据库系统都没有提供真正的隔离性,最初或许是因为系统实现者并没有真正理解这些问题。如今这些问题已经弄清楚了,但是数据库实现者在正确性和性能之间做了妥协。ISO和ANIS SQL标准制定了四种事务隔离级别的标准,但是很少有数据库开发厂商遵循这些标准,比如Oracle数据库就不支持read uncommitted和repeatable read的事务隔离级别。
SQL标准定义的四个隔离级别为:
- READ UNCOMMITTED
- READ COMMITTED
- REPEATABLE READ
- SERIALIZABLE
READ UNCOMMITTED称为浏览访问(browse access),仅仅只对事务而言的。
READ COMMITTED称为游标稳定(cursor stability)。
REPEATABLE READ是2.9999˚的隔离,没有幻读的保护。
SERIALIZABLE称为隔离,或3˚。SQL和SQL 2标准的默认事务隔离级别是SERIALIZABLE。
InnoDB存储引擎默认的支持隔离级别是REPEATABLE READ,但是与标准SQL不同的是,InnoDB存储引擎在REPEATABLE READ事务隔离级别下,使用Next-Key Lock锁的算法,因此避免幻读的产生。这与其他数据库系统(如Microsoft SQL Server数据库)是不同的。所以说,InnoDB存储引擎在默认REPEATABLE READ的事务隔离级别下已经能完全保证事务的隔离性要求,即达到SQL标准的SERIALIZABLE隔离级别。
隔离级别越低,事务请求的锁越少,或者保持锁的时间就越短。这也是为什么大多数数据库系统默认的事务隔离级别是READ COMMITTED。
在InnoDB存储引擎中,可以使用以下命令来设置当前会话或者全局的事务隔离级别:
SET [GLOBAL|SESSION] TRANSACTION ISOLATION LEVEL
{
READ UNCOMMITTED
|READ COMMITTED
|REPEATABLE READ
|SERIALIZABLE
}
如果想在MySQL库启动时就设置事务的默认隔离级别,那就需要修改MySQL的配置文件,在[mysqld]中添加如下行:
[mysqld]
transaction-isolation=READ-COMMITTED
查看当前会话的事务隔离级别,可以使用:
select @@tx_isolation\G
查看全局的事务隔离级别,可以使用:
select @@global.tx_isolation\G
在SERIALIABLE的事务隔离级别,InnoDB存储引擎会对每个SELECT语句后自动加上LOCK IN SHARE MODE,即给每个读取操作加一个共享锁。因此在这个事务隔离级别下,读占用锁了,一致性的非锁定读不再予以支持。因为InnoDB存储引擎在REPEATABLE READ隔离级别下就可以达到3˚的隔离,所以一般不在本地事务中使用SERIALIABLE的隔离级别,SERIALIABLE的事务隔离级别主要用于InnoDB存储引擎的分布式事务。
在READ COMMITTED的事务隔离级别下,除了唯一性的约束检查以及外键约束的检查需要Gap Lock,InnoDB存储引擎不会使用Gap Lock的锁算法。但是使用这个事务隔离级别需要注意一些问题。首先,在MySQL 5.1中,READ COMMITTED事务隔离级别默认只能工作在Replication(复制)的二进制日志为ROW的格式下。如果二进制日志工作在默认的STATEMENT下,则会指出如下的错误:
create table a(b int,primary key(b))engine=innodb;
set @@tx_isolation='read-committed';
select @@tx_isolation\G
begin;
insert into a select 1;
ERROR 1598(HY000):Binary logging not possible.Message:Transaction level
'READ-COMMITTED'in InnoDB is not safe for binlog mode'STATEMENT'
MySQL 5. 0版本之前不支持ROW格式的二进制日志时,也许有人知道,可以通过将参数innodb_locks_unsafe_for_binlog设置为1,来使得可以在二进制日志为STATEMENT下使用READ COMMITTED的事务隔离级别:
select @@version\G
system cat/etc/my.cnf | grep innodb_locks_unsafe_for_binlog
innodb_locks_unsafe_for_binlog=1
show variables like 'innodb_locks_unsafe_for_binlog'\G
***************************1.row***************************
Variable_name:innodb_locks_unsafe_for_binlog
Value:ON
set @@tx_isolation='read-committed';
begin;
insert into a select 1;
commit;
是的,的确可以通过上述办法使得在MySQL 5.0的版本之前使用READ COMMITED事务隔离级别。但是就像参数innodb_locks_unsafe_for_binlog的名称一样,它是unsafe的。在某些情况下,可能会导致master和slave之间数据的不一致。
接着来演示一种可能导致不同步的情况,首先来看下表a中的数据:
select * from a\G
***************************1.row***************************
b:1
***************************2.row***************************
b:2
***************************3.row***************************
b:4
***************************4.row***************************
b:5
4 rows in set(0.00 sec)
接着在master上开启一个会话A执行如下事务,并且不要提交:
#Session A on master
begin;
delete from a where b<=5;
在master上开启另一个会话B,执行如下事务,并且提交:
#Session B on master
begin;
insert into a select 3;
commit;
接着会话A提交,并查看表a中的数据:
#Session A on master
commit;
select * from a\G
***************************1.row***************************
b:3
但是在slave上看到的结果却是:
#Slave
select * from a;
可以看到,数据产生了不一致。导致这个问题发生的原因有两点:首先,在READ COMMITTED事务隔离级别下,事务是没有Gap Lock锁的,因此我们可以在小于等于5的范围内再插入一条记录;其次,statement记录的是master上产生的SQL语句,因此在master上是先删后插,但是在STATEMENT格式中记录的却是先插后删,逻辑上就产生了不一致。因此,使用READ REPEATABLE事务隔离级别就可以避免第一种情况的发生,因而也就避免了master和slave不一致问题的产生。
在MySQL 5.1的版本之后,因为支持了ROW格式的二进制日志记录格式,所以避免了第二种情况的发生,因此可以放心使用READ COMMITTED的事务隔离级别。即使不使用READ COMMITTED的事务隔离级别,也应该考虑将二进制日志的格式更换成ROW,因为这个格式记录的是行的变更,而不是简单的SQL语句,因此可以避免一些不同步现象的产生。