其他分享
首页 > 其他分享> > 事务和锁机制

事务和锁机制

作者:互联网

事务的使用

1. 什么是事务
    (事务)transactions, 顾名思义就是要做或所做的事情, 数据库事务指的是作为单个工作单位执行的一系列操作(sql语句).
    这些操作要么全部执行. 要么全部都不执行


2. 为什么要有事务
    将一系列sql语句放到一个事务里面有两个目的:
        1. 为数据库操作提供一个从失败中恢复到正常状态的方法. 同时提供了数据库即使在异常状态下仍能保持一致性的方法
        2. 当多个应用该程序在并发访问数据库时. 可以在这些应用程序之间你提供一个隔离方法. 以防止彼此的操作互相干扰


事务有四大特性(ACID)

   1. **原子性(atomicity)**
      事务的一个整体被执行. 包含在其中的对数据库的操作要么全部被执行. 要么都不执行
   2. **一致性(consistency)**
      事务应该确保数据库的状态从一个一直状态到另一个一直状态.例如转账行为中.一个人减了50元. 另外一个人就应该加上50元 而不是40元
      其他一致状态的含义是数据库中的数据应该满足完整性约束. 例如字段约束不能为负数. 事务执行完毕后的该字段页同样同样不能是负数
   3. **隔离性(Isolation)**
      多个事务并发执行时. 一个事务的执行不应影响其他事务执行
   4. **持久性(Durability)**
      一个事务一旦提交. 他对数据库的修改应该永久保存在数据库中

mysql事务的三种运行模式

  1. 自动提交事务(隐式开启. 隐式提交)
  2. 隐式事务(隐式开启. 显示提交)
  3. 显示事务(显示开启. 显示提交)

开启事务:
start transaction; -- 开启事务
update test set age = 32312 where id = 1; -- 修改数据
commit; -- 提交
rollback; -- 回滚

commit 和 rollback事务都结束了

pymysql中隐式开启. 显示提交

try:
cursor.execute(sql_1)
cursor.execute(sql_2)
cursor.execute(sql_3)
except Exception as e:
connect.rollback() # 事务回滚
print("事务处理失败", e)
else:
connect.commit() # 事务提交
print('事务处理成功', cursor.rowcount) # 关闭连接
cursor.close()
connect.close()

事务的控制语句

start transaction(或 begin):显式开始一个新事务 #开启事务
savepoint:分配事务过程中的一个位置,以供将来引用 #临时存档
commit:永久记录当前事务所做的更改 #提交
rollback:取消当前事务所做的更改 #回滚
rollback to savepoint:取消在 savepoint 之后执行的更改 #回到存档点
release savepoint:删除 savepoint 标识符 #删除临时存档
set autocommit:为当前连接禁用或启用默认 autocommit 模式

事务的使用原则

  1. 保持事务短小
  2. 尽量避免事务中rollback
  3. 尽量避免savepoint
  4. 显式声明打开事务
  5. 默认情况下,依赖于悲观锁,为吞吐量要求苛刻的事务考虑乐观锁
  6. 锁的行越少越好,锁的时间越短越好

数据库读现象

数据库读现象: 多个事务并发执行的时候. 在读数据方面可能会遇到各种各样的问题: 脏读. 不可重复读. 幻读

脏读(dirty read)

​ 事务T2更新了一行记录的内容,但是并没有提交所做的修改。
事务T1读取更新后的行,然后T1执行回滚操作,取消了刚才所做的修改。此时T1所读取的行就无效了,称之为脏数据

不可重复读(nonrepeatable read)

​ 事务T1读取一行记录,紧接着事务T2修改了T1刚才读取的那一行记录并且提交了。
然后T1又再次读取这行记录,发现与刚才读取的结果不同。这就称为“不可重复”读,因为T1原来读取的那行记录已经发生了变化。

幻读(phantom read)

​ 事务T1读取或修改了指定的where子句所返回的结果集。
然后事务T2新插入一行记录,这行记录恰好可以满足T1所使用的查询条件中的WHERE 子句的条件。
然后T1又使用相同的查询再次对表进行检索,但是此时却看到了事务T2刚才插入的新行或者发现了处于where子句范围内,但却未曾修改过的记录。
就好像“幻觉”一样,因为对T1来说这一行就像突然出现的一样。
一般解决幻读的方法是增加范围锁RangeS,锁定检锁范围为只读,这样就避免了幻读。

解决方案

​ 其实,脏写、脏读、不可重复读、幻读,都是因为业务系统会多线程并发执行,每个线程可能都会开启一个事务,每个事务都会执行增删改查操作。
​ 然后数据库会并发执行多个事务,多个事务可能会并发地对缓存页里的同一批数据进行增删改查操作,于是这个并发增删改查同一批数据的问题,可能就会导致我们说的脏写、脏读、不可重复读、幻读这些问题。
​ 所以这些问题的本质,都是数据库的多事务并发问题,那么为了解决多事务并发带来的脏读、不可重复读、幻读等读等问题,
​ 数据库才设计了锁机制、事务隔离机制、MVCC 多版本隔离机制,用一整套机制来解决多事务并发问题

