欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

MySQL涉及锁的问题

程序员文章站 2022-04-16 09:45:33
如何并发的访问数据库呢?答案就是加锁。 接下来说一下,数据库的锁机制,数据库中都有哪些锁? 首先呢,锁是一种并发控制技术,锁是用来在多个用户同时访问同一个数据的时候保护数据的。 有2种基本的锁类型: 共享(S)锁:多个事务可*一个共享页;任何事务都不能修改该页;通常是该页被读取完毕,S锁立即被释放 ......

如何并发的访问数据库呢?答案就是加锁

接下来说一下,数据库的锁机制,数据库中都有哪些锁?

  首先呢,锁是一种并发控制技术,锁是用来在多个用户同时访问同一个数据的时候保护数据的。

有2种基本的锁类型:

  共享(s)锁:多个事务可*一个共享页;任何事务都不能修改该页;通常是该页被读取完毕,s锁立即被释放。在执行select语句的时候需要给操作对象(表或一些记录)加上共享锁,但加锁之前需要检查是否有排他锁,如果没有,则可以加共享锁(一个对象上可以加n个共享锁),否则不行。共享锁通常在执行完select语句之后被释放,当然也可能是在事务结束(包括正常结束和异常结束)的时候被释放,主要取决与数据库所设置的事务隔离级别。

  排它(x)锁:仅允许一个事务*此页;其他任何事务必须等到x锁被释放才能对该页进行访问;x锁一直到事务结束才能被释放。执行insert、update、delete语句的时候需要给操作的对象加排它锁,在加排他锁之前必须确认该对象上没有其他任何锁,一旦加上排它锁之后,就不能再给这个对象加其他任何锁。排它锁的释放通常是在事务结束的时候(当然也有例外,就是在数据库事务隔离级别被设置为read uncommitted(读未提交数据)的时候,这种情况下排他锁会在执行完更新操作之后被释放,而不是在事务结束的时候)。

按锁的机制

既然使用了锁,就有出现死锁的可能。

产生死锁的四个必要条件:

  互斥条件:一个资源每次只能被一个进程使用。

  请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。

  不可剥夺条件:进程已获得的资源,在未使用完之前,不能强行剥夺。

  环路等待条件:若干个进程之间形成一种头尾相接的循环等待资源关系。

只要系统发生了死锁,这些条件必然成立,而只要上述条件之一不满足,就不会发生死锁。

预防死锁

预防死锁的发生只需要破坏死锁产生的四个必要条件之一即可。

1)破坏互斥条件

  如果允许系统资源都能共享使用,则系统不会进入死锁状态。但有些资源根本不能同时访问,如打印机等临界资源只能互斥使用。所以破坏互斥条件而预防死锁的方法不太可行,而且在有些场合应该保护这种互斥性。

2)破坏不可剥夺条件

  当一个已保持了某些不可剥夺资源的进程,请求新的资源而得不到满足时,它必须释放已经保持的所有资源,待以后需要时再重新申请。这意味着,一个进程已占有的资源会被暂时释放,或者说是被剥夺了,或从而破坏了不可剥夺条件。

  该策略实现起来比较复杂,释放已获得的资源可能造成前一阶段工作的失效,反复地申请和释放资源会增加系统开销,降低系统吞吐量。这种方法常用于状态易于保存和恢复的资源,如cpu的寄存器及内存资源,一般不能用于打印机之类的资源。

3)破坏请求和保持的条件

  采用预先静态分配方法,即进程在运行前一次申请完它所需要的全部资源,在它的资源为满足前,不把它投入运行。一旦投入运行后,这些资源就一直归它所有,也不再提出其他资源请求,这样就可以保证系统不会发生死锁。

  这种方式实现简单,但缺点也显而易见,系统资源被严重浪费,其中有些资源可能仅在运行初期或运行快结束时才使用,甚至根本不使用。而且还会导致“饥饿”现象,当由于个别资源长期被其他进程占用时,将致使等待该资源的进程迟迟不能开始运行。

4)破坏环路等待条件

  为了破坏环路等待条件,可采用顺序资源分配法。首先给系统中的资源编号,规定每个进程,必须按编号递增的顺序请求资源,同类资源一次申请完。也就是说,只要进程提出申请分配资源ri,则该进程在以后的资源申请种,只能申请编号大于ri的资源。

  这种方法存在的问题时,编号必须相对稳定,这就限制了新类型设备的增加;尽管在为资源编号时已考虑到大多数作业实际使用这些资源的顺序,但也经常会发生作业使用资源的顺序与系统规定顺序不同的情况,造成资源的浪费;此外,这种按规定次序申请资源的方法,也必然会给用户的编程带来麻烦。

