第三章:
事务
事务是DBMS中操作的基本执行单位,事务本身就是构成单一逻辑工作单元的数据库操作的有限序列,由一组DML语句INSERT、DELETE、 UPDATE组成。
在关系型数据库中,事务可以是一条、一组SQL语句,或整个程序。
事务和程序的区别:程序包含多个事务。
隐含事务与自动提交的SQL语句
- DDL语句:ALTER, CREATE, RENAME, DROP, TRUNCATE
- 用户权限管理操作:CREATE USER, GRANT, REVOKE, SET PASSWORD
- 管理语句:ANALYZE TABLE, CHECK INDEX, REPAIR TABLE, LOAD INDEX INTO CACHE
命令:
- set autocommit=0:关闭事务自动提交
- begin:开始事务
- commit:提交事务
- rollback:回滚事务
- start transaction:显示开启事务
InnoDB存储引擎默认自动提交事务,隐式每行提交一次,效率极慢,当大量插入时发生错误无法回滚。
↑针对该案例进行优化,显式启动事务并提交,把循环体放入事务内避免循环提交。
事务的四大特性ACID
- 原子性(Atomicity):事务中的操作,要么都做,要么都不做,是不可分割的。原子性是事务概念的本质体现和基本要求。
- 一致性(Consistency ):事务执行的结果必须使数据库从一个一致性状态变到另一个一致性状态。
- 隔离性(Isolation):并发执行的各事务不能互相干扰。
- 持续性(Durability): 事务一旦提交,他对数据库的更新不再受后继操作或者故障的影响
事务的隔离级别
事务读写中存在的问题
MySQL作为多线程并发访问的数据库,当多个用户(多个事务)同时访问相同的数据库资源,也就是并发环境下,可能会出现以下几种不确定的情况。
- 脏读:一个事务读取了某行数据,而另外一个事务已经更新了此行的数据,但没有及时提交,**例如,事务A读取了事务B更新的数据,随后事务B因为某些原因进行了回滚操作,那么事务A读取到的数据就是脏数据。**这种情况是非常危险的,很可能造成所有的操作都被回滚。
- 不可重复读:不可重复读指一个事务的修改和提交造成另一个事务在同一范围内的两次相同查询的返回结果不同。例如,事务A需要多次读取同一个数据,在事务A还没有结束时,事务B 访问并修改了该数据,那么,事务A两次读取到的数据就可能不一致,因此称为不可重复读。
- 幻读:幻读是指一个线程中的事务读取到了另外一个线程中提交的INSERT数据。例如,用户A将数据库中所有学生的成绩从具体分数改为ABCDE等级,但是用户B此时插入了一条具体分数的记录,用户A修改完成后发现还有一条记录没有改过来,这种情况为幻读或者虚读。
MySQL中的事务隔离级别
特性 | 说明 |
---|---|
ReadUncommitted 读未提交 | 允许事务读取其他事务未提交的结果(即允许脏读),是事务隔离级别中等级最低的,也是最危险的,该级别很少用于实际应用。 |
Read Committed 读已提交 | 允许事务只能读取其他事务已经提交的结果,该隔离级别可以避免脏读,但不能避免重复读和幻读的情况。 |
Repeatable Read 可重复读(默认) | 该级别确保了同一事务的多个实例在并发读取数据时,可以读取到同样的数据行。这种级别可以避免脏读和不可重复读的问题,但不能避免幻读的问题,是 MySQL 默认的隔离级别。 |
Serializable 可串行化 | 强制性的对事务进行排序,使之不可能相互冲突,从而解决幻读的问题。实际上,这种方式是在每个读的数据行上加了共享锁,但这种级别可能会导致大量的超时现象和锁竞争,所以很少用于实际应用,是事务中最高的隔离级别 |
命令:
- SELECT @@tx_isolation; 查看当前会话的隔离级别(5.7)
- SELECT @@transaction_isolation; 查看当前会话的隔离级别(8.0)
- SET SESSION TRANSACTION ISOLATION LEVEL {READ UNCOMMITTED | READ COMMITTED | REPEATABLE READ | SERIALIZABLE}:修改当前会话的隔离级别。
锁机制
锁机制主要是为了使用户对数据的访问变得有序,保证数据的一致性。锁机制是实现宏观上高并发最简单的方式,但从微观的角度来说,锁机制其实是读写串行化。
锁的粒度
锁的粒度是指锁的作用范围。InnoDB存储引擎支持表级锁以及行级锁,MyISAM存储引擎支持表级锁。
隐式锁与显式锁
MySQL自动加锁被称为隐式锁,数据库开发人员手动加锁被称为显式锁。
MySQL的读写锁
读锁:读锁也被称为共享锁,允许其他用户对数据同时“读”,但不允许其他用户对数据同时“写”
写锁:写锁也被称为排他锁或者独占锁。写锁既不允许其他用户对数据同时“读”,也不允许其他用对数据同时“写”
InnoDB的锁类型
表级锁:表级锁指整个表被客户锁定。表级锁分为读锁和写锁与意向锁。
- 命令:LOCK TABLES table_name[AS alias]{READ [LOCAL]|[LOS_PRIORITY]WRITE}
- READ:读锁定,确保用户可以读取表,但是不能修改表。
- WRITE:写锁定,只有锁定该表的用户可以修改表,其他用户只能读取表。
- 意向锁:锁定的粒度是整张表。意向锁指如果对一个结点加意向锁,则说明该结点的下层结点正在被加锁。意向锁分为意向共享锁(IS)和意向排他锁(IX)两类。是MySQL自动的向该表加的锁。主要作用在于提高系统性能,不然第二个事务想加锁时每次要看表是否有被锁住,然后还要看行是否有被锁住,十分消耗性能。
行级锁:只有线程使用的行是被锁定的。表中的其他行对于其他线程都是可用的。行级锁分为读锁和写锁。
- READ:读锁定,确保用户可以读取表,但是不能修改表。
- WRITE:写锁定,只有锁定该表的用户可以修改表,其他用户只能读取表。
间隙锁:间隙锁( Gap Lock)是 InnoDB 引擎在可重复读的隔离级别下为了解决幻读和数据误删问题而引入的锁机制。select * from test where id > 0 and id < 5 for update;时,如果表中不存在 id 为 2 的数据(只有1,3,4),这个id=2的数据被称为“间隙”, InnoDB引擎也会对这些“间隙”加锁。此时,如果事务T2执行 INSERT 语句,插入一条 id 为 2 的数据,则需要等到事务 T1结束才可以插入成功。
锁命令
设置共享锁:SELECT * FROM 表名 WHERE 条件 LOCK IN SHARE MODE;
设置排他锁:SELECT * FROM 表名 WHERE 条件 FOR UPDATE; (InnoDB引擎会自动在默认的修改语句update、delete、insert加上排他锁)
锁等待
锁等待是指在一个事务执行过程中,一个锁需要等到上一个事务的锁释放后才可以使用该资源。
参数:
- innodb_lock_wait_timout :锁等待时间参数
- select * from sys.innodb_lock_waits\G:查看锁等待发生情况
- show full processlist:输出线程id号
死锁
在MySQL的InnoDB存储引擎中,当检测到死锁时,通常会使一个持有最少行级排它锁的事务释放锁并回滚,而让另一个事务获得锁并继续完成事务。
监控事务和锁
查看和监控事务、锁信息,可以通过执行show engine innodb status 命令。
MySQL将事务和锁信息记录在了information_schema数据库中,我们只需要查询即可。涉及的表主要有3个,即innodb_trx(查看事务情况)、innodb_locks(查询锁情况)、innodb_lock_waits(查看锁阻塞情况)。
MySQL5.6以上版本,可以设置innodb_print_all_deadlocks参数为1来记录死锁信息,并放到错误日志里。
避免死锁的方法
避免死锁的方法
(I) 不同程序并发存取多个表或者涉及多行记录时,尽量约定以相同的顺序访问表, 可以大大降低死锁的机会。
(2) 对应用程序进行调整,在某些情况下,通过把大事务分解成多个小事务,使得锁能够更快被释放,及时提交或者回滚事务,可减少死锁发生的概率。
(3) 在同一个事务中,尽可能做到一次锁定所需要的所有资源,减少死锁产生的概率。
(4) 为表添加合理的索引,不用索引将会为表的每一行记录加上锁,死锁的概率大大增大。
(5) 对非常容易产生死锁的业务,可尝试升级锁粒度,通过表锁定来减少死锁产生的概率。