一、并发控制中锁的概念

  锁是并发控制中最核心的概念之一,在MySQL中的锁分两大类,一种是读锁,一种是写锁,读锁也可以称为共享锁(shared lock),写锁也通常称为排它锁(exclusive lock)。

  这里先不讨论锁的具体实现,描述一下锁的概念:读锁是共享的,或者说是相互不阻塞的。多个客户在同一时刻可以同时读取一个资源,且互不干扰。写锁则是排他的,就是说一个写锁会阻塞其他的写锁和读锁,这是出于安全策略的考虑,只有这样,才能确保在给定时间里,只有一个用户能执行写入,并防止其他用户读取正在写入的同一资源。另外在一般情况下,写锁比读锁优先级高。

  MySQL中的锁有两种粒度,一种是表锁,在表级别加锁,是MySQL中最基本的锁策略,并且开销最小,这种锁的并发性能较低;另一种为行锁,在行级加锁,并发性较高。表锁与行锁没有绝对的性能强弱之分,在应用中可以根据实际场景选择,在锁粒度与数据安全之间寻求一种平衡机制。

  锁的具体实现协议大体分为两种:显式锁和隐式锁。显式锁是指根据用户需要手动去请求的锁。隐式锁则是指存储引擎自行根据需要施加的锁。显式锁的用法示例:

  例1:开启两个ssh连接同一主机,进入MySQL,在连接A上对表tbl2做读锁操作:

  1. mysql> USE mysql;
  2. mysql> LOCK TABLE tbl2 READ;

  在连接B上读取数据是可以的,但是写入数据不行:

  1. mysql> USE mysql;
  2. mysql> SELECT * FROM tbl2;
  3. mysql> INSERT INTO tbl2 VALUES (,'tom'); #会一直卡在这一步,不向后执行。

  当在连接1上将tbl2解锁后,就能写入数据了:

  例2: FLUSH TABLES 命令可以将整个库上锁,表示刷写所有表,把所有表在缓存中的数据全写入磁盘。在对整个数据库进行备份时可能会用到。

  1. mysql> FLUSH TABLES WITH READ LOCK; #刷写所有表,并持有读锁;

  解锁也是用命令 UNLOCK TABLES 。

  锁是任何存储引擎都支持的并发访问控制机制,但对事务型存储引擎来讲,这种锁机制都是单语句级别的,而事务存储引擎更需要多语句的并发控制机制。以上两个例子所实现的锁都是服务器层的,和存储引擎无关。另外在生产环境中除了事务中禁用了 AUTOCOMMIT 时可以使用 LOCK TABLES 之外,其他任何时候都不要显式的执行 LOCK TABLES 。

二、事务

1.事务的概念

  事务其实就是一组原子性的SQL查询,或者说一个独立的工作单元。用一个经典的存取钱的例子可以来解释什么是事务:假设一个银行的数据库有支票(checking)和存钱(savings)两张表。现在要从用户A的支票账户中转移200元到他的存钱账户,逻辑上我们需要三个步骤:

1.检查支票账户余额大于200;

2.在支票账户中减去200;

3.在存钱账户中加上200。

上述三个步骤必须作为一个整体来操作,这个整体就可以称为事务。事务中任何一步失败都必须回滚(ROLLBACK)所有步骤。

  可以用 START TRANSACTION 语句开始一个事务,然后可以用 COMMIT 将事务提交并将修改的数据永久保存在磁盘,也可用 ROLLBACK 撤销所有修改。银行例子的SQL样本如下:

  1. START TRANSACTION;
  2. SELECT balance FROM checking WHERE customer_id = ;
  3. UPDATE checking SET balance = balance - WHERE customer_id = ;
  4. UPDATE savings SET balance = balance + WHERE customer_id = ;
  5. COMMIT;

  而一个运行良好的事务处理系统,必须具备ACID特性。ACID是指原子性(atomicity)、一致性(consistency)、隔离性(isolation)和持久性(durability)。

原子性:一个事务必须被视为一个不可分割的最小工作单元,整个事务中的所有操作要么全部提交成功,要么全部失败回滚。

一致性:数据库总是从一个一致性的状态转换到另一个一致性的状态。

隔离性:通常来说,一个事务所做的修改在最终提交前,对其它事务是不可见的。例如在前面的例子中,当第二条语句执行完,第三条语句还未开始时,另一个账户的汇款事务也开始执行,则其看到的账户状态是存取事务运行前的状态。这就涉及到了隔离级别,将在后面来介绍。

