分类: Mysql/postgreSQL
2008-05-12 15:16:58
从Connector/J 3.1.7开始,我们提供了1个驱动程序变体,它能自动发出读/写主服务器的查询,或根据Connection.getReadOnly()的状态对从主机进行故障切换或循环负载平衡设置。
应用程序发出信号,通过调用Connection.setReadOnly(true)指明事务为只读的,该具有“复制意识”的连接将使用从连接之一,从连接是采用了循环方案的负载平衡per-vm(给定连接与从连接密切相关,除非在服务中删除了从连接)。如果你有1项写事务,或1项对时间敏感的读事务(记住,在MySQL中,复制是以异步方式进行的),请调用Connection.setReadOnly(false),将连接设置为非只读的,驱动程序会确保进一步的调用均将被发送到主MySQL服务器。驱动程序负责传播autocommit的当前状态,隔离级别,以及用于完成该负载平衡功能的所有连接之间的目录。
要想启用该该功能,在配置应用服务器的连接池时,或为独立应用程序创建JDBC驱动实例时,请使用“com.mysql.jdbc.ReplicationDriver”类。由于它能接受与标准MySQL JDBC驱动相同的URL格式,ReplicationDriver目前不能与基于java.sql.DriverManager的连接一起使用,除非它是用DriverManager注册的唯一MySQL JDBC驱动程序。
下面给出了一个简短的简单示例,介绍了如何在独立应用程序中使用ReplicationDriver的方法。
import java.sql.Connection;
import java.sql.ResultSet;
import java.util.Properties;
import com.mysql.jdbc.ReplicationDriver;
public class ReplicationDriverDemo {
public static void main(String[] args) throws Exception {
ReplicationDriver driver = new ReplicationDriver();
Properties props = new Properties();
// We want this for failover on the slaves
props.put("autoReconnect", "true");
// We want to load balance between the slaves
props.put("roundRobinLoadBalance", "true");
props.put("user", "foo");
props.put("password", "bar");
//
// Looks like a normal MySQL JDBC url, with a comma-separated list
// of hosts, the first being the 'master', the rest being any number
// of slaves that the driver will load balance against
//
Connection conn =
driver.connect("jdbc:mysql://master,slave1,slave2,slave3/test",
props);
//
// Perform read/write work on the master
// by setting the read-only flag to "false"
//
conn.setReadOnly(false);
conn.setAutoCommit(false);
conn.createStatement().executeUpdate("UPDATE some_table ....");
conn.commit();
//
// Now, do a query from a slave, the driver automatically picks one
// from the list
//
conn.setReadOnly(true);
ResultSet rs = conn.createStatement().executeQuery("SELECT a,b,c FROM some_other_table");
.......
}
}
事实上,当某一线程需要用JDBC在MySQL或其他数据库上执行操作时,需要用到由连接池提供的连接。使用连接完成线程后,线程会将连接返回给连接池,以便该连接能够被其他需要使用连接的线程使用。
从连接池“借出”连接时,该连接仅供请求它的线程使用。从编程观点看,其效果等同于每次需要JDBC连接时调用DriverManager.getConnection(),但是,采用连接池技术,可通过使用新的或已有的连接结束线程。
连接池技术能显著增加Java应用程序的性能,同时还能降低资源使用率。连接池技术的主要优点包括:
· 缩短了连接创建时间
与其他数据库相比,MySQL提供了快速的连接设置功能,连接时间通常不是问题,但创建新的JDBC连接仍会导致联网操作和一定的IDBC驱动开销,如果这类连接是“循环”使用的,使用该方式,可避免这类不利因素。
· 简化的编程模型
使用连接池技术时,每个单独线程能够像创建了自己的JDBC连接那样进行操作,从而允许使用直接的JDBC编程技术。
· 受控的资源使用
如果不使用连接池技术,而是在每次需要时为线程创建新的连接,那么应用程序的资源使用将十分浪费,而且在负载较重的情况下会导致无法预期的结果。
注意,与MySQL的每个连接均会在客户端和服务器端造成一定的开销(每寸、CPU、关联转换等)。每个连接均会对应用程序和MySQL服务器的可用资源带来一定的限制。无论连接是否执行任何有用的任务,仍将使用这些资源中的相当一部分。
连接池能够使性能最大化,同时还能将资源利用控制在一定的水平之下,如果超过该水平,应用程序将崩溃而不仅仅是变慢。
幸运的是,Sun公司通过JDBC-2.0“可选”接口,完成了JDBC中连接池概念的标准化实施,所有主要应用服务器均实施了能够与MySQL Connector/J一起良好工作的这类API。
通常,你可以在应用服务器的配置文件中配置连接池,并通过Java命名和目录接口(JNDI)访问它。在下面的代码中,介绍了在J2E应用服务器上运行的应用程序中使用连接池的方法:
示例26.12. 与J2EE应用服务器一起使用连接池
import java.sql.Connection; import java.sql.SQLException; import java.sql.Statement; import javax.naming.InitialContext; import javax.sql.DataSource; public class MyServletJspOrEjb { public void doSomething() throws Exception { /* * Create a JNDI Initial context to be able to * lookup the DataSource * * In production-level code, this should be cached as * an instance or static variable, as it can * be quite expensive to create a JNDI context. * * Note: This code only works when you are using servlets * or EJBs in a J2EE application server. If you are * using connection pooling in standalone Java code, you * will have to create/configure datasources using whatever * mechanisms your particular connection pooling library * provides. */ InitialContext ctx = new InitialContext(); /* * Lookup the DataSource, which will be backed by a pool * that the application server provides. DataSource instances * are also a good candidate for caching as an instance * variable, as JNDI lookups can be expensive as well. */ DataSource ds = (DataSource)ctx.lookup("java:comp/env/jdbc/MySQLDB"); /* * The following code is what would actually be in your * Servlet, JSP or EJB 'service' method...where you need * to work with a JDBC connection. */ Connection conn = null; Statement stmt = null; try { conn = ds.getConnection(); /* * Now, use normal JDBC programming to work with * MySQL, making sure to close each resource when you're * finished with it, which allows the connection pool * resources to be recovered as quickly as possible */ stmt = conn.createStatement(); stmt.execute("SOME SQL QUERY"); stmt.close(); stmt = null; conn.close(); conn = null; } finally { /* * close any jdbc instances here that weren't * explicitly closed during normal code path, so * that we don't 'leak' resources... */ if (stmt != null) { try { stmt.close(); } catch (sqlexception sqlex) { // ignore -- as we can't do anything about it here } stmt = null; } if (conn != null) { try { conn.close(); } catch (sqlexception sqlex) { // ignore -- as we can't do anything about it here } conn = null; } } } }
如上例所示,获得JNDI InitialContext并查找到数据库后,其余代码与过去在JDBC编程中使用的类似。
使用连接池时需要牢记的最重要事项是,无论在代码中出现了什么(异常、控制流等),连接以及由连接创建的任何部分(语句、结果集等)均应被关闭,以便能再次使用它们。如不然,它们将纠缠在一起,在最好的情况下,意味着它们所代表的MySQL服务器资源(缓冲区、锁定、套接字等)可能会捆绑一段时间,在最坏的情况下,可能会导致永久捆绑。
连接池的最佳大小是什么?
与所有其他配置经验规则一样,回答是“它取决于具体情况”。尽管最佳大小取决与预期的负载和平均的数据库事务时间,最佳的连接池大小小于你的预期。例如,如果使用的是Sun公司的Java Petstore Blueprint应用程序,对于包含15~20个连接的连接池,使用MySQL和Tomcat,在可接受的相应时间下,可服务于中等程度的负载(600个并发用户)。
要想确定用于应用程序的连接池大小,应使用诸如Apache Jmeter或The Grinder等工具创建负载测试脚本,并对应用程序进行负载测试。
确定出发点的一种简单方法是,将连接池的最大连接数配置为“无限”,运行负载测试,并测量最大的并发连接数。随后,应进行反向操作,确定出使应用程序具有最佳性能的连接池的最小和最大值。
下述内容基于关于Tomcat-5.x的指,在编写本文档时它是最新的。
首先安装与Connector/J in $CATALINA_HOME/common/lib一起提供的.jar文件,以便它能用于已安装的所有应用程序。
其次,在定义Web应用程序的Context(场景)内,通过为$CATALINA_HOME/conf/server.xml增加声明资源,配置JNDI DataSource:
... factory org.apache.commons.dbcp.BasicDataSourceFactory maxActive 10 maxIdle 5 validationQuery SELECT 1 testOnBorrow true testWhileIdle true timeBetweenEvictionRunsMillis 10000 minEvictableIdleTimeMillis 60000 username someuser password somepass driverClassName com.mysql.jdbc.Driver url jdbc:mysql://localhost:3306/test
一般而言,应遵循关于相应Tomcat版本的安装说明,这是因为,在Tomcat中配置数据源的方式会随时变化,很不幸,如果在XML文件中使用了错误的语法,很可能会以异常结束,如下所示:
Error: java.sql.SQLException: Cannot load JDBC driver class 'null ' SQL state: null
下述说明适用于JBoss-4.x。要想使应用服务器能够使用JDBC驱动类,请将与Connector/J一起提供的.jar文件拷贝到用于服务器配置的lib目录下(通常称为“默认”)。随后,在相同的配置目录下,在名为“deploy”的子目录下,创建以“-ds.xml”结尾的数据源配置文件,用于通知Jboss将该文件当作JDBC数据源。该文件应包含下述内容:
MySQLDB jdbc:mysql://localhost:3306/dbname com.mysql.jdbc.Driver user pass 5 20 5 com.mysql.jdbc.integration.jboss.ExtendedMysqlExceptionSorter com.mysql.jdbc.integration.jboss.MysqlValidConnectionChecker
对于MySQL Connector/J用户,会遇到一些常见的共同问题。在本节中,介绍了它们的症状和相应的解决方法。 关于更进一步的信息,请参见“支持”一节。
27.3.5.1.1:
问题:
当我尝试用MySQL Connector/J连接到数据库时,遇到下述异常:
SQLException: Server configuration denies access to data source
SQLState: 08001
VendorError: 0
出现了什么问题? 使用MySQL命令行客户端时,连接良好。
回答:
MySQL Connector/J必须使用TCP/IP套接字来连接MySQL,原因在于Java不支持Unix Domain套接字。因此,当MySQL Connector/J连接到MySQL时,MySQL服务器的安全管理器将使用其授权表判断是否允许连接。必须添加授权才能允许该操作。下面给出了一个执行该操作的示例(但并非最安全的)。
从mysql命令行客户端以能够授权的用户身份登录,并发出下述命令:
GRANT ALL PRIVILEGES ON [dbname].* to
'[user]'@'[hostname]' identified by
'[password]'
用你的数据库名称替换[dbname],用用户名替换[user],用MySQL Connector/J将连接的主机替换[hostname],并用打算使用的密码替换[password]。注意,对于从本地主机进行连接的主机名部分,RedHat Linux将失败。在这种情况下,对于[hostname]值,需要使用“localhost.localdomain”。随后,发出FLUSH PRIVILEGES命令。
除非添加了“--host”标志,并为主机使用了不同于“localhost”的其他设置,否则将无法使用mysql命令行客户mysql命令行客户端将使用Unix域套接字。如果正在测试与“localhost”的连通性,请使用“127.0.0.1”作为主机名。
如果你不了解“GRANT”命令是干什么的,或不了解该命令的工作方式,在尝试更改权限之前,请阅读MySQL手册中的 一节。
如果在MySQL中不恰当地更改了权限和许可,可能会使服务器不会具有最佳的安全性能。
27.3.5.1.2:
问题:
我的应用程序抛出SQLException“无恰当的驱动程序”。为什么会出现该情况?
回答:
出现了两种情况之一。或是1驱动程序未位于你的CLASSPATH中(请参见前面的“安装部分”),或是URL格式不正确(请参见用MySQL Connector/J开发应用程序)。
27.3.5.1.3:
问题:
当我试图在Java程序或应用程序中使用MySQL Connector/J时,遇到类似下面的异常:
SQLException: 无法连接到host:3306上的MySQL服务器。
在你尝试连接的机器/端口上是否有正在运行的MySQL服务器?
(java.security.AccessControlException)
SQLState: 08S01
VendorError: 0
回答:
或许是因为你正在运行Applet,你的MySQL服务器是采用“--skip-networking”选项集安装的;或许是因为MySQL服务器位于防火墙之后。
Applet仅能使网络连接返回运行Web服务器的机器,该Web服务器提供了用于Applet的.class文件。这意味着,要想使其工作,MySQL必须运行在相同的机器上(或必须使某类端口重定向)。这还意味着,你无法通过你的本地文件系统来测试Java程序,你必须将它们放在Web服务器上。
MySQL Connector/J仅能使用TCP/IP与MySQL进行通信,这是因为Java不支持Unix域套接字。如果MySQL是用“--skip-networking”标志启动的,或采用了防火墙,TCP/IP与MySQL的通信可能会受到影响。
如果MySQL是用“--skip-networking”选项集启动的(例如MySQL服务器的Debian Linux包即用于该目的),需要在文件/etc/mysql/my.cnf或/etc/my.cnf中将其注释掉。当然,my.cnf文件也可能位于MySQl服务器的“data”目录下或其他地方(取决于系统中MySQL的编译方式)。MySQL AB创建的二进制文件总会在查找/etc/my.cnf和[datadir]/my.cnf。如果为MySQL服务器部署了防火墙,需要对防火墙进行配置,允许从运行Java代码的主机在MySQL监听的端口上(默认为3306)建立与 MySQL服务器的TCP/IP连接。
27.3.5.1.4:
问题:
I我的小服务程序/应用程序白天工作良好,但在晚上却停止工作。
回答:
不工作时间超过8小时后,MySQL关闭了连接。你或许需要使用能处理失效连接的连接池,或使用“autoReconnect”参数(请参见用MySQL Connector/J开发应用程序)。
此外,你应在应用程序中俘获 SQLException并处理它们,而不是在应用程序退出前一直传播它们,这是1个好的编程习惯。在查询处理过程中遇到网络连通性方面的问题时,MySQL Connector/J会将SQLState(参见APIDOCS中的java.sql.SQLException.getSQLState())设置为“08S01”。随后,应用程序代码将尝试再次连接到MySQL。
在下面的示例(simplistic)中,给出了能够处理这类异常的代码:
示例26.13. 重试逻辑的事务示例
public void doBusinessOp() throws SQLException { Connection conn = null; Statement stmt = null; ResultSet rs = null; // // How many times do you want to retry the transaction // (or at least _getting_ a connection)? // int retryCount = 5; boolean transactionCompleted = false; do { try { conn = getConnection(); // assume getting this from a // javax.sql.DataSource, or the // java.sql.DriverManager conn.setAutoCommit(false); // // Okay, at this point, the 'retry-ability' of the // transaction really depends on your application logic, // whether or not you're using autocommit (in this case // not), and whether you're using transacational storage // engines // // For this example, we'll assume that it's _not_ safe // to retry the entire transaction, so we set retry count // to 0 at this point // // If you were using exclusively transaction-safe tables, // or your application could recover from a connection going // bad in the middle of an operation, then you would not // touch 'retryCount' here, and just let the loop repeat // until retryCount == 0. // retryCount = 0; stmt = conn.createStatement(); String query = "SELECT foo FROM bar ORDER BY baz"; rs = stmt.executeQuery(query); while (rs.next()) { } rs.close(); rs = null; stmt.close(); stmt = null; conn.commit(); conn.close(); conn = null; transactionCompleted = true; } catch (SQLException sqlEx) { // // The two SQL states that are 'retry-able' are 08S01 // for a communications error, and 41000 for deadlock. // // Only retry if the error was due to a stale connection, // communications problem or deadlock // String sqlState = sqlEx.getSQLState(); if ("08S01".equals(sqlState) || "41000".equals(sqlState)) { retryCount--; } else { retryCount = 0; } } finally { if (rs != null) { try { rs.close(); } catch (SQLException sqlEx) { // You'd probably want to log this . . . } } if (stmt != null) { try { stmt.close(); } catch (SQLException sqlEx) { // You'd probably want to log this as well . . . } } if (conn != null) { try { // // If we got here, and conn is not null, the // transaction should be rolled back, as not // all work has been done try { conn.rollback(); } finally { conn.close(); } } catch (SQLException sqlEx) { // // If we got an exception here, something // pretty serious is going on, so we better // pass it up the stack, rather than just // logging it. . . throw sqlEx; } } } } while (!transactionCompleted && (retryCount > 0)); }
27.3.5.1.5:
问题:
我正尝试使用JDBC-2.0可更新结果集,但遇到异常,说我的结果集不可更新。
回答:
由于MySQL没有行ID,MySQL Connector/J仅能更新来自查询且位于有至少一个主键的表上的结果集,查询必须选择所有的主键,而且查询即能作用在1个表上(即不存在联合)。在JDBC规范中给出了这方面的介绍。