PS:文章整理的知识内容及资料均来自极客时间《SQL必知必会》专栏mysql
MySQL的InnoDB引擎支持事务,MyISAM不支持事务;
事务的4大特性:ACIDsql
持久性是经过事务日志来保证的。日志包括了回滚日志和重作日志。当咱们经过事务对数据进行修改的时候,首先会将数据库的变化信息记录到重作日志中,而后再对数据库中对应的行进行修改。这样作的好处是,即便数据库系统崩溃,数据库重启后也能找到没有更新到数据库系统中的重作日志,从新执行,从而使事务具备持久性。
事务的经常使用操做语句数据库
使用事务有两种方式,分别为隐式事务和显式事务。隐式事务实际上就是自动提交,Oracle 默认不自动提交,须要手写 COMMIT 命令,而 MySQL 默认自动提交,固然咱们能够配置 MySQL 的参数:
CREATE TABLE test(name varchar(255), PRIMARY KEY (name)) ENGINE=InnoDB; BEGIN; INSERT INTO test SELECT '关羽'; COMMIT; INSERT INTO test SELECT '张飞'; INSERT INTO test SELECT '张飞'; ROLLBACK; SELECT * FROM test;
运行结果(1 行数据):服务器
CREATE TABLE test(name varchar(255), PRIMARY KEY (name)) ENGINE=InnoDB; SET @@completion_type = 1; BEGIN; INSERT INTO test SELECT '关羽'; COMMIT; INSERT INTO test SELECT '张飞'; INSERT INTO test SELECT '张飞'; ROLLBACK; SELECT * FROM test;
运行结果(2 行数据):并发
三种异常状况的特色:
一、脏读:读到了其余事务尚未提交的数据。(侧重于未提交的数据)
二、不可重复读:对某数据进行读取,发现两次读取的结果不一样,也就是说没有读到相同的内容。这是由于有其余事务对这个数据同时进行了修改或删除。(侧重于数据修改,UPDATE或DELETE)
三、幻读:事务 A 根据条件查询获得了 N 条数据,但此时事务 B 更改或者增长了 M 条符合事务 A 查询条件的数据,这样当事务 A 再次进行查询的时候发现会有 N+M 条数据,产生了幻读。(侧重于数据新增,INSERT)隔离级别越低,意味着系统吞吐量(并发程度)越大,但同时也意味着出现异常问题的可能性会更大。在实际使用过程当中咱们每每须要在性能和正确性上进行权衡和取舍,没有完美的解决方案,只有适合与否。高并发
模拟异常状况就不做记录了性能
MySQL中的锁spa
隔离级别的实现是经过锁来完成的,实际上加锁是为了保证数据的一致性,当多个线程并发访问某个数据的时候,尤为是针对一些敏感的数据(好比订单、金额等),咱们就须要保证这个数据在任什么时候刻最多只有一个线程在进行访问,保证数据的完整性和一致性。
乐观锁大可能是基于数据版本记录机制实现,通常是给数据库表增长一个"version"字段。读取数据时,将此版本号一同读出,以后更新时,对此版本号加一。此时将提交数据的版本数据与数据库表对应记录的当前版本信息进行比对,若是提交的数据版本号大于数据库表当前版本号,则予以更新,不然认为是过时数据。线程
悲观锁依靠数据库提供的锁机制实现。MySQL中的共享锁和排它锁都是悲观锁。数据库的增删改操做默认都会加排他锁,而查询不会加任何锁。3d
共享锁指的就是对于多个不一样的事务,对于一个资源共享同一个锁。对某一资源加共享锁,自身可可读该资源,其余人也能够读该资源(也能够再加共享锁,即共享锁共享多个内存),但没法修改。要想修改就必须等全部共享锁都释放完以后。语法:SELECT * FROM table lock in share mode;
排它锁指的就是对于多个不一样的事务,对同一个资源只能有一把锁。对某一资源加排它锁,自身能够进行增删改查,其余人没法进行加锁操做,更没法进行增删改操做。语法:select * from table for update。
行锁就是给一行数据进行加锁,操做对象是数据表中的一行(共享锁和排他锁多是行锁也多是表锁,取决于对数据加锁的范围,是一行仍是整个表)。是MVCC技术用的比较多的,但在MYISAM用不了,行级锁用mysql的储存引擎实现而不是mysql服务器。但行级锁对系统开销较大,处理高并发较好。
InnoDB行锁的3种方式:
一、记录锁:针对单个行记录加锁;
二、间隙锁:锁住一个范围(索引之间的空隙),但不包括记录自己,可防止幻读;
三、NEXT-KEY锁:锁住一个范围,包括记录自己,至关于间隙锁+记录锁,可防止幻读
表锁就是对一张表进行加锁,操做对象是数据表。Mysql大多数锁策略都支持(常见mysql innodb),是系统开销最低但并发性最低的一个锁策略。事务t对整个表加读锁,则其余事务可读不可写,若加写锁,则其余事务增删改都不行。
意向锁(Intent Lock),简单来讲就是给更大一级别的空间示意里面是否已经上过锁。举个例子,若是咱们给某一行数据加上了锁,数据库会自动给更大一级的空间,好比数据页或数据表加上意向锁,告诉其余人这个数据页或数据表已经有人上过锁了,这样当其余人想要获取数据表的锁的时候,只须要了解是否有人已经获取了这个数据表的意向锁便可,而不须要逐条记录去判断是否有锁。
MySQL的MVCC(多版本并发控制)
不加锁的简单的 SELECT 都属于快照读:
SELECT * FROM table WHERE ...
当前读就是读取最新数据,而不是历史版本的数据。加锁的 SELECT,或者对数据进行增删改都会进行当前读:
SELECT * FROM table LOCK IN SHARE MODE; SELECT * FROM table FOR UPDATE; INSERT INTO table values ...; DELETE FROM table WHERE ...; UPDATE table SET ...;
InnoDB 中 MVCC 的数据包括事务版本号、行记录中的隐藏列和Undo Log。
每开启一个事务,咱们都会从数据库中得到一个事务 ID(也就是事务版本号),这个事务 ID 是自增加的,经过 ID 大小,咱们就能够判断事务的时间顺序。
InnoDB 将行记录快照保存在了 Undo Log 里,咱们能够在回滚段中找到它们,以下图所示:
从图中能看到回滚指针将数据行的全部快照记录都经过链表的结构串联了起来,每一个快照的记录都保存了当时的 db_trx_id,也是那个时间点操做这个数据的事务 ID。这样若是咱们想要找历史快照,就能够经过遍历回滚指针的方式进行查找。
在 MVCC 机制中,多个事务对同一个行记录进行更新会产生多个历史快照,这些历史快照保存在 Undo Log 里。若是一个事务想要查询这个行记录,须要读取哪一个版本的行记录呢?这时就须要用到 Read View 了,它帮咱们解决了行的可见性问题。Read View 保存了当前事务开启时全部活跃(尚未提交)的事务列表,换个角度你能够理解为 Read View 保存了不该该让这个事务看到的其余的事务 ID 列表。
Read VIew 几个重要的属性:
如图所示,trx_ids 为 trx二、trx三、trx5 和 trx8 的集合,活跃的最大事务 ID(low_limit_id)为 trx8,活跃的最小事务 ID(up_limit_id)为 trx2。
假设当前的事务 creator_trx_id 想要读取某个行记录,这个行记录的事务 ID 为 trx_id,那么会出现如下几种状况:
当查询一条记录的时候,使用多版本并发控制技术找到对应记录的过程:
InnoDB 中,MVCC 是经过 Undo Log + Read View 进行数据读取,Undo Log 保存了历史快照,而 Read View 规则帮咱们判断当前版本的数据是否可见。
在读已提交的隔离级别下,一样的查询语句都会从新获取一次 Read View,这时若是 Read View 不一样,就可能产生不可重复读或者幻读的状况。
若是咱们同时开启事务 A 和事务 B,先在事务 A 中进行某个条件范围的查询,读取的时候采用排它锁,在事务 B 中增长一条符合该条件范围的数据,并进行提交,而后咱们在事务 A 中再次查询该条件范围的数据,就会发现结果集中多出一个符合条件的数据,这样就出现了幻读。出现幻读的缘由是在读已提交的状况下,InnoDB 只采用了记录锁(Record Locking:即只锁定对应的行记录)。
咱们能看到当咱们想要插入球员艾利克斯·伦(身高 2.16 米)的时候,事务 B 会超时,没法插入该数据。这是由于采用了 Next-Key 锁,会将 height>2.08 的范围都进行锁定,就没法插入符合这个范围的数据了。而后事务 A 从新进行条件范围的查询,就不会出现幻读的状况。