mysql事务

事务的四大特性(ACID)

  • 原子性(Atomicity)
  • 一致性(Consistency)
  • 隔离性(Isolation)
  • 持久性(Durability)

原子性:事务内执行的sql操作当作一个整体,要么同时成功,要么同时失败回滚
一致性:强调的是数据的状态,指的是数据库从一个一致性状态到另外一个一致性状态,打个比方,A有200,B有100,A转100给B,那么最后的一致性状态是A有100,B有200,如果状态是A有100,B还是原来的100,那这就不符合数据的一致性
隔离性:一个事务所做的修改在最终提交以前,对其他事务是不可见的,也就是说,A有200,B有100,A转100,B加100,还没有提交的时候,又开启了一个事物,此时看到的是A有200,B有100,也就是第一个事物没有commit之前的状态;还有一点就是说事物对某 行的某个字段进行加操作,那么在没有提交之前,其他事物是不能修改这一行的这个字段的,因为mysql此时的行锁被第一个事物拿到,commit之后才会释放
持久性:事物一旦进行了提交,那么数据落盘,所做的修改会永久保存到数据库

事务的隔离级别

事务的隔离级别是为了解决mysql并发问题的几种控制手段。

事务的并发问题

1、脏读:事务A读取了事务B更新的数据,然后B回滚操作,那么A读取到的数据是脏数据
2、不可重复读:事务 A 多次读取同一数据,事务 B 在事务A多次读取的过程中,对数据作了更新并提交,导致事务A多次读取同一数据时,结果 不一致。
3、幻读:系统管理员A将数据库中所有学生的成绩从具体分数改为ABCDE等级,但是系统管理员B就在这个时候插入了一条具体分数的记录,当系统管理员A改结束后发现还有一条记录没有改过来,就好像发生了幻觉一样,这就叫幻读。
小结:不可重复读的和幻读很容易混淆,不可重复读侧重于修改,幻读侧重于新增或删除。解决不可重复读的问题只需锁住满足条件的行,解决幻读需要锁表
单条修改数据的命令会自动的触发事务,包括insert、update、delete。
而在SQL语句中有手动开启事务的原因是:可以进行多次数据的修改,如果成功一起成功,否则一起会滚到之前的数据.

4种隔离级别

Read Uncommitted(读取未提交内容)

在该隔离级别,所有事务都可以看到其他未提交事务的执行结果。本隔离级别很少用于实际应用,因为它的性能也不比其他级别好多少。读取未提交的数据,也被称之为脏读(Dirty Read)。

Read Committed(读取提交内容)

这是大多数数据库系统的默认隔离级别(但不是MySQL默认的)。它满足了隔离的简单定义:一个事务只能看见已经提交事务所做的改变。,因为同一事务的其他实例在该实例处理其间可能会有新的commit,所以同一select可能返回不同结果,这就是不可重复读

Repeatable Read(可重读)

这是MySQL的默认事务隔离级别,它确保同一事务的多个实例在并发读取数据时,会看到同样的数据行。不过理论上,这会导致另一个棘手的问题:幻读 (Phantom Read)。简单的说,幻读指当用户读取某一范围的数据行时,另一个事务又在该范围内插入了新行,当用户再读取该范围的数据行时,会发现有新的“幻影” 行。InnoDB和Falcon存储引擎通过多版本并发控制(MVCC,Multiversion Concurrency Control)机制解决了该问题。

Serializable(可串行化)

这是最高的隔离级别,它通过强制事务排序,使之不可能相互冲突,从而解决幻读问题。简言之,它是在每个读的数据行上加上共享锁。在这个级别,可能导致大量的超时现象和锁竞争。

在测试的时候,把mysql事务级别设置成 Repeatable Read,一个事务往里插数据,另一个事务是看不到其他事务插入的数据的,即读的不是最新数据,我们把这种现象也看作是幻读

查看和设置隔离级别

SELECT @@tx_isolation
设置mysql的隔离级别:set session transaction isolation level 设置事务隔离级别

pymysql

对于pymysql有几个点需要注意

默认支持事务

pymysql默认开启事务,从每次执行执行增删改都需要最终commit就能知道。

通过pymysql增加数据但未提交时候,发现表的AUTO_INCREMENT是一直递增的(说明数据库已经收到插入指令了,就等着拍板呢),这是为了解决并发的考量,并且如果回滚AUTO_INCREMENT并不会减少,而是一直递增下去,这样才不会出错。

cursor对象存放查询的数据

import pymysql
conn = pymysql.connect(host='localhost', user='root', password="123456",database='test', port=3306, charset='utf8')
cur = conn.cursor()
cur.execute('select * from department')
Out[5]: 4
cur.fetchone()
Out[6]: (200, '技术')
cur.execute('select * from employee')
Out[7]: 18
cur.fetchone()
Out[8]: 
(1,
 'egon',
 'male',
 18,
 datetime.date(2017, 3, 1),
 '办事处外交大使',
 None,
 7300.33,
 401,
 1)

通过上面我们可以发现,当我们查询的时候,取数据没有取完就执行另一条查询语句,那么先前的查询结果会被覆盖。也就是说执行了一条查询语句,那么就应该立即取出结果,否则拿着这个cursor去执行任何增删改查操作,原来存储的查询数据都会被清掉

pymysql模块的threadsafety = 1

如果在多个线程中的cursor共用一个conn连接,那么就会报类似pymysql.err.InternalError: Packet sequence number wrong - got 45 expected 0的错误

  1. 每个线程拥有自己的连接
  2. 所有线程共用一个连接池,需要考虑线程总数和连接池连接数上限的问题

pymysql遇到的幻读

之前我开了两个线程,一个线程取数据,一个线程往表插入数据,最终的结果是每次取得的数据都是一样的,但是数据表确确实实是在不停地增加数据。这是因为数据库的隔离级别造成的.解决办法有两个:

  1. 修改隔离级别
  2. 建立conn的时候加上autocommit=True参数
posted @ 2018-07-17 16:28  龙云飞谷  阅读(322)  评论(0编辑  收藏  举报