mysql锁机制即其他知识点

1. 什么是锁
    锁是计算机协调多个进程或线程并发访问某一资源的机制

2. 为什么要加入锁机制
    当并发事务访问一个共享资源的时. 有时候可能导致数据不一致.数据无效等问题
    而锁机制可以将并发的数据访问顺序化,以保证数据库中数据的一致性与有效性
    此外,锁冲突也是影响数据库并发访问性能的一个重要因素,锁对数据库而言显得尤其重要,也更加复杂。

3. 怎么加锁
    对于insert. update. delete语句. innodb会自动给涉及的数据加锁. 而且是排他锁.
    对于普通的select语句. innodb不会加任和锁. 需要我们手动自己加. 可以加两种类型的锁
    1. 共享锁: select ... lock in share mode;  -- 查出的记录行都会被锁住
    2. 排他锁: select ... for update;  -- 查出的记录行都会被锁住

锁介绍

共享锁(Share lock): 共享锁就是多个事务对于同一数据可以共享一把锁,获准共享锁的事务只能读数据,
不能修改数据直到已释放所有共享锁,所以共享锁可以支持并发读
如果事务T对数据A加上共享锁后,则其他事务只能对A再加共享锁或不加锁(在其他事务里一定不能再加排他锁,但是在事务T自己里面是可以加的)
排它锁(eXclusive Lock): 排他锁就是不能与其他所并存,如一个事务获取了一个数据行的排他锁,
其他事务就不能再对该行加任何类型的其他他锁(共享锁和排他锁),但是获取排他锁的事务是可以对数据就行读取和修改。

锁中出现的一些问题

innodb行锁的特性:

  1. 如果没有命中索引,InnoDB 会通过隐藏的聚簇索引来对记录加锁。
    也就是说:如果不通过索引条件检索数据,那么InnoDB将对表中所有数据加锁,实际效果跟表级锁一样。
    例如: id没有primary key主键索引. 锁行相当于锁表
  2. 如果一条sql语句操作了主键索引. mysql就会锁住这条语句就会命中这条主键索引
  3. 如果一条sql语句操作了非主键索引. mysql会先锁住该非主键索引. 在锁定相关的主键索引

在实际应用中要非常注意innodb行锁这个特性. 不然的话. 可能出现各种锁冲突. 从而影响并发性能
1、在不通过索引条件查询的时候,InnoDB 的效果就相当于表锁
2、当表有多个索引的时候,不同的事务可以使用不同的索引锁定不同的行,另外,不论 是使用主键索引、唯一索引或普通索引,InnoDB 都会使用行锁来对数据加锁。
3、由于 MySQL 的行锁是针对索引加的锁,不是针对记录加的锁,所以即便你的sql语句访问的是不同的记录行,但如果命中的是相同的被锁住的索引键,也还是会出现锁冲突的。
4、即便在条件中使用了索引字段,但是否使用索引来检索数据是由 MySQL 通过判断不同 执行计划的代价来决定的,如果 MySQL 认为全表扫 效率更高,比如对一些很小的表,它 就不会使用索引,这种情况下 InnoDB 将锁住所有行,相当于表锁。因此,在分析锁冲突时, 别忘了检查 SQL 的执行计划,以确认是否真正使用了索引

死锁问题

  1. 简单情况
    事务一:
    begin;
    select * from employee where id = 1 for update; -- 第一步(加上了排它锁)

    update employee set name = "xxx" where id = 3; -- 第三步, 阻塞

    事务二:
    begin;
    delete from employee where id = 3; -- 第二步(默认排它锁)

    update employee set name = "yyy" where id = 1; -- 第四部, 阻塞

  2. 复杂情况: 有innodb行锁特性导致的 -> 如果命中辅助索引. 则先锁住辅助索引在锁住主键索引
    两个事务. 每个事务就一个sql语句. 但是每个事务都是命中辅助索引. 交叉将对方锁住. 导致出现了死锁现象

三种行锁算法

  1. Record lock : 单个行记录上的锁
  2. Gap lock: 间隙锁. 锁定一个范围. 但是不包括记录本身.gap锁的目的是为了防止同一事务的两次当前读. 出现幻读现象
  3. Next-lock: 等于Record Lock结合Gap Lock. 注意: Innodb储存引擎还会对辅助索引下一个键值加上GAP lock

​ innodb采用的都是next-key-lock. 主要目的是解决幻读的问题.

事务隔离机制

​ mysql默认使用的是Repeatable read机制 + next-lock解决脏读. 幻读等各种问题
​ django框架中采用的Read committed机制. 其自身框架会解决不可重复读和幻读问题

多版本并发控制MVCC

快照读: 不加锁读
当前读: 加锁读取

​ MVCC好处: 读不加锁,读写不冲突
​ MVCC原理分析: https://www.cnblogs.com/linhaifeng/articles/14424181.html

标签:事务,并发,幻读,数据库,T1,索引,机制
来源: https://www.cnblogs.com/Maxs-message/p/16695067.html