1. 事务

1.1 什么是事务:

事务就是一组连续的操作,要不都执行,要不都不执行。
举个例子:

  1. # 将数据插入的时候,发生错误
  2. # 开启事务
  3. mysql> begin;
  4. Query OK, 0 rows affected (0.00 sec)
  5. #插入数据,产生冲突
  6. mysql> insert into data values ( 11, 'test');
  7. ERROR 1136 (21S01):
  8. # 不产生冲突的数据
  9. mysql> insert into data values ( 12, 'test', 'yby');
  10. Query OK, 1 row affected (0.00 sec)
  11. # 发现存在错误需要回滚
  12. mysql> rollback;
  13. Query OK, 0 rows affected (0.00 sec)
  14. #提交事务
  15. mysql> commit;
  16. Query OK, 0 rows affected (0.00 sec)
  17. # 最终没有插入数据
  18. mysql> select * from data;
  19. +----+-------+--------+
  20. | id | title | author |
  21. +----+-------+--------+
  22. | 1 | yby | yby |
  23. | 4 | yby3 | yby3 |
  24. | 5 | yby4 | yby4 |
  25. | 6 | yby5 | yby5 |
  26. | 7 | yby6 | yby6 |
  27. | 8 | yby7 | yby7 |
  28. | 9 | test | yby |
  29. | 10 | test | yby |
  30. | 11 | test | yby |
  31. +----+-------+--------+

