在Django中,如果一个项目涉及了金融,他的要求是十分严格的。

所以嘞,这里就有一些坑,很多坑,第一次开发的时候很容易出现一系列的错误

在涉及金融计算的地方,不能使用float类型

什么鬼,但事实就是如此,千万不要用float进行计算.....

所以,Python中为我们提供了一个专门的模块计算,decimal

而同样的,Django中也提供了相应的计算字段,DecimalField

class DecimalField(max_digits=None, decimal_places=None[, **options])
  • max_digits:表示最大位数
  • decimal_places:表示小数点后面的位数
  • 假设你最多存999人民币,小数点要精确到2位,需要的max_digits就为6,decimal_places就为2

下面是一些例子

1.在models.py中定义:

from django.db import models

class UserProfile(models.Model):
price = models.DecimalField(max_digits=16,decimal_places=2)

2.添加一条记录:

from decimal import Decimal

obj = models.UserProfile.objects.create(price = Decimal("123.45"))

3.更新记录:

obj.price -= Decimal("1.00")

obj.save()

4.我们输出查看一下SQL语句:

from django.db import connection

print(connection.queries[-1])

//UPDATE `table` SET `price` = '122.45' WHERE `id` = 1

我们之前使用的update会有问题。在并发很高的时候,会遇到类似多线程的问题,因为加减操作都在客户端,某个线程写入price的时候,可能之前拿到的已经被别人更新过了,所以我们需要原子写入。

UPDATE `table` SET `price` = 'price' - '1.00' WHERE `id` = 1

在ORM及是使用F()

obj.price = F("price") - Decimal("1.00")

obj.save(update_fields = ["price"])

注意:在调用save()方法的时候,我们可以用update_fields传入需要update的字段。否则Django可能会把所有的字段都放在SQL中,影响效率


好吧,上面看似已经是把问题都解决了,但是只是看似。

我们在数据库中,定义的字段的精确到小数点后两位,如果我减去一个3位的小数会怎样呢?

obj.price = F("price") - Decimal("1.001")

obj.save(update_field=["price"])

好吧,结果不出意料的报错了

会抛出一个Traceback的错误

这个错误其实是MySQL抛出的一个异常,所以传递到了Django,使更新操作无法成功

怎么解决呢?

//我们手动写个方法进行一个精度的转换

def to_decimal(s,precision=2):

    r = pow(10,precision+1)
v = s if type(s) is Decimal else Decimal(str(s)) try:
return Decimal(round(int(v * r),-1))/r
except:
return Decimal(s) obj.price = F("price") - to_decimal("1.001",2)
obj.save(update_fields=["price"])

这样貌似有解决了一个问题。。。实际呢?

这只是加或者减,如果是乘除呢?

由于这是MySQL层次上出的错误,也就是说实在最后存储的上面出的错,我们是没有办法在Django的层面上对计算出来的结果在进行一次类型转换的。这时候怎么办,只有上raw sql了。

//完整一点
//这次带上事务 from django.db import transaction,connection try:
with transaction.atomic():
cursor = connection.cursor()
ret = cursor.execute(
"UPDATE 'table' SET 'price'=CAST(('price'*%s) AS DECIMAL(16,2)) WHERE 'id' = %s ",
[Decimal("1.001"),obj.id]
)
except:
print("失败")

注意:在MySQL层面上,我们使用CAST(%s AS DECIMAL(16,2))来把结果转化为price字段同样个是Decimal类型

返回更新数据的行数,如果成功了,ret就是1


你以为结束了?

太天真了

注意:如果是事务操作,一定要考虑到多线程并发的造成的数据冲突的问题

即:假设连个线程获取了同意对象,进行了更改,怎么办?

这里就要用到select_for_update()

注意:这点很重要,金钱的操作,我们最好不要使用自增自减运算,而是使用select_for_update()的行级索来避免冲突。

所以嘞,我们的例子又可以改进了。

try:
with transaction.atomic():
locked_obj =UserProfile.objects.select_for_update().get(pk=obj.id)
locked_obj.price -= to_decimal('11.11111', 2)
assert locked_obj.price >= 0 #断言判断是不是合理
locked_obj.save(update_fields=['price'])
except:
print 'save failed'

到这里,才算是告一段落。

还有,补充一点:

  网上都说使用select_for_update可能会产生死锁,具体可以看我的上一篇文章。

  Django中管理并发操作