持久性:一旦事务提交,则所有修改就永久保存在数据库的磁盘中。

1.事务日志

  要保持事务的原子性,需要依靠事务日志。在一个事务运行到一半发生错误时,事务需要根据事务日志的操作记录来回滚至原来的状态。在事务运行中,事务执行修改的需求是先放在事务日志中的,再对磁盘数据进行修改,事务日志中存放的是修改的操作,而不是修改数据本身。

  事务日志可以帮助提高事务的效率。写事务日志的操作是磁盘上一小块区域的顺序I/O,而不像随机I/O在磁盘的多个地方移动磁头,这就提高了存储效率。目前大多数存储引擎都是这样实现的,修改数据通常需要写两次吸盘。

  事务日志丢失会造成很大的损失,所以建议做双写。

2.事务的提交

  自动提交(AUTOCOMMIT):MySQL默认采用自动提交模式。也就是说只要不是显式的开始一个事务,则每个查询都被当作一个事务执行提交操作。设置如下:

  1. mysql> SHOW VARIABLES LIKE 'AUTOCOMMIT';
  2. +---------------+-------+
  3. | Variable_name | Value |
  4. +---------------+-------+
  5. | autocommit | ON |
  6. +---------------+-------+
  7. row in set (0.01 sec)
  8.  
  9. mysql> SET AUTOCOMMIT = ; #设置为0表示OFF,当AUTOAOMMIT=0时,所有的查询都在一个事务中,直到显式的执行COMMIT或者ROLLBACK,该事物才结束。

  事务提交相关语句:

  1. mysql> HELP TRANSACTIONS;
  2. topics:
  3. CHANGE MASTER TO
  4. CHANGE REPLICATION FILTER
  5. DEALLOCATE PREPARE
  6. EXECUTE STATEMENT
  7. ISOLATION
  8. LOCK
  9. PREPARE
  10. PURGE BINARY LOGS
  11. RESET MASTER
  12. RESET SLAVE
  13. SAVEPOINT
  14. SET GLOBAL SQL_SLAVE_SKIP_COUNTER
  15. SET SQL_LOG_BIN
  16. START SLAVE
  17. START TRANSACTION #启动事务
  18. STOP SLAVE
  19. XA

  下面将 SET AUTOCOMMIT 改为OFF,用手动方式提交事务进行简单演示:MySQL默认库中创建表tbl2,插入行id=1,Name=tom

  1. mysql> SELECT * FROM tbl2;
  2. +------+------+
  3. | id | Name |
  4. +------+------+
  5. | | tom |
  6. +------+------+
  7. row in set (0.00 sec)
  1. mysql> START TRANSACTION;
  2. mysql> USE mysql;
  3. mysql> UPDATE tbl2 SET Name='Tom' WHERE id=; #将小写t改为大写T
  4. mysql> SELECT * FROM tbl2;
  5. +------+------+
  6. | id | Name |
  7. +------+------+
  8. | | Tom |
  9. +------+------+
  10. row in set (0.00 sec)
  1. mysql> START TRANSACTION; #开始事务
  2. mysql> USE mysql;
  3. mysql> UPDATE tbl2 SET Name='Tom' WHERE id=; #将小写t改为大写T
  4. mysql> SELECT * FROM tbl2; #查看
  5. +------+------+
  6. | id | Name |
  7. +------+------+
  8. | | Tom |
  9. +------+------+
  10. mysql> ROLLBACK; #回滚
  11. mysql> SELECT * FROM tbl2; #恢复到小写t
  12. +------+------+
  13. | id | Name |
  14. +------+------+
  15. | | tom |
  16. +------+------+

  要注意的是,创建表操作的滚动操作只对DML语言有效。

  还可以在每个操作后用指令 SAVEPOINT 做存档:

  1. mysql> START TRANSACTION;
  2. mysql> SELECT * FROM tbl2;
  3. +------+------+
  4. | id | Name |
  5. +------+------+
  6. | | tom |
  7. +------+------+
  8. row in set (0.00 sec)
  9. mysql> INSERT INTO tbl2 VALUES (,'jerry');
  10. mysql> SAVEPOINT first; #创建保存点1,名为first
  11. mysql> INSERT INTO tbl2 VALUES (,'cat');
  12. mysql> SAVEPOINT second; #创建保存点2,名为second
  13. mysql> DELETE FROM tbl2 WHERE id=;
  14. mysql> SELECT * FROM tbl2;
  15. +------+------+
  16. | id | Name |
  17. +------+------+
  18. | | tom |
  19. | | cat |
  20. +------+------+
  21. rows in set (0.00 sec)
  1. mysql> ROLLBACK TO second; #滚到保存点second
  2. mysql> SELECT * FROM tbl2;
  3. +------+-------+
  4. | id | Name |
  5. +------+-------+
  6. | | tom |
  7. | | jerry |
  8. | | cat |
  9. +------+-------+
  10. rows in set (0.00 sec)

  输入命令 COMMIT 后表示事务已提交,就无法再回滚了。