1.2 事务的四大特性(ACID):

  1. 原子性: 最小的执行单位,不允许分割。事务的原子性确保要么动作全部完成,要么完全不起作用。
  2. 一致性:执行事务前后,数据需要保持一致,多个事务对同一个数据读取的结果是相同的。事务执行的前后,都是合法的数据状态。

    看概念没看懂,这个性质应该是需要结合生活中的实际来看,你比如幻读,对吧那就不一致,但是假如结合具体的业务来看,那就对了:

    1. # 假如你的数据库,要扣钱
    2. 第一步:从建设银行卡的余额里扣除1000
    3. 第二步:然后再从中国银行的卡的余额里增加1000

    那假如第一步完成了,第二步没有完成,那么这就是不一致性。这样看来实际上 AID 都是为了实现C

  3. 隔离性:并发访问数据库时,一个用户的事务不被其他事务所干扰,各并发事务之间数据库是独立的。

  4. 持久性:一个事务被提交后,对数据库中,数据的改变是持久的,即使数据库发生故障也不会有任何影响。

    1.3 事务的隔离级别:

    1.3.1 读取未提交(RU):

  • 最低的隔离级别,允许读取未提交的数据变更。 ```plsql

    用户 A 开启事务

    mysql> begin; Query OK, 0 rows affected (0.00 sec)

修改数据但未提交

mysql> update data set title = 1 where id = 4; Query OK, 1 row affected (0.00 sec) Rows matched: 1 Changed: 1 Warnings: 0

用户 B 开启事务

mysql> select * from data; +——+———-+————+ | id | title | author | +——+———-+————+ | 1 | 1 | yby | | 4 | yby3 | yby3 | | 5 | yby4 | yby4 | | 6 | yby5 | yby5 | | 7 | yby6 | yby6 | | 8 | yby7 | yby7 | | 9 | test | yby | | 10 | test | yby | | 11 | 5000 | wj | +——+———-+————+ 9 rows in set (0.00 sec)

读取到未提交的修改

mysql> select * from data; +——+———-+————+ | id | title | author | +——+———-+————+ | 1 | 1 | yby | | 4 | 1 | yby3 | <— | 5 | yby4 | yby4 | | 6 | yby5 | yby5 | | 7 | yby6 | yby6 | | 8 | yby7 | yby7 | | 9 | test | yby | | 10 | test | yby | | 11 | 5000 | wj | +——+———-+————+ 9 rows in set (0.00 sec)

  1. <a name="y4Ynd"></a>
  2. ### 1.3.2 读取已提交(RC)
  3. - 允许读取**并发事务已提交的数据。(和读未提交相反)**
  4. <a name="Q4Ak6"></a>
  5. ### 1.3.3 可重复读(RR)
  6. - 对同**一字段的多次读取结果**都是一致的,除非数据是被事务自己所修改。
  7. 举个例子:
  8. ```plsql
  9. # 用户A 开启事务
  10. mysql> use test;
  11. Database changed
  12. # 查看表中数据
  13. mysql> select * from data;
  14. +----+-------+--------+
  15. | id | title | author |
  16. +----+-------+--------+
  17. | 1 | yby | yby |
  18. | 4 | yby3 | yby3 |
  19. | 5 | yby4 | yby4 |
  20. | 6 | yby5 | yby5 |
  21. | 7 | yby6 | yby6 |
  22. | 8 | yby7 | yby7 |
  23. | 9 | test | yby |
  24. | 10 | test | yby |
  25. | 11 | test | yby |
  26. +----+-------+--------+
  27. 9 rows in set (0.00 sec)
  28. # 用户 B 更改表数据
  29. mysql> select * from data;
  30. +----+-------+--------+
  31. | id | title | author |
  32. +----+-------+--------+
  33. | 1 | yby | yby |
  34. | 4 | yby3 | yby3 |
  35. | 5 | yby4 | yby4 |
  36. | 6 | yby5 | yby5 |
  37. | 7 | yby6 | yby6 |
  38. | 8 | yby7 | yby7 |
  39. | 9 | test | yby |
  40. | 10 | test | yby |
  41. | 11 | test | yby |
  42. +----+-------+--------+
  43. 9 rows in set (0.00 sec)
  44. # 更新数据
  45. mysql> update data set title = '5000', author= 'wj' where id
  46. = 11;
  47. Query OK, 1 row affected (0.01 sec)
  48. Rows matched: 1 Changed: 1 Warnings: 0
  49. # 用户 A 在事务内查询
  50. # 不产生变化
  51. mysql> select * from data;
  52. +----+-------+--------+
  53. | id | title | author |
  54. +----+-------+--------+
  55. | 1 | yby | yby |
  56. | 4 | yby3 | yby3 |
  57. | 5 | yby4 | yby4 |
  58. | 6 | yby5 | yby5 |
  59. | 7 | yby6 | yby6 |
  60. | 8 | yby7 | yby7 |
  61. | 9 | test | yby |
  62. | 10 | test | yby |
  63. | 11 | test | yby |
  64. +----+-------+--------+
  65. 9 rows in set (0.00 sec)
  • 然后用户 A 提交事务后,再进行查看

    1. # 数据已更改
    2. mysql> select * from data;
    3. +----+-------+--------+
    4. | id | title | author |
    5. +----+-------+--------+
    6. | 1 | yby | yby |
    7. | 4 | yby3 | yby3 |
    8. | 5 | yby4 | yby4 |
    9. | 6 | yby5 | yby5 |
    10. | 7 | yby6 | yby6 |
    11. | 8 | yby7 | yby7 |
    12. | 9 | test | yby |
    13. | 10 | test | yby |
    14. | 11 | 5000 | wj |
    15. +----+-------+--------+
    16. 9 rows in set (0.00 sec)

    1.3.4 可串行化(SE)

  • 最高隔离级别,完全服从ACID的隔离级别,所有事务一次逐个执行,事务之间完全不可能产生干扰。

    1.4 并发时产生的问题:

    1.4.1 脏读:(只在读取未提交情况下出现)

    当一个事务正在访问数据,并且对数据进行了修改,这种修改还没提交到数据库中,这时另一个事务也访问了这个数据,然后使用了此数据,但是这个数据还没有提交,依据脏数据的操作可能是不正确的

    1.4.2 更新丢失(这个说实话,很容易解决):

    指一个事务读取另一个数据时,另一个事务也读取该数据,第一个事务将该数据修改后,第二个事务也对该数据进行了修改。这样第一个数据的修改结果就被丢失,因此称为丢失修改。

    1.4.3 不可重复读:

    指在一个事务内多次读同一数据。这个事务还没结束时,另一个事务也访问该数据。那么在第一个事务的两次读数据之间,由于第二个事务的修改导致第一个事务两次读取的数据可能不太一样。这就发生了在一个事务内两次读到的数据是不一样的情况。

    1.4.4 幻读:

    幻读与不可重复读类似。它发⽣在⼀个事务(T1)读取了⼏⾏数据,接着另⼀个并发事务(T2)插⼊了⼀些数据时。在随后的查询中,第⼀个事务(T1) 就会发现多了⼀些原本不存在的记录,就好像发⽣了幻觉⼀样,所以称为幻读。

幻读 和 不可重复读的区别,不可重复读传递重点是修改,幻读的重点在于新增或者删除数据。

1.5 事务隔离级别和并行问题的关系:

隔离级别 读数据一致性 脏读 不可重复读 幻读
可读未提交 最低级别 Y Y Y
可读已提交 语句级 N Y Y
可重复读 事务级 N N Y
串行化 事务级 N N N

2. Mysql 中的锁:

2.1 按照颗粒度区分:

