数据库事务,锁

事务

事务特色ACID

1.原子性:一个事务被视为一个不可分割的最小工做单元,整个事务中全部操做要么所有提交成功,要么所有失败回滚,对于一个事务来讲,不可能只执行一部分操做。
2.一致性:数据库老是从一个一致性状态转换到另外一个一致性状态。
3.隔离性:一般来讲,一个事务所作的修改在最终提交以前,对其余食物是不可见的。这里是“一般来讲”,后面有事务隔离级别。
4.持久性:一旦事务提交,则其所作的修改就会永久保存在数据库中。此时即便是系统崩溃,修改的数据也不会丢失。(持久性的安全性与刷新日志级别也存在必定关
系,不一样级别对应不一样的数据安全级别)。

理解ACID

以银行转帐为例

START TRANSACTION;
SELECT balance FROM checking WHERE customer_id = 10233276;
UPDATE checking SET balance = balance - 200.00 WHERE customer_id = 10233276;
UPDATE savings SET balance = balance + 200.00 WHERE customer_id = 10233276;
COMMIT;

set autocommit = 0;//关闭自动提交
start transaction; //开启一个事务
DML操做;          //两个及两个以上的DML操做
....
commit;             //手动提交
1.原子性:要么彻底提交(10233276的checking余额减小200,savings 的余额增长200),要么彻底回滚(两个表的余额都不发生变化)
2.一致性:体如今200元不会由于数据库运行到第三行以后,第四行以前系统崩溃而不知去向,由于事务尚未提交。
3.隔离性:事务A运行到第三行以后,第四行以前,此时事务B去查询checking的余额,它仍然可以看到被减去的200元(帐户的钱不变),由于AB是彼此隔离的,
在A提交以前,事务B是看不到数据变化的。

注:事务的隔离性是经过锁实现的。
    事务的原子性,一致性和持久性是经过事务日志实现的。

MySQL锁

锁是MySQL在服务器层和存储引擎层的并发控制。

锁机制

1.共享锁(读锁):其余事务能够读,可是不能写。
2.排他锁(写锁):其余事物不能读,也不能写。

MySQL不一样的存储引擎支持不一样的锁机制。全部的存储引擎都以本身的方式显现了锁机制。
1.MyISAM:采用的是表级锁。
2.InnoDB:支持行级锁,也支持表级锁,默认状况下是行级锁。
默认状况下:表锁和行锁都是自动得到的。
可是在一些状况下,用户须要明确的进行锁表,或者事务控制,以确保事务的完整性,这个就须要事务控制和锁定语句来完成。

锁比较:
1.表级锁:开销小,速度快,不会出现死锁,粒度大,锁冲突几率高,并发度低。
    1)这些引擎老是一次性同时得到所须要的锁,老是按相同的顺序获取表锁来避免死锁。
    2)表级锁更适合于查询为主,并发用户少,只有少许更新数据的应用。
2.行级锁:开销大,速度慢,会出现死锁,粒度小,发生锁冲突的几率低,并发度高。
    1)最大程度支持并发,同时带来了最大的锁开销。
    2)在InnoDB中,除单个SQL组成的事务外,锁是逐步得到的,因此发生死锁是可能的。
    3)行级锁适合于有大量索引条件并发更新数据,同时又并发查询的应用。
3.页面锁:锁开销在表级锁和行级锁之间,会出现死锁,粒度介于行级锁和表级锁之间,并发度通常。

MyISAM表锁

表级锁模式

1)表共享读锁:不会阻塞其余用户对同一张表的读请求,但会阻塞对同一张表的写请求。(有人在读时不能写)
2)表独占写锁:会阻塞其余用户对同一张表的读和写请求。(有人在写是不能读也不能写)
注:当一个线程得到一个表的写锁时,只有持有锁的线程可以对表进行更新操做,其余线程的读写操做都会等待,直到锁被释放,默认状况下:写锁比读锁有更高的
优先级:当一个锁被释放时,这个锁会优先给请求写锁队列,而后再给请求读锁队列。这也是MyISAM不适合有大量更新操做和查询操做的应用。由于大量的更新操做
会形成查询操做难以得到锁,从而永远阻塞。
    一些须要长时间运行的查询操做,也会使线程饿死,应用中尽可能避免出现这种状况,可使用中间表,能够对SQL语句进行分解,使每一个查询都在短期完成,
从而减小锁冲突。
解决办法:
    1)指定启动参数low-priority-updates,是MyISAM引擎默认给予渡请求优先权。
    2)执行命令SET LOW_PRIORITY_UPDATES=1,使该链接发出的更新请求优先级下降。
    3)指定INSERT、UPDATE、DELETE语句的LOW_PRIORITY属性,下降该语句的优先级。
    4)MySQL系统参数max_write_lock_count设置一个合适的值,当一个表的读锁达到这个值后,MySQL就暂时将写请求的优先级下降,给读进程必定得到锁的机会。

