一、基础知识
事务:
事务是一组原子性sql查询语句,被当作一个工作单元。若mysql对改事务单元内的所有sql语句都正常的执行完,则事务操作视为成功,所有的sql语句才对数据生效,若sql中任意不能执行或出错则事务操作失败,所有对数据的操作则无效(通过回滚恢复数据)。事务有四个属性:
1、原子性:事务被认为不可分的一个工作单元,要么全部正常执行,要么全部不执行。
2、一致性:事务操作对数据库总是从一种一致性的状态转换成另外一种一致性状态。
3、隔离性:一个事务的操作结果在内部一致,可见,而对除自己以外的事务是不可见的。
4、永久性:事务在未提交前数据一般情况下可以回滚恢复数据,一旦提交(commit)数据的改变则变成永久(当然用update肯定还能修改)。
ps:MYSAM
读锁:
也叫共享锁、S锁,若事务T对数据对象A加上S锁,则事务T可以读A但不能修改A,其他事务只能再对A加S锁,而不能加X锁,直到T释放A上的S
写锁:
又称排他锁、X锁。若事务T对数据对象A加上X锁,事务T可以读A也可以修改A,其他事务不能再对A加任何锁,直到T释放A上的锁。这保证了其他事务在T释放A上的锁之前不能再读取和修改A。
表锁:操作对象是数据表。Mysql大多数锁策略都支持(常见mysql innodb),是系统开销最低但并发性最低的一个锁策略。事务t对整个表加读锁,则其他事务可读不可写,若加写锁,则其他事务增删改都不行。
行级锁:操作对象是数据表中的一行。是MVCC技术用的比较多的,但在MYISAM用不了,行级锁用mysql的储存引擎实现而不是mysql服务器。但行级锁对系统开销较大,处理高并发较好。
MVCC:多版本并发控制(MVCC,Multiversion Currency Control)。一般情况下,事务性储存引擎不是只使用表锁,行加锁的处理数据,而是结合了MVCC机制,以处理更多的并发问题。Mvcc处理高并发能力最强,但系统开销比最大(较表锁、行级锁),这是最求高并发付出的代价。
Autocommit:是mysql一个系统变量,默认情况下autocommit=1表示mysql把没一条sql语句自动的提交,而不用commit语句。所以,当要开启事务操作时,要把autocommit设为0,可以通过“set session autocommit=0;
二、MVCC实现原理以及例化理解(包含些测试以便理解)
第一:先看看网络上几乎全部一样的理解,包括《高性能mysql第二版(中文版)》也如此说明,这样是很容易理解。但笔者觉得2个地方不妥,先看内容,在后面笔者会给出不妥地方用(1、2…)加粗标志出来,且给出测试证明。
Ps:这些只是外部看来的理解层面,深层次在第三点讲解
------------------------------------------
InnoDB实现MVCC的方法是,它存储了每一行的两个(1)额外的隐藏字段,这两个隐藏字段分别记录了行的创建的时间和删除的时间。在每个事件发生的时候,每行存储版本号,而不是存储事件实际发生的时间。每次事物的开始这个版本号都会增加。自记录时间开始,每个事物都会保存记录的系统版本号。依照事物的
SELECT
Innodb检查没行数据,确保他们符合两个标准:
符合了以上两点则返回查询结果。
INSERT
DELETE
UPDATE
InnoDB复制了一行。这个新行的版本号使用了系统版本号。它也把系统版本号作为了删除行的版本。
----------------------------------------------
(1)
1DB_TRX_ID:一个6byte的标识,每处理一个事务,其值自动+1,上述说到的“创建时间”和“删除时间”记录的就是这个DB_TRX_ID的值,如insert、update、delete操作时,删除操作用1个bit表示。
------------
Trx id counter 0 430621
Purge done for trx's n:o < 0 430136 undo n:o < 0 0
History list length 7
……
2DB_ROLL_PTR:
3DB_ROW_ID:
(2)
(3)
第二、下面用图形化形式表示MVCC如何处理select、insert、delete、update
有两个事务A、B
假设开始时间顺序ABCD,且DB_TRX_ID满足以下情况
A. DB_TRX_ID = 2010
B. DB_TRX_ID = 2011
C. DB_TRX_ID = 2012
D. DB_TRX_ID = 2013
注意:
1、B. DB_TRX_ID> A. DB_TRX_ID是因为DB_TRX_ID的值是系统版本号的值,系统版本号是自动增加的,所以DB_TRX_ID也是自动增加。但是会出现这种情况,假如A事务开始后B事务开始前有一个insert操作插入一行数据(没有bengin、comint),则B. DB_TRX_ID= A. DB_TRX_ID+1+1
2、下面例化图只是笔者方便大家理解而设计的图片,红色框代表隐藏两列
例化1:SECLET
这是表test数据
trx代表改行数据是那个事务创建
creat_num是“创建时间”,也就是DB_TRX_ID值
dele_num是“删除时间
B事务有select * from test;语句,按照MVCC原理,该语句相当于:select * from test where creat_num>=2011 and (dele_num=NULL OR dele_num>2011),所以返回数据是id为1、2行。
D事务select * from test;则返回出id为2的行。因为2行被C事务删除了。
例化2:UPDATE
A事务一条语句“update from test set col=’winben’ where col=’benwin’”。
则先复制一条数据如蓝色框,creat_num=DB_TRX_ID(这里是2010),dele_num=NULL,然后把旧行数据的设dele_num=2010,等commit后则删除旧数据行
例化3:DELET
删除就是设dele_num=
-------于2012.12.23加上start----------------------------------------------------------------
和一位淘宝网友讨论一个问题(关于事务隔离级别,这里就直接贴不整理了)
网友: 请教个问题,innodb的事务,一定是按ID顺序提交么? ID为101的一定在ID为100的事务之后?
笔者:这个问题我也不确定。我认为不是按顺序的,可以这样想一下,加入a事务很大是id100,然后还没commit之前有id为101的事务b并发开始处理,但b事务很小处理完了,如果要等a事务的话则是一个鸡肋了。当然还有考虑锁的问题,如果a事务设置了排他锁,且b事务有写操作那不事务则在等待队列中了,那commit的顺序肯定是a然后b的!
网友:如果是这样的话,假设有100,101,102三个事务,101最先提交了,这时新事务103,应该能看到101的更改,而如果按当前活跃ID的最小的比较(这时为100),那就看不到101的更新。
笔者:结合事务隔离级别:
1、READ UNCOMMITTED ,不适用MVCC读,可以读到其他事务修改甚至未提交的
2、READ COMMITTED ,其他事务对数据库的修改,只要已经提交,其修改的结果就是可见的,与这两个事务开始的先后顺序无关,不完全适用于MVCC读,
像你说的100的读101的是可以的(按照MVCC理论应该不行的),但适用102读101(能套MVCC理论)。
3、REPEATABLE READ,可重复读,完全适用MVCC,只能读取在它开始之前已经提交的事务对数据库的修改,在它开始以后,所有其他事务对数据库的修改对它来说均不可见
4、 SERIALIZABLE ,完全不适合适用MVCC,这样所有的query都会加锁,再它之后的事务都要等待
MVCC只工作在REPEATABLE READ和READ COMMITED隔离级别下
-------于2012.12.23加上start----------------------------------------------
三、深入MVCC实现机制
1、到这里很多人就会发现,如果确实根据creat_num
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
代码:
- read_view_t*
- read_view_open_now(
-
-
trx_id_t cr_trx_id, -
mem_heap_t* heap) - {
-
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; -
-
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); -
-
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++; -
-
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) { -
-
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); -
- }
2、MVCC如何控制update操作
前面说先复制新数据,并插入DB_TRX_ID的值,在把旧数据的删除标志DB_TRX_ID
现在先介绍几个概念:
DB_ROLL_PTR是指向回滚段中旧版本7byte回滚指针。
redo log:重做日志,就是每次mysql在执行写入数据前先把要写的信息保存在重写日志中,但出现断电,奔溃,重启等等导致数据不能正常写入期望数据时,服务器可以通过redo_log中的信息重新写入数据。
undo log:撤销日志,与redo log恰恰相反,当一些更改在执行一半时,发生意外,而无法完成,则可以根据撤消日志恢复到更改之前的壮态。
mvcc中update步骤:
1、
2、
3、
以上骤详细代码内容可看:
http://hi.baidu.com/gao1738/blog/item/dcec39d6185af2049d163d8c
4、
5、
一、基础知识
事务:
事务是一组原子性sql查询语句,被当作一个工作单元。若mysql对改事务单元内的所有sql语句都正常的执行完,则事务操作视为成功,所有的sql语句才对数据生效,若sql中任意不能执行或出错则事务操作失败,所有对数据的操作则无效(通过回滚恢复数据)。事务有四个属性:
1、原子性:事务被认为不可分的一个工作单元,要么全部正常执行,要么全部不执行。
2、一致性:事务操作对数据库总是从一种一致性的状态转换成另外一种一致性状态。
3、隔离性:一个事务的操作结果在内部一致,可见,而对除自己以外的事务是不可见的。
4、永久性:事务在未提交前数据一般情况下可以回滚恢复数据,一旦提交(commit)数据的改变则变成永久(当然用update肯定还能修改)。
ps:MYSAM
读锁:
也叫共享锁、S锁,若事务T对数据对象A加上S锁,则事务T可以读A但不能修改A,其他事务只能再对A加S锁,而不能加X锁,直到T释放A上的S
写锁:
又称排他锁、X锁。若事务T对数据对象A加上X锁,事务T可以读A也可以修改A,其他事务不能再对A加任何锁,直到T释放A上的锁。这保证了其他事务在T释放A上的锁之前不能再读取和修改A。
表锁:操作对象是数据表。Mysql大多数锁策略都支持(常见mysql innodb),是系统开销最低但并发性最低的一个锁策略。事务t对整个表加读锁,则其他事务可读不可写,若加写锁,则其他事务增删改都不行。
行级锁:操作对象是数据表中的一行。是MVCC技术用的比较多的,但在MYISAM用不了,行级锁用mysql的储存引擎实现而不是mysql服务器。但行级锁对系统开销较大,处理高并发较好。
MVCC:多版本并发控制(MVCC,Multiversion Currency Control)。一般情况下,事务性储存引擎不是只使用表锁,行加锁的处理数据,而是结合了MVCC机制,以处理更多的并发问题。Mvcc处理高并发能力最强,但系统开销比最大(较表锁、行级锁),这是最求高并发付出的代价。
Autocommit:是mysql一个系统变量,默认情况下autocommit=1表示mysql把没一条sql语句自动的提交,而不用commit语句。所以,当要开启事务操作时,要把autocommit设为0,可以通过“set session autocommit=0;
二、MVCC实现原理以及例化理解(包含些测试以便理解)
第一:先看看网络上几乎全部一样的理解,包括《高性能mysql第二版(中文版)》也如此说明,这样是很容易理解。但笔者觉得2个地方不妥,先看内容,在后面笔者会给出不妥地方用(1、2…)加粗标志出来,且给出测试证明。
Ps:这些只是外部看来的理解层面,深层次在第三点讲解
------------------------------------------
InnoDB实现MVCC的方法是,它存储了每一行的两个(1)额外的隐藏字段,这两个隐藏字段分别记录了行的创建的时间和删除的时间。在每个事件发生的时候,每行存储版本号,而不是存储事件实际发生的时间。每次事物的开始这个版本号都会增加。自记录时间开始,每个事物都会保存记录的系统版本号。依照事物的
SELECT
Innodb检查没行数据,确保他们符合两个标准:
符合了以上两点则返回查询结果。
INSERT
DELETE
UPDATE
InnoDB复制了一行。这个新行的版本号使用了系统版本号。它也把系统版本号作为了删除行的版本。
----------------------------------------------
(1)
1DB_TRX_ID:一个6byte的标识,每处理一个事务,其值自动+1,上述说到的“创建时间”和“删除时间”记录的就是这个DB_TRX_ID的值,如insert、update、delete操作时,删除操作用1个bit表示。
------------
Trx id counter 0 430621
Purge done for trx's n:o < 0 430136 undo n:o < 0 0
History list length 7
……
2DB_ROLL_PTR:
3DB_ROW_ID:
(2)
(3)
第二、下面用图形化形式表示MVCC如何处理select、insert、delete、update
有两个事务A、B
假设开始时间顺序ABCD,且DB_TRX_ID满足以下情况
A. DB_TRX_ID = 2010
B. DB_TRX_ID = 2011
C. DB_TRX_ID = 2012
D. DB_TRX_ID = 2013
注意:
1、B. DB_TRX_ID> A. DB_TRX_ID是因为DB_TRX_ID的值是系统版本号的值,系统版本号是自动增加的,所以DB_TRX_ID也是自动增加。但是会出现这种情况,假如A事务开始后B事务开始前有一个insert操作插入一行数据(没有bengin、comint),则B. DB_TRX_ID= A. DB_TRX_ID+1+1
2、下面例化图只是笔者方便大家理解而设计的图片,红色框代表隐藏两列
例化1:SECLET
这是表test数据
trx代表改行数据是那个事务创建
creat_num是“创建时间”,也就是DB_TRX_ID值
dele_num是“删除时间
B事务有select * from test;语句,按照MVCC原理,该语句相当于:select * from test where creat_num>=2011 and (dele_num=NULL OR dele_num>2011),所以返回数据是id为1、2行。
D事务select * from test;则返回出id为2的行。因为2行被C事务删除了。
例化2:UPDATE
A事务一条语句“update from test set col=’winben’ where col=’benwin’”。
则先复制一条数据如蓝色框,creat_num=DB_TRX_ID(这里是2010),dele_num=NULL,然后把旧行数据的设dele_num=2010,等commit后则删除旧数据行
例化3:DELET
删除就是设dele_num=
-------于2012.12.23加上start----------------------------------------------------------------
和一位淘宝网友讨论一个问题(关于事务隔离级别,这里就直接贴不整理了)
网友: 请教个问题,innodb的事务,一定是按ID顺序提交么? ID为101的一定在ID为100的事务之后?
笔者:这个问题我也不确定。我认为不是按顺序的,可以这样想一下,加入a事务很大是id100,然后还没commit之前有id为101的事务b并发开始处理,但b事务很小处理完了,如果要等a事务的话则是一个鸡肋了。当然还有考虑锁的问题,如果a事务设置了排他锁,且b事务有写操作那不事务则在等待队列中了,那commit的顺序肯定是a然后b的!
网友:如果是这样的话,假设有100,101,102三个事务,101最先提交了,这时新事务103,应该能看到101的更改,而如果按当前活跃ID的最小的比较(这时为100),那就看不到101的更新。
笔者:结合事务隔离级别:
1、READ UNCOMMITTED ,不适用MVCC读,可以读到其他事务修改甚至未提交的
2、READ COMMITTED ,其他事务对数据库的修改,只要已经提交,其修改的结果就是可见的,与这两个事务开始的先后顺序无关,不完全适用于MVCC读,
像你说的100的读101的是可以的(按照MVCC理论应该不行的),但适用102读101(能套MVCC理论)。
3、REPEATABLE READ,可重复读,完全适用MVCC,只能读取在它开始之前已经提交的事务对数据库的修改,在它开始以后,所有其他事务对数据库的修改对它来说均不可见
4、 SERIALIZABLE ,完全不适合适用MVCC,这样所有的query都会加锁,再它之后的事务都要等待
MVCC只工作在REPEATABLE READ和READ COMMITED隔离级别下
-------于2012.12.23加上start----------------------------------------------
三、深入MVCC实现机制
1、到这里很多人就会发现,如果确实根据creat_num
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
代码:
- read_view_t*
- read_view_open_now(
-
-
trx_id_t cr_trx_id, -
mem_heap_t* heap) - {
-
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; -
-
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); -
-
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++; -
-
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) { -
-
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); -
- }
2、MVCC如何控制update操作
前面说先复制新数据,并插入DB_TRX_ID的值,在把旧数据的删除标志DB_TRX_ID
现在先介绍几个概念:
DB_ROLL_PTR是指向回滚段中旧版本7byte回滚指针。
redo log:重做日志,就是每次mysql在执行写入数据前先把要写的信息保存在重写日志中,但出现断电,奔溃,重启等等导致数据不能正常写入期望数据时,服务器可以通过redo_log中的信息重新写入数据。
undo log:撤销日志,与redo log恰恰相反,当一些更改在执行一半时,发生意外,而无法完成,则可以根据撤消日志恢复到更改之前的壮态。
mvcc中update步骤:
1、
2、
3、
以上骤详细代码内容可看:
http://hi.baidu.com/gao1738/blog/item/dcec39d6185af2049d163d8c
4、
5、