事务以及其 ACID 属性
事务是由一组 SQL 语句组成的逻辑处理单元,事务具有 4 个属性,通常简称为事务的 ACID 属性。
- 原子性(Atpmicity) : 事务是一个原子操作单元,其对数据的修改,要么全部执行成功,要么全部失败回滚,主要是体现事务操作层面。
- 一致性(Consistent):在事务开始和完成时,数据都必须保持一致状态,着意味着所有相关的数据规则都必须应用于事务的修改,以保持数据的完整性。
- 隔离性(Isolation):数据库系统提供一定的隔离机制,保证事务不在受外部并发操作影响的 “独立” 环境执行。这意味着事务处理过中的中间状态对外部是不可见的。反之亦然。
- 持久性(Durable): 事务处理完成之后,它对于数据的修改是永久性的, 即出现系统故障也能保持。
并发事务处理带来的问题
更新丢失(Lost Update)或脏写
当两个或者多个事务选择同一个行, 然后给予最初选定的值更新该行时,由于每个事务都不知道其他事务的存在,就会发生丢失更新的问题-最后的更新覆盖了其他事务所做的更新
脏读(Dirty Reads)
一个事务正在对一条数据做秀噶,但是这个事务在完成并提前之前,这条数据就已经就处于不一致的状态;这时,另一个事务也来取同一条记录,如果不加以控制,第二个事务读取了这些 “脏” 数据, 并且做进一步的处理,就会产生未提交的数据以来关系。这种现象叫做“脏”读。
简单理解:事务 A 读取到了事务 B 已经修改但是尚未提交的数据,并且在这个数据的基础上做了操作。此时如果 B 回滚了, A读取的数据就无效了,不符合一致性原理。
不可重复读(No-Repeatable Reads)
一个事务在读取某些数据后的某个事件,再次读取以前读取过的数据,却发现了读出的数据已经发生的改变、或某些数据已经被删除了,这种现象就叫做 “不可重复读”
简单理解: 事务 A 内部相同查询语句在不同的时刻读取出来的结果不一致,不符合隔离性。
幻读(Phantom Reads)
一个事务按相同的时间查询重新读取以前检索过的数据, 却发现其他事务插入了满足查询条件的新数据,这种现象称为“幻读”。
简单理解:事务 A 读取到了事务 B 提交的新增数据,不符合隔离性。
事务隔离级别
“脏读” 、“不可重复读”、和 “幻读” 其实都是数据库一致性问题,必须由数据库提供一定的事务隔离机制来解决。
数据库的事务隔离越严格,并发带来的问题就越小,但是付出的代价也就越大,因为事务隔离实质上就是事务在一定程度上 “串行化” 进行,这显然与 “并发” 是矛盾的。同时,不同的应用对读一致性和事务隔离成都的要求也是不同的,比如许多应用对 “不可重复读” 和 “幻读” 并不敏感, 可能更关心数据并发访问的能力。
可以通过一下命令查询数据库的当前事务隔离级别:
mysql> show variables like 'tx_isolation'; +---------------+-----------------+ | Variable_name | Value | +---------------+-----------------+ | tx_isolation | REPEATABLE-READ | +---------------+-----------------+ 1 row in set (0.01 sec)
设置事务隔离级别:
set tx_isolation = 'REPEATABLE-READ';
MySQL 默认的事务隔离级别是可重复读,用 Spring 开发程序时,如果不设置隔离级别默认采用 MySQL 的隔离级别,如果 Spring 配置中已经设置的就用配置的隔离级别
MySQL 中的锁详解
锁是计算机协调多个进程或者线程并发访问某一资源的机制。
在数据库中,除开传统的计算资源(如 CPU 、RAM、 I/O)的争用之外,数据也是一种需要用户共享的资源。如何保证数据并发访问的一致性、有效性是所有数据库必须解决的问题,锁冲突也是影响数据库并发访问性能的一个重要因素。
锁的分类
- 性能上分为 乐观锁(用版本号对比来实现)和 悲观锁
- 对数据操作类型分,可以分为 读锁和写锁(都属于悲观锁)
读锁(共享锁, S 锁 (Shared)): 针对同一份数据,多个读操作可以同时进行而不会互相影响。
写锁(排他锁, X 锁(eXclusive)): 当前写操作没有完成前,它会阻隔其他写锁和读锁。
- 从数据操作的粒度分,分为表锁和行锁
表锁
每次操作会锁住整张表。 开销小,加锁快;不会出现死锁;锁定粒度打,发生锁冲突的概率最高,并发度最低;一般用在整张表数据迁移的场景。
基本操作
-- 创建表 SQL create table `mylock` ( `id` int(11) not null auto_increment, `name` varchar(20) default null, primary key(`id`) ) engine = Myisam default charset = utf8; -- 插入数据 insert into `mylock` (`id`, `name`) values ('1' , 'a'); insert into `mylock` (`id`, `name`) values ('2' , 'b'); insert into `mylock` (`id`, `name`) values ('3' , 'c'); insert into `mylock` (`id`, `name`) values ('4' , 'd');
手动增加表锁
lock table 表名称 read(write), 表名称2 read(write)
查看表上加过的锁
show open tables;
删除表锁
unlock tables;
加读锁
mysql> lock table mylock read; Query OK, 0 rows affected (0.00 sec)
当前 session 和其他 session 都可以读取该表
当前 session 中插入或者更新锁的表都会报错,其他 session 插入或更新则会等待。
mysql> insert into `mylock` (`id`, `name`) values ('5' , 'd'); ERROR 1099 (HY000): Table 'mylock' was locked with a READ lock and can't be updated
加写锁
mysql> lock table mylock write; Query OK, 0 rows affected (0.00 sec)
当前的 session 对当前表增删改查都没有问题,其他 session 对该表所有的操作都被阻塞。
结论
- 对 MyISAM 表的读操作(加锁读),不会阻塞其他进程对同一个表的读请求,但是会阻塞对同一个表的写请求。只有当读锁匙放之后,才会执行其他进程的写操作。
- 对 MyISAM 表的写操作(加写锁),会阻塞其他进程对同一个表的读和写操作,只有当写锁释放后,才会执行其他进程的写操作。
行锁
每次操作都锁住一行数据。开销大(会涉及到表的查询、所以开销大),加锁慢;会出现死锁;锁定粒度最小,发生冲突的概率最低,并发高度最高。
InnoDB 与 MySIAM 的最大不同点
- InnoDB 支持事务(Transaction)
- InnoDB 支持行级锁