MyISAM加表锁方法

MyISAM 在执行查询语句(SELECT)前,会自动给涉及的表加读锁,在执行更新操做 (UPDATE、DELETE、INSERT 等)前,会自动给涉及的表加写锁。
注:在自动加锁的状况下,MyISAM 老是一次得到 SQL 语句所须要的所有锁,这也正是 MyISAM 表不会出现死锁(Deadlock Free)的缘由。

MyISAM并发插入

MyISAM存储引擎支持并发插入,以减小给定表的读和写操做之间的争用。
1)若是表在数据文件中间没有空闲块,则行始终插入在数据文件末尾。这个时候可并发混合使用Insert和select语句而不须要加锁。
    即:在其余线程进行读操做是的时候,你能够将行插入到表中。
注:文件中的空闲块多是从表中删除或更新产生的。
2)若是文件中有空闲块,并发插入就会被禁用,可是当全部的空闲块都填充新数据时,从新启动。
3)要控制此行为:可使用MySQL的系统变量-----concurrent_insert
    3.1)设置为0时,不容许并发插入。
    3.2)设置为1时,没有空闲块容许插入,容许一个线程读,一个线程在表尾插入数据。这是MyISAM默认的设置。
    3.3)设置为2时,不管有没有空闲块,都容许在表尾并发插入数据。

InnoDB行级锁和表级锁

InnoDB锁模式

1)共享锁:容许一个事务去读取一行,阻止其余事务得到相同数据的排它锁。(手动加,加上以后其余事务只能查不能更新,本身能够更新)
2)排他锁:容许得到排它锁的事务更新数据,阻止其余事务取得相同数据的共享锁和排它锁。(自动加,加上后不容许再加共享锁,其余会话能够查询,不容许其余事务
得到排它锁,即:更新)
为了容许行锁和表锁共存,实现多粒度锁机制,InnoDB 还有两种内部使用的意向锁(Intention Locks)。
这两种意向锁都是表锁:
    1)意向共享锁(IS):事务打算给数据行加行共享锁,事务在给一个数据行加共享锁前必须先取得该表的 IS 锁。 
    2)意向排他锁(IX):事务打算给数据行加行排他锁,事务在给一个数据行加排他锁前必须先取得该表的 IX 锁。

InnoDB加锁方法

1)意向锁是 InnoDB 自动加的, 不需用户干预。 
2)对于 UPDATE、 DELETE 和 INSERT 语句, InnoDB 会自动给涉及数据集加排他锁(X);
3)对于普通 SELECT 语句,InnoDB 不会加任何锁;
4)事务能够经过如下语句显式给记录集加共享锁或排他锁:
    4.1)共享锁(S):SELECT * FROM table_name WHERE ... LOCK IN SHARE MODE。其余会话仍然能够查询录,并也能够对该记录加 share mode的共享锁。
    可是若是当前事务须要对该记录进行更新操做,则颇有可能形成死锁。
    4.2)排他锁(X):SELECT * FROM table_name WHERE ... FOR UPDATE。其余会话能够查询该记录,可是不能对该记录加共享锁或排他锁,而是等待得到锁。
注:锁只有在执行commit或者rollback的时候才会释放,而且全部的锁都是在同一时刻被释放。

显示加锁:

1)select...for update:
在执行这个select查询语句的时候,会将对应的索引访问条目进行上排他锁(X 锁),也就是说这个语句对应的锁就至关于update带来的效果。
select *** for update 的使用场景:为了让本身查到的数据确保是最新数据,而且查到后的数据只容许本身来修改的时候,须要用到 for update 子句。

2)select lock in share mode :
in share mode 子句的做用就是将查找到的数据加上一个 share 锁,这个就是表示其余的事务只能对这些数据进行简单的select 操做,并不可以进行 DML 操做。
select *** lock in share mode 使用场景:为了确保本身查到的数据没有被其余的事务正在修改,也就是说确保查到的数据是最新的数据,而且不容许其余人来
修改数据。可是本身不必定可以修改数据,由于有可能其余的事务也对这些数据 使用了 in share mode 的方式上了 S 锁。

性能影响:
1)select for update 语句,至关于一个 update 语句。在业务繁忙的状况下,若是事务没有及时的commit或者rollback 可能会形成其余事务长时间的等待,从而
影响数据库的并发使用效率。
2)select lock in share mode 语句是一个给查找的数据上一个共享锁(S 锁)的功能,它容许其余的事务也对该数据上S锁,可是不可以容许对该数据进行修改。若是
不及时的commit 或者rollback 也可能会形成大量的事务等待。

for update 和 lock in share mode 的区别:
前一个上的是排他锁(X 锁),一旦一个事务获取了这个锁,其余的事务是无法在这些数据上执行 for update ;后一个是共享锁,多个事务能够同时的对相同数据
执行lock in share mode。

InnoDB 行锁实现方式:

