重庆市建立网站的网络公司温州哪里有网站建设
news/
2025/9/27 6:06:27/
文章来源:
重庆市建立网站的网络公司,温州哪里有网站建设,软硬件开发都包括什么,做简单的网站首页什么是数据库事务 数据库事务是指作为单个逻辑工作单元执行的一系列操作。设想网上购物的一次交易#xff0c;其付款过程至少包括以下几步数据库操作#xff1a; 更新客户所购商品的库存信息 保存客户付款信息--可能包括与银行系统的交互 生成订单并且保… 什么是数据库事务 数据库事务是指作为单个逻辑工作单元执行的一系列操作。设想网上购物的一次交易其付款过程至少包括以下几步数据库操作 · 更新客户所购商品的库存信息 · 保存客户付款信息--可能包括与银行系统的交互 · 生成订单并且保存到数据库中 · 更新用户相关信息例如购物数量等等 正常的情况下这些操作将顺利进行最终交易成功与交易相关的所有数据库信息也成功地更新。但是如果在这一系列过程中任何一个环节出了差错例如在更新商品库存信息时发生异常、该顾客银行帐户存款不足等都将导致交易失败。一旦交易失败数据库中所有信息都必须保持交易前的状态不变比如最后一步更新用户信息时失败而导致交易失败那么必须保证这笔失败的交易不影响数据库的状态--库存信息没有被更新、用户也没有付款订单也没有生成。否则数据库的信息将会一片混乱而不可预测。数据库事务正是用来保证这种情况下交易的平稳性和可预测性的技术。 数据库事务的ACID属性事务处理可以确保除非事务性单元内的所有操作都成功完成否则不会永久更新面向数据的资源。通过将一组相关操作组合为一个要么全部成功要么全部失败的单元可以简化错误恢复并使应用程序更加可靠。一个逻辑工作单元要成为事务必须满足所谓的ACID(原子性、一致性、隔离性和持久性)属性 · 原子性事务必须是原子工作单元对于其数据修改要么全都执行要么全都不执行。通常与某个事务关联的操作具有共同的目标并且是相互依赖的。如果系统只执行这些操作的一个子集则可能会破坏事务的总体目标。原子性消除了系统处理操作子集的可能性。 · 一致性事务在完成时必须使所有的数据都保持一致状态。在相关数据库中所有规则都必须应用于事务的修改以保持所有数据的完整性。事务结束时所有的内部数据结构如 B 树索引或双向链表都必须是正确的。某些维护一致性的责任由应用程序开发人员承担他们必须确保应用程序已强制所有已知的完整性约束。例如当开发用于转帐的应用程序时应避免在转帐过程中任意移动小数点。 · 隔离性由并发事务所作的修改必须与任何其它并发事务所作的修改隔离。事务查看数据时数据所处的状态要么是另一并发事务修改它之前的状态要么是另一事务修改它之后的状态事务不会查看中间状态的数据。这称为可串行性因为它能够重新装载起始数据并且重播一系列事务以使数据结束时的状态与原始事务执行的状态相同。当事务可序列化时将获得最高的隔离级别。在此级别上从一组可并行执行的事务获得的结果与通过连续运行每个事务所获得的结果相同。由于高度隔离会限制可并行执行的事务数所以一些应用程序降低隔离级别以换取更大的吞吐量。 · 持久性 事务完成之后它对于系统的影响是永久性的。该修改即使出现致命的系统故障也将一直保持。DBMS的责任和我们的任务企业级的数据库管理系统DBMS都有责任提供一种保证事务的物理完整性的机制。就常用的SQL Server2000系统而言它具备锁定设备隔离事务、记录设备保证事务持久性等机制。因此我们不必关心数据库事务的物理完整性而应该关注在什么情况下使用数据库事务、事务对性能的影响如何使用事务等等。本文将涉及到在.net框架下使用C#语言操纵数据库事务的各个方面。 体验SQL语言的事务机制作为大型的企业级数据库SQL Server2000对事务提供了很好的支持。我们可以使用SQL语句来定义、提交以及回滚一个事务。如下所示的SQL代码定义了一个事务并且命名为MyTransaction限于篇幅本文并不讨论如何编写SQL语言程序请读者自行参考相关书籍 DECLARE TranName VARCHAR(20)SELECT TranName MyTransaction BEGIN TRANSACTION TranNameGOUSE pubsGOUPDATE royschedSET royalty royalty * 1.10WHERE title_id LIKE Pc%GOCOMMIT TRANSACTION MyTransactionGO 这里用到了SQL Server2000自带的示例数据库pubs提交事务后将为所有畅销计算机书籍支付的版税增加 10%。打开SQL Server2000的查询分析器选择pubs数据库然后运行这段程序结果显而易见。可是如何在C#程序中运行呢我们记得在普通的SQL查询中一般需要把查询语句赋值给SalCommand.CommandText属性这里也就像普通的SQL查询语句一样将这些语句赋给SqlCommand.CommandText属性即可。要注意的一点是其中的GO语句标志着SQL批处理的结束编写SQL脚本是需要的但是在这里是不必要的。我们可以编写如下的程序来验证这个想法 //TranSql.csusing System;using System.Data;using System.Data.SqlClient;namespace Aspcn{ public class DbTranSql { file://将事务放到SQL Server中执行 public void DoTran() { file://建立连接并打开 SqlConnection myConnGetConn();myConn.Open(); SqlCommand myCommnew SqlCommand(); try { myComm.ConnectionmyConn; myComm.CommandTextDECLARE TranName VARCHAR(20) ; myComm.CommandTextSELECT TranName MyTransaction ; myComm.CommandTextBEGIN TRANSACTION TranName ; myComm.CommandTextUSE pubs ; myComm.CommandTextUPDATE roysched SET royalty royalty * 1.10 WHERE title_id LIKE Pc% ; myComm.CommandTextCOMMIT TRANSACTION MyTransaction ; myComm.ExecuteNonQuery(); } catch(Exception err) { throw new ApplicationException(事务操作出错系统信息err.Message); } finally { myConn.Close(); } } file://获取数据连接 private SqlConnection GetConn() { string strSqlData Sourcelocalhost;Integrated SecuritySSPI;user idsa;password; SqlConnection myConnnew SqlConnection(strSql); return myConn; } } public class Test { public static void Main() { DbTranSql tranTestnew DbTranSql(); tranTest.DoTran(); Console.WriteLine(事务处理已经成功完成。); Console.ReadLine(); } }} 注意到其中的SqlCommand对象myComm它的CommandText属性仅仅是前面SQL代码字符串连接起来即可当然其中的GO语句已经全部去掉了。这个语句就像普通的查询一样程序将SQL文本事实上提交给DBMS去处理了然后接收返回的结果如果有结果返回的话。很自然我们最后看到了输出事务处理已经成功完成再用企业管理器查看pubs数据库的roysched表所有title_id字段以PC开头的书籍的royalty字段的值都增加了0.1倍。这里我们并没有使用ADO.net的事务处理机制而是简单地将执行事务的SQL语句当作普通的查询来执行因此事实上该事务完全没有用到.net的相关特性。了解.net中的事务机制如你所知在.net框架中主要有两个命名空间(namespace)用于应用程序同数据库系统的交互System.Data.SqlClient和System.Data.OleDb。前者专门用于连接Microsoft公司自己的SQL Server数据库而后者可以适应多种不同的数据库。这两个命名空间中都包含有专门用于管理数据库事务的类分别是System.Data.SqlClient.SqlTranscation类和System.Data.OleDb.OleDbTranscation类。就像它们的名字一样这两个类大部分功能是一样的二者之间的主要差别在于它们的连接机制前者提供一组直接调用 SQL Server 的对象而后者使用本机 OLE DB 启用数据访问。 事实上ADO.net 事务完全在数据库的内部处理且不受 Microsoft 分布式事务处理协调器 (DTC) 或任何其他事务性机制的支持。本文将主要介绍System.Data.SqlClient.SqlTranscation类下面的段落中除了特别注明都将使用System.Data.SqlClient.SqlTranscation类。 事务的开启和提交现在我们对事务的概念和原理都了然于心了并且作为已经有一些基础的C#开发者我们已经熟知编写数据库交互程序的一些要点即使用SqlConnection类的对象的Open()方法建立与数据库服务器的连接然后将该连接赋给SqlCommand对象的Connection属性将欲执行的SQL语句赋给它的CommandText属性于是就可以通过SqlCommand对象进行数据库操作了。对于我们将要编写的事务处理程序当然还需要定义一个SqlTransaction类型的对象。并且看到SqlCommand对象的Transcation属性我们很容易想到新建的SqlTransaction对象应该与它关联起来。基于以上认识下面我们就开始动手写我们的第一个事务处理程序。我们可以很熟练地写出下面这一段程序 //DoTran.csusing System;using System.Data;using System.Data.SqlClient;namespace Aspcn{ public class DbTran { file://执行事务处理 public void DoTran() { file://建立连接并打开 SqlConnection myConnGetConn(); myConn.Open(); SqlCommand myCommnew SqlCommand(); SqlTransaction myTrannew SqlTransaction(); try { myComm.ConnectionmyConn; myComm.TransactionmyTran; file://定位到pubs数据库 myComm.CommandTextUSE pubs; myComm.ExecuteNonQuery(); file://更新数据 file://将所有的计算机类图书 myComm.CommandTextUPDATE roysched SET royalty royalty * 1.10 WHERE title_id LIKE Pc%; myComm.ExecuteNonQuery();//提交事务 myTran.Commit(); } catch(Exception err) { throw new ApplicationException(事务操作出错系统信息err.Message); } finally { myConn.Close(); } } file://获取数据连接 private SqlConnection GetConn() { string strSqlData Sourcelocalhost;Integrated SecuritySSPI;user idsa;password; SqlConnection myConnnew SqlConnection(strSql); return myConn; } } public class Test{public static void Main() { DbTran tranTestnew DbTran(); tranTest.DoTran(); Console.WriteLine(事务处理已经成功完成。); Console.ReadLine(); }}} 显然这个程序非常简单我们非常自信地编译它但是出乎意料的结果使我们的成就感顿时烟消云散error CS1501: 重载SqlTransaction方法未获取0参数是什么原因呢注意到我们初始化的代码 SqlTransaction myTrannew SqlTransaction(); 显然问题出在这里事实上SqlTransaction类并没有公共的构造函数我们不能这样新建一个SqlTrancaction类型的变量。在事务处理之前确实需要有一个SqlTransaction类型的变量将该变量关联到SqlCommand类的Transcation属性也是必要的但是初始化方法却比较特别一点。在初始化SqlTransaction类时你需要使用SqlConnection类的BeginTranscation()方法 SqlTransaction myTran; myTranmyConn.BeginTransaction(); 该方法返回一个SqlTransaction类型的变量。在调用BeginTransaction()方法以后所有基于该数据连接对象的SQL语句执行动作都将被认为是事务MyTran的一部分。同时你也可以在该方法的参数中指定事务隔离级别和事务名称如 SqlTransaction myTran; myTranmyConn.BeginTransaction(IsolationLevel.ReadCommitted,SampleTransaction); 关于隔离级别的概念我们将在随后的内容中探讨在这里我们只需牢记一个事务是如何被启动并且关联到特定的数据链接的。先不要急着去搞懂我们的事务都干了些什么看到这一行 myTran.Commit(); 是的这就是事务的提交方式。该语句执行后事务的所有数据库操作将生效并且为数据库事务的持久性机制所保持--即使系统在这以后发生致命错误该事务对数据库的影响也不会消失。对上面的程序做了修改之后我们可以得到如下代码为了节约篇幅重复之处已省略请参照前文 //DoTran.cs……file://执行事务处理public void DoTran(){ file://建立连接并打开 SqlConnection myConnGetConn(); myConn.Open(); SqlCommand myCommnew SqlCommand(); file://SqlTransaction myTrannew SqlTransaction(); file://注意SqlTransaction类无公开的构造函数 SqlTransaction myTran; file://创建一个事务 myTranmyConn.BeginTransaction(); try { file://从此开始基于该连接的数据操作都被认为是事务的一部分 file://下面绑定连接和事务对象 myComm.ConnectionmyConn; myComm.TransactionmyTran; file://定位到pubs数据库 myComm.CommandTextUSE pubs; myComm.ExecuteNonQuery();//更新数据 file://将所有的计算机类图书 myComm.CommandTextUPDATE roysched SET royalty royalty * 1.10 WHERE title_id LIKE Pc%; myComm.ExecuteNonQuery(); file://提交事务 myTran.Commit(); } catch(Exception err) { throw new ApplicationException(事务操作出错系统信息err.Message); } finally { myConn.Close(); }}…… 到此为止我们仅仅掌握了如何开始和提交事务。下一步我们必须考虑的是在事务中可以干什么和不可以干什么。 另一个走向极端的错误 满怀信心的新手们可能为自己所掌握的部分知识陶醉不已刚接触数据库库事务处理的准开发者们也一样踌躇满志地准备将事务机制应用到他的数据处理程序的每一个模块每一条语句中去。的确事务机制看起来是如此的诱人——简洁、美妙而又实用我当然想用它来避免一切可能出现的错误——我甚至想用事务把我的数据操作从头到尾包裹起来。看着吧下面我要从创建一个数据库开始 using System;using System.Data;using System.Data.SqlClient;namespace Aspcn{ public class DbTran { file://执行事务处理 public void DoTran() { file://建立连接并打开 SqlConnection myConnGetConn(); myConn.Open(); SqlCommand myCommnew SqlCommand(); SqlTransaction myTran; myTranmyConn.BeginTransaction(); file://下面绑定连接和事务对象 myComm.ConnectionmyConn; myComm.TransactionmyTran; file://试图创建数据库TestDB myComm.CommandTextCREATE database TestDB; myComm.ExecuteNonQuery(); file://提交事务 myTran.Commit(); } file://获取数据连接 private SqlConnection GetConn() { string strSqlData Sourcelocalhost;Integrated SecuritySSPI;user idsa;password; SqlConnection myConnnew SqlConnection(strSql); return myConn; } } public class Test { public static void Main() { DbTran tranTestnew DbTran(); tranTest.DoTran(); Console.WriteLine(事务处理已经成功完成。); Console.ReadLine(); } }} //--------------- 未处理的异常 System.Data.SqlClient.SqlException: 在多语句事务内不允许使用 CREATE DATABASE 语句。 at System.Data.SqlClient.SqlCommand.ExecuteNonQuery()at Aspcn.DbTran.DoTran()at Aspcn.Test.Main() 注意如下的SQL语句不允许出现在事务中 ALTER DATABASE 修改数据库 BACKUP LOG 备份日志 CREATE DATABASE 创建数据库 DISK INIT 创建数据库或事务日志设备 DROP DATABASE 删除数据库 DUMP TRANSACTION 转储事务日志 LOAD DATABASE 装载数据库备份复本 LOAD TRANSACTION 装载事务日志备份复本 RECONFIGURE 更新使用 sp_configure 系统存储过程更改的配置选项的当前配置sp_configure 结果集中的 config_value 列值。 RESTORE DATABASE 还原使用BACKUP命令所作的数据库备份 RESTORE LOG 还原使用BACKUP命令所作的日志备份 UPDATE STATISTICS 在指定的表或索引视图中对一个或多个统计组集合有关键值分发的信息进行更新 除了这些语句以外你可以在你的数据库事务中使用任何合法的SQL语句。 事务回滚事务的四个特性之一是原子性其含义是指对于特定操作序列组成的事务要么全部完成要么就一件也不做。如果在事务处理的过程中发生未知的不可预料的错误如何保证事务的原子性呢当事务中止时必须执行回滚操作以便消除已经执行的操作对数据库的影响。一般的情况下在异常处理中使用回滚动作是比较好的想法。前面我们已经得到了一个更新数据库的程序并且验证了它的正确性稍微修改一下可以得到 //RollBack.csusing System;using System.Data;using System.Data.SqlClient;namespace Aspcn{ public class DbTran { file://执行事务处理 public void DoTran() { file://建立连接并打开 SqlConnection myConnGetConn(); myConn.Open(); SqlCommand myCommnew SqlCommand(); SqlTransaction myTran; file://创建一个事务 myTranmyConn.BeginTransaction(); file://从此开始基于该连接的数据操作都被认为是事务的一部分 file://下面绑定连接和事务对象 myComm.ConnectionmyConn; myComm.TransactionmyTran; try { file://定位到pubs数据库 myComm.CommandTextUSE pubs; myComm.ExecuteNonQuery(); myComm.CommandTextUPDATE roysched SET royalty royalty * 1.10 WHERE title_id LIKE Pc%; myComm.ExecuteNonQuery(); file://下面使用创建数据库的语句制造一个错误 myComm.CommandTextCreate database testdb; myComm.ExecuteNonQuery(); myComm.CommandTextUPDATE roysched SET royalty royalty * 1.20 WHERE title_id LIKE Ps%; myComm.ExecuteNonQuery(); file://提交事务 myTran.Commit(); } catch(Exception err) { myTran.Rollback(); Console.Write(事务操作出错已回滚。系统信息err.Message); } } file://获取数据连接 private SqlConnection GetConn() { string strSqlData Sourcelocalhost;Integrated SecuritySSPI;user idsa;password; SqlConnection myConnnew SqlConnection(strSql); return myConn; } } public class Test { public static void Main() { DbTran tranTestnew DbTran(); tranTest.DoTran(); Console.WriteLine(事务处理已经成功完成。); Console.ReadLine(); } }} 首先我们在中间人为地制造了一个错误——使用前面讲过的Create database语句。然后在异常处理的catch块中有如下语句 myTran.Rollback(); 当异常发生时程序执行流跳转到catch块中首先执行的就是这条语句它将当前事务回滚。在这段程序可以看出在Create database之前已经有了一个更新数据库的操作——将pubs数据库的roysched表中的所有title_id字段以“PC”开头的书籍的royalty字段的值都增加0.1倍。但是由于异常发生而导致的回滚使得对于数据库来说什么都没有发生。由此可见Rollback()方法维护了数据库的一致性及事务的原子性。 使用存储点事务只是一种最坏情况下的保障措施事实上平时系统的运行可靠性都是相当高的错误很少发生因此在每次事务执行之前都检查其有效性显得代价太高——绝大多数的情况下这种耗时的检查是不必要的。我们不得不想另外一种办法来提高效率。事务存储点提供了一种机制用于回滚部分事务。因此我们可以不必在更新之前检查更新的有效性而是预设一个存储点在更新之后如果没有出现错误就继续执行否则回滚到更新之前的存储点。存储点的作用就在于此。要注意的是更新和回滚代价很大只有在遇到错误的可能性很小而且预先检查更新的有效性的代价相对很高的情况下使用存储点才会非常有效。使用.net框架编程时你可以非常简单地定义事务存储点和回滚到特定的存储点。下面的语句定义了一个存储点“NoUpdate” myTran.Save(NoUpdate); 当你在程序中创建同名的存储点时新创建的存储点将替代原有的存储点。在回滚事务时只需使用Rollback()方法的一个重载函数即可 myTran.Rollback(NoUpdate); 下面这段程序说明了回滚到存储点的方法和时机 using System;using System.Data;using System.Data.SqlClient;namespace Aspcn{ public class DbTran { file://执行事务处理 public void DoTran() { file://建立连接并打开 SqlConnection myConnGetConn(); myConn.Open(); SqlCommand myCommnew SqlCommand(); SqlTransaction myTran; file://创建一个事务 myTranmyConn.BeginTransaction(); file://从此开始基于该连接的数据操作都被认为是事务的一部分 file://下面绑定连接和事务对象 myComm.ConnectionmyConn; myComm.TransactionmyTran; try { myComm.CommandTextuse pubs; myComm.ExecuteNonQuery(); myTran.Save(NoUpdate); myComm.CommandTextUPDATE roysched SET royalty royalty * 1.10 WHERE title_id LIKE Pc%; myComm.ExecuteNonQuery(); file://提交事务 myTran.Commit(); } catch(Exception err) { file://更新错误回滚到指定存储点 myTran.Rollback(NoUpdate); throw new ApplicationException(事务操作出错系统信息err.Message); } } file://获取数据连接 private SqlConnection GetConn() { string strSqlData Sourcelocalhost;Integrated SecuritySSPI;user idsa;password; SqlConnection myConnnew SqlConnection(strSql); return myConn; } } public class Test { public static void Main() { DbTran tranTestnew DbTran(); tranTest.DoTran(); Console.WriteLine(事务处理已经成功完成。); Console.ReadLine(); } }} 很明显在这个程序中更新无效的几率是非常小的而且在更新前验证其有效性的代价相当高因此我们无须在更新之前验证其有效性而是结合事务的存储点机制提供了数据完整性的保证。 隔离级别的概念企业级的数据库每一秒钟都可能应付成千上万的并发访问因而带来了并发控制的问题。由数据库理论可知由于并发访问在不可预料的时刻可能引发如下几个可以预料的问题 脏读包含未提交数据的读取。例如事务1 更改了某行。事务2 在事务1 提交更改之前读取已更改的行。如果事务1 回滚更改则事务2 便读取了逻辑上从未存在过的行。 不可重复读取当某个事务不止一次读取同一行并且一个单独的事务在两次或多次读取之间修改该行时因为在同一个事务内的多次读取之间修改了该行所以每次读取都生成不同值从而引发不一致问题。 幻象通过一个任务在以前由另一个尚未提交其事务的任务读取的行的范围中插入新行或删除现有行。带有未提交事务的任务由于该范围中行数的更改而无法重复其原始读取。 如你所想这些情况发生的根本原因都是因为在并发访问的时候没有一个机制避免交叉存取所造成的。而隔离级别的设置正是为了避免这些情况的发生。事务准备接受不一致数据的级别称为隔离级别。隔离级别是一个事务必须与其它事务进行隔离的程度。较低的隔离级别可以增加并发但代价是降低数据的正确性。相反较高的隔离级别可以确保数据的正确性但可能对并发产生负面影响。根据隔离级别的不同DBMS为并行访问提供不同的互斥保证。在SQL Server数据库中提供四种隔离级别未提交读、提交读、可重复读、可串行读。这四种隔离级别可以不同程度地保证并发的数据完整性 隔离级别 脏 读 不可重复读取 幻 像 未提交读 是 是 是 提交读 否 是 是 可重复读 否 否 是 可串行读 否 否 否 可以看出“可串行读”提供了最高级别的隔离这时并发事务的执行结果将与串行执行的完全一致。如前所述最高级别的隔离也就意味着最低程度的并发因此在此隔离级别下数据库的服务效率事实上是比较低的。尽管可串行性对于事务确保数据库中的数据在所有时间内的正确性相当重要然而许多事务并不总是要求完全的隔离。例如多个作者工作于同一本书的不同章节。新章节可以在任意时候提交到项目中。但是对于已经编辑过的章节没有编辑人员的批准作者不能对此章节进行任何更改。这样尽管有未编辑的新章节但编辑人员仍可以确保在任意时间该书籍项目的正确性。编辑人员可以查看以前编辑的章节以及最近提交的章节。这样其它的几种隔离级别也有其存在的意义。在.net框架中事务的隔离级别是由枚举System.Data.IsolationLevel所定义的 [Flags][Serializable]public enum IsolationLevel 其成员及相应的含义如下 成 员 含 义 Chaos 无法改写隔离级别更高的事务中的挂起的更改。 ReadCommitted 在正在读取数据时保持共享锁以避免脏读但是在事务结束之前可以更改数据从而导致不可重复的读取或幻像数据。 ReadUncommitted 可以进行脏读意思是说不发布共享锁也不接受独占锁。 RepeatableRead 在查询中使用的所有数据上放置锁以防止其他用户更新这些数据。防止不可重复的读取但是仍可以有幻像行。 Serializable 在DataSet上放置范围锁以防止在事务完成之前由其他用户更新行或向数据集中插入行。 Unspecified 正在使用与指定隔离级别不同的隔离级别但是无法确定该级别。 显而意见数据库的四个隔离级别在这里都有映射。默认的情况下SQL Server使用ReadCommitted(提交读)隔离级别。关于隔离级别的最后一点就是如果你在事务执行的过程中改变了隔离级别那么后面的命名都在最新的隔离级别下执行——隔离级别的改变是立即生效的。有了这一点你可以在你的事务中更灵活地使用隔离级别从而达到更高的效率和并发安全性。 最后的忠告无疑引入事务处理是应对可能出现的数据错误的好方法但是也应该看到事务处理需要付出的巨大代价——用于存储点、回滚和并发控制所需要的CPU时间和存储空间。本文的内容只是针对Microsoft SQL Server数据库的对应于.net框架中的System.Data.SqlClient命名空间对于使用OleDb的情形具体的实现稍有不同但这不是本文的内容有兴趣的读者可以到.net中华网www.aspcn.com的论坛里找到答案. 转载于:https://www.cnblogs.com/wlq2000/archive/2006/12/15/592960.html
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/919106.shtml
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!