3.事务隔离级别

  事务的隔离级别有四种:

1.READ UNCOMMITTED(未提交读)

  在READ UNCOMMITTED级别,事务中的修改即使没有提交,对其它事务也都是可见的。即事务可读取未提交的数据,这称为脏读(Dirty Read)。这会导致很多问题,在实际应用中一般很少用到。

2.READ COMMITED(提交读)

  READ COMMITTED表示只能读取事务修改提交后的数据。此级别有时候也叫做不可重复读(nonrepeatable read),因为两次执行同样的查询,可能会得到不一样的结果。

3.REPEATABLE READ(可重复读)

  此级别解决了脏读的问题,保证了在同一个事务中多次同样记录的结果是一致的。但会带来新的问题——幻读(Phantom Read)。MySQL默认使用此级别。

4.SERIALIZABLE(可串行化)

  SERIALIZABLE是最高的隔离级别。它会强制事务串行执行,避免了幻读、脏读的问题,但是牺牲了并发性。

  示例:

  1. mysql> SELECT @@session.tx_isolation; #查看当前事务隔离级别
  2. +------------------------+
  3. | @@session.tx_isolation |
  4. +------------------------+
  5. | REPEATABLE-READ |
  6. +------------------------+
  7. row in set (0.00 sec)

mysql> SET @@session.tx_isolation='级别'; 可设置隔离级别

下面演示幻读的情形,启动两个连接至同一MySQL,交叉启动事务:

连接1的操作:

  1. mysql> START TRANSACTION; #启动事务
  2. mysql> USE mysql;
  3. mysql> SELECT * FROM tbl2;
  4. +------+-------+
  5. | id | Name |
  6. +------+-------+
  7. | | tom |
  8. | | jerry |
  9. | | cat |
  10. +------+-------+
  11. rows in set (0.00 sec)
  12. mysql> INSERT INTO tbl2 VALUES (,'dog'); #插入数据
  13. mysql> SELECT * FROM tbl2;
  14. +------+-------+
  15. | id | Name |
  16. +------+-------+
  17. | | tom |
  18. | | jerry |
  19. | | cat |
  20. | | dog |
  21. +------+-------+
  22. rows in set (0.00 sec)

同时在连接2的操作:

  1. mysql> START TRANSACTION;
  2. Database changed
  3. mysql> SELECT * FROM tbl2;
  4. +------+-------+
  5. | id | Name |
  6. +------+-------+
  7. | | tom |
  8. | | jerry |
  9. | | cat |
  10. +------+-------+
  11. rows in set (0.00 sec)

这时看不到插入的"4 dog"的数据,在连接1上执行 COMMIT 提交事务:

  1. mysql> COMMIT;

在连接2上依然看不到插入的数据,但数据确实已经写入到了磁盘,这时只有在连接2上输入 COMMIT 和 ROLLBACK 才能看到修改的数据;

  1. mysql> ROLLBACK;
  2. mysql> SELECT * FROM tbl2;
  3. +------+-------+
  4. | id | Name |
  5. +------+-------+
  6. | | tom |
  7. | | jerry |
  8. | | cat |
  9. | | dog |
  10. +------+-------+
  11. rows in set (0.00 sec)