1)InnoDB 行锁是经过给索引上的索引项加锁来实现的,这一点 MySQL 与 Oracle 不一样,后者是经过在数据块中对相应数据行加锁来实现的。InnoDB 这种行锁实现特色
意味着:只有经过索引条件检索数据,InnoDB 才使用行级锁,不然,InnoDB 将使用表锁!

2)不管是使用主键索引、惟一索引或普通索引,InnoDB 都会使用行锁来对数据加锁。

3)只有执行计划真正使用了索引,才能使用行锁:即使在条件中使用了索引字段,可是否使用索引来检索数据是由 MySQL 经过判断不一样执行计划的代价来决定的,若是 
MySQL 认为全表扫描效率更高,好比对一些很小的表,它就不会使用索引,这种状况下 InnoDB 将使用表锁,而不是行锁。所以,在分析锁冲突时, 别忘了检查 SQL 
的执行计划(能够经过 explain 检查 SQL 的执行计划),以确认是否真正使用了索引。(更多阅读:MySQL索引总结)

4)因为 MySQL 的行锁是针对索引加的锁,不是针对记录加的锁,因此虽然多个session是访问不一样行的记录, 可是若是是使用相同的索引键, 是会出现锁冲突的
(后使用这些索引的session须要等待先使用索引的session释放锁后,才能获取锁)。 应用设计的时候要注意这一点。

InnoDB使用间隙锁的目的

防止幻读:
    幻读:一个事务使用范围索引,知足条件的行以及空隙都会被锁,如id>5的name修改成xxxx, 则id=7被锁,就算id=7没有数据,此时另一个事务添加一个id=7的数据,则第一个事务查询出来的数据中Id=7的name并非xxxx,此时就出现了幻读。

死锁

死锁的产生

1)死锁是指两个或者多个事务在同一资源上互相占用,并请求锁定对方占用的资源,从而致使恶性循环。
2)当事务试图以不一样顺序锁定资源时,就可能产生死锁。多个事务同时锁定同一个资源时也会产生死锁。
3)死锁产生与锁的行为和顺序和存储引擎相关。以一样的顺序执行语句,有些存储引擎会产生死锁有些不会。

检测死锁

数据库系统实现了各类死锁检测和死锁超时机制,InnoDB能检测到死锁的循环依赖并当即返回一个错误。

死锁恢复

死锁发生后,只有部分或彻底回滚其中一个事务,才能打破死锁。InnoDB目前处理死锁的方法是,将持有最少行级排他锁的事务进行回滚。因此事务型应用程序在设计时必须考虑如何处理死锁,多数状况下只须要从新执行因死锁回滚的事务便可。

外部锁的死锁检测

发生死锁后,InnoDB 通常都能自动检测到,并使一个事务释放锁并回退,另外一个事务得到锁,继续完成事务。但在涉及外部锁,或涉及表锁的状况下,InnoDB 并不能彻底自动检测到死锁, 这须要经过设置锁等待超时参数 innodb_lock_wait_timeout 来解决

死锁影响性能

死锁会影响性能而不是会产生严重错误,由于InnoDB会自动检测死锁情况并回滚其中一个受影响的事务。在高并发系统上,当许多线程等待同一个锁时,死锁检测可能致使速度变慢。 有时当发生死锁时,禁用死锁检测(使用innodb_deadlock_detect配置选项)可能会更有效,这时能够依赖innodb_lock_wait_timeout设置进行事务回滚。

避免死锁

1)提早申请足够级别的锁,要更新就申请排它锁,而不是申请共享锁。
2)多个事务修改多个表,每一个事务中以相同顺序加锁。
3)并发存取多个表,尽可能约定以相同顺序去访问表。
4)改变事务隔离级别。
注:使用SHOW INNODB STATUS来查看事务得到所得状况,死锁缘由等。

锁性能优化

1)尽可能使用较低的隔离级别。

2)精心设计索引, 并尽可能使用索引访问数据, 使加锁更精确, 从而减小锁冲突的机会。

3)选择合理的事务大小,小事务发生锁冲突的概率也更小。

4)给记录集显示加锁时,最好一次性请求足够级别的锁。好比要修改数据的话,最好直接申请排他锁,而不是先申请共享锁,修改时再请求排他锁,这样容易产生死锁。

5)不一样的程序访问一组表时,应尽可能约定以相同的顺序访问各表,对一个表而言,尽量以固定的顺序存取表中的行。这样能够大大减小死锁的机会。

6)尽可能用相等条件访问数据,这样能够避免间隙锁对并发插入的影响。

7)不要申请超过实际须要的锁级别。

8)除非必须,查询时不要显示加锁。 MySQL的MVCC能够实现事务中的查询不用加锁,优化事务性能;MVCC只在COMMITTED READ(读提交)和REPEATABLE READ(可重复读)两种隔离级别下工做。

9)对于一些特定的事务,可使用表锁来提升处理速度或减小死锁的可能。