美文网首页程序员
MySQL 的事务和锁(一)

MySQL 的事务和锁(一)

作者: t2othick | 来源:发表于2016-07-19 00:31 被阅读1508次

最近做了一些和交易系统有关的东西,也乘机复习了一下 MySQL 的事务和锁机制。

1、事务

什么是事务呢?按照标准的描述:

A transaction symbolizes a unit of work performed within a database management system (or similar system) against a database, and treated in a coherent and reliable way independent of other transactions. A transaction generally represents any change in database. Transactions in a database environment have two main purposes:

  1. To provide reliable units of work that allow correct recovery from failures and keep a database consistent even in cases of system failure, when execution stops (completely or partially) and many operations upon a database remain uncompleted, with unclear status.
  2. To provide isolation between programs accessing a database concurrently. If this isolation is not provided, the programs' outcomes are possibly erroneous.

翻译过来就是,事务是对于数据库的操作序列,事务的目的有两个:

  • 提供一种从失败中回复的可靠机制,同时在系统挂掉的时候保证数据库的一致性
  • 为并发访问数据库提供一种隔离机制

如何使用事务:


# -*- coding: utf-8 -*-
import time
from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.dialects.mysql import BIGINT, INTEGER, VARCHAR
from sqlalchemy.schema import Column

Model = declarative_base()
engine = create_engine(
     'mysql+mysqldb://root@127.0.0.1/test_session?charset=utf8mb4',
    echo=True,
    pool_size=2,
    max_overflow=5,
    pool_timeout=0,
    pool_recycle=3600
)

Session = sessionmaker(bind=engine)

import 了一大堆东东之后,创建了一个 engine,然后 bind 到一个 session 中,后面就可以用 Session() 生成可以用的 session 了.

使用事务创建两个用户

session = Session()
account = Account(
    member_id=1,
    amount=20
)

session.add(account)

account = Account(
    member_id=2,
    amount=0
)

session.add(account)session.commit()

上面,在账户里面创建了两个账户,第一个账户,初始有 20 元,第二个账户,初始为 0 元

使用事务进行转账


# 第一笔转账 account1 -> account2
session1 = Session()
account1 = session1.query(Account).get(1)
account2 = session1.query(Account).get(2)
account1.amount -= 10
account2.amount += 10

# 第二笔转账 account1 -> account2
session2 = Session()
account1 = session2.query(Account).get(1)
account2 = session2.query(Account).get(2)
account1.amount -= 10
account2.amount += 10

# 提交事务
session1.commit()
session2.commit()

# 查询余额
session = Session()
account1 = session.query(Account).get(1)
account2 = session.query(Account).get(2)
print account1.amount, account2.amount

上面大致模拟了,两个并发的事务,理论上,两笔转账以后,account1 的余额为 0 元,account2 的余额为 20 元,但实际上输出为:

10 10

可见,数据库的事务虽然提供了所谓的隔离,但是依然不能保证结果的正确性,这里得使用数据库提供的锁。

2、锁

首先看看 Mysql 关于锁的文档:

LOCK TABLES
tbl_name [[AS] alias] lock_type
[, tbl_name [[AS] alias] lock_type] ...

lock_type:
READ [LOCAL]
| [LOW_PRIORITY] WRITE

UNLOCK TABLES

所以看起来,锁的类型主要有 READ、READ LOCAL、LOW_PRIORITY WRITE、WRITE,但是,文档下面又有两句:

  1. For InnoDB tables, READ LOCAL is the same as READ.
  1. The LOW_PRIORITY modifier has no effect. In previous versions of MySQL, it affected locking behavior, but this is no longer true. It is now deprecated and its use produces a warning. Use WRITE without LOW_PRIORITY instead.

所以我们只需要关注,READ LOCK 和 WRITE LOCK 就可以了,接下来试验一下,两种锁的区别:

READ LOCK


session1 = Session()
account1 = session1.query(Account).with_lockmode('read').get(1)
account2 = session1.query(Account).with_lockmode('read').get(2)

account1.amount -= 10
account2.amount += 10