解除死锁

  1)从死锁进程处剥夺资源;

  2)终止部分或全部进程;

mysql锁的粒度(即锁的级别)

mysql各存储引擎使用了三种类型(级别)的锁定机制:行级锁定、页级锁定和表级锁定。

  1、表级锁:直接锁定整张表,在你锁定期间,其他进程无法对该表进行写操作。如果你是写锁,则其他进程则读也不允许。特点:开销小,加锁快;不会出现死锁;锁粒度最大,发生锁冲突的概率最高,并发度最低。

    myisam存储引擎采用的是表级锁。

    有两种模式:表共享读锁和表独占写锁。加读锁的命令:lock table 表名 read;  去掉锁的命令:unlock tables。

    支持并发插入:支持查询和插入操作并发运行(在表尾并发插入)。

    锁调度机制:写锁优先。一个进程请求某个myisam表的读锁,同时另一个进程也请求同一表的写锁,mysql如何处理呢?答案是写进程先获得锁。

  2、行级锁:仅对指定的记录进行加锁,这样其他进程还是可以对同一个表中的其他记录进行操作。特点:开销大,加锁慢;会出现死锁;锁粒度最小,发生锁冲突的概率最低,并发度也最高。

    innodb存储引擎既支持行级锁,也支持表级锁,但默认情况下是采用行级锁。

  3、页级锁:一次锁定相邻的一组记录。开销和加锁时间介于表锁和行锁之间;会出现死锁;锁定粒度介于表锁和行锁治安,并发度一般。

  最常用的处理多用户并发访问的方法是加锁。当一个用户锁定数据库中的某个对象时,其他用户就不能再访问该对象。加锁对并发访问的影响体现在锁的粒度上。比如,(表锁)放在一个表上的锁限制对整个表的并发访问;(页锁)放在数据页上的锁限制了对整个数据页的访问;(行锁)放在行上的锁只限制对该行的并发访问。

乐观锁和悲观锁的概念,实现方式和使用场景

锁有两种机制:悲观锁和乐观锁

  悲观锁,锁如其名,它对世界是悲观的,它认为别人访问正在改变的数据的概率是很高的,所以从数据开始更改时就将数据锁住,直到更改完成才释放。

一个典型的依赖数据库的悲观锁调用:

  select * from account where name="erica" for update

  这条sql语句锁定了account表中所有符合检索条件(name="erica")的记录。本事务提交之前(事务提交时会释放事务过程中的锁),外界无法修改这些记录。该语句用来锁定特定的行(如果where子句,就是满足where条件的那些行)。当这些行被锁定后,其他会话可以选择这些行,但不能更改或删除这些行,直到该语句的事务被commit语句或rollback语句结束终止。需要注意的是,select ...for update要放到mysql的事务种,即begin和commit中,否则不起作用。

  悲观所可能会造成加锁的时间很长,并发行不好,特别是长事务,影响系统的整体性能。

  悲观所的实现方式:

    悲观锁,也是基于数据库的锁机制实现。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁、写锁等,都是在做操作之前先上锁。

  乐观锁,它对世界比较乐观,认为别人访问正在改变的数据的概率是很低的,所以直到修改完成准备提交所作的修改到数据库的时候才会将数据锁住,当你读取以及改变该对象时并不加锁,完成更改后释放。乐观锁不能解决脏读的问题。

  乐观锁加锁的时间要比悲观锁短,大大提升了大并发量下的系统整体性能表现。

  乐观锁的实现方式

    1、大多是基于数据版本(version)记录机制实现,需要为每一行数据增加一个版本标识(也就是每一行数据多一个字段version),每次更新数据都要更新对应的版本号+1。

    工作原理:读出数据时,将此版本一同读出,之后更新时,对此版本号加一。此时,将提交的数据的版本信息与数据库表对应记录的当前版本信息进行比对,如果提交的数据版本号大于数据库表当前版本号,则予以更新,否则认为是过期数据,不得不重新读取该对象并作出更改。

    2、使用时间戳来实现

    同样是在需要乐观锁控制的table中增加一个字段,名称无所谓,字段类型使用时间戳(timestamp),和上面的version类似,也是在更新提交的时候检查当前数据库中数据的时间戳和自己更新前取到的时间戳进行对比,如果一致则ok,否则就是版本冲突。

悲观锁与乐观锁的适用场景:

  如果并发量不大,可以使用悲观锁解决并发问题;但如果系统的并发量非常大的话,悲观所定会带来非常大的性能问题,所以我们就要选择乐观锁定的方法。现在大部分应用都应该是乐观锁的。