10.1 事务
1. 事务的概念
事务通常包含一系列更新操作(update、insert和delete等操作语句),这些更新操作是一个不可分割的逻辑工作单元
成功执行,那么该事务等中所有的更新操作都会成功执行,并将执行结果提交到数据库文件中,成为数据库永久的组成部分。
执行失败,那么事务中的所有更新操作均被撤销,所有影响到的数据将返回到事务开始以前的状态。
简言之:事务中的更新操作要么都执行,要么都不执行,这个特征叫作事务的原子性。
2. 事务的ACID特性
ACID”是一个简称,每个事务的处理必须满足ACID原则,即原子性(A)、一致性(C)、隔离性(I)和持久性(D)。
原子性(A)
原子性意味着每个事务都必须被认为是一个不可分割的单元。
假设一个事务由两个或者多个任务组成,其中的语句必须同时成功才能认为事务是成功的。如果事务失败,系统将会返回到事务以前的状态。
一致性(C)
不管事务是完全成功完成还是中途失败,当事务使系统处于一致的状态时存在一致性。参照前面的例子,一致性是指如果从系统中删除了一个雇员,则所有和该雇员相关的数据,包括工资数据和组的成员资格也要被删除。
隔离性(I)
隔离性是指每个事务在它自己的空间发生,和其他发生在系统中的事务隔离,而且事务的结果只有在它完全被执行时才能看到。即使在这样的一个系统中同时发生了多个事务,隔离性原则保证某个特定事务在完全完成之前,其结果是看不见的。
持久性(D)
持久性是指即使系统崩溃,一个提交的事务仍然存在。当一个事务完成,数据库的日志已经被更新时,持久性就开始发生作用。大多数RDBMS产品通过保存所有行为的日志来保证数据的持久性,这些行为是指在数据库中以任何方法更改数据。
3. MySQL事务控制语句
begin开始事务,使用commint结束事务,中间可以使用rollback回滚事务。MySQL通过set autocommint 、start transaction 、commit和rollback等语句支持本地事务。
语法格式:
start transaction | begin [work]
commit [work] [and [no] chain] [[no] release]
rollback [work] [and [no] chain] [[no] release]
set autocommit = {0 | 1}
技巧:
如果只是对某些语句需要进行事务控制,则使用start transaction 开始一个事务比较方便,这样事务结束之后可以自动回到自动提交的方式,如果我们希望我们所有的事务都不是自动提交的,那么通过修改autocommit来控制事务比较方便,这样不用在每个事务开始的时候再执行start transaction 。
4. 事务的隔离性级别
每一个事务都有一个所谓的隔离级,它定义了用户彼此之间隔离和交互的程度。
MySQL提供了下面4种隔离级:
(1)序列化(serializable)
语法格式:
set [global|session] transaction isolation level seriaizable
(2)可重复读(repeatable read
语法格式:
set [global|session] transaction isolation level repeatable read
(3)提交读(read committed)
语法格式:
set [global|session] transaction isolation level read committed
(4)未提交读(read uncommitted)
语法格式:
set [global|session] transaction isolation level read committed
这个隔离级适用于大多数应用程序,只有在应用程序有具体的对于更高或更低隔离级的要求时才需要改动。
没有一个标准公式来决定哪个隔离级适用于应用程序——大多数情况下,这是一个主观的决定,它是基于应用程序的容错能力和应用程序开发者对于潜在数据错误的影响的判断。
10.2 MySQL的多用户
1. 并发概述
交叉并发方式。
同时并发方式。
当多个用户并发地存取数据库时就会产生多个事务同时存取同一数据的情况。若对并发操作不加控制可能会存取和存储不正确的数据,就会出现数据的不一致问题。
(1)丢失更新(lost update) 问题
当两个或多个事务选择同一行,然后基于最初选定的值更新该行时,由于每个事务都不知道其他事务的存在,就会发生丢失更新问题——最后的更新覆盖了由其他事务所做的更新。
(2)脏读(dirty read)问题
一个事务正在对一条记录做修改,在这个事务完成并提交前,这条记录的数据就处于不一致状态;这时,另—-个事务也来读取同一条记录,如果不加控制,第二个事务读取了这些“脏”数据,并据此做进一步的处理,就会产生未交的数据依赖关系,这种现象被形象地叫作“脏读”。
(3)不可重复读( unrepeatableread) 问题
当一个事务多次访问同一行而且每次读取不同的数据时,会发生不可重复读( unrepeatableread)问题。
(4)幻读(phantom read) 问题
当一个事务对某行执行插入或删除操作,而该行属于某个事务正在读取的行的范围时,会发生幻读(phantom read)问题。
2. 锁的概述
实现数据库并发控制的主要手段。
防止丢失更新、脏读、不可重复读和幻读。
锁是一种用来防止多个客户端同时访问数据而产生问题的机制。
不同的存储引擎支持不同的锁机制。
MyISAM和MEMORY存储引擎采用的是表级锁(TABLE-LEVEL LOCKING);
BDB存储引擎采用的是页面锁(PAGE-LEVEL LOCKING),但也支持表级锁;
InnoDB存储引擎既支持行级锁(row-level locking),也支持表级锁,但默认情况下是采用行级锁。
MySQL三种锁的特点如下:
表级锁:一个特殊类型的访问,整个表被客户锁定。
面锁:MySQL将锁定表中的某些行称为页。
行级锁:行级锁比表级锁或页面锁对锁定过程提供了更精细的控制。
说明:从上述特点可见,很难笼统地说哪种锁更好,只能就具体应用的特 点来说哪种锁更合适!
3. MyISAM表的表级锁
MyISAM在执行查询语句( select)前,会自动给涉及的所有表加读锁,在执行更新操作(update、delete、insert等)前,会自动给涉及的表加写锁,这个过程并不需要用户干预,因此,用户一般不需要直接用lock tables命令给MyISAM表显示加锁。
所以对MyISAM表进行操作,会有以下情况:
对MyISAM表的读操作(加读锁),不会阻塞其他进程对同一表的读请求,但会阻塞对同一表的写请求。
对MyISAM表的写操作(加写锁),会阻塞其他进程对同一表的读和写操作,只有当写锁释放后,才会执行其他进程的读写操作。
(1)查询表级锁争用情况
例如: 查看系统上的表锁定情况。
show status like’table%’
如果table_locks_waited的值比较高,则说明存在着较严重的表级锁争用情况。
(2)MySQL表级锁的锁模式
MySQL的表级锁有两种模式:表共享读锁(table read lock)和表独占写锁(tablewrite i.ock) 。
(3)表级锁的加锁方法
MySQL提供了LOCK TABLES语句来锁定当前线程
lock tables
tbl_name[[as] alias] read[local]| [low_priority] write
[, tbl_name [[as] alias] read[local]| [low_priority] write]
...
unlock tables
在对一个事务表使用表锁定的时候需要注意以下几点:
(1)在锁定表时会隐式地提交所有事务,在开始一个事务时,如start transaction ,会隐式解开所有表锁定。
(2)在事务表中,系统变量autocommit值必须设为0。否则,MySQL会在调用lock tables之后立刻释放表锁定,并且很容易形成死锁。
4. InnoDB表行级锁
(1)获取InnoDB行锁争用情况
例如: 查看系统上的行锁的争夺情况
show status like ‘innoDB_rowiock%’;
如果发现innoDB_row_lock_waits和innoDB_row_lock_time_avg的值比较高,则说明锁争用比较严重。
(2)InnoDB行级锁的锁模式
InnoDB实现了以下两种类型的行锁。
共享锁(S):允许一个事务去读一行,阻止其他事务获得相同数据集的排他锁。
排他锁(X):允许获得排他事务更新数据,阻止其他事务取得相同数据集的共享读锁和排他写锁。
(3)InnoDB行级锁的加锁方法
通过给索引上的索引项加锁来实现的,这一点MySQL与Oracle不同,后者是通过在数据块中对相应数据行加锁来实现的。
只有通过索引条件检索数据,InnoDB才使用行级锁,否则,InnoDB将使用表锁。
在中,要特别注意InnoDB行锁的这一特性,不然的话,可能导致大量的锁冲突,从而影响并发性能。
5.死锁
死锁。
通常来说,死锁都是应用设计的问题,通过调整业务流程、数据库对象设计、事务大小,以及访问数据库的SQL语句,绝大部分死锁都可以避免。