2.1.1 行级锁:

Mysql中锁定 粒度最小 的一种锁,只针对当前操作的行进行加锁。 行级锁能大大减少数据库操作的冲突。其加锁粒度最小,并发度高,但加锁的开销也最大,加锁慢,会出现死锁。 InnoDB支持的行级锁,包括如下几种。

1. Record Lock:

  1. 记录锁。record lock 是加在**具体记录对应聚簇索引上的锁,它锁住的是索引本身而不是记录**。它既可以是共享锁也可以是排它锁。任何级别都会存在。

2. Gap Lock:

对索引项之间的“间隙”加锁,锁定记录的范围(对第一条记录前的间隙或最后一条将记录后的间隙加锁),不包含索引项本身。其他事务不能在锁范围内插入数据,这样就防止了别的事务新增幻影行。只有在可重复读以上级别才会有。

3. Next-key Lock:

是 Record Lock 和 Gap Lock 的结合。针对 Gap Lock 中的例子

  1. # 事务 1 执行
  2. select * from dept where id > 4 and id < 8;
  3. # 则对于数据 (5, "aa") 和 (7, "bb")对应的聚簇索引也会添加 Record Lock
  4. # 同时 (4,5) , (5,7) , (7,8) 也会加上间隙锁

同 Gap Lock 一样,只有可重复读以上级别才会出现。Next-key lock 都是 “左开右闭” 的,也就是以前面的事务1来举例,会添加的锁依次是:net-key lock (4,5]、(5,6]、(6,7],间隙锁(7,8)。

2.1.3 表级锁:

Mysql中锁定 粒度最大 的一种锁,对当前操作的整张表加锁,实现简单 ,资源消耗也比较少,加锁快,不会出现死锁 。其锁定粒度最大,触发锁冲突的概率最高,并发度最低,MyISAM和 InnoDB引擎都支持表级锁。

二者的优缺点:

  • 事务更新大表中的大部分数据直接使用表级锁效率更高;
  • 事务比较复杂,使用行级索很可能引起死锁导致回滚。

    2.2 按照种类划分:

    2.2.1 共享锁(读锁):

    共享锁是对应 MySQL 中的读操作,所以共享锁也叫读锁,一个事务进行读操作时,会对读取的数据加锁,加锁后其他事务也可以对加锁的数据进行读取。
    假如事务(T1)为数据对象 A 加上读锁,那么事务T1 只能读 A,其他事务只能再对 A 加 读锁,不能加写锁,直到锁被释放。这就保证其他事务可以读 A ,但是在 T1 释放读锁前,不能进行任何修改。
    添加了读锁之后,只能对其进行读取,不能修改,也不能读取其他表的数据。

    2.2.2 排他锁(写锁):

    又称为写锁,假设 事务 T 对数据 A 加上 写锁,则只允许 T 读取 和 修改 A,其他任何事务都不能再对A加任何类型的锁,直到 T 释放 A 上的锁。它防止其他任何事务获取资源上的锁,直到在事务的末尾将资源上的原始锁释放为止。在 insert / update / delete 中始终应用排他锁。
    所有隔离级别都会添加排他锁。

    二者的区别:

  1. 共享锁:假如数据已经被加上了共享锁,那么其他事务只能对数据加上共享锁,不能再加 排他锁。获取共享锁的事务只能读数据,不能修改数据。
  2. 排他锁:如果事务对 数据 加上排它锁之后,则其他事务不能再加上任何其他的锁。获取排它锁的事务既能读数据,又能修改数据。

意向锁的作用就是当一个事务在需要获取资源锁定的时候,如果遇到自己需要的资源已经被排他锁占用的时候,该事务可以需要锁定行的表上面添加一个合适的意向锁。
如果自己需要一个共享锁,那么就在表上面添加一个意向共享锁。而如果自己需要的是某行(或者某些行)上面添加一个排他锁的话,则先在表上面添加一个意向排他锁。意向共享锁可以同时并存多个,但是意向排他锁同时只能有一个存在

2.2.3 IS 意向共享锁(表级锁):

表示事务准备给数据行加入共享锁,事务在一个数据行加共享锁之前,必须先获得表的 IS 锁。

2.2.4 IX 意向排它锁(表级锁):

表示事务准备给数据行加入排他锁,事务在一个数据行加排它锁之前,必须先获得表的 IX 锁。

  • 注意这里的意向仅仅表示事务正在读或写某一行记录,在真正加行锁时,才会判断是否冲突。意向锁是 Innodb 自动加的,不需要用户干预。

image.png