设计一个库存系统。在库存系统中,最重要的就是要防止超卖。模拟的SQL语句以下:
首先查询是否有剩余量,正常的操做为:javascript
select * from t_goods where id=1 and rest>0;复制代码
而后发现有剩余量,开始执行更新操做:html
update t_goods set rest=rest-1 where id=1;复制代码
假设有A,B,C 三个用户进来,那么同时执行select语句,假设剩余量只剩下1个,那么A,B,C同时操做会引发超卖。java
那么能够在update
的时候严格一下,也就是在update
的时候再去判断一次库存是否大于0:mysql
update t_goods set rest=rest-1 where id=1 and rest > 0;复制代码
那么,一个线程在update
的时候,另一个线程同时能访问这条数据吗?这看起来是一个简单的问题,然而要清楚明白的了解发生了什么,却并不容易。
这里就涉及到了数据库的三大板块:事务、,锁、存储引擎。sql
先来讲说什么是事务,或者说把一个操做叫作事务,应该知足什么特征。数据库
当事务处理系统建立事务时,将确保事务具备某些特性。组件的开发者们假设事务的特性应该是一些不须要他们亲自管理的特性。这些特性称为ACID特性。 ACID就是:原子性(Atomicity )、一致性( Consistency )、隔离性或独立性( Isolation)和持久性(Durabilily)。并发
原子性属性用于标识事务是否彻底地完成,一个事务的任何更新要在系统上彻底完成,若是因为某种缘由出错,事务不能完成它的所有任务,系统将返回到事务开始前的状态。让咱们再看一下银行转账的例子。若是在转账的过程当中出现错误,整个事务将会回滚。只有当事务中的全部部分都成功执行了,才将事务写入磁盘并使变化 永久化。为了提供回滚或者撤消未提交的变化的能力,许多数据源采用日志机制。例如,SQL Server使用一个预写事务日志,在将数据应用于(或提交到)实际数据页面前,先写在事务日志上。可是,其余一些数据源不是关系型数据库管理系统 (RDBMS),它们管理未提交事务的方式彻底不一样。只要事务回滚时,数据源能够撤消全部未提交的改变,那么这种技术应该可用于管理事务。less
事务在系统完整性中实施一致性,这经过保证系统的任何事务最后都处于有效状态来实现。若是事务成功地完成,那么系统中全部变化将正确地应用,系统处于有效状态。若是在事务中出现错误,那么系统中的全部变化将自动地回滚,系统返回到原始状态。由于事务开
始时系统处于一致状态,因此如今系统仍然处于一致状态。 再让咱们回头看一下银行转账的例子,在账户转换和资金转移前,账户处于有效状态。若是事务成功地完成,而且提交事务,则账户处于新的有效的状态。若是事务出错,终止后,账户返回到原先的有效状态。
记住,事务不负责实施数据完整性,而仅仅负责在事务提交或终止之后确保数据返回到一致状态。理解数据完整性规则并写代码实现完整性的重任一般落在 开发者肩上,他们根据业务要求进行设计。 当许多用户同时使用和修改一样的数据时,事务必须保持其数据的完整性和一致性。所以咱们进一步研究ACID特性中的下一个特性:隔离性。ide
隔离性是当多个用户并发访问数据库时,好比操做同一张表时,数据库为每个用户开启的事务,不能被其余事务的操做所干扰,多个并发事务之间要相互隔离。即要达到这么一种效果:对于任意两个并发的事务T1和T2,在事务T1看来,T2要么在T1开始以前就已经结束,要么在T1结束以后才开始,这样每一个事务都感受不到有其余事务在并发地执行。事务的隔离性经过使用锁定来实现。性能
持久性意味着一旦事务执行成功,在系统中产生的全部变化将是永久的。应该存在一些检查点防止在系统失败时丢失信息。甚至硬件自己失败,系统的状态仍能经过在日志中记录事务完成的任务进行重建。持久性的概念容许开发者认为无论系统之后发生了什么变化,完 成的事务是系统永久的部分。
对于数据库来讲,一、二、4特性较容易知足。数据库是一个共享资源,能够同时供多个用户使用。也就是须要对事务进行并发的控制,以知足隔离性。若是一个个事务是串行的执行,也就是一次只能执行一个事务,只有一个事务等到另一个事务彻底提交修改之后再执行另一个事务,那么彻底知足了隔离性。可是此时数据库系统大部分都是处于空闲状态,其效率将会很低。所以,事务应该容许并发的执行,而且应该对事务进行并发控制。若是不对事务进行并发控制,会出现如下三种状况:
两个事务都同时更新一行数据,可是第二个事务却覆盖了第一个事务的修改。好比T1,T2事务都发现当前剩余量为16,而后减1, T1修改之后为15,T2修改之后一样为15,T2的修改覆盖了T1。
一个事务对同一行数据重复读取两次,可是却获得了不一样的结果。同一查询在同一事务中屡次进行,因为其余提交事务所作的修改或删除,每次返回不一样的结果集,此时发生非重复读。 更为通俗的说法是:在一个事务内,屡次读同一个数据。在这个事务尚未结束时,另外一个事务也访问该同一数据。那么,在第一个事务的两次读数据之间。因为第二个事务的修改,那么第一个事务读到的数据可能不同,这样就发生了在一个事务内两次读到的数据是不同的,所以称为不可重复读,即原始读取不可重复。若是同一个事务在第二次读取的时候发现这条记录消失了或者增长了,这种状况叫作幻读。
一个事务开始读取了某行数据,可是另一个事务已经更新了此数据但没有可以及时提交或者回滚,那么这个事务有可能读取的是修改前的值,致使了脏读。脏读和丢失更新的惟一区别就在于丢失更新所读取的数据是正确的。
出现这样的缘由是事务的隔离性遭到了破坏。可是在某些状况下,并发所形成的事务问题并非彻底不可接受的,好比有可能出现幻读。所以,在性能与事务特性的平衡选择下,SQL给出了4种隔离级别。SQL标准定义了4类隔离级别,包括了一些具体规则,用来限定事务内外的哪些改变是可见的,哪些是不可见的。低级别的隔离级通常支持更高的并发处理,并拥有更低的系统开销。
1.Read Uncommitted(读取未提交内容)
在该隔离级别,全部事务均可以看到其余未提交事务的执行结果。本隔离级别不多用于实际应用,由于它的性能也不比其余级别好多少。读取未提交的数据,也被称之为脏读(Dirty Read)。
2.Read Committed(读取提交内容)
这是大多数数据库系统的默认隔离级别(但不是MySQL默认的)。它知足了隔离的简单定义:一个事务只能看见已经提交事务所作的改变。这种隔离级别 也支持所谓的不可重复读(Nonrepeatable Read),由于同一事务的其余实例在该实例处理其间可能会有新的commit,因此同一select可能返回不一样结果。
3.Repeatable Read(可重读)
这是MySQL的默认事务隔离级别,它确保同一事务的多个实例在并发读取数据时,会看到一样的数据行。不过理论上,这会致使另外一个棘手的问题:幻读 (Phantom Read)。简单的说,幻读指当用户读取某一范围的数据行时,另外一个事务又在该范围内插入了新行,当用户再读取该范围的数据行时,会发现有新的“幻影” 行。InnoDB和Falcon存储引擎经过多版本并发控制(MVCC,Multiversion Concurrency Control)机制解决了该问题。
4.Serializable(可串行化)
这是最高的隔离级别,它经过强制事务排序,使之不可能相互冲突,从而解决幻读问题。简言之,它是在每一个读的数据行上加上共享锁。在这个级别,可能致使大量的超时现象和锁竞争。
使用select @@tx_isolation;
能够查看查看Mysql的默认的事务隔离级别:
mysql> select @@tx_isolation;
+-----------------+
| @@tx_isolation |
+-----------------+
| REPEATABLE-READ |
+-----------------+
1 rows in set (0.02 sec)
mysql>复制代码
Mysql使用锁来完成并发控制。要特别说明的是,对于原子性,一致性和持久性来讲,用户没法介入,可是对于并发控制,用户却能手工的灵活操纵,也就是说,数据库事务的隔离性有时候须要用户手工控制。
基本的封锁类型有三种种:排它锁(X锁)和共享锁(S锁)已经意向锁。
所谓X锁,是事务T对数据A加上X锁时,只容许事务T读取和修改数据A,其余事务不能读取也不能修改数据库A。
所谓S锁,是事务T对数据A加上S锁时,其余事务只能再对数据A加S锁,而不能加X锁,直到T释放A上的S锁。若事务T对数据对象A加了S锁,则T就能够对A进行读取,但不能进行更新(S锁所以又称为读锁),在T释放A上的S锁之前,其余事务能够再对A加S锁,但不能加X锁,从而能够读取A,但不能更新A。
多粒度封锁协议容许多粒树中的每一个结点被独立加锁。对一个结点加锁意味着这个结点的全部后裔结点也被加以一样类型的锁。
显示封锁是数据库直接对数据对象加锁,隐式封锁是该对象没有被独立封锁,而是其上级对象被封锁而致使该数据对象被加锁。所以系统在对某一数据对象加锁时不只要检查该数据对象上有无(显式和隐式)封锁与之冲突;还要检查其全部上级结点和全部下级结点,看申请的封锁是否与这些结点上的(显式和隐式)封锁冲突;显然,这样的检查方法效率很低。为此引进了意向锁。意向锁的含义是:对任一结点加锁时,必须先对它的上层结点加意向锁。
例如事务T要对某个元组加X锁,则首先要对关系和数据库加IX锁。换言之,对关系和数据库加IX锁,表示它的后裔结点—某个元组拟(意向)加X锁。
引进意向锁后,系统对某一数据对象加锁时没必要逐个检查与下一级结点的封锁冲突了。例如,事务T要对关系R加X锁时,系统只要检查根结点数据库和R自己是否已加了不相容的锁(如发现已经加了IX,则与X冲突),而再也不须要搜索和检查R中的每个元组是否加了X锁或S锁。
锁保持的时间长度为保护所请求级别上的资源所需的时间长度。用于保护读取操做的共享锁的保持时间取决于事务隔离级别。采用 READ COMMITTED
的事务隔离级别时,只在读取页的期间内控制共享锁。在扫描中,直到在扫描内的下一页上获取锁时才释放锁。若是指定 HOLDLOCK
提示或者将事务隔离级别设置为 REPEATABLE READ
或 SERIALIZABLE
,则直到事务结束才释放锁。
根据为游标设置的并发选项,游标能够获取共享模式的滚动锁以保护提取。当须要滚动锁时,直到下一次提取或关闭游标(以先发生者为准)时才释放滚动锁。可是,若是指定 HOLDLOCK,则直到事务结束才释放滚动锁。
请注意用于保护更新的排它锁(X锁)将直到事务结束才释放。
若是一个链接试图获取一个锁,而该锁与另外一个链接所控制的锁冲突,则试图获取锁的链接将一直阻塞到将冲突锁释放并且链接获取了所请求的锁或者链接的超时间隔已到期。默认状况下没有超时间隔,可是一些应用程序设置超时间隔以防止无限期等待。
并非全部的存储引擎都支持事务和锁,使用show engines
查看各个存储引擎对事务和锁的概况:
mysql> show engines;
+------------+---------+------------------------------------------------------------+--------------+------+------------+
| Engine | Support | Comment | Transactions | XA | Savepoints |
+------------+---------+------------------------------------------------------------+--------------+------+------------+
| MRG_MYISAM | YES | Collection of identical MyISAM tables | NO | NO | NO |
| CSV | YES | CSV storage engine | NO | NO | NO |
| MyISAM | DEFAULT | Default engine as of MySQL 3.23 with great performance | NO | NO | NO |
| InnoDB | YES | Supports transactions, row-level locking, and foreign keys | YES | YES | YES |
| MEMORY | YES | Hash based, stored in memory, useful for temporary tables | NO | NO | NO |
+------------+---------+------------------------------------------------------------+--------------+------+------------+
5 rows in set (0.02 sec)复制代码
能够发现,只有InnoDB才彻底支持事务,行级锁和外键。MyISAM引擎不支持事务。若是要使用事务,必须是innodb引擎:alter table table_name engine=innodb;
假设有以下三条语句组成的事务
//1.查询出商品信息
select status from t_goods where id=1;
//2.根据商品信息生成订单
insert into t_orders (id,goods_id) values (null,1);
//3.修改商品status为2
update t_goods set status=2;复制代码
事务采用默认的MySQL默认的Repeatable Read
隔离级别。若是要有关各类sql语句到底加什么锁的信息能够访问[dev.mysql.com/doc/refman/…]
select status from t_goods where id=1; 没有加锁,当前事务读,其余事务也能够读
insert 加排它锁,但不影响其余事务的插入其余记录。(排它锁,行级锁)
update 加排它锁,影响其余事务读这条记录(排它锁,行级锁)复制代码
这里只列举了Mysql手册中关于SELECT ... FROM 中锁的描述:
>
SELECT ... FROM is a consistent read, reading a snapshot of the database and setting no locks unless the transaction isolation level is set to SERIALIZABLE. For SERIALIZABLE level, the search sets shared next-key locks on the index records it encounters. However, only an index record lock is required for statements that lock rows using a unique index to search for a unique row.
能够看到,默认并无加锁,那么有可能出现不可重复读的状况。只有在SERIALIZABLE的级别下,才会加shared next-key locks。
用户能够为select手工的加上一些锁,以防止其余事务访问。好比select for update
:
//1.查询出商品信息
select status from t_goods where id=1 for update;
//2.根据商品信息生成订单
insert into t_orders (id,goods_id) values (null,1);
//3.修改商品status为2
update t_goods set status=2;复制代码
上面咱们提到,使用select…for update会把数据给锁住,加上一个排它锁。不过咱们须要注意一些锁的级别,MySQL InnoDB默认Row-Level Lock,因此只有「明确」地指定主键,MySQL 才会执行Row lock (只锁住被选取的数据) ,不然MySQL 将会执行Table Lock (将整个数据表单给锁住)。select…for update 的排它锁,只有等到事务结束才释放。
有关select for update
的更多信息,能够访问:dev.mysql.com/doc/refman/… 了解更多。
Mysql默认自动提交事务,所以一条SQL语句就是一个事务。可使用show variables like 'autocommit'
查看事务自动提交状态:
show variables like 'autocommit';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| autocommit | ON |
+---------------+-------+
1 rows in set (0.03 sec)复制代码
若是一个事务要包含多条事务,那么必须显示指定事务的边界。好比使用 begin
和commit
关键字。
排它锁,读锁和意向锁只是从锁的做用来划分。从锁的粒度来划分,Mysql锁的类型能够从dev.mysql.com/doc/refman/… 了解到。请注意,行级锁,表级锁,页级锁只是一种级别划分。并不存在这样的锁的名字叫作“行级锁,表级锁,页级锁”。
SHOW TABLE STATUS FROM database_name
;
从JDBC的角度来讲,提交事务就是提交sql给数据库来执行。由数据库来保证最后的事务处理。JDBC的事务其实就是保证这些事务之间的数据要么所有交给数据库执行,要么所有不交给数据库。若是设置了自动提交,那么sql一旦出现,就当即执行。 MYSQL默认是自动提交的,也就是你提交一个QUERY,它就直接执行!咱们能够经过 set autocommit=0 禁止自动提交 set autocommit=1 开启自动提交因此,在JDBC中,一块儿提交sql语句才起做用。(JDBC会给数据库增长事务处理语句,而后再发给数据库)所以,mysql是否开启自动提交关系不大。若是事务不是由jdbc来生成的,那么提交一系列的sql语句到数据库之后就被数据库当作单条数据库执行了,那么就没有意义了。