美文网首页
数据库事务,锁

数据库事务,锁

作者: YoungChen_ | 来源:发表于2018-02-28 02:39 被阅读0次

    事务

    事务特点ACID

    1.原子性:一个事务被视为一个不可分割的最小工作单元,整个事务中所有操作要么全部提交成功,要么全部失败回滚,对于一个事务来说,不可能只执行一部分操作。
    2.一致性:数据库总是从一个一致性状态转换到另一个一致性状态。
    3.隔离性:通常来说,一个事务所做的修改在最终提交之前,对其他食物是不可见的。这里是“通常来说”,后面有事务隔离级别。
    4.持久性:一旦事务提交,则其所做的修改就会永久保存在数据库中。此时即使是系统崩溃,修改的数据也不会丢失。(持久性的安全性与刷新日志级别也存在一定关
    系,不同级别对应不同的数据安全级别)。
    

    理解ACID

    以银行转账为例

    START TRANSACTION;
    SELECT balance FROM checking WHERE customer_id = 10233276;
    UPDATE checking SET balance = balance - 200.00 WHERE customer_id = 10233276;
    UPDATE savings SET balance = balance + 200.00 WHERE customer_id = 10233276;
    COMMIT;
    
    set autocommit = 0;//关闭自动提交
    start transaction; //开启一个事务
    DML操作;          //两个及两个以上的DML操作
    ....
    commit;             //手动提交
    1.原子性:要么完全提交(10233276的checking余额减少200,savings 的余额增加200),要么完全回滚(两个表的余额都不发生变化)
    2.一致性:体现在200元不会因为数据库运行到第三行之后,第四行之前系统崩溃而不翼而飞,因为事务还没有提交。
    3.隔离性:事务A运行到第三行之后,第四行之前,此时事务B去查询checking的余额,它仍然能够看到被减去的200元(账户的钱不变),因为AB是彼此隔离的,
    在A提交之前,事务B是看不到数据变化的。
    
    注:事务的隔离性是通过锁实现的。
        事务的原子性,一致性和持久性是通过事务日志实现的。
    

    MySQL锁

    锁是MySQL在服务器层和存储引擎层的并发控制。

    锁机制

    1.共享锁(读锁):其他事务可以读,但是不能写。
    2.排他锁(写锁):其他事物不能读,也不能写。
    
    MySQL不同的存储引擎支持不同的锁机制。所有的存储引擎都以自己的方式显现了锁机制。
    1.MyISAM:采用的是表级锁。
    2.InnoDB:支持行级锁,也支持表级锁,默认情况下是行级锁。
    默认情况下:表锁和行锁都是自动获得的。
    但是在一些情况下,用户需要明确的进行锁表,或者事务控制,以确保事务的完整性,这个就需要事务控制和锁定语句来完成。
    
    锁比较:
    1.表级锁:开销小,速度快,不会出现死锁,粒度大,锁冲突概率高,并发度低。
        1)这些引擎总是一次性同时获得所需要的锁,总是按相同的顺序获取表锁来避免死锁。
        2)表级锁更适合于查询为主,并发用户少,只有少量更新数据的应用。
    2.行级锁:开销大,速度慢,会出现死锁,粒度小,发生锁冲突的概率低,并发度高。
        1)最大程度支持并发,同时带来了最大的锁开销。
        2)在InnoDB中,除单个SQL组成的事务外,锁是逐步获得的,所以发生死锁是可能的。
        3)行级锁适合于有大量索引条件并发更新数据,同时又并发查询的应用。
    3.页面锁:锁开销在表级锁和行级锁之间,会出现死锁,粒度介于行级锁和表级锁之间,并发度一般。
    

    MyISAM表锁

    表级锁模式

    1)表共享读锁:不会阻塞其他用户对同一张表的读请求,但会阻塞对同一张表的写请求。(有人在读时不能写)
    2)表独占写锁:会阻塞其他用户对同一张表的读和写请求。(有人在写是不能读也不能写)
    注:当一个线程获得一个表的写锁时,只有持有锁的线程能够对表进行更新操作,其他线程的读写操作都会等待,直到锁被释放,默认情况下:写锁比读锁有更高的
    优先级:当一个锁被释放时,这个锁会优先给请求写锁队列,然后再给请求读锁队列。这也是MyISAM不适合有大量更新操作和查询操作的应用。因为大量的更新操作
    会造成查询操作难以获得锁,从而永远阻塞。
        一些需要长时间运行的查询操作,也会使线程饿死,应用中尽量避免出现这种情况,可以使用中间表,可以对SQL语句进行分解,使每个查询都在短时间完成,
    从而减少锁冲突。
    解决办法:
        1)指定启动参数low-priority-updates,是MyISAM引擎默认给予渡请求优先权。
        2)执行命令SET LOW_PRIORITY_UPDATES=1,使该连接发出的更新请求优先级降低。
        3)指定INSERT、UPDATE、DELETE语句的LOW_PRIORITY属性,降低该语句的优先级。
        4)MySQL系统参数max_write_lock_count设置一个合适的值,当一个表的读锁达到这个值后,MySQL就暂时将写请求的优先级降低,给读进程一定获得锁的机会。
    

    MyISAM加表锁方法

    MyISAM 在执行查询语句(SELECT)前,会自动给涉及的表加读锁,在执行更新操作 (UPDATE、DELETE、INSERT 等)前,会自动给涉及的表加写锁。
    注:在自动加锁的情况下,MyISAM 总是一次获得 SQL 语句所需要的全部锁,这也正是 MyISAM 表不会出现死锁(Deadlock Free)的原因。
    

    MyISAM并发插入

    MyISAM存储引擎支持并发插入,以减少给定表的读和写操作之间的争用。
    1)如果表在数据文件中间没有空闲块,则行始终插入在数据文件末尾。这个时候可并发混合使用Insert和select语句而不需要加锁。
        即:在其他线程进行读操作是的时候,你可以将行插入到表中。
    注:文件中的空闲块可能是从表中删除或更新产生的。
    2)如果文件中有空闲块,并发插入就会被禁用,但是当所有的空闲块都填充新数据时,重新启动。
    3)要控制此行为:可以使用MySQL的系统变量-----concurrent_insert
        3.1)设置为0时,不允许并发插入。
        3.2)设置为1时,没有空闲块允许插入,允许一个线程读,一个线程在表尾插入数据。这是MyISAM默认的设置。
        3.3)设置为2时,无论有没有空闲块,都允许在表尾并发插入数据。
    

    InnoDB行级锁和表级锁

    InnoDB锁模式

    1)共享锁:允许一个事务去读取一行,阻止其他事务获得相同数据的排它锁。(手动加,加上之后其他事务只能查不能更新,自己可以更新)
    2)排他锁:允许获得排它锁的事务更新数据,阻止其他事务取得相同数据的共享锁和排它锁。(自动加,加上后不允许再加共享锁,其他会话可以查询,不允许其他事务
    获得排它锁,即:更新)
    为了允许行锁和表锁共存,实现多粒度锁机制,InnoDB 还有两种内部使用的意向锁(Intention Locks)。
    这两种意向锁都是表锁:
        1)意向共享锁(IS):事务打算给数据行加行共享锁,事务在给一个数据行加共享锁前必须先取得该表的 IS 锁。 
        2)意向排他锁(IX):事务打算给数据行加行排他锁,事务在给一个数据行加排他锁前必须先取得该表的 IX 锁。
    

    InnoDB加锁方法

    1)意向锁是 InnoDB 自动加的, 不需用户干预。 
    2)对于 UPDATE、 DELETE 和 INSERT 语句, InnoDB 会自动给涉及数据集加排他锁(X);
    3)对于普通 SELECT 语句,InnoDB 不会加任何锁;
    4)事务可以通过以下语句显式给记录集加共享锁或排他锁:
        4.1)共享锁(S):SELECT * FROM table_name WHERE ... LOCK IN SHARE MODE。其他会话仍然可以查询录,并也可以对该记录加 share mode的共享锁。
        但是如果当前事务需要对该记录进行更新操作,则很有可能造成死锁。
        4.2)排他锁(X):SELECT * FROM table_name WHERE ... FOR UPDATE。其他会话可以查询该记录,但是不能对该记录加共享锁或排他锁,而是等待获得锁。
    注:锁只有在执行commit或者rollback的时候才会释放,并且所有的锁都是在同一时刻被释放。 
    

    显示加锁:

    1)select...for update:
    在执行这个select查询语句的时候,会将对应的索引访问条目进行上排他锁(X 锁),也就是说这个语句对应的锁就相当于update带来的效果。
    select *** for update 的使用场景:为了让自己查到的数据确保是最新数据,并且查到后的数据只允许自己来修改的时候,需要用到 for update 子句。
    
    2)select lock in share mode :
    in share mode 子句的作用就是将查找到的数据加上一个 share 锁,这个就是表示其他的事务只能对这些数据进行简单的select 操作,并不能够进行 DML 操作。
    select *** lock in share mode 使用场景:为了确保自己查到的数据没有被其他的事务正在修改,也就是说确保查到的数据是最新的数据,并且不允许其他人来
    修改数据。但是自己不一定能够修改数据,因为有可能其他的事务也对这些数据 使用了 in share mode 的方式上了 S 锁。
    
    性能影响:
    1)select for update 语句,相当于一个 update 语句。在业务繁忙的情况下,如果事务没有及时的commit或者rollback 可能会造成其他事务长时间的等待,从而
    影响数据库的并发使用效率。
    2)select lock in share mode 语句是一个给查找的数据上一个共享锁(S 锁)的功能,它允许其他的事务也对该数据上S锁,但是不能够允许对该数据进行修改。如果
    不及时的commit 或者rollback 也可能会造成大量的事务等待。
    
    for update 和 lock in share mode 的区别:
    前一个上的是排他锁(X 锁),一旦一个事务获取了这个锁,其他的事务是没法在这些数据上执行 for update ;后一个是共享锁,多个事务可以同时的对相同数据
    执行lock in share mode。
    

    InnoDB 行锁实现方式:

    1)InnoDB 行锁是通过给索引上的索引项加锁来实现的,这一点 MySQL 与 Oracle 不同,后者是通过在数据块中对相应数据行加锁来实现的。InnoDB 这种行锁实现特点
    意味着:只有通过索引条件检索数据,InnoDB 才使用行级锁,否则,InnoDB 将使用表锁!
    
    2)不论是使用主键索引、唯一索引或普通索引,InnoDB 都会使用行锁来对数据加锁。
    
    3)只有执行计划真正使用了索引,才能使用行锁:即便在条件中使用了索引字段,但是否使用索引来检索数据是由 MySQL 通过判断不同执行计划的代价来决定的,如果 
    MySQL 认为全表扫描效率更高,比如对一些很小的表,它就不会使用索引,这种情况下 InnoDB 将使用表锁,而不是行锁。因此,在分析锁冲突时, 别忘了检查 SQL 
    的执行计划(可以通过 explain 检查 SQL 的执行计划),以确认是否真正使用了索引。(更多阅读:MySQL索引总结)
    
    4)由于 MySQL 的行锁是针对索引加的锁,不是针对记录加的锁,所以虽然多个session是访问不同行的记录, 但是如果是使用相同的索引键, 是会出现锁冲突的
    (后使用这些索引的session需要等待先使用索引的session释放锁后,才能获取锁)。 应用设计的时候要注意这一点。
    

    InnoDB使用间隙锁的目的

    防止幻读:
        幻读:一个事务使用范围索引,满足条件的行以及空隙都会被锁,如id>5的name修改为xxxx, 则id=7被锁,就算id=7没有数据,此时另外一个事务添加一个id=7的数据,则第一个事务查询出来的数据中Id=7的name并不是xxxx,此时就出现了幻读。
    

    死锁

    死锁的产生

    1)死锁是指两个或者多个事务在同一资源上互相占用,并请求锁定对方占用的资源,从而导致恶性循环。
    2)当事务试图以不同顺序锁定资源时,就可能产生死锁。多个事务同时锁定同一个资源时也会产生死锁。
    3)死锁产生与锁的行为和顺序和存储引擎相关。以同样的顺序执行语句,有些存储引擎会产生死锁有些不会。
    

    检测死锁

    数据库系统实现了各种死锁检测和死锁超时机制,InnoDB能检测到死锁的循环依赖并立即返回一个错误。
    

    死锁恢复

    死锁发生后,只有部分或完全回滚其中一个事务,才能打破死锁。InnoDB目前处理死锁的方法是,将持有最少行级排他锁的事务进行回滚。所以事务型应用程序在设计时必须考虑如何处理死锁,多数情况下只需要重新执行因死锁回滚的事务即可。
    

    外部锁的死锁检测

    发生死锁后,InnoDB 一般都能自动检测到,并使一个事务释放锁并回退,另一个事务获得锁,继续完成事务。但在涉及外部锁,或涉及表锁的情况下,InnoDB 并不能完全自动检测到死锁, 这需要通过设置锁等待超时参数 innodb_lock_wait_timeout 来解决
    

    死锁影响性能

    死锁会影响性能而不是会产生严重错误,因为InnoDB会自动检测死锁状况并回滚其中一个受影响的事务。在高并发系统上,当许多线程等待同一个锁时,死锁检测可能导致速度变慢。 有时当发生死锁时,禁用死锁检测(使用innodb_deadlock_detect配置选项)可能会更有效,这时可以依赖innodb_lock_wait_timeout设置进行事务回滚。 
    

    避免死锁

    1)提前申请足够级别的锁,要更新就申请排它锁,而不是申请共享锁。
    2)多个事务修改多个表,每个事务中以相同顺序加锁。
    3)并发存取多个表,尽量约定以相同顺序去访问表。
    4)改变事务隔离级别。
    注:使用SHOW INNODB STATUS来查看事务获得所得情况,死锁原因等。
    

    锁性能优化

    1)尽量使用较低的隔离级别。
    
    2)精心设计索引, 并尽量使用索引访问数据, 使加锁更精确, 从而减少锁冲突的机会。
    
    3)选择合理的事务大小,小事务发生锁冲突的几率也更小。
    
    4)给记录集显示加锁时,最好一次性请求足够级别的锁。比如要修改数据的话,最好直接申请排他锁,而不是先申请共享锁,修改时再请求排他锁,这样容易产生死锁。
    
    5)不同的程序访问一组表时,应尽量约定以相同的顺序访问各表,对一个表而言,尽可能以固定的顺序存取表中的行。这样可以大大减少死锁的机会。
    
    6)尽量用相等条件访问数据,这样可以避免间隙锁对并发插入的影响。
    
    7)不要申请超过实际需要的锁级别。
    
    8)除非必须,查询时不要显示加锁。 MySQL的MVCC可以实现事务中的查询不用加锁,优化事务性能;MVCC只在COMMITTED READ(读提交)和REPEATABLE READ(可重复读)两种隔离级别下工作。
    
    9)对于一些特定的事务,可以使用表锁来提高处理速度或减少死锁的可能。
    

    相关文章

      网友评论

          本文标题:数据库事务,锁

          本文链接:https://www.haomeiwen.com/subject/iiivxftx.html