session2 = Session()
account1 = session2.query(Account).with_lockmode('read').get(1)
account2 = session2.query(Account).with_lockmode('read').get(2)

account1.amount -= 10
account2.amount += 10

session1.commit()
session2.commit()

我们的程序会阻塞在 session1.commit 这里,最后报了这个异常:

sqlalchemy.exc.OperationalError: (_mysql_exceptions.OperationalError) 
(1205, 'Lock wait timeout exceeded; try restarting transaction')
[SQL: u'UPDATE account SET amount=%s WHERE account.id = %s'] [parameters: ((960L, 1L), (2040L, 2L))]

Lock wait timeout exceeded 等待锁超时,此时如果我们捕获这个异常,那么 session2.commit 就会成功。

所以关于 READ 锁,结论就是:

  • 可以被多个 session 持有
  • 没有被释放之前,其他事务不能更新被加锁的内容

上面就是因为,READ LOCK 同时被 session1 和 session2 持有,所以 session1 试图更新内容的时候,会因为等待 session2 而超时,所以 READ LOCK 通常用在一个场景下:就是本事务未完成或者回滚之前,不希望其他事务更新读取的内容,比如需要定时导出某个记录的快照,我希望在我导出完成之前,这条记录不被更新,就可以用 READ LOCK.

WRITE LOCK

同样是上面的代码,把 read 换成 update

这次卡在了 account1 = session2.query(Account).with_lockmode('update').get(1) 上,所以结论是:

  • WRITE LOCK 只能被一个 session 持有
  • 没有释放前,其他会话不能读取加锁的内容

这个使用场景比较多,比如上面咱们的转账,当这里并发的转账请求到来的时候,第一个会话,持有了 12 的锁,session2 就需要等待 session1 完成,才能继续,在这里的结局就是超时报错

总结

所以,想要改写内容的时候,如果要求保证数据一致性,就得使用 WRITE LOCK。READ LOCK 的使用场景,多见与上下逻辑中,对于某个字段值有依赖,需要在进入会话之后,保持该字段不被其他会话修改。
同时,也总结出一点,就是写这种程序的时候,一定要专心。

关于加锁的规则,下次再说,这里面也有很多坑,搞不好,就锁了整个表

相关文章

  • 高性能Mysql笔记

    一、Mysql架构与历史 1、架构图 2、锁 表锁 行级锁 3、事务 死锁 Mysql中的事务 1

  • mysql的锁+事务,联合的地方

    mysql的锁+事务,联合的地方 全文是对《Mysql 技术内幕 Innodb》中,锁和事务两个章节,自己能看懂或...

  • 数据库事务,锁

    事务 事务特点ACID 理解ACID 以银行转账为例 MySQL锁 锁是MySQL在服务器层和存储引擎层的并发控制...

  • MySQL数据库高级(七)——事务和锁

    MySQL数据库高级(七)——事务和锁 一、事务简介 1、事务简介 事务(Transaction) 是指作为单个逻...

  • MySQL事务和锁

    事务特性 原子性(Atomicity [ˌætəˈmɪsəti]):事务是不可分割的最小单元一致性(Consist...

  • Mysql事务和锁

    1.什么是数据库的事务? 数据库事务的典型场景? 订单,转账 支付,电商等全部成功/全部不成功。金融 什么是事务?...

  • Mysql事务和锁

    ACID 其实AC是一个概念,就是要么一起执行,要么都不执行,只是看问题的指标不同而已,一个侧重过程,一个侧重结果...

  • MySQL 的事务和锁(一)

    最近做了一些和交易系统有关的东西,也乘机复习了一下 MySQL 的事务和锁机制。 1、事务 什么是事务呢?按照标准...

  • MySQL相关(一)——— 事务和锁

    事务的隔离级别和锁机制 【1】InnoDB 事务隔离级别和锁 【2】脏读、不可重复读、幻读 mysql 相关 常用...

  • mysql 的 事务 和 锁

    mysql事务select 只是进行了隔离,保证数据的一致性,并没有加锁,如果要加锁可以用for update。 ...

网友评论

    本文标题:MySQL 的事务和锁(一)

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