Django中涉及金融的项目的更多相关文章

  1. Django中Celery的实现介绍(一)

    Django中Celery的实现 Celery官网http://www.celeryproject.org/ 学习资料:http://docs.jinkan.org/docs/celery/ Cele ...

  2. Django中使用Bootstrap----带view.py视图函数(也就是项目下的脚本文件)

    一.Django中使用Bootstrap 1.首先建立工程,建立工程请参照:https://www.cnblogs.com/effortsing/p/10394511.html 2.在Firstdja ...

  3. Django中-事务操作

    如何在Django中进行事务操作呢? 近期,公司里要使用Django开发一套金融相关的系统. 涉及钱了.....安全安全安全 如果钱转到一半,系统崩了,咋办? 如果钱汇到一半,系统崩了,咋办? 如果东 ...

  4. Django中ORM介绍和字段及其参数

    ORM介绍 ORM概念 对象关系映射(Object Relational Mapping,简称ORM)模式是一种为了解决面向对象与关系数据库存在的互不匹配的现象的技术. 简单的说,ORM是通过使用描述 ...

  5. Django中ORM介绍

    Object Relational Mapping(ORM) ORM介绍 ORM概念 对象关系映射(Object Relational Mapping,简称ORM)模式是一种为了解决面向对象与关系数据 ...

  6. django中的跨表查询梳理

    1.前言 最近在写一个小项目,里面主要涉及的就是表与表之间复杂的关系.当真正开发起来的时候,才发现自己对复杂的表关系间的查询有点混乱,趁着这几天的时间,重新梳理了一下. 2.概念 在开始之前,先明确几 ...

  7. Django中的信号

    信号 Django 提供一个“信号分发器”,允许解耦的应用在框架的其它地方发生操作时会被通知到. 简单来说,信号允许特定的sender通知一组receiver某些操作已经发生. 这在多处代码和同一事件 ...

  8. Django中ORM介绍和字段及字段参数

    Object Relational Mapping(ORM) 1 ORM介绍 1.1 ORM概念 对象关系映射(Object Relational Mapping,简称ORM)模式是一种为了解决面向对 ...

  9. django中的ORM介绍和字段及字段参数

    Object Relational Mapping(ORM) ORM介绍 ORM概念 对象关系映射(Object Relational Mapping,简称ORM)模式是一种为了解决面向对象与关系数据 ...

随机推荐

  1. UVa 11167 Monkeys in the Emei Mountain (最大流)

    题意:雪雪是一只猴子.它在每天的 2:00 —— 9:00之间非常渴,所以在这个期间它必须喝掉2个单位的水.它可以多次喝水,只要它喝水的总量是2.它从不多喝,在一小时内他只能喝一个单位的水.所以它喝水 ...

  2. Android继承BaseAdapter时要重写的函数的说明

    原文来自:http://www.2cto.com/kf/201405/299601.html,我自己做了一些修改 Android中继承BaseAdapter后需要重写四个函数,但一般还要写一个构造函数 ...

  3. 【转】启动、停止Windows服务的DOS命令

    需要用管理员身份运行 在图形界面中启动.停止服务很方便,但是操作系统有时候会出故障,此时不妨使用原始的DOS命令启动.停止服务,也许会收到意想不到的效果的! 方法/步骤 1 开始→所有程序. 2 附件 ...

  4. nodejs 上传文件 upload

    只是现在主要用nodejs做后端了,所以记录一些上传文件的使用方法. 上传文件的主要方式: 1.form上传,优点是方便,缺点是没法回调,上传后返回的数据没法处理 2.ajax上传,优点是可控制,有回 ...

  5. spring案列——xml配置

    一.需要的jar包 spring.jar(官网下载) commons-logging.jar 二.项目结构 三.entity(实体类) package com.team.model; public c ...

  6. delphi_xe开发ios环境的安装与设置

     http://wenku.baidu.com/link?url=NE3xJOZiLppdxCbXJX3W0vyLHv6uA_U8uamjx9NJIIcxnfuC2P9eWx3d6Xwco-ugS8G ...

  7. gdb用法

    mickole@test:~/ctest/05gdb$ gdb simple //开始gdb调试 GNU gdb (Ubuntu/Linaro 7.4-2012.04-0ubuntu2.1) 7.4- ...

  8. centos:开启和关闭selinux

    5.4. Enabling and Disabling SELinux Use the /usr/sbin/getenforce or /usr/sbin/sestatus commands to c ...

  9. AgentJob--无法重启Job--22022错误

    错误描述: 发现有个Job计划为长期重复运行,但从某个时间点后未运行,无任何错误信息. 手动启动该Job后爆以下错误 运行环境: Window Server 2008 R2 Enterprise Mi ...

  10. SqlServer Session共享注意点

    公司下派任务,之前的网站是一台服务器,由于用户过多,负载过大,现在老大要求多加一台服务器.加就加贝,应该跟我这DEV没有 关系吧,应该不会碰到Source的吧.但是,之前网站有一些数据是放在Sessi ...