MySQL中的事务及读写锁实现并发访问控制的更多相关文章

  1. 【MySQL】漫谈MySQL中的事务及其实现

    最近一直在做订单类的项目,使用了事务.我们的数据库选用的是MySQL,存储引擎选用innoDB,innoDB对事务有着良好的支持.这篇文章我们一起来扒一扒事务相关的知识. 为什么要有事务? 事务广泛的 ...

  2. 漫谈MySql中的事务

    最近一直在做订单类的项目,使用了事务.我们的数据库选用的是MySql,存储引擎选用innoDB,innoDB对事务有着良好的支持.这篇文章我们一起来扒一扒事务相关的知识. 为什么要有事务? 事务广泛的 ...

  3. (转)漫谈MySql中的事务

    最近一直在做订单类的项目,使用了事务.我们的数据库选用的是MySql,存储引擎选用innoDB,innoDB对事务有着良好的支持.这篇文章我们一起来扒一扒事务相关的知识. 为什么要有事务? 事务广泛的 ...

  4. 【转】全面了解Mysql中的事务

    为什么要有事务? 事务广泛的运用于订单系统.银行系统等多种场景.如果有以下一个场景:A用户和B用户是银行的储户.现在A要给B转账500元.那么需要做以下几件事: 1. 检查A的账户余额>500元 ...

  5. [MySQL数据库之事务、读现象、数据库锁机制、多版本控制MVCC、事务隔离机制]

    [MySQL数据库之事务.读现象.数据库锁机制.多版本控制MVCC.事务隔离机制] 事务 1.什么是事务: 事务(Transaction),顾名思义就是要做的或所做的事情,数据库事务指的则是作为单个逻 ...

  6. MySql中的事务、JDBC事务、事务隔离级别

    一.MySql事务 之前在Oracle中已经学习过事务了,这个东西就是这个东西,但是在MySql中用法还是有一点不同,正好再次回顾一下. 先看看MySql中的事务,默认情况下,每执行一条SQL语句,都 ...

  7. 在MySQL中设置事务隔离级别有2种方法:

    在MySQL中设置事务隔离级别有2种方法: 1 在my.cnf中设置,在mysqld选项中如下设置 [mysqld] transaction-isolation = READ-COMMITTED 2 ...

  8. 十:MYSQL中的事务

    前言: 因为没有多少时间和精力,目前无法深入研究数据库中的事务,比如 但是,对于事务的一些基本知识,还是需要牢牢掌握的,做到了解事务的基本常识,在实际开发中能够理解各个持久层框架对事务的处理 一:是么 ...

  9. MySql中的事务嵌套

    1.Mysql中的事务必须是InnoDB.Berkeley DB引擎,myisam不支持. 2.Mysql是不支持嵌套事务的,开启了一个事务的情况下,再开启一个事务,会隐式的提交上一个事务. 3.My ...

随机推荐

  1. rabbitmq初学之连接测试

    Login was refused using authentication mechanism PLAIN. 用户名或密码没有设置,或者错误

  2. selenium+java+chrome 自动化测试环境搭建

    安装jdk    (jdk 配置环境变量)    eclipse(可用免安装的) 安装谷歌浏览器 下载chorme driver (chorme driver 也要配置环境变量,将chormedriv ...

  3. BASH 基本语法

    本节内容 1.  什么是shell script 2.  变量 3.  运算符 4.  流程控制 5.  函数 6.  计划任务 crontab 一  什么是shell script 将OS命令堆积到 ...

  4. JDBC创建链接的几种方式

    首先,使用java程序访问数据库的前提 数据库的主机地址(ip地址) 端口 数据库用户名 数据库用户密码 连接的数据库 代码: private static String url = "jd ...

  5. Android开发 - 掌握ConstraintLayout(一)传统布局的问题

    在传统的Android开发中,页面布局占用了我们很多的开发时间,而且面对复杂页面的时候,传统的一些布局会显得非常复杂,每种布局都有特定的应用场景,我们通常需要各种布局结合起来使用来实现复杂的页面.随着 ...

  6. 优化以及bug

    优化1:节流函数2:城市查询时,之前用事件(拿到DOM中innerHTML,后触发事件),后改用v-model双向绑定:应该是更符合数据驱动.3:使用localstorage等本地存储,如果用户关闭本 ...

  7. Redis 客户端命令总结

    注意:括号里是参数,具体使用的时候不需要括号和逗号,直接使用空格分隔命令以及各个参数即可. 1.对Key操作的命令 exists(key):确认一个key是否存在.存在返回1,不存在返回0. del( ...

  8. 第二十四节:Java语言基础-讲解数组的综合应用

    数组的综合应用 // 打印数组 public static void printArray(int[] arr) { for(int x=0;x<arr.length;x++) { if(x!= ...

  9. 详解Android数据存储技术

    前言 学习Android相关知识,数据存储是其中的重点之一,如果不了解数据,那么让你跟一款没有数据的应用玩,你能玩多久呢?答案是这和没有手机几乎是差不多的.我们聊QQ,聊微信,看新闻,刷朋友圈等都是看 ...

  10. 周末,说声php的setter&getter(魔术)方法,你们辛苦了

    php 作为快速迭代项目的语言,其牛逼性质自不必多说.今天咱们要来说说php语言几个魔术方法,当然了,本文主要以setter&getter方法说明为主. 首先,咱们得知道什么叫魔术方法